pax_global_header00006660000000000000000000000064145314316520014516gustar00rootroot0000000000000052 comment=3c8c1f22dc332a8a6b51033f6a7c0566b885d290 golang-k8s-apimachinery-0.29.0/000077500000000000000000000000001453143165200162275ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/.github/000077500000000000000000000000001453143165200175675ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000002251453143165200233670ustar00rootroot00000000000000Sorry, we do not accept changes directly against this repository. Please see CONTRIBUTING.md for information on where and how to contribute instead. golang-k8s-apimachinery-0.29.0/CONTRIBUTING.md000066400000000000000000000013521453143165200204610ustar00rootroot00000000000000# Contributing guidelines Do not open pull requests directly against this repository, they will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes. This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/apimachinery](https://git.k8s.io/kubernetes/staging/src/k8s.io/apimachinery) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot). Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/sig-architecture/staging.md) for more information golang-k8s-apimachinery-0.29.0/LICENSE000066400000000000000000000261361453143165200172440ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-k8s-apimachinery-0.29.0/OWNERS000066400000000000000000000006271453143165200171740ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners approvers: - smarterclayton - deads2k - sttts - liggitt - caesarxuchao - jpbetz reviewers: - apelisse - thockin - smarterclayton - wojtek-t - deads2k - derekwaynecarr - caesarxuchao - cheftako - mikedanese - liggitt - sttts - ncdc - logicalhan - jpbetz labels: - sig/api-machinery emeritus_approvers: - lavalamp golang-k8s-apimachinery-0.29.0/README.md000066400000000000000000000023351453143165200175110ustar00rootroot00000000000000# apimachinery Scheme, typing, encoding, decoding, and conversion packages for Kubernetes and Kubernetes-like API objects. ## Purpose This library is a shared dependency for servers and clients to work with Kubernetes API infrastructure without direct type dependencies. Its first consumers are `k8s.io/kubernetes`, `k8s.io/client-go`, and `k8s.io/apiserver`. ## Compatibility There are *NO compatibility guarantees* for this repository. It is in direct support of Kubernetes, so branches will track Kubernetes and be compatible with that repo. As we more cleanly separate the layers, we will review the compatibility guarantee. ## Where does it come from? `apimachinery` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery. Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. ## Things you should *NOT* do 1. Add API types to this repo. This is for the machinery, not for the types. 2. Directly modify any files under `pkg` in this repo. Those are driven from `k8s.io/kubernetes/staging/src/k8s.io/apimachinery`. 3. Expect compatibility. This repo is direct support of Kubernetes and the API isn't yet stable enough for API guarantees. golang-k8s-apimachinery-0.29.0/SECURITY_CONTACTS000066400000000000000000000010461453143165200207200ustar00rootroot00000000000000# Defined below are the security contacts for this repo. # # They are the contact point for the Product Security Committee to reach out # to for triaging and handling of incoming issues. # # The below names agree to abide by the # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) # and will be removed and replaced if they violate that agreement. # # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # INSTRUCTIONS AT https://kubernetes.io/security/ cheftako deads2k lavalamp sttts golang-k8s-apimachinery-0.29.0/code-of-conduct.md000066400000000000000000000002241453143165200215200ustar00rootroot00000000000000# Kubernetes Community Code of Conduct Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) golang-k8s-apimachinery-0.29.0/doc.go000066400000000000000000000011601453143165200173210ustar00rootroot00000000000000/* Copyright 2021 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 apimachinery // import "k8s.io/apimachinery" golang-k8s-apimachinery-0.29.0/go.mod000066400000000000000000000040601453143165200173350ustar00rootroot00000000000000// This is a generated file. Do not edit directly. module k8s.io/apimachinery go 1.21 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/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/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 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 ) require ( github.com/go-logr/logr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // 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/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/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 gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-k8s-apimachinery-0.29.0/go.sum000066400000000000000000000340471453143165200173720ustar00rootroot00000000000000github.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/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 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/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/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/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= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/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/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/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/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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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/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/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/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/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= 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/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= golang-k8s-apimachinery-0.29.0/pkg/000077500000000000000000000000001453143165200170105ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/OWNERS000066400000000000000000000002331453143165200177460ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners approvers: - caesarxuchao - deads2k - smarterclayton - liggitt emeritus_approvers: - lavalamp golang-k8s-apimachinery-0.29.0/pkg/api/000077500000000000000000000000001453143165200175615ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/000077500000000000000000000000001453143165200217305ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/codec.go000066400000000000000000000076731453143165200233510ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package apitesting import ( "fmt" "mime" "os" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" ) var ( testCodecMediaType string testStorageCodecMediaType string ) // TestCodec returns the codec for the API version to test against, as set by the // KUBE_TEST_API_TYPE env var. func TestCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec { if len(testCodecMediaType) != 0 { serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testCodecMediaType) if !ok { panic(fmt.Sprintf("no serializer for %s", testCodecMediaType)) } return codecs.CodecForVersions(serializerInfo.Serializer, codecs.UniversalDeserializer(), schema.GroupVersions(gvs), nil) } return codecs.LegacyCodec(gvs...) } // TestStorageCodec returns the codec for the API version to test against used in storage, as set by the // KUBE_TEST_API_STORAGE_TYPE env var. func TestStorageCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec { if len(testStorageCodecMediaType) != 0 { serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testStorageCodecMediaType) if !ok { panic(fmt.Sprintf("no serializer for %s", testStorageCodecMediaType)) } // etcd2 only supports string data - we must wrap any result before returning // TODO: remove for etcd3 / make parameterizable serializer := serializerInfo.Serializer if !serializerInfo.EncodesAsText { serializer = runtime.NewBase64Serializer(serializer, serializer) } decoder := recognizer.NewDecoder(serializer, codecs.UniversalDeserializer()) return codecs.CodecForVersions(serializer, decoder, schema.GroupVersions(gvs), nil) } return codecs.LegacyCodec(gvs...) } func init() { var err error if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 { testCodecMediaType, _, err = mime.ParseMediaType(apiMediaType) if err != nil { panic(err) } } if storageMediaType := os.Getenv("KUBE_TEST_API_STORAGE_TYPE"); len(storageMediaType) > 0 { testStorageCodecMediaType, _, err = mime.ParseMediaType(storageMediaType) if err != nil { panic(err) } } } // InstallOrDieFunc mirrors install functions that require success type InstallOrDieFunc func(scheme *runtime.Scheme) // SchemeForInstallOrDie builds a simple test scheme and codecfactory pair for easy unit testing from higher level install methods func SchemeForInstallOrDie(installFns ...InstallOrDieFunc) (*runtime.Scheme, runtimeserializer.CodecFactory) { scheme := runtime.NewScheme() codecFactory := runtimeserializer.NewCodecFactory(scheme) for _, installFn := range installFns { installFn(scheme) } return scheme, codecFactory } // InstallFunc mirrors install functions that can return an error type InstallFunc func(scheme *runtime.Scheme) error // SchemeForOrDie builds a simple test scheme and codecfactory pair for easy unit testing from the bare registration methods. func SchemeForOrDie(installFns ...InstallFunc) (*runtime.Scheme, runtimeserializer.CodecFactory) { scheme := runtime.NewScheme() codecFactory := runtimeserializer.NewCodecFactory(scheme) for _, installFn := range installFns { if err := installFn(scheme); err != nil { panic(err) } } return scheme, codecFactory } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/fuzzer/000077500000000000000000000000001453143165200232555ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/fuzzer/fuzzer.go000066400000000000000000000030701453143165200251310ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package fuzzer import ( "math/rand" "github.com/google/gofuzz" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" ) // FuzzerFuncs returns a list of func(*SomeType, c fuzz.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) if src != nil { f.RandSource(src) } f.Funcs(funcs(codecs)...) return f } // MergeFuzzerFuncs will merge the given funcLists, overriding early funcs with later ones if there first // argument has the same type. func MergeFuzzerFuncs(funcs ...FuzzerFuncs) FuzzerFuncs { return FuzzerFuncs(func(codecs runtimeserializer.CodecFactory) []interface{} { result := []interface{}{} for _, f := range funcs { if f != nil { result = append(result, f(codecs)...) } } return result }) } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/fuzzer/valuefuzz.go000066400000000000000000000044121453143165200256400ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package fuzzer import ( "reflect" ) // ValueFuzz recursively changes all basic type values in an object. Any kind of references will not // be touch, i.e. the addresses of slices, maps, pointers will stay unchanged. func ValueFuzz(obj interface{}) { valueFuzz(reflect.ValueOf(obj)) } func valueFuzz(obj reflect.Value) { switch obj.Kind() { case reflect.Array: for i := 0; i < obj.Len(); i++ { valueFuzz(obj.Index(i)) } case reflect.Slice: if obj.IsNil() { // TODO: set non-nil value } else { for i := 0; i < obj.Len(); i++ { valueFuzz(obj.Index(i)) } } case reflect.Interface, reflect.Pointer: if obj.IsNil() { // TODO: set non-nil value } else { valueFuzz(obj.Elem()) } case reflect.Struct: for i, n := 0, obj.NumField(); i < n; i++ { valueFuzz(obj.Field(i)) } case reflect.Map: if obj.IsNil() { // TODO: set non-nil value } else { for _, k := range obj.MapKeys() { // map values are not addressable. We need a copy. v := obj.MapIndex(k) copy := reflect.New(v.Type()) copy.Elem().Set(v) valueFuzz(copy.Elem()) obj.SetMapIndex(k, copy.Elem()) } // TODO: set some new value } case reflect.Func: // ignore, we don't have function types in our API default: if !obj.CanSet() { return } switch obj.Kind() { case reflect.String: obj.SetString(obj.String() + "x") case reflect.Bool: obj.SetBool(!obj.Bool()) case reflect.Float32, reflect.Float64: obj.SetFloat(obj.Float()*2.0 + 1.0) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: obj.SetInt(obj.Int() + 1) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: obj.SetUint(obj.Uint() + 1) default: } } } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/fuzzer/valuefuzz_test.go000066400000000000000000000024761453143165200267070ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package fuzzer import "testing" func TestValueFuzz(t *testing.T) { type ( Y struct { I int B bool F float32 U uint } X struct { Ptr *X Y Y Map map[string]int Slice []int } ) x := X{ Ptr: &X{}, Map: map[string]int{"foo": 42}, Slice: []int{1, 2, 3}, } p := x.Ptr m := x.Map s := x.Slice ValueFuzz(x) if x.Ptr.Y.I == 0 { t.Errorf("x.Ptr.Y.I should have changed") } if x.Map["foo"] == 42 { t.Errorf("x.Map[foo] should have changed") } if x.Slice[0] == 1 { t.Errorf("x.Slice[0] should have changed") } if x.Ptr != p { t.Errorf("x.Ptr changed") } m["foo"] = 7 if x.Map["foo"] != m["foo"] { t.Errorf("x.Map changed") } s[0] = 7 if x.Slice[0] != s[0] { t.Errorf("x.Slice changed") } } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/naming/000077500000000000000000000000001453143165200232015ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/naming/naming.go000066400000000000000000000134341453143165200250060ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package naming import ( "encoding/json" "fmt" "reflect" "strings" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" ) var ( marshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() unmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() ) // VerifyGroupNames ensures that all groups in the scheme ends with the k8s.io suffix. // Exceptions can be tolerated using the legacyUnsuffixedGroups parameter func VerifyGroupNames(scheme *runtime.Scheme, legacyUnsuffixedGroups sets.String) error { errs := []error{} for _, gv := range scheme.PrioritizedVersionsAllGroups() { if !strings.HasSuffix(gv.Group, ".k8s.io") && !legacyUnsuffixedGroups.Has(gv.Group) { errs = append(errs, fmt.Errorf("group %s does not have the standard kubernetes API group suffix of .k8s.io", gv.Group)) } } return errors.NewAggregate(errs) } // VerifyTagNaming ensures that all types in the scheme have JSON tags set on external types, and JSON tags not set on internal types. // Exceptions can be tolerated using the typesAllowedTags and allowedNonstandardJSONNames parameters func VerifyTagNaming(scheme *runtime.Scheme, typesAllowedTags map[reflect.Type]bool, allowedNonstandardJSONNames map[reflect.Type]string) error { errs := []error{} for gvk, knownType := range scheme.AllKnownTypes() { var err error if gvk.Version == runtime.APIVersionInternal { err = errors.NewAggregate(ensureNoTags(gvk, knownType, nil, typesAllowedTags)) } else { err = errors.NewAggregate(ensureTags(gvk, knownType, nil, allowedNonstandardJSONNames)) } if err != nil { errs = append(errs, err) } } return errors.NewAggregate(errs) } func ensureNoTags(gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect.Type, typesAllowedTags map[reflect.Type]bool) []error { errs := []error{} if _, ok := typesAllowedTags[tp]; ok { return errs } // Don't look at the same type multiple times if containsType(parents, tp) { return nil } parents = append(parents, tp) switch tp.Kind() { case reflect.Map, reflect.Slice, reflect.Pointer: errs = append(errs, ensureNoTags(gvk, tp.Elem(), parents, typesAllowedTags)...) case reflect.String, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Interface: // no-op case reflect.Struct: for i := 0; i < tp.NumField(); i++ { f := tp.Field(i) if f.PkgPath != "" { continue // Ignore unexported fields } jsonTag := f.Tag.Get("json") protoTag := f.Tag.Get("protobuf") if len(jsonTag) > 0 || len(protoTag) > 0 { errs = append(errs, fmt.Errorf("internal types should not have json or protobuf tags. %#v has tag on field %v: %v.\n%s", gvk, f.Name, f.Tag, fmtParentString(parents))) } errs = append(errs, ensureNoTags(gvk, f.Type, parents, typesAllowedTags)...) } default: errs = append(errs, fmt.Errorf("unexpected type %v in %#v.\n%s", tp.Kind(), gvk, fmtParentString(parents))) } return errs } func ensureTags(gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect.Type, allowedNonstandardJSONNames map[reflect.Type]string) []error { errs := []error{} // This type handles its own encoding/decoding and doesn't need json tags if tp.Implements(marshalerType) && (tp.Implements(unmarshalerType) || reflect.PointerTo(tp).Implements(unmarshalerType)) { return errs } // Don't look at the same type multiple times if containsType(parents, tp) { return nil } parents = append(parents, tp) switch tp.Kind() { case reflect.Map, reflect.Slice, reflect.Pointer: errs = append(errs, ensureTags(gvk, tp.Elem(), parents, allowedNonstandardJSONNames)...) case reflect.String, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Interface: // no-op case reflect.Struct: for i := 0; i < tp.NumField(); i++ { f := tp.Field(i) jsonTag := f.Tag.Get("json") if len(jsonTag) == 0 { errs = append(errs, fmt.Errorf("external types should have json tags. %#v tags on field %v are: %s.\n%s", gvk, f.Name, f.Tag, fmtParentString(parents))) } jsonTagName := strings.Split(jsonTag, ",")[0] if len(jsonTagName) > 0 && (jsonTagName[0] < 'a' || jsonTagName[0] > 'z') && jsonTagName != "-" && allowedNonstandardJSONNames[tp] != jsonTagName { errs = append(errs, fmt.Errorf("external types should have json names starting with lowercase letter. %#v has json tag on field %v with name %s.\n%s", gvk, f.Name, jsonTagName, fmtParentString(parents))) } errs = append(errs, ensureTags(gvk, f.Type, parents, allowedNonstandardJSONNames)...) } default: errs = append(errs, fmt.Errorf("unexpected type %v in %#v.\n%s", tp.Kind(), gvk, fmtParentString(parents))) } return errs } func fmtParentString(parents []reflect.Type) string { str := "Type parents:\n" for i, tp := range parents { str += fmt.Sprintf("%s%v\n", strings.Repeat(" ", i), tp) } return str } // containsType returns true if s contains t, false otherwise func containsType(s []reflect.Type, t reflect.Type) bool { for _, u := range s { if t == u { return true } } return false } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/roundtrip/000077500000000000000000000000001453143165200237565ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/roundtrip/compatibility.go000066400000000000000000000435361453143165200271710ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package roundtrip import ( "bytes" gojson "encoding/json" "io/ioutil" "os" "os/exec" "path/filepath" "reflect" "sort" "strings" "testing" "github.com/google/go-cmp/cmp" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" "k8s.io/apimachinery/pkg/util/sets" ) // CompatibilityTestOptions holds configuration for running a compatibility test using in-memory objects // and serialized files on disk representing the current code and serialized data from previous versions. // // Example use: `NewCompatibilityTestOptions(scheme).Complete(t).Run(t)` type CompatibilityTestOptions struct { // Scheme is used to create new objects for filling, decoding, and for constructing serializers. // Required. Scheme *runtime.Scheme // TestDataDir points to a directory containing compatibility test data. // Complete() populates this with "testdata" if unset. TestDataDir string // TestDataDirCurrentVersion points to a directory containing compatibility test data for the current version. // Complete() populates this with "/HEAD" if unset. // Within this directory, `...[json|yaml|pb]` files are required to exist, and are: // * verified to match serialized FilledObjects[GVK] // * verified to decode without error // * verified to round-trip byte-for-byte when re-encoded // * verified to be semantically equal when decoded into memory TestDataDirCurrentVersion string // TestDataDirsPreviousVersions is a list of directories containing compatibility test data for previous versions. // Complete() populates this with "/v*" directories if nil. // Within these directories, `...[json|yaml|pb]` files are optional. If present, they are: // * verified to decode without error // * verified to round-trip byte-for-byte when re-encoded (or to match a `...[json|yaml|pb].after_roundtrip.[json|yaml|pb]` file if it exists) // * verified to be semantically equal when decoded into memory TestDataDirsPreviousVersions []string // Kinds is a list of fully qualified kinds to test. // Complete() populates this with Scheme.AllKnownTypes() if unset. Kinds []schema.GroupVersionKind // FilledObjects is an optional set of pre-filled objects to use for verifying HEAD fixtures. // Complete() populates this with the result of CompatibilityTestObject(Kinds[*], Scheme, FillFuncs) for any missing kinds. // Objects must deterministically populate every field and be identical on every invocation. FilledObjects map[schema.GroupVersionKind]runtime.Object // FillFuncs is an optional map of custom functions to use to fill instances of particular types. FillFuncs map[reflect.Type]FillFunc JSON runtime.Serializer YAML runtime.Serializer Proto runtime.Serializer } // FillFunc is a function that populates all serializable fields in obj. // s and i are string and integer values relevant to the object being populated // (for example, the json key or protobuf tag containing the object) // that can be used when filling the object to make the object content identifiable type FillFunc func(s string, i int, obj interface{}) func NewCompatibilityTestOptions(scheme *runtime.Scheme) *CompatibilityTestOptions { return &CompatibilityTestOptions{Scheme: scheme} } // coreKinds includes kinds that typically only need to be tested in a single API group var coreKinds = sets.NewString( "CreateOptions", "UpdateOptions", "PatchOptions", "DeleteOptions", "GetOptions", "ListOptions", "ExportOptions", "WatchEvent", ) func (c *CompatibilityTestOptions) Complete(t *testing.T) *CompatibilityTestOptions { t.Helper() // Verify scheme if c.Scheme == nil { t.Fatal("scheme is required") } // Populate testdata dirs if c.TestDataDir == "" { c.TestDataDir = "testdata" } if c.TestDataDirCurrentVersion == "" { c.TestDataDirCurrentVersion = filepath.Join(c.TestDataDir, "HEAD") } if c.TestDataDirsPreviousVersions == nil { dirs, err := filepath.Glob(filepath.Join(c.TestDataDir, "v*")) if err != nil { t.Fatal(err) } sort.Strings(dirs) c.TestDataDirsPreviousVersions = dirs } // Populate kinds if len(c.Kinds) == 0 { gvks := []schema.GroupVersionKind{} for gvk := range c.Scheme.AllKnownTypes() { if gvk.Version == "" || gvk.Version == runtime.APIVersionInternal { // only test external types continue } if strings.HasSuffix(gvk.Kind, "List") { // omit list types continue } if gvk.Group != "" && coreKinds.Has(gvk.Kind) { // only test options types in the core API group continue } gvks = append(gvks, gvk) } c.Kinds = gvks } // Sort kinds to get deterministic test order sort.Slice(c.Kinds, func(i, j int) bool { if c.Kinds[i].Group != c.Kinds[j].Group { return c.Kinds[i].Group < c.Kinds[j].Group } if c.Kinds[i].Version != c.Kinds[j].Version { return c.Kinds[i].Version < c.Kinds[j].Version } if c.Kinds[i].Kind != c.Kinds[j].Kind { return c.Kinds[i].Kind < c.Kinds[j].Kind } return false }) // Fill any missing objects if c.FilledObjects == nil { c.FilledObjects = map[schema.GroupVersionKind]runtime.Object{} } fillFuncs := defaultFillFuncs() for k, v := range c.FillFuncs { fillFuncs[k] = v } for _, gvk := range c.Kinds { if _, ok := c.FilledObjects[gvk]; ok { continue } obj, err := CompatibilityTestObject(c.Scheme, gvk, fillFuncs) if err != nil { t.Fatal(err) } c.FilledObjects[gvk] = obj } if c.JSON == nil { c.JSON = json.NewSerializer(json.DefaultMetaFactory, c.Scheme, c.Scheme, true) } if c.YAML == nil { c.YAML = json.NewYAMLSerializer(json.DefaultMetaFactory, c.Scheme, c.Scheme) } if c.Proto == nil { c.Proto = protobuf.NewSerializer(c.Scheme, c.Scheme) } return c } func (c *CompatibilityTestOptions) Run(t *testing.T) { usedHEADFixtures := sets.NewString() for _, gvk := range c.Kinds { t.Run(makeName(gvk), func(t *testing.T) { t.Run("HEAD", func(t *testing.T) { c.runCurrentVersionTest(t, gvk, usedHEADFixtures) }) for _, previousVersionDir := range c.TestDataDirsPreviousVersions { t.Run(filepath.Base(previousVersionDir), func(t *testing.T) { c.runPreviousVersionTest(t, gvk, previousVersionDir, nil) }) } }) } // Check for unused HEAD fixtures t.Run("unused_fixtures", func(t *testing.T) { files, err := os.ReadDir(c.TestDataDirCurrentVersion) if err != nil { t.Fatal(err) } allFixtures := sets.NewString() for _, file := range files { allFixtures.Insert(file.Name()) } if unused := allFixtures.Difference(usedHEADFixtures); len(unused) > 0 { t.Fatalf("remove unused fixtures from %s:\n%s", c.TestDataDirCurrentVersion, strings.Join(unused.List(), "\n")) } }) } func (c *CompatibilityTestOptions) runCurrentVersionTest(t *testing.T, gvk schema.GroupVersionKind, usedFiles sets.String) { expectedObject := c.FilledObjects[gvk] expectedJSON, expectedYAML, expectedProto := c.encode(t, expectedObject) actualJSON, actualYAML, actualProto, err := read(c.TestDataDirCurrentVersion, gvk, "", usedFiles) if err != nil && !os.IsNotExist(err) { t.Fatal(err) } needsUpdate := false if os.IsNotExist(err) { t.Errorf("current version compatibility files did not exist: %v", err) needsUpdate = true } else { if !bytes.Equal(expectedJSON, actualJSON) { t.Errorf("json differs") t.Log(cmp.Diff(string(actualJSON), string(expectedJSON))) needsUpdate = true } if !bytes.Equal(expectedYAML, actualYAML) { t.Errorf("yaml differs") t.Log(cmp.Diff(string(actualYAML), string(expectedYAML))) needsUpdate = true } if !bytes.Equal(expectedProto, actualProto) { t.Errorf("proto differs") needsUpdate = true t.Log(cmp.Diff(dumpProto(t, actualProto[4:]), dumpProto(t, expectedProto[4:]))) // t.Logf("json (for locating the offending field based on surrounding data): %s", string(expectedJSON)) } } if needsUpdate { const updateEnvVar = "UPDATE_COMPATIBILITY_FIXTURE_DATA" if os.Getenv(updateEnvVar) == "true" { writeFile(t, c.TestDataDirCurrentVersion, gvk, "", "json", expectedJSON) writeFile(t, c.TestDataDirCurrentVersion, gvk, "", "yaml", expectedYAML) writeFile(t, c.TestDataDirCurrentVersion, gvk, "", "pb", expectedProto) t.Logf("wrote expected compatibility data... verify, commit, and rerun tests") } else { t.Logf("if the diff is expected because of a new type or a new field, re-run with %s=true to update the compatibility data", updateEnvVar) } return } emptyObj, err := c.Scheme.New(gvk) if err != nil { t.Fatal(err) } { // compact before decoding since embedded RawExtension fields retain indenting compacted := &bytes.Buffer{} if err := gojson.Compact(compacted, actualJSON); err != nil { t.Error(err) } jsonDecoded := emptyObj.DeepCopyObject() jsonDecoded, _, err = c.JSON.Decode(compacted.Bytes(), &gvk, jsonDecoded) if err != nil { t.Error(err) } else if !apiequality.Semantic.DeepEqual(expectedObject, jsonDecoded) { t.Errorf("expected and decoded json objects differed:\n%s", cmp.Diff(expectedObject, jsonDecoded)) } } { yamlDecoded := emptyObj.DeepCopyObject() yamlDecoded, _, err = c.YAML.Decode(actualYAML, &gvk, yamlDecoded) if err != nil { t.Error(err) } else if !apiequality.Semantic.DeepEqual(expectedObject, yamlDecoded) { t.Errorf("expected and decoded yaml objects differed:\n%s", cmp.Diff(expectedObject, yamlDecoded)) } } { protoDecoded := emptyObj.DeepCopyObject() protoDecoded, _, err = c.Proto.Decode(actualProto, &gvk, protoDecoded) if err != nil { t.Error(err) } else if !apiequality.Semantic.DeepEqual(expectedObject, protoDecoded) { t.Errorf("expected and decoded proto objects differed:\n%s", cmp.Diff(expectedObject, protoDecoded)) } } } func (c *CompatibilityTestOptions) encode(t *testing.T, obj runtime.Object) (json, yaml, proto []byte) { jsonBytes := bytes.NewBuffer(nil) if err := c.JSON.Encode(obj, jsonBytes); err != nil { t.Fatalf("error encoding json: %v", err) } yamlBytes := bytes.NewBuffer(nil) if err := c.YAML.Encode(obj, yamlBytes); err != nil { t.Fatalf("error encoding yaml: %v", err) } protoBytes := bytes.NewBuffer(nil) if err := c.Proto.Encode(obj, protoBytes); err != nil { t.Fatalf("error encoding proto: %v", err) } return jsonBytes.Bytes(), yamlBytes.Bytes(), protoBytes.Bytes() } func read(dir string, gvk schema.GroupVersionKind, suffix string, usedFiles sets.String) (json, yaml, proto []byte, err error) { jsonFilename := makeName(gvk) + suffix + ".json" actualJSON, jsonErr := ioutil.ReadFile(filepath.Join(dir, jsonFilename)) yamlFilename := makeName(gvk) + suffix + ".yaml" actualYAML, yamlErr := ioutil.ReadFile(filepath.Join(dir, yamlFilename)) protoFilename := makeName(gvk) + suffix + ".pb" actualProto, protoErr := ioutil.ReadFile(filepath.Join(dir, protoFilename)) if usedFiles != nil { usedFiles.Insert(jsonFilename) usedFiles.Insert(yamlFilename) usedFiles.Insert(protoFilename) } if jsonErr != nil { return actualJSON, actualYAML, actualProto, jsonErr } if yamlErr != nil { return actualJSON, actualYAML, actualProto, yamlErr } if protoErr != nil { return actualJSON, actualYAML, actualProto, protoErr } return actualJSON, actualYAML, actualProto, nil } func writeFile(t *testing.T, dir string, gvk schema.GroupVersionKind, suffix, extension string, data []byte) { if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil { t.Fatal("error making directory", err) } if err := ioutil.WriteFile(filepath.Join(dir, makeName(gvk)+suffix+"."+extension), data, os.FileMode(0644)); err != nil { t.Fatalf("error writing %s: %v", extension, err) } } func deleteFile(t *testing.T, dir string, gvk schema.GroupVersionKind, suffix, extension string) { if err := os.Remove(filepath.Join(dir, makeName(gvk)+suffix+"."+extension)); err != nil { t.Fatalf("error removing %s: %v", extension, err) } } func (c *CompatibilityTestOptions) runPreviousVersionTest(t *testing.T, gvk schema.GroupVersionKind, previousVersionDir string, usedFiles sets.String) { jsonBeforeRoundTrip, yamlBeforeRoundTrip, protoBeforeRoundTrip, err := read(previousVersionDir, gvk, "", usedFiles) if os.IsNotExist(err) || (len(jsonBeforeRoundTrip) == 0 && len(yamlBeforeRoundTrip) == 0 && len(protoBeforeRoundTrip) == 0) { t.SkipNow() return } if err != nil { t.Fatal(err) } emptyObj, err := c.Scheme.New(gvk) if err != nil { t.Fatal(err) } // compact before decoding since embedded RawExtension fields retain indenting compacted := &bytes.Buffer{} if err := gojson.Compact(compacted, jsonBeforeRoundTrip); err != nil { t.Fatal(err) } jsonDecoded := emptyObj.DeepCopyObject() jsonDecoded, _, err = c.JSON.Decode(compacted.Bytes(), &gvk, jsonDecoded) if err != nil { t.Fatal(err) } jsonBytes := bytes.NewBuffer(nil) if err := c.JSON.Encode(jsonDecoded, jsonBytes); err != nil { t.Fatalf("error encoding json: %v", err) } jsonAfterRoundTrip := jsonBytes.Bytes() yamlDecoded := emptyObj.DeepCopyObject() yamlDecoded, _, err = c.YAML.Decode(yamlBeforeRoundTrip, &gvk, yamlDecoded) if err != nil { t.Fatal(err) } else if !apiequality.Semantic.DeepEqual(jsonDecoded, yamlDecoded) { t.Errorf("decoded json and yaml objects differ:\n%s", cmp.Diff(jsonDecoded, yamlDecoded)) } yamlBytes := bytes.NewBuffer(nil) if err := c.YAML.Encode(yamlDecoded, yamlBytes); err != nil { t.Fatalf("error encoding yaml: %v", err) } yamlAfterRoundTrip := yamlBytes.Bytes() protoDecoded := emptyObj.DeepCopyObject() protoDecoded, _, err = c.Proto.Decode(protoBeforeRoundTrip, &gvk, protoDecoded) if err != nil { t.Fatal(err) } else if !apiequality.Semantic.DeepEqual(jsonDecoded, protoDecoded) { t.Errorf("decoded json and proto objects differ:\n%s", cmp.Diff(jsonDecoded, protoDecoded)) } protoBytes := bytes.NewBuffer(nil) if err := c.Proto.Encode(protoDecoded, protoBytes); err != nil { t.Fatalf("error encoding proto: %v", err) } protoAfterRoundTrip := protoBytes.Bytes() jsonNeedsRemove := false yamlNeedsRemove := false protoNeedsRemove := false expectedJSONAfterRoundTrip, expectedYAMLAfterRoundTrip, expectedProtoAfterRoundTrip, _ := read(previousVersionDir, gvk, ".after_roundtrip", usedFiles) if len(expectedJSONAfterRoundTrip) == 0 { expectedJSONAfterRoundTrip = jsonBeforeRoundTrip } else if bytes.Equal(jsonBeforeRoundTrip, expectedJSONAfterRoundTrip) { t.Errorf("JSON after_roundtrip file is identical and should be removed") jsonNeedsRemove = true } if len(expectedYAMLAfterRoundTrip) == 0 { expectedYAMLAfterRoundTrip = yamlBeforeRoundTrip } else if bytes.Equal(yamlBeforeRoundTrip, expectedYAMLAfterRoundTrip) { t.Errorf("YAML after_roundtrip file is identical and should be removed") yamlNeedsRemove = true } if len(expectedProtoAfterRoundTrip) == 0 { expectedProtoAfterRoundTrip = protoBeforeRoundTrip } else if bytes.Equal(protoBeforeRoundTrip, expectedProtoAfterRoundTrip) { t.Errorf("Proto after_roundtrip file is identical and should be removed") protoNeedsRemove = true } jsonNeedsUpdate := false yamlNeedsUpdate := false protoNeedsUpdate := false if !bytes.Equal(expectedJSONAfterRoundTrip, jsonAfterRoundTrip) { t.Errorf("json differs") t.Log(cmp.Diff(string(expectedJSONAfterRoundTrip), string(jsonAfterRoundTrip))) jsonNeedsUpdate = true } if !bytes.Equal(expectedYAMLAfterRoundTrip, yamlAfterRoundTrip) { t.Errorf("yaml differs") t.Log(cmp.Diff(string(expectedYAMLAfterRoundTrip), string(yamlAfterRoundTrip))) yamlNeedsUpdate = true } if !bytes.Equal(expectedProtoAfterRoundTrip, protoAfterRoundTrip) { t.Errorf("proto differs") protoNeedsUpdate = true t.Log(cmp.Diff(dumpProto(t, expectedProtoAfterRoundTrip[4:]), dumpProto(t, protoAfterRoundTrip[4:]))) // t.Logf("json (for locating the offending field based on surrounding data): %s", string(expectedJSON)) } if jsonNeedsUpdate || yamlNeedsUpdate || protoNeedsUpdate || jsonNeedsRemove || yamlNeedsRemove || protoNeedsRemove { const updateEnvVar = "UPDATE_COMPATIBILITY_FIXTURE_DATA" if os.Getenv(updateEnvVar) == "true" { if jsonNeedsUpdate { writeFile(t, previousVersionDir, gvk, ".after_roundtrip", "json", jsonAfterRoundTrip) } else if jsonNeedsRemove { deleteFile(t, previousVersionDir, gvk, ".after_roundtrip", "json") } if yamlNeedsUpdate { writeFile(t, previousVersionDir, gvk, ".after_roundtrip", "yaml", yamlAfterRoundTrip) } else if yamlNeedsRemove { deleteFile(t, previousVersionDir, gvk, ".after_roundtrip", "yaml") } if protoNeedsUpdate { writeFile(t, previousVersionDir, gvk, ".after_roundtrip", "pb", protoAfterRoundTrip) } else if protoNeedsRemove { deleteFile(t, previousVersionDir, gvk, ".after_roundtrip", "pb") } t.Logf("wrote expected compatibility data... verify, commit, and rerun tests") } else { t.Logf("if the diff is expected because of a new type or a new field, re-run with %s=true to update the compatibility data", updateEnvVar) } return } } func makeName(gvk schema.GroupVersionKind) string { g := gvk.Group if g == "" { g = "core" } return g + "." + gvk.Version + "." + gvk.Kind } func dumpProto(t *testing.T, data []byte) string { t.Helper() protoc, err := exec.LookPath("protoc") if err != nil { t.Log(err) return "" } cmd := exec.Command(protoc, "--decode_raw") cmd.Stdin = bytes.NewBuffer(data) d, err := cmd.CombinedOutput() if err != nil { t.Log(err) return "" } return string(d) } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/roundtrip/construct.go000066400000000000000000000147321453143165200263400ustar00rootroot00000000000000/* Copyright 2022 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 ( "fmt" "reflect" "strconv" "strings" "time" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" ) func defaultFillFuncs() map[reflect.Type]FillFunc { funcs := map[reflect.Type]FillFunc{} funcs[reflect.TypeOf(&runtime.RawExtension{})] = func(s string, i int, obj interface{}) { // generate a raw object in normalized form // TODO: test non-normalized round-tripping... YAMLToJSON normalizes and makes exact comparisons fail obj.(*runtime.RawExtension).Raw = []byte(`{"apiVersion":"example.com/v1","kind":"CustomType","spec":{"replicas":1},"status":{"available":1}}`) } funcs[reflect.TypeOf(&metav1.TypeMeta{})] = func(s string, i int, obj interface{}) { // APIVersion and Kind are not serialized in all formats (notably protobuf), so clear by default for cross-format checking. obj.(*metav1.TypeMeta).APIVersion = "" obj.(*metav1.TypeMeta).Kind = "" } funcs[reflect.TypeOf(&metav1.FieldsV1{})] = func(s string, i int, obj interface{}) { obj.(*metav1.FieldsV1).Raw = []byte(`{}`) } funcs[reflect.TypeOf(&metav1.Time{})] = func(s string, i int, obj interface{}) { // use the integer as an offset from the year obj.(*metav1.Time).Time = time.Date(2000+i, 1, 1, 1, 1, 1, 0, time.UTC) } funcs[reflect.TypeOf(&metav1.MicroTime{})] = func(s string, i int, obj interface{}) { // use the integer as an offset from the year, and as a microsecond obj.(*metav1.MicroTime).Time = time.Date(2000+i, 1, 1, 1, 1, 1, i*int(time.Microsecond), time.UTC) } funcs[reflect.TypeOf(&intstr.IntOrString{})] = func(s string, i int, obj interface{}) { // use the string as a string value obj.(*intstr.IntOrString).Type = intstr.String obj.(*intstr.IntOrString).StrVal = s + "Value" } return funcs } // CompatibilityTestObject returns a deterministically filled object for the specified GVK func CompatibilityTestObject(scheme *runtime.Scheme, gvk schema.GroupVersionKind, fillFuncs map[reflect.Type]FillFunc) (runtime.Object, error) { // Construct the object obj, err := scheme.New(gvk) if err != nil { return nil, err } fill("", 0, reflect.TypeOf(obj), reflect.ValueOf(obj), fillFuncs, map[reflect.Type]bool{}) // Set the kind and apiVersion if typeAcc, err := apimeta.TypeAccessor(obj); err != nil { return nil, err } else { typeAcc.SetKind(gvk.Kind) typeAcc.SetAPIVersion(gvk.GroupVersion().String()) } return obj, nil } func fill(dataString string, dataInt int, t reflect.Type, v reflect.Value, fillFuncs map[reflect.Type]FillFunc, filledTypes map[reflect.Type]bool) { if filledTypes[t] { // we already filled this type, avoid recursing infinitely return } filledTypes[t] = true defer delete(filledTypes, t) // if nil, populate pointers with a zero-value instance of the underlying type if t.Kind() == reflect.Pointer && v.IsNil() { if v.CanSet() { v.Set(reflect.New(t.Elem())) } else if v.IsNil() { panic(fmt.Errorf("unsettable nil pointer of type %v in field %s", t, dataString)) } } if f, ok := fillFuncs[t]; ok { // use the custom fill function for this type f(dataString, dataInt, v.Interface()) return } switch t.Kind() { case reflect.Slice: // populate with a single-item slice v.Set(reflect.MakeSlice(t, 1, 1)) // recurse to populate the item, preserving the data context if t.Elem().Kind() == reflect.Pointer { fill(dataString, dataInt, t.Elem(), v.Index(0), fillFuncs, filledTypes) } else { fill(dataString, dataInt, reflect.PointerTo(t.Elem()), v.Index(0).Addr(), fillFuncs, filledTypes) } case reflect.Map: // construct the key, which must be a string type, possibly converted to a type alias of string key := reflect.ValueOf(dataString + "Key").Convert(t.Key()) // construct a zero-value item item := reflect.New(t.Elem()) // recurse to populate the item, preserving the data context fill(dataString, dataInt, t.Elem(), item.Elem(), fillFuncs, filledTypes) // store in the map v.Set(reflect.MakeMap(t)) v.SetMapIndex(key, item.Elem()) case reflect.Struct: for i := 0; i < t.NumField(); i++ { field := t.Field(i) if !field.IsExported() { continue } // use the json field name, which must be stable dataString := strings.Split(field.Tag.Get("json"), ",")[0] if dataString == "-" { // unserialized field, no need to fill it continue } if len(dataString) == 0 { // fall back to the struct field name if there is no json field name dataString = " " + field.Name } // use the protobuf tag, which must be stable dataInt := 0 if protobufTagParts := strings.Split(field.Tag.Get("protobuf"), ","); len(protobufTagParts) > 1 { if tag, err := strconv.Atoi(protobufTagParts[1]); err != nil { panic(err) } else { dataInt = tag } } if dataInt == 0 { // fall back to the length of dataString as a backup dataInt = -len(dataString) } fieldType := field.Type fieldValue := v.Field(i) fill(dataString, dataInt, reflect.PointerTo(fieldType), fieldValue.Addr(), fillFuncs, filledTypes) } case reflect.Pointer: fill(dataString, dataInt, t.Elem(), v.Elem(), fillFuncs, filledTypes) case reflect.String: // use Convert to set into string alias types correctly v.Set(reflect.ValueOf(dataString + "Value").Convert(t)) case reflect.Bool: // set to true to ensure we serialize omitempty fields v.Set(reflect.ValueOf(true).Convert(t)) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // use Convert to set into int alias types and different int widths correctly v.Set(reflect.ValueOf(dataInt).Convert(t)) case reflect.Float32, reflect.Float64: // use Convert to set into float types v.Set(reflect.ValueOf(float32(dataInt) + 0.5).Convert(t)) default: panic(fmt.Errorf("unhandled type %v in field %s", t, dataString)) } } golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/roundtrip/fuzz_norace.go000066400000000000000000000013271453143165200266350ustar00rootroot00000000000000//go:build !race // +build !race /* Copyright 2021 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 // in non-race-detection mode, a higher number of iterations is reasonable const defaultFuzzIters = 20 golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/roundtrip/fuzz_race.go000066400000000000000000000013451453143165200263000ustar00rootroot00000000000000//go:build race // +build race /* Copyright 2021 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 // in race-detection mode, lower the number of iterations to keep reasonable runtimes in CI const defaultFuzzIters = 5 golang-k8s-apimachinery-0.29.0/pkg/api/apitesting/roundtrip/roundtrip.go000066400000000000000000000414771453143165200263500ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package roundtrip import ( "bytes" "encoding/hex" "math/rand" "reflect" "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" apitesting "k8s.io/apimachinery/pkg/api/apitesting" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" apiequality "k8s.io/apimachinery/pkg/api/equality" apimeta "k8s.io/apimachinery/pkg/api/meta" metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/sets" ) type InstallFunc func(scheme *runtime.Scheme) // RoundTripTestForAPIGroup is convenient to call from your install package to make sure that a "bare" install of your group provides // enough information to round trip func RoundTripTestForAPIGroup(t *testing.T, installFn InstallFunc, fuzzingFuncs fuzzer.FuzzerFuncs) { scheme := runtime.NewScheme() installFn(scheme) RoundTripTestForScheme(t, scheme, fuzzingFuncs) } // RoundTripTestForScheme is convenient to call if you already have a scheme and want to make sure that its well-formed func RoundTripTestForScheme(t *testing.T, scheme *runtime.Scheme, fuzzingFuncs fuzzer.FuzzerFuncs) { codecFactory := runtimeserializer.NewCodecFactory(scheme) f := fuzzer.FuzzerFor( fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, fuzzingFuncs), rand.NewSource(rand.Int63()), codecFactory, ) RoundTripTypesWithoutProtobuf(t, scheme, codecFactory, f, nil) } // RoundTripProtobufTestForAPIGroup is convenient to call from your install package to make sure that a "bare" install of your group provides // enough information to round trip func RoundTripProtobufTestForAPIGroup(t *testing.T, installFn InstallFunc, fuzzingFuncs fuzzer.FuzzerFuncs) { scheme := runtime.NewScheme() installFn(scheme) RoundTripProtobufTestForScheme(t, scheme, fuzzingFuncs) } // RoundTripProtobufTestForScheme is convenient to call if you already have a scheme and want to make sure that its well-formed func RoundTripProtobufTestForScheme(t *testing.T, scheme *runtime.Scheme, fuzzingFuncs fuzzer.FuzzerFuncs) { codecFactory := runtimeserializer.NewCodecFactory(scheme) fuzzer := fuzzer.FuzzerFor( fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, fuzzingFuncs), rand.NewSource(rand.Int63()), codecFactory, ) RoundTripTypes(t, scheme, codecFactory, fuzzer, nil) } var FuzzIters = flag.Int("fuzz-iters", defaultFuzzIters, "How many fuzzing iterations to do.") // globalNonRoundTrippableTypes are kinds that are effectively reserved across all GroupVersions // They don't roundtrip var globalNonRoundTrippableTypes = sets.NewString( "ExportOptions", "GetOptions", // WatchEvent does not include kind and version and can only be deserialized // implicitly (if the caller expects the specific object). The watch call defines // the schema by content type, rather than via kind/version included in each // object. "WatchEvent", // ListOptions is now part of the meta group "ListOptions", // Delete options is only read in metav1 "DeleteOptions", ) // GlobalNonRoundTrippableTypes returns the kinds that are effectively reserved across all GroupVersions. // They don't roundtrip and thus can be excluded in any custom/downstream roundtrip tests // // kinds := scheme.AllKnownTypes() // for gvk := range kinds { // if roundtrip.GlobalNonRoundTrippableTypes().Has(gvk.Kind) { // continue // } // t.Run(gvk.Group+"."+gvk.Version+"."+gvk.Kind, func(t *testing.T) { // // roundtrip test // }) // } func GlobalNonRoundTrippableTypes() sets.String { return sets.NewString(globalNonRoundTrippableTypes.List()...) } // 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) { 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) { 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) { for _, group := range groupsFromScheme(scheme) { t.Logf("starting group %q", group) internalVersion := schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal} internalKindToGoType := scheme.KnownTypes(internalVersion) for kind := range internalKindToGoType { if globalNonRoundTrippableTypes.Has(kind) { continue } internalGVK := internalVersion.WithKind(kind) roundTripSpecificKind(t, internalGVK, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, skipProtobuf) } t.Logf("finished group %q", group) } } // 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) { kinds := scheme.AllKnownTypes() for gvk := range kinds { if gvk.Version == runtime.APIVersionInternal || globalNonRoundTrippableTypes.Has(gvk.Kind) { continue } t.Run(gvk.Group+"."+gvk.Version+"."+gvk.Kind, func(t *testing.T) { roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, false) }) } } // 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) { kinds := scheme.AllKnownTypes() for gvk := range kinds { if gvk.Version == runtime.APIVersionInternal || globalNonRoundTrippableTypes.Has(gvk.Kind) { continue } t.Run(gvk.Group+"."+gvk.Version+"."+gvk.Kind, func(t *testing.T) { roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, true) }) } } func RoundTripSpecificKindWithoutProtobuf(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, 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) { 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) { if nonRoundTrippableTypes[gvk] { t.Logf("skipping %v", gvk) return } // Try a few times, since runTest uses random values. for i := 0; i < *FuzzIters; i++ { if gvk.Version == runtime.APIVersionInternal { roundTripToAllExternalVersions(t, scheme, codecFactory, fuzzer, gvk, nonRoundTrippableTypes, skipProtobuf) } else { roundTripOfExternalType(t, scheme, codecFactory, fuzzer, gvk, skipProtobuf) } if t.Failed() { break } } } // 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) j, err := apimeta.TypeAccessor(object) if err != nil { t.Fatalf("Unexpected error %v for %#v", err, object) } j.SetKind("") j.SetAPIVersion("") return object } func groupsFromScheme(scheme *runtime.Scheme) []string { ret := sets.String{} for gvk := range scheme.AllKnownTypes() { ret.Insert(gvk.Group) } 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) { object, err := scheme.New(internalGVK) if err != nil { t.Fatalf("Couldn't make a %v? %v", internalGVK, err) } if _, err := apimeta.TypeAccessor(object); err != nil { t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v", internalGVK, err) } fuzzInternalObject(t, fuzzer, object) // find all potential serializations in the scheme. // TODO fix this up to handle kinds that cross registered with different names. for externalGVK, externalGoType := range scheme.AllKnownTypes() { if externalGVK.Version == runtime.APIVersionInternal { continue } if externalGVK.GroupKind() != internalGVK.GroupKind() { continue } if nonRoundTrippableTypes[externalGVK] { t.Logf("\tskipping %v %v", externalGVK, externalGoType) continue } t.Logf("\tround tripping to %v %v", externalGVK, externalGoType) roundTrip(t, scheme, apitesting.TestCodec(codecFactory, externalGVK.GroupVersion()), object) // TODO remove this hack after we're past the intermediate steps if !skipProtobuf && externalGVK.Group != "kubeadm.k8s.io" { s := protobuf.NewSerializer(scheme, scheme) protobufCodec := codecFactory.CodecForVersions(s, s, externalGVK.GroupVersion(), nil) roundTrip(t, scheme, protobufCodec, object) } } } func roundTripOfExternalType(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *fuzz.Fuzzer, externalGVK schema.GroupVersionKind, skipProtobuf bool) { object, err := scheme.New(externalGVK) if err != nil { t.Fatalf("Couldn't make a %v? %v", externalGVK, err) } typeAcc, err := apimeta.TypeAccessor(object) if err != nil { t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v", externalGVK, err) } fuzzInternalObject(t, fuzzer, object) typeAcc.SetKind(externalGVK.Kind) typeAcc.SetAPIVersion(externalGVK.GroupVersion().String()) roundTrip(t, scheme, json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false), object) // TODO remove this hack after we're past the intermediate steps if !skipProtobuf { roundTrip(t, scheme, protobuf.NewSerializer(scheme, scheme), object) } } // roundTrip applies a single round-trip test to the given runtime object // using the given codec. The round-trip test ensures that an object can be // deep-copied, converted, marshaled and back without loss of data. // // For internal types this means // // internal -> external -> json/protobuf -> external -> internal. // // For external types this means // // external -> json/protobuf -> external. func roundTrip(t *testing.T, scheme *runtime.Scheme, codec runtime.Codec, object runtime.Object) { original := object // deep copy the original object object = object.DeepCopyObject() name := reflect.TypeOf(object).Elem().Name() if !apiequality.Semantic.DeepEqual(original, object) { t.Errorf("%v: DeepCopy altered the object, diff: %v", name, cmp.Diff(original, object)) t.Errorf("%s", dump.Pretty(original)) t.Errorf("%s", dump.Pretty(object)) return } // encode (serialize) the deep copy using the provided codec data, err := runtime.Encode(codec, object) if err != nil { if runtime.IsNotRegisteredError(err) { t.Logf("%v: not registered: %v (%s)", name, err, dump.Pretty(object)) } else { t.Errorf("%v: %v (%s)", name, err, dump.Pretty(object)) } return } // ensure that the deep copy is equal to the original; neither the deep // copy or conversion should alter the object // TODO eliminate this global if !apiequality.Semantic.DeepEqual(original, object) { t.Errorf("%v: encode altered the object, diff: %v", name, cmp.Diff(original, object)) return } // encode (serialize) a second time to verify that it was not varying secondData, err := runtime.Encode(codec, object) if err != nil { if runtime.IsNotRegisteredError(err) { t.Logf("%v: not registered: %v (%s)", name, err, dump.Pretty(object)) } else { t.Errorf("%v: %v (%s)", name, err, dump.Pretty(object)) } return } // serialization to the wire must be stable to ensure that we don't write twice to the DB // when the object hasn't changed. if !bytes.Equal(data, secondData) { t.Errorf("%v: serialization is not stable: %s", name, dump.Pretty(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)) 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)) return } // decode the encoded data into a new object (instead of letting the codec // create a new object) obj3 := reflect.New(reflect.TypeOf(object).Elem()).Interface().(runtime.Object) if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Errorf("%v: %v", name, err) return } // special case for kinds which are internal and external at the same time (many in meta.k8s.io are). For those // runtime.DecodeInto above will return the external variant and set the APIVersion and kind, while the input // object might be internal. Hence, we clear those values for obj3 for that case to correctly compare. intAndExt, err := internalAndExternalKind(scheme, object) if err != nil { t.Errorf("%v: %v", name, err) return } if intAndExt { typeAcc, err := apimeta.TypeAccessor(object) if err != nil { t.Fatalf("%v: error accessing TypeMeta: %v", name, err) } if len(typeAcc.GetAPIVersion()) == 0 { typeAcc, err := apimeta.TypeAccessor(obj3) if err != nil { t.Fatalf("%v: error accessing TypeMeta: %v", name, err) } typeAcc.SetAPIVersion("") typeAcc.SetKind("") } } // ensure that the new runtime object is equal to the original after being // decoded into if !apiequality.Semantic.DeepEqual(object, obj3) { t.Errorf("%v: diff: %v\nCodec: %#v", name, cmp.Diff(object, obj3), codec) return } // do structure-preserving fuzzing of the deep-copied object. If it shares anything with the original, // the deep-copy was actually only a shallow copy. Then original and obj3 will be different after fuzzing. // NOTE: we use the encoding+decoding here as an alternative, guaranteed deep-copy to compare against. fuzzer.ValueFuzz(object) if !apiequality.Semantic.DeepEqual(original, obj3) { t.Errorf("%v: fuzzing a copy altered the original, diff: %v", name, cmp.Diff(original, obj3)) return } } func internalAndExternalKind(scheme *runtime.Scheme, object runtime.Object) (bool, error) { kinds, _, err := scheme.ObjectKinds(object) if err != nil { return false, err } internal, external := false, false for _, k := range kinds { if k.Version == runtime.APIVersionInternal { internal = true } else { external = true } } return internal && external, nil } // dataAsString returns the given byte array as a string; handles detecting // protocol buffers. 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 } golang-k8s-apimachinery-0.29.0/pkg/api/equality/000077500000000000000000000000001453143165200214165ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/equality/semantic.go000066400000000000000000000030251453143165200235500ustar00rootroot00000000000000/* 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 equality import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" ) // Semantic can do semantic deep equality checks for api objects. // Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true var Semantic = conversion.EqualitiesOrDie( func(a, b resource.Quantity) bool { // Ignore formatting, only care that numeric value stayed the same. // TODO: if we decide it's important, it should be safe to start comparing the format. // // Uninitialized quantities are equivalent to 0 quantities. return a.Cmp(b) == 0 }, func(a, b metav1.MicroTime) bool { return a.UTC() == b.UTC() }, func(a, b metav1.Time) bool { return a.UTC() == b.UTC() }, func(a, b labels.Selector) bool { return a.String() == b.String() }, func(a, b fields.Selector) bool { return a.String() == b.String() }, ) golang-k8s-apimachinery-0.29.0/pkg/api/errors/000077500000000000000000000000001453143165200210755ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/errors/OWNERS000066400000000000000000000003631453143165200220370ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - thockin - smarterclayton - wojtek-t - deads2k - derekwaynecarr - caesarxuchao - mikedanese - liggitt - saad-ali - janetkuo - tallclair - dims - cjcullen golang-k8s-apimachinery-0.29.0/pkg/api/errors/doc.go000066400000000000000000000013031453143165200221660ustar00rootroot00000000000000/* 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 errors provides detailed error types for api field validation. package errors // import "k8s.io/apimachinery/pkg/api/errors" golang-k8s-apimachinery-0.29.0/pkg/api/errors/errors.go000066400000000000000000000750351453143165200227520ustar00rootroot00000000000000/* 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 errors import ( "encoding/json" "errors" "fmt" "net/http" "reflect" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" ) // StatusError is an error intended for consumption by a REST API server; it can also be // reconstructed by clients from a REST response. Public to allow easy type switches. type StatusError struct { ErrStatus metav1.Status } // APIStatus is exposed by errors that can be converted to an api.Status object // for finer grained details. type APIStatus interface { Status() metav1.Status } var _ error = &StatusError{} var knownReasons = map[metav1.StatusReason]struct{}{ // metav1.StatusReasonUnknown : {} metav1.StatusReasonUnauthorized: {}, metav1.StatusReasonForbidden: {}, metav1.StatusReasonNotFound: {}, metav1.StatusReasonAlreadyExists: {}, metav1.StatusReasonConflict: {}, metav1.StatusReasonGone: {}, metav1.StatusReasonInvalid: {}, metav1.StatusReasonServerTimeout: {}, metav1.StatusReasonTimeout: {}, metav1.StatusReasonTooManyRequests: {}, metav1.StatusReasonBadRequest: {}, metav1.StatusReasonMethodNotAllowed: {}, metav1.StatusReasonNotAcceptable: {}, metav1.StatusReasonRequestEntityTooLarge: {}, metav1.StatusReasonUnsupportedMediaType: {}, metav1.StatusReasonInternalError: {}, metav1.StatusReasonExpired: {}, metav1.StatusReasonServiceUnavailable: {}, } // Error implements the Error interface. func (e *StatusError) Error() string { return e.ErrStatus.Message } // Status allows access to e's status without having to know the detailed workings // of StatusError. func (e *StatusError) Status() metav1.Status { return e.ErrStatus } // DebugError reports extended info about the error to debug output. func (e *StatusError) DebugError() (string, []interface{}) { if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil { return "server response object: %s", []interface{}{string(out)} } return "server response object: %#v", []interface{}{e.ErrStatus} } // HasStatusCause returns true if the provided error has a details cause // with the provided type name. // It supports wrapped errors and returns false when the error is nil. func HasStatusCause(err error, name metav1.CauseType) bool { _, ok := StatusCause(err, name) return ok } // StatusCause returns the named cause from the provided error if it exists and // the error unwraps to the type APIStatus. Otherwise it returns false. func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) { status, ok := err.(APIStatus) if (ok || errors.As(err, &status)) && status.Status().Details != nil { for _, cause := range status.Status().Details.Causes { if cause.Type == name { return cause, true } } } return metav1.StatusCause{}, false } // UnexpectedObjectError can be returned by FromObject if it's passed a non-status object. type UnexpectedObjectError struct { Object runtime.Object } // Error returns an error message describing 'u'. func (u *UnexpectedObjectError) Error() string { return fmt.Sprintf("unexpected object: %v", u.Object) } // FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise, // returns an UnexpecteObjectError. func FromObject(obj runtime.Object) error { switch t := obj.(type) { case *metav1.Status: return &StatusError{ErrStatus: *t} case runtime.Unstructured: var status metav1.Status obj := t.UnstructuredContent() if !reflect.DeepEqual(obj["kind"], "Status") { break } if err := runtime.DefaultUnstructuredConverter.FromUnstructured(t.UnstructuredContent(), &status); err != nil { return err } if status.APIVersion != "v1" && status.APIVersion != "meta.k8s.io/v1" { break } return &StatusError{ErrStatus: status} } return &UnexpectedObjectError{obj} } // NewNotFound returns a new error which indicates that the resource of the kind and the name was not found. func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusNotFound, Reason: metav1.StatusReasonNotFound, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name), }} } // NewAlreadyExists returns an error indicating the item requested exists by that identifier. func NewAlreadyExists(qualifiedResource schema.GroupResource, name string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonAlreadyExists, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name), }} } // NewGenerateNameConflict returns an error indicating the server // was not able to generate a valid name for a resource. func NewGenerateNameConflict(qualifiedResource schema.GroupResource, name string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonAlreadyExists, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, RetryAfterSeconds: int32(retryAfterSeconds), }, Message: fmt.Sprintf( "%s %q already exists, the server was not able to generate a unique name for the object", qualifiedResource.String(), name), }} } // NewUnauthorized returns an error indicating the client is not authorized to perform the requested // action. func NewUnauthorized(reason string) *StatusError { message := reason if len(message) == 0 { message = "not authorized" } return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusUnauthorized, Reason: metav1.StatusReasonUnauthorized, Message: message, }} } // NewForbidden returns an error indicating the requested action was forbidden func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError { var message string if qualifiedResource.Empty() { message = fmt.Sprintf("forbidden: %v", err) } else if name == "" { message = fmt.Sprintf("%s is forbidden: %v", qualifiedResource.String(), err) } else { message = fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err) } return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusForbidden, Reason: metav1.StatusReasonForbidden, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: message, }} } // NewConflict returns an error indicating the item can't be updated as provided. func NewConflict(qualifiedResource schema.GroupResource, name string, err error) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, }, Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err), }} } // NewApplyConflict returns an error including details on the requests apply conflicts func NewApplyConflict(causes []metav1.StatusCause, message string) *StatusError { return &StatusError{ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ // TODO: Get obj details here? Causes: causes, }, Message: message, }} } // NewGone returns an error indicating the item no longer available at the server and no forwarding address is known. // DEPRECATED: Please use NewResourceExpired instead. func NewGone(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusGone, Reason: metav1.StatusReasonGone, Message: message, }} } // NewResourceExpired creates an error that indicates that the requested resource content has expired from // the server (usually due to a resourceVersion that is too old). func NewResourceExpired(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusGone, Reason: metav1.StatusReasonExpired, Message: message, }} } // NewInvalid returns an error indicating the item is invalid and cannot be processed. func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError { causes := make([]metav1.StatusCause, 0, len(errs)) for i := range errs { err := errs[i] causes = append(causes, metav1.StatusCause{ Type: metav1.CauseType(err.Type), Message: err.ErrorBody(), Field: err.Field, }) } err := &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusUnprocessableEntity, Reason: metav1.StatusReasonInvalid, Details: &metav1.StatusDetails{ Group: qualifiedKind.Group, Kind: qualifiedKind.Kind, Name: name, Causes: causes, }, }} aggregatedErrs := errs.ToAggregate() if aggregatedErrs == nil { err.ErrStatus.Message = fmt.Sprintf("%s %q is invalid", qualifiedKind.String(), name) } else { err.ErrStatus.Message = fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, aggregatedErrs) } return err } // NewBadRequest creates an error that indicates that the request is invalid and can not be processed. func NewBadRequest(reason string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, Message: reason, }} } // NewTooManyRequests creates an error that indicates that the client must try again later because // the specified endpoint is not accepting requests. More specific details should be provided // if client should know why the failure was limited. func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusTooManyRequests, Reason: metav1.StatusReasonTooManyRequests, Message: message, Details: &metav1.StatusDetails{ RetryAfterSeconds: int32(retryAfterSeconds), }, }} } // NewServiceUnavailable creates an error that indicates that the requested service is unavailable. func NewServiceUnavailable(reason string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusServiceUnavailable, Reason: metav1.StatusReasonServiceUnavailable, Message: reason, }} } // NewMethodNotSupported returns an error indicating the requested action is not supported on this kind. func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusMethodNotAllowed, Reason: metav1.StatusReasonMethodNotAllowed, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, }, Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()), }} } // NewServerTimeout returns an error indicating the requested action could not be completed due to a // transient error, and the client should try again. func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonServerTimeout, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: operation, RetryAfterSeconds: int32(retryAfterSeconds), }, Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()), }} } // NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we // happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this. func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError { return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds) } // NewInternalError returns an error indicating the item is invalid and cannot be processed. func NewInternalError(err error) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError, Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{{Message: err.Error()}}, }, Message: fmt.Sprintf("Internal error occurred: %v", err), }} } // NewTimeoutError returns an error indicating that a timeout occurred before the request // could be completed. Clients may retry, but the operation may still complete. func NewTimeoutError(message string, retryAfterSeconds int) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusGatewayTimeout, Reason: metav1.StatusReasonTimeout, Message: fmt.Sprintf("Timeout: %s", message), Details: &metav1.StatusDetails{ RetryAfterSeconds: int32(retryAfterSeconds), }, }} } // NewTooManyRequestsError returns an error indicating that the request was rejected because // the server has received too many requests. Client should wait and retry. But if the request // is perishable, then the client should not retry the request. func NewTooManyRequestsError(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusTooManyRequests, Reason: metav1.StatusReasonTooManyRequests, Message: fmt.Sprintf("Too many requests: %s", message), }} } // NewRequestEntityTooLargeError returns an error indicating that the request // entity was too large. func NewRequestEntityTooLargeError(message string) *StatusError { return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusRequestEntityTooLarge, Reason: metav1.StatusReasonRequestEntityTooLarge, Message: fmt.Sprintf("Request entity too large: %s", message), }} } // NewGenericServerResponse returns a new error for server responses that are not in a recognizable form. func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError { reason := metav1.StatusReasonUnknown 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" { reason = metav1.StatusReasonAlreadyExists } else { reason = metav1.StatusReasonConflict } message = "the server reported a conflict" case http.StatusNotFound: reason = metav1.StatusReasonNotFound message = "the server could not find the requested resource" case http.StatusBadRequest: reason = metav1.StatusReasonBadRequest message = "the server rejected our request for an unknown reason" case http.StatusUnauthorized: reason = metav1.StatusReasonUnauthorized message = "the server has asked for the client to provide credentials" case http.StatusForbidden: reason = metav1.StatusReasonForbidden // the server message has details about who is trying to perform what action. Keep its message. message = serverMessage case http.StatusNotAcceptable: reason = metav1.StatusReasonNotAcceptable // the server message has details about what types are acceptable if len(serverMessage) == 0 || serverMessage == "unknown" { message = "the server was unable to respond with a content type that the client supports" } else { message = serverMessage } case http.StatusUnsupportedMediaType: reason = metav1.StatusReasonUnsupportedMediaType // the server message has details about what types are acceptable message = serverMessage case http.StatusMethodNotAllowed: reason = metav1.StatusReasonMethodNotAllowed message = "the server does not allow this method on the requested resource" case http.StatusUnprocessableEntity: reason = metav1.StatusReasonInvalid message = "the server rejected our request due to an error in our request" case http.StatusServiceUnavailable: reason = metav1.StatusReasonServiceUnavailable message = "the server is currently unable to handle the request" case http.StatusGatewayTimeout: reason = metav1.StatusReasonTimeout message = "the server was unable to return a response in the time allotted, but may still be processing the request" case http.StatusTooManyRequests: reason = metav1.StatusReasonTooManyRequests message = "the server has received too many requests and has asked us to try again later" default: if code >= 500 { reason = metav1.StatusReasonInternalError message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage) } } switch { case !qualifiedResource.Empty() && len(name) > 0: message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name) case !qualifiedResource.Empty(): message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String()) } var causes []metav1.StatusCause if isUnexpectedResponse { causes = []metav1.StatusCause{ { Type: metav1.CauseTypeUnexpectedServerResponse, Message: serverMessage, }, } } else { causes = nil } return &StatusError{metav1.Status{ Status: metav1.StatusFailure, Code: int32(code), Reason: reason, Details: &metav1.StatusDetails{ Group: qualifiedResource.Group, Kind: qualifiedResource.Resource, Name: name, Causes: causes, RetryAfterSeconds: int32(retryAfterSeconds), }, Message: message, }} } // IsNotFound returns true if the specified error was created by NewNotFound. // It supports wrapped errors and returns false when the error is nil. func IsNotFound(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonNotFound { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusNotFound { return true } return false } // IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists. // It supports wrapped errors and returns false when the error is nil. func IsAlreadyExists(err error) bool { return ReasonForError(err) == metav1.StatusReasonAlreadyExists } // IsConflict determines if the err is an error which indicates the provided update conflicts. // It supports wrapped errors and returns false when the error is nil. func IsConflict(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonConflict { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusConflict { return true } return false } // IsInvalid determines if the err is an error which indicates the provided resource is not valid. // It supports wrapped errors and returns false when the error is nil. func IsInvalid(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonInvalid { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnprocessableEntity { return true } return false } // IsGone is true if the error indicates the requested resource is no longer available. // It supports wrapped errors and returns false when the error is nil. func IsGone(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonGone { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusGone { return true } return false } // IsResourceExpired is true if the error indicates the resource has expired and the current action is // no longer possible. // It supports wrapped errors and returns false when the error is nil. func IsResourceExpired(err error) bool { return ReasonForError(err) == metav1.StatusReasonExpired } // IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header // It supports wrapped errors and returns false when the error is nil. func IsNotAcceptable(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonNotAcceptable { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusNotAcceptable { return true } return false } // IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header // It supports wrapped errors and returns false when the error is nil. func IsUnsupportedMediaType(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonUnsupportedMediaType { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnsupportedMediaType { return true } return false } // IsMethodNotSupported determines if the err is an error which indicates the provided action could not // be performed because it is not supported by the server. // It supports wrapped errors and returns false when the error is nil. func IsMethodNotSupported(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonMethodNotAllowed { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusMethodNotAllowed { return true } return false } // IsServiceUnavailable is true if the error indicates the underlying service is no longer available. // It supports wrapped errors and returns false when the error is nil. func IsServiceUnavailable(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonServiceUnavailable { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusServiceUnavailable { return true } return false } // IsBadRequest determines if err is an error which indicates that the request is invalid. // It supports wrapped errors and returns false when the error is nil. func IsBadRequest(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonBadRequest { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusBadRequest { return true } return false } // IsUnauthorized determines if err is an error which indicates that the request is unauthorized and // requires authentication by the user. // It supports wrapped errors and returns false when the error is nil. func IsUnauthorized(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonUnauthorized { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusUnauthorized { return true } return false } // IsForbidden determines if err is an error which indicates that the request is forbidden and cannot // be completed as requested. // It supports wrapped errors and returns false when the error is nil. func IsForbidden(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonForbidden { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusForbidden { return true } return false } // IsTimeout determines if err is an error which indicates that request times out due to long // processing. // It supports wrapped errors and returns false when the error is nil. func IsTimeout(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonTimeout { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusGatewayTimeout { return true } return false } // IsServerTimeout determines if err is an error which indicates that the request needs to be retried // by the client. // It supports wrapped errors and returns false when the error is nil. func IsServerTimeout(err error) bool { // do not check the status code, because no https status code exists that can // be scoped to retryable timeouts. return ReasonForError(err) == metav1.StatusReasonServerTimeout } // IsInternalError determines if err is an error which indicates an internal server error. // It supports wrapped errors and returns false when the error is nil. func IsInternalError(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonInternalError { return true } if _, ok := knownReasons[reason]; !ok && code == http.StatusInternalServerError { return true } return false } // IsTooManyRequests determines if err is an error which indicates that there are too many requests // that the server cannot handle. // It supports wrapped errors and returns false when the error is nil. func IsTooManyRequests(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonTooManyRequests { return true } // IsTooManyRequests' checking of code predates the checking of the code in // the other Is* functions. In order to maintain backward compatibility, this // does not check that the reason is unknown. if code == http.StatusTooManyRequests { return true } return false } // IsRequestEntityTooLargeError determines if err is an error which indicates // the request entity is too large. // It supports wrapped errors and returns false when the error is nil. func IsRequestEntityTooLargeError(err error) bool { reason, code := reasonAndCodeForError(err) if reason == metav1.StatusReasonRequestEntityTooLarge { return true } // IsRequestEntityTooLargeError's checking of code predates the checking of // the code in the other Is* functions. In order to maintain backward // compatibility, this does not check that the reason is unknown. if code == http.StatusRequestEntityTooLarge { return true } return false } // IsUnexpectedServerError returns true if the server response was not in the expected API format, // and may be the result of another HTTP actor. // It supports wrapped errors and returns false when the error is nil. func IsUnexpectedServerError(err error) bool { status, ok := err.(APIStatus) if (ok || errors.As(err, &status)) && status.Status().Details != nil { for _, cause := range status.Status().Details.Causes { if cause.Type == metav1.CauseTypeUnexpectedServerResponse { return true } } } return false } // IsUnexpectedObjectError determines if err is due to an unexpected object from the master. // It supports wrapped errors and returns false when the error is nil. func IsUnexpectedObjectError(err error) bool { uoe, ok := err.(*UnexpectedObjectError) return err != nil && (ok || errors.As(err, &uoe)) } // 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 // request delay without retry. // It supports wrapped errors and returns false when the error is nil. func SuggestsClientDelay(err error) (int, bool) { t, ok := err.(APIStatus) if (ok || errors.As(err, &t)) && t.Status().Details != nil { switch t.Status().Reason { // this StatusReason explicitly requests the caller to delay the action case metav1.StatusReasonServerTimeout: return int(t.Status().Details.RetryAfterSeconds), true } // If the client requests that we retry after a certain number of seconds if t.Status().Details.RetryAfterSeconds > 0 { return int(t.Status().Details.RetryAfterSeconds), true } } return 0, false } // ReasonForError returns the HTTP status for a particular error. // It supports wrapped errors and returns StatusReasonUnknown when // the error is nil or doesn't have a status. func ReasonForError(err error) metav1.StatusReason { if status, ok := err.(APIStatus); ok || errors.As(err, &status) { return status.Status().Reason } return metav1.StatusReasonUnknown } func reasonAndCodeForError(err error) (metav1.StatusReason, int32) { if status, ok := err.(APIStatus); ok || errors.As(err, &status) { return status.Status().Reason, status.Status().Code } return metav1.StatusReasonUnknown, 0 } // ErrorReporter converts generic errors into runtime.Object errors without // requiring the caller to take a dependency on meta/v1 (where Status lives). // This prevents circular dependencies in core watch code. type ErrorReporter struct { code int verb string reason string } // NewClientErrorReporter will respond with valid v1.Status objects that report // unexpected server responses. Primarily used by watch to report errors when // we attempt to decode a response from the server and it is not in the form // we expect. Because watch is a dependency of the core api, we can't return // meta/v1.Status in that package and so much inject this interface to convert a // generic error as appropriate. The reason is passed as a unique status cause // on the returned status, otherwise the generic "ClientError" is returned. func NewClientErrorReporter(code int, verb string, reason string) *ErrorReporter { return &ErrorReporter{ code: code, verb: verb, reason: reason, } } // AsObject returns a valid error runtime.Object (a v1.Status) for the given // error, using the code and verb of the reporter type. The error is set to // indicate that this was an unexpected server response. func (r *ErrorReporter) AsObject(err error) runtime.Object { status := NewGenericServerResponse(r.code, r.verb, schema.GroupResource{}, "", err.Error(), 0, true) if status.ErrStatus.Details == nil { status.ErrStatus.Details = &metav1.StatusDetails{} } reason := r.reason if len(reason) == 0 { reason = "ClientError" } status.ErrStatus.Details.Causes = append(status.ErrStatus.Details.Causes, metav1.StatusCause{ Type: metav1.CauseType(reason), Message: err.Error(), }) return &status.ErrStatus } golang-k8s-apimachinery-0.29.0/pkg/api/errors/errors_test.go000066400000000000000000000470361453143165200240110ustar00rootroot00000000000000/* 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 errors import ( "errors" "fmt" "net/http" "reflect" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" ) func resource(resource string) schema.GroupResource { return schema.GroupResource{Group: "", Resource: resource} } func kind(kind string) schema.GroupKind { return schema.GroupKind{Group: "", Kind: kind} } func TestErrorNew(t *testing.T) { err := NewAlreadyExists(resource("tests"), "1") if !IsAlreadyExists(err) { t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) } if IsConflict(err) { t.Errorf("expected to not be %s", metav1.StatusReasonConflict) } if IsNotFound(err) { t.Errorf("expected to not be %s", metav1.StatusReasonNotFound) } if IsInvalid(err) { t.Errorf("expected to not be %s", metav1.StatusReasonInvalid) } if IsBadRequest(err) { t.Errorf("expected to not be %s", metav1.StatusReasonBadRequest) } if IsForbidden(err) { t.Errorf("expected to not be %s", metav1.StatusReasonForbidden) } if IsServerTimeout(err) { t.Errorf("expected to not be %s", metav1.StatusReasonServerTimeout) } if IsMethodNotSupported(err) { t.Errorf("expected to not be %s", metav1.StatusReasonMethodNotAllowed) } if !IsConflict(NewConflict(resource("tests"), "2", errors.New("message"))) { t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) } if !IsNotFound(NewNotFound(resource("tests"), "3")) { t.Errorf("expected to be %s", metav1.StatusReasonNotFound) } if !IsInvalid(NewInvalid(kind("Test"), "2", nil)) { t.Errorf("expected to be %s", metav1.StatusReasonInvalid) } if !IsBadRequest(NewBadRequest("reason")) { t.Errorf("expected to be %s", metav1.StatusReasonBadRequest) } if !IsForbidden(NewForbidden(resource("tests"), "2", errors.New("reason"))) { t.Errorf("expected to be %s", metav1.StatusReasonForbidden) } if !IsUnauthorized(NewUnauthorized("reason")) { t.Errorf("expected to be %s", metav1.StatusReasonUnauthorized) } if !IsServerTimeout(NewServerTimeout(resource("tests"), "reason", 0)) { t.Errorf("expected to be %s", metav1.StatusReasonServerTimeout) } if !IsMethodNotSupported(NewMethodNotSupported(resource("foos"), "delete")) { t.Errorf("expected to be %s", metav1.StatusReasonMethodNotAllowed) } if !IsAlreadyExists(NewGenerateNameConflict(resource("tests"), "3", 1)) { t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) } if time, ok := SuggestsClientDelay(NewGenerateNameConflict(resource("tests"), "3", 1)); time != 1 || !ok { t.Errorf("unexpected %d", time) } if time, ok := SuggestsClientDelay(NewServerTimeout(resource("tests"), "doing something", 10)); time != 10 || !ok { t.Errorf("unexpected %d", time) } if time, ok := SuggestsClientDelay(NewServerTimeout(resource("tests"), "doing something", 0)); time != 0 || !ok { t.Errorf("unexpected %d", time) } if time, ok := SuggestsClientDelay(NewTimeoutError("test reason", 10)); time != 10 || !ok { t.Errorf("unexpected %d", time) } if time, ok := SuggestsClientDelay(NewTooManyRequests("doing something", 10)); time != 10 || !ok { t.Errorf("unexpected %d", time) } 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 { t.Errorf("unexpected %d", time) } if time, ok := SuggestsClientDelay(NewGenericServerResponse(500, "get", 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 { t.Errorf("unexpected %d", time) } } func TestNewInvalid(t *testing.T) { testCases := []struct { Err *field.Error Details *metav1.StatusDetails Msg string }{ { field.Duplicate(field.NewPath("field[0].name"), "bar"), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueDuplicate, Field: "field[0].name", }}, }, `Kind "name" is invalid: field[0].name: Duplicate value: "bar"`, }, { field.Invalid(field.NewPath("field[0].name"), "bar", "detail"), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueInvalid, Field: "field[0].name", }}, }, `Kind "name" is invalid: field[0].name: Invalid value: "bar": detail`, }, { field.NotFound(field.NewPath("field[0].name"), "bar"), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueNotFound, Field: "field[0].name", }}, }, `Kind "name" is invalid: field[0].name: Not found: "bar"`, }, { field.NotSupported[string](field.NewPath("field[0].name"), "bar", nil), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueNotSupported, Field: "field[0].name", }}, }, `Kind "name" is invalid: field[0].name: Unsupported value: "bar"`, }, { field.Required(field.NewPath("field[0].name"), ""), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueRequired, Field: "field[0].name", }}, }, `Kind "name" is invalid: field[0].name: Required value`, }, { nil, &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{}, }, `Kind "name" is invalid`, }, } for i, testCase := range testCases { vErr, expected := testCase.Err, testCase.Details if vErr != nil && expected != nil { expected.Causes[0].Message = vErr.ErrorBody() } var errList field.ErrorList if vErr != nil { errList = append(errList, vErr) } err := NewInvalid(kind("Kind"), "name", errList) status := err.ErrStatus if status.Code != 422 || status.Reason != metav1.StatusReasonInvalid { t.Errorf("%d: unexpected status: %#v", i, status) } if !reflect.DeepEqual(expected, status.Details) { t.Errorf("%d: expected %#v, got %#v", i, expected, status.Details) } if testCase.Msg != status.Message { t.Errorf("%d: expected\n%s\ngot\n%s", i, testCase.Msg, status.Message) } } } func TestReasonForError(t *testing.T) { if e, a := metav1.StatusReasonUnknown, ReasonForError(nil); e != a { t.Errorf("unexpected reason type: %#v", a) } } type TestType struct{} func (obj *TestType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *TestType) DeepCopyObject() runtime.Object { if obj == nil { return nil } clone := *obj return &clone } func TestFromObject(t *testing.T) { table := []struct { obj runtime.Object message string }{ {&metav1.Status{Message: "foobar"}, "foobar"}, {&TestType{}, "unexpected object: &{}"}, } for _, item := range table { if e, a := item.message, FromObject(item.obj).Error(); e != a { t.Errorf("Expected %v, got %v", e, a) } } } func TestReasonForErrorSupportsWrappedErrors(t *testing.T) { testCases := []struct { name string err error expectedReason metav1.StatusReason }{ { name: "Direct match", err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonUnauthorized}}, expectedReason: metav1.StatusReasonUnauthorized, }, { name: "No match", err: errors.New("some other error"), expectedReason: metav1.StatusReasonUnknown, }, { name: "Nested match", err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonAlreadyExists}})), expectedReason: metav1.StatusReasonAlreadyExists, }, { name: "Nested, no match", err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), expectedReason: metav1.StatusReasonUnknown, }, { name: "Nil", expectedReason: metav1.StatusReasonUnknown, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if result := ReasonForError(tc.err); result != tc.expectedReason { t.Errorf("expected reason: %q, but got known reason: %q", tc.expectedReason, result) } }) } } func TestIsTooManyRequestsSupportsWrappedErrors(t *testing.T) { testCases := []struct { name string err error expectMatch bool }{ { name: "Direct match via status reason", err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonTooManyRequests}}, expectMatch: true, }, { name: "Direct match via status code", err: &StatusError{ErrStatus: metav1.Status{Code: http.StatusTooManyRequests}}, expectMatch: true, }, { name: "No match", err: &StatusError{}, expectMatch: false, }, { name: "Nested match via status reason", err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonTooManyRequests}}), expectMatch: true, }, { name: "Nested match via status code", err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusTooManyRequests}}), expectMatch: true, }, { name: "Nested,no match", err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusNotFound}}), expectMatch: false, }, { name: "Nil", expectMatch: false, }, } for _, tc := range testCases { if result := IsTooManyRequests(tc.err); result != tc.expectMatch { t.Errorf("Expect match %t, got match %t", tc.expectMatch, result) } } } func TestIsRequestEntityTooLargeErrorSupportsWrappedErrors(t *testing.T) { testCases := []struct { name string err error expectMatch bool }{ { name: "Direct match via status reason", err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonRequestEntityTooLarge}}, expectMatch: true, }, { name: "Direct match via status code", err: &StatusError{ErrStatus: metav1.Status{Code: http.StatusRequestEntityTooLarge}}, expectMatch: true, }, { name: "No match", err: &StatusError{}, expectMatch: false, }, { name: "Nested match via status reason", err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonRequestEntityTooLarge}}), expectMatch: true, }, { name: "Nested match via status code", err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusRequestEntityTooLarge}}), expectMatch: true, }, { name: "Nested,no match", err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusNotFound}}), expectMatch: false, }, { name: "Nil", expectMatch: false, }, } for _, tc := range testCases { if result := IsRequestEntityTooLargeError(tc.err); result != tc.expectMatch { t.Errorf("Expect match %t, got match %t", tc.expectMatch, result) } } } func TestIsUnexpectedServerError(t *testing.T) { unexpectedServerErr := func() error { return &StatusError{ ErrStatus: metav1.Status{ Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{{Type: metav1.CauseTypeUnexpectedServerResponse}}, }, }, } } testCases := []struct { name string err error expectMatch bool }{ { name: "Direct match", err: unexpectedServerErr(), expectMatch: true, }, { name: "No match", err: errors.New("some other error"), expectMatch: false, }, { name: "Nested match", err: fmt.Errorf("wrapping: %w", unexpectedServerErr()), expectMatch: true, }, { name: "Nested, no match", err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), expectMatch: false, }, { name: "Nil", expectMatch: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if result := IsUnexpectedServerError(tc.err); result != tc.expectMatch { t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) } }) } } func TestIsUnexpectedObjectError(t *testing.T) { unexpectedObjectErr := func() error { return &UnexpectedObjectError{} } testCases := []struct { name string err error expectMatch bool }{ { name: "Direct match", err: unexpectedObjectErr(), expectMatch: true, }, { name: "No match", err: errors.New("some other error"), expectMatch: false, }, { name: "Nested match", err: fmt.Errorf("wrapping: %w", unexpectedObjectErr()), expectMatch: true, }, { name: "Nested, no match", err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), expectMatch: false, }, { name: "Nil", expectMatch: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if result := IsUnexpectedObjectError(tc.err); result != tc.expectMatch { t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) } }) } } func TestSuggestsClientDelaySupportsWrapping(t *testing.T) { suggestsClientDelayErr := func() error { return &StatusError{ ErrStatus: metav1.Status{ Reason: metav1.StatusReasonServerTimeout, Details: &metav1.StatusDetails{}, }, } } testCases := []struct { name string err error expectMatch bool }{ { name: "Direct match", err: suggestsClientDelayErr(), expectMatch: true, }, { name: "No match", err: errors.New("some other error"), expectMatch: false, }, { name: "Nested match", err: fmt.Errorf("wrapping: %w", suggestsClientDelayErr()), expectMatch: true, }, { name: "Nested, no match", err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), expectMatch: false, }, { name: "Nil", expectMatch: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if _, result := SuggestsClientDelay(tc.err); result != tc.expectMatch { t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) } }) } } func TestIsErrorTypesByReasonAndCode(t *testing.T) { testCases := []struct { name string knownReason metav1.StatusReason otherReason metav1.StatusReason otherReasonConsidered bool code int32 fn func(error) bool }{ { name: "IsRequestEntityTooLarge", knownReason: metav1.StatusReasonRequestEntityTooLarge, otherReason: metav1.StatusReasonForbidden, otherReasonConsidered: false, code: http.StatusRequestEntityTooLarge, fn: IsRequestEntityTooLargeError, }, { name: "TooManyRequests", knownReason: metav1.StatusReasonTooManyRequests, otherReason: metav1.StatusReasonForbidden, otherReasonConsidered: false, code: http.StatusTooManyRequests, fn: IsTooManyRequests, }, { name: "Forbidden", knownReason: metav1.StatusReasonForbidden, otherReason: metav1.StatusReasonNotFound, otherReasonConsidered: true, code: http.StatusForbidden, fn: IsForbidden, }, { name: "NotFound", knownReason: metav1.StatusReasonNotFound, otherReason: metav1.StatusReasonForbidden, otherReasonConsidered: true, code: http.StatusNotFound, fn: IsNotFound, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Run("by known reason", func(t *testing.T) { err := &StatusError{ metav1.Status{ Reason: tc.knownReason, }, } got := tc.fn(err) if !got { t.Errorf("expected reason %s to match", tc.knownReason) } }) t.Run("by code and unknown reason", func(t *testing.T) { err := &StatusError{ metav1.Status{ Reason: metav1.StatusReasonUnknown, // this could be _any_ reason that isn't in knownReasons. Code: tc.code, }, } got := tc.fn(err) if !got { t.Errorf("expected code %d with reason %s to match", tc.code, tc.otherReason) } }) if !tc.otherReasonConsidered { return } t.Run("by code and other known reason", func(t *testing.T) { err := &StatusError{ metav1.Status{ Reason: tc.otherReason, Code: tc.code, }, } got := tc.fn(err) if got { t.Errorf("expected code %d with reason %s to not match", tc.code, tc.otherReason) } }) }) } } func TestStatusCauseSupportsWrappedErrors(t *testing.T) { err := &StatusError{ErrStatus: metav1.Status{ Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{{Type: "SomeCause"}}, }, }} if cause, ok := StatusCause(nil, "SomeCause"); ok { t.Errorf("expected no cause for nil, got %v: %#v", ok, cause) } if cause, ok := StatusCause(errors.New("boom"), "SomeCause"); ok { t.Errorf("expected no cause for wrong type, got %v: %#v", ok, cause) } if cause, ok := StatusCause(err, "Other"); ok { t.Errorf("expected no cause for wrong name, got %v: %#v", ok, cause) } if cause, ok := StatusCause(err, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { t.Errorf("expected cause, got %v: %#v", ok, cause) } wrapped := fmt.Errorf("once: %w", err) if cause, ok := StatusCause(wrapped, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { t.Errorf("expected cause when wrapped, got %v: %#v", ok, cause) } nested := fmt.Errorf("twice: %w", wrapped) if cause, ok := StatusCause(nested, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { t.Errorf("expected cause when nested, got %v: %#v", ok, cause) } } func BenchmarkIsAlreadyExistsWrappedErrors(b *testing.B) { err := NewAlreadyExists(schema.GroupResource{}, "") wrapped := fmt.Errorf("once: %w", err) b.Run("Nil", func(b *testing.B) { for i := 0; i < b.N; i++ { IsAlreadyExists(nil) } }) b.Run("Bare", func(b *testing.B) { for i := 0; i < b.N; i++ { IsAlreadyExists(err) } }) b.Run("Wrapped", func(b *testing.B) { for i := 0; i < b.N; i++ { IsAlreadyExists(wrapped) } }) } func BenchmarkIsNotFoundWrappedErrors(b *testing.B) { err := NewNotFound(schema.GroupResource{}, "") wrapped := fmt.Errorf("once: %w", err) b.Run("Nil", func(b *testing.B) { for i := 0; i < b.N; i++ { IsNotFound(nil) } }) b.Run("Bare", func(b *testing.B) { for i := 0; i < b.N; i++ { IsNotFound(err) } }) b.Run("Wrapped", func(b *testing.B) { for i := 0; i < b.N; i++ { IsNotFound(wrapped) } }) } golang-k8s-apimachinery-0.29.0/pkg/api/meta/000077500000000000000000000000001453143165200205075ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/meta/OWNERS000066400000000000000000000003241453143165200214460ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - thockin - smarterclayton - wojtek-t - deads2k - derekwaynecarr - caesarxuchao - mikedanese - liggitt - janetkuo - ncdc - dims golang-k8s-apimachinery-0.29.0/pkg/api/meta/conditions.go000066400000000000000000000102431453143165200232070ustar00rootroot00000000000000/* Copyright 2020 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 meta import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // SetStatusCondition sets the corresponding condition in conditions to newCondition and returns true // if the conditions are changed by this call. // conditions must be non-nil. // 1. if the condition of the specified type already exists (all fields of the existing condition are updated to // newCondition, LastTransitionTime is set to now if the new status differs from the old status) // 2. if a condition of the specified type does not exist (LastTransitionTime is set to now() if unset, and newCondition is appended) func SetStatusCondition(conditions *[]metav1.Condition, newCondition metav1.Condition) (changed bool) { if conditions == nil { return false } existingCondition := FindStatusCondition(*conditions, newCondition.Type) if existingCondition == nil { if newCondition.LastTransitionTime.IsZero() { newCondition.LastTransitionTime = metav1.NewTime(time.Now()) } *conditions = append(*conditions, newCondition) return true } if existingCondition.Status != newCondition.Status { existingCondition.Status = newCondition.Status if !newCondition.LastTransitionTime.IsZero() { existingCondition.LastTransitionTime = newCondition.LastTransitionTime } else { existingCondition.LastTransitionTime = metav1.NewTime(time.Now()) } changed = true } if existingCondition.Reason != newCondition.Reason { existingCondition.Reason = newCondition.Reason changed = true } if existingCondition.Message != newCondition.Message { existingCondition.Message = newCondition.Message changed = true } if existingCondition.ObservedGeneration != newCondition.ObservedGeneration { existingCondition.ObservedGeneration = newCondition.ObservedGeneration changed = true } return changed } // RemoveStatusCondition removes the corresponding conditionType from conditions if present. Returns // true if it was present and got removed. // conditions must be non-nil. func RemoveStatusCondition(conditions *[]metav1.Condition, conditionType string) (removed bool) { if conditions == nil || len(*conditions) == 0 { return false } newConditions := make([]metav1.Condition, 0, len(*conditions)-1) for _, condition := range *conditions { if condition.Type != conditionType { newConditions = append(newConditions, condition) } } removed = len(*conditions) != len(newConditions) *conditions = newConditions return removed } // FindStatusCondition finds the conditionType in conditions. func FindStatusCondition(conditions []metav1.Condition, conditionType string) *metav1.Condition { for i := range conditions { if conditions[i].Type == conditionType { return &conditions[i] } } return nil } // IsStatusConditionTrue returns true when the conditionType is present and set to `metav1.ConditionTrue` func IsStatusConditionTrue(conditions []metav1.Condition, conditionType string) bool { return IsStatusConditionPresentAndEqual(conditions, conditionType, metav1.ConditionTrue) } // IsStatusConditionFalse returns true when the conditionType is present and set to `metav1.ConditionFalse` func IsStatusConditionFalse(conditions []metav1.Condition, conditionType string) bool { return IsStatusConditionPresentAndEqual(conditions, conditionType, metav1.ConditionFalse) } // IsStatusConditionPresentAndEqual returns true when conditionType is present and equal to status. func IsStatusConditionPresentAndEqual(conditions []metav1.Condition, conditionType string, status metav1.ConditionStatus) bool { for _, condition := range conditions { if condition.Type == conditionType { return condition.Status == status } } return false } golang-k8s-apimachinery-0.29.0/pkg/api/meta/conditions_test.go000066400000000000000000000153461453143165200242570ustar00rootroot00000000000000/* Copyright 2020 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 meta import ( "reflect" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestSetStatusCondition(t *testing.T) { oneHourBefore := time.Now().Add(-1 * time.Hour) oneHourAfter := time.Now().Add(1 * time.Hour) tests := []struct { name string conditions []metav1.Condition toAdd metav1.Condition expectChanged bool expected []metav1.Condition }{ { name: "should-add", conditions: []metav1.Condition{ {Type: "first"}, {Type: "third"}, }, toAdd: metav1.Condition{Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, expectChanged: true, expected: []metav1.Condition{ {Type: "first"}, {Type: "third"}, {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, }, }, { name: "use-supplied-time", conditions: []metav1.Condition{ {Type: "first"}, {Type: "second", Status: metav1.ConditionFalse}, {Type: "third"}, }, toAdd: metav1.Condition{Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, expectChanged: true, expected: []metav1.Condition{ {Type: "first"}, {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, {Type: "third"}, }, }, { name: "update-fields", conditions: []metav1.Condition{ {Type: "first"}, {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}}, {Type: "third"}, }, toAdd: metav1.Condition{Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourAfter}, Reason: "reason", Message: "message", ObservedGeneration: 3}, expectChanged: true, expected: []metav1.Condition{ {Type: "first"}, {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message", ObservedGeneration: 3}, {Type: "third"}, }, }, { name: "nothing changes", conditions: []metav1.Condition{{ Type: "type", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, }}, toAdd: metav1.Condition{Type: "type", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}}, expected: []metav1.Condition{{ Type: "type", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, }}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { changed := SetStatusCondition(&test.conditions, test.toAdd) if test.expectChanged != changed { t.Errorf("expectChanged=%t != changed=%t", test.expectChanged, changed) } if !reflect.DeepEqual(test.conditions, test.expected) { t.Error(test.conditions) } }) } } func TestRemoveStatusCondition(t *testing.T) { tests := []struct { name string conditions []metav1.Condition conditionType string expectRemoval bool expected []metav1.Condition }{ { name: "present", conditions: []metav1.Condition{ {Type: "first"}, {Type: "second"}, {Type: "third"}, }, conditionType: "second", expectRemoval: true, expected: []metav1.Condition{ {Type: "first"}, {Type: "third"}, }, }, { name: "not-present", conditions: []metav1.Condition{ {Type: "first"}, {Type: "second"}, {Type: "third"}, }, conditionType: "fourth", expected: []metav1.Condition{ {Type: "first"}, {Type: "second"}, {Type: "third"}, }, }, { name: "empty_conditions", conditions: []metav1.Condition{}, conditionType: "Foo", expected: []metav1.Condition{}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { removed := RemoveStatusCondition(&test.conditions, test.conditionType) if test.expectRemoval != removed { t.Errorf("expectRemoval=%t != removal=%t", test.expectRemoval, removed) } if !reflect.DeepEqual(test.conditions, test.expected) { t.Error(test.conditions) } }) } } func TestFindStatusCondition(t *testing.T) { tests := []struct { name string conditions []metav1.Condition conditionType string expected *metav1.Condition }{ { name: "not-present", conditions: []metav1.Condition{ {Type: "first"}, }, conditionType: "second", expected: nil, }, { name: "present", conditions: []metav1.Condition{ {Type: "first"}, {Type: "second"}, }, conditionType: "second", expected: &metav1.Condition{Type: "second"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := FindStatusCondition(test.conditions, test.conditionType) if !reflect.DeepEqual(actual, test.expected) { t.Error(actual) } }) } } func TestIsStatusConditionPresentAndEqual(t *testing.T) { tests := []struct { name string conditions []metav1.Condition conditionType string conditionStatus metav1.ConditionStatus expected bool }{ { name: "doesnt-match-true", conditions: []metav1.Condition{ {Type: "first", Status: metav1.ConditionUnknown}, }, conditionType: "first", conditionStatus: metav1.ConditionTrue, expected: false, }, { name: "does-match-true", conditions: []metav1.Condition{ {Type: "first", Status: metav1.ConditionTrue}, }, conditionType: "first", conditionStatus: metav1.ConditionTrue, expected: true, }, { name: "does-match-false", conditions: []metav1.Condition{ {Type: "first", Status: metav1.ConditionFalse}, }, conditionType: "first", conditionStatus: metav1.ConditionFalse, expected: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := IsStatusConditionPresentAndEqual(test.conditions, test.conditionType, test.conditionStatus) if actual != test.expected { t.Error(actual) } }) } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/doc.go000066400000000000000000000013441453143165200216050ustar00rootroot00000000000000/* 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 meta provides functions for retrieving API metadata from objects // belonging to the Kubernetes API package meta // import "k8s.io/apimachinery/pkg/api/meta" golang-k8s-apimachinery-0.29.0/pkg/api/meta/errors.go000066400000000000000000000103531453143165200223540ustar00rootroot00000000000000/* 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 meta import ( "errors" "fmt" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" ) // AmbiguousResourceError is returned if the RESTMapper finds multiple matches for a resource type AmbiguousResourceError struct { PartialResource schema.GroupVersionResource MatchingResources []schema.GroupVersionResource MatchingKinds []schema.GroupVersionKind } func (e *AmbiguousResourceError) Error() string { switch { case len(e.MatchingKinds) > 0 && len(e.MatchingResources) > 0: return fmt.Sprintf("%v matches multiple resources %v and kinds %v", e.PartialResource, e.MatchingResources, e.MatchingKinds) case len(e.MatchingKinds) > 0: return fmt.Sprintf("%v matches multiple kinds %v", e.PartialResource, e.MatchingKinds) case len(e.MatchingResources) > 0: return fmt.Sprintf("%v matches multiple resources %v", e.PartialResource, e.MatchingResources) } return fmt.Sprintf("%v matches multiple resources or kinds", e.PartialResource) } func (*AmbiguousResourceError) Is(target error) bool { _, ok := target.(*AmbiguousResourceError) return ok } // AmbiguousKindError is returned if the RESTMapper finds multiple matches for a kind type AmbiguousKindError struct { PartialKind schema.GroupVersionKind MatchingResources []schema.GroupVersionResource MatchingKinds []schema.GroupVersionKind } func (e *AmbiguousKindError) Error() string { switch { case len(e.MatchingKinds) > 0 && len(e.MatchingResources) > 0: return fmt.Sprintf("%v matches multiple resources %v and kinds %v", e.PartialKind, e.MatchingResources, e.MatchingKinds) case len(e.MatchingKinds) > 0: return fmt.Sprintf("%v matches multiple kinds %v", e.PartialKind, e.MatchingKinds) case len(e.MatchingResources) > 0: return fmt.Sprintf("%v matches multiple resources %v", e.PartialKind, e.MatchingResources) } return fmt.Sprintf("%v matches multiple resources or kinds", e.PartialKind) } func (*AmbiguousKindError) Is(target error) bool { _, ok := target.(*AmbiguousKindError) return ok } func IsAmbiguousError(err error) bool { if err == nil { return false } return errors.Is(err, &AmbiguousResourceError{}) || errors.Is(err, &AmbiguousKindError{}) } // NoResourceMatchError is returned if the RESTMapper can't find any match for a resource type NoResourceMatchError struct { PartialResource schema.GroupVersionResource } func (e *NoResourceMatchError) Error() string { return fmt.Sprintf("no matches for %v", e.PartialResource) } func (*NoResourceMatchError) Is(target error) bool { _, ok := target.(*NoResourceMatchError) return ok } // NoKindMatchError is returned if the RESTMapper can't find any match for a kind type NoKindMatchError struct { // GroupKind is the API group and kind that was searched GroupKind schema.GroupKind // SearchedVersions is the optional list of versions the search was restricted to SearchedVersions []string } func (e *NoKindMatchError) Error() string { searchedVersions := sets.NewString() for _, v := range e.SearchedVersions { searchedVersions.Insert(schema.GroupVersion{Group: e.GroupKind.Group, Version: v}.String()) } switch len(searchedVersions) { case 0: return fmt.Sprintf("no matches for kind %q in group %q", e.GroupKind.Kind, e.GroupKind.Group) case 1: return fmt.Sprintf("no matches for kind %q in version %q", e.GroupKind.Kind, searchedVersions.List()[0]) default: return fmt.Sprintf("no matches for kind %q in versions %q", e.GroupKind.Kind, searchedVersions.List()) } } func (*NoKindMatchError) Is(target error) bool { _, ok := target.(*NoKindMatchError) return ok } func IsNoMatchError(err error) bool { if err == nil { return false } return errors.Is(err, &NoResourceMatchError{}) || errors.Is(err, &NoKindMatchError{}) } golang-k8s-apimachinery-0.29.0/pkg/api/meta/errors_test.go000066400000000000000000000047041453143165200234160ustar00rootroot00000000000000/* Copyright 2022 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 meta import ( "errors" "fmt" "testing" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestErrorMatching(t *testing.T) { testCases := []struct { name string // input should contain an error that is _not_ empty, otherwise the naive reflectlite.DeepEqual matching of // the errors lib will always succeed, but for all of these we want to verify that the matching is based on // type. input error new func() error matcherFunc func(error) bool }{ { name: "AmbiguousResourceError", input: &AmbiguousResourceError{MatchingResources: []schema.GroupVersionResource{{}}}, new: func() error { return &AmbiguousResourceError{} }, matcherFunc: IsAmbiguousError, }, { name: "AmbiguousKindError", input: &AmbiguousKindError{MatchingResources: []schema.GroupVersionResource{{}}}, new: func() error { return &AmbiguousKindError{} }, matcherFunc: IsAmbiguousError, }, { name: "NoResourceMatchError", input: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: "foo"}}, new: func() error { return &NoResourceMatchError{} }, matcherFunc: IsNoMatchError, }, { name: "NoKindMatchError", input: &NoKindMatchError{SearchedVersions: []string{"foo"}}, new: func() error { return &NoKindMatchError{} }, matcherFunc: IsNoMatchError, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if !errors.Is(tc.input, tc.new()) { t.Error("error doesn't match itself directly") } if !errors.Is(fmt.Errorf("wrapepd: %w", tc.input), tc.new()) { t.Error("error doesn't match itself when wrapped") } if !tc.matcherFunc(tc.input) { t.Errorf("error doesn't get matched by matcherfunc") } if errors.Is(tc.input, errors.New("foo")) { t.Error("error incorrectly matches other error") } }) } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/firsthit_restmapper.go000066400000000000000000000055401453143165200251400ustar00rootroot00000000000000/* 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 meta import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" ) var ( _ ResettableRESTMapper = &FirstHitRESTMapper{} ) // FirstHitRESTMapper is a wrapper for multiple RESTMappers which returns the // first successful result for the singular requests type FirstHitRESTMapper struct { MultiRESTMapper } func (m FirstHitRESTMapper) String() string { return fmt.Sprintf("FirstHitRESTMapper{\n\t%v\n}", m.MultiRESTMapper) } func (m FirstHitRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { errors := []error{} for _, t := range m.MultiRESTMapper { ret, err := t.ResourceFor(resource) if err == nil { return ret, nil } errors = append(errors, err) } return schema.GroupVersionResource{}, collapseAggregateErrors(errors) } func (m FirstHitRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { errors := []error{} for _, t := range m.MultiRESTMapper { ret, err := t.KindFor(resource) if err == nil { return ret, nil } errors = append(errors, err) } return schema.GroupVersionKind{}, collapseAggregateErrors(errors) } // RESTMapping provides the REST mapping for the resource based on the // kind and version. This implementation supports multiple REST schemas and // return the first match. func (m FirstHitRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { errors := []error{} for _, t := range m.MultiRESTMapper { ret, err := t.RESTMapping(gk, versions...) if err == nil { return ret, nil } errors = append(errors, err) } return nil, collapseAggregateErrors(errors) } func (m FirstHitRESTMapper) Reset() { m.MultiRESTMapper.Reset() } // collapseAggregateErrors returns the minimal errors. it handles empty as nil, handles one item in a list // by returning the item, and collapses all NoMatchErrors to a single one (since they should all be the same) func collapseAggregateErrors(errors []error) error { if len(errors) == 0 { return nil } if len(errors) == 1 { return errors[0] } allNoMatchErrors := true for _, err := range errors { allNoMatchErrors = allNoMatchErrors && IsNoMatchError(err) } if allNoMatchErrors { return errors[0] } return utilerrors.NewAggregate(errors) } golang-k8s-apimachinery-0.29.0/pkg/api/meta/help.go000066400000000000000000000235301453143165200217710ustar00rootroot00000000000000/* Copyright 2015 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 meta import ( "errors" "fmt" "reflect" "sync" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" ) var ( // isListCache maintains a cache of types that are checked for lists // which is used by IsListType. // TODO: remove and replace with an interface check isListCache = struct { lock sync.RWMutex byType map[reflect.Type]bool }{ byType: make(map[reflect.Type]bool, 1024), } ) // IsListType returns true if the provided Object has a slice called Items. // TODO: Replace the code in this check with an interface comparison by // creating and enforcing that lists implement a list accessor. func IsListType(obj runtime.Object) bool { switch t := obj.(type) { case runtime.Unstructured: return t.IsList() } t := reflect.TypeOf(obj) isListCache.lock.RLock() ok, exists := isListCache.byType[t] isListCache.lock.RUnlock() if !exists { _, err := getItemsPtr(obj) ok = err == nil // cache only the first 1024 types isListCache.lock.Lock() if len(isListCache.byType) < 1024 { isListCache.byType[t] = ok } isListCache.lock.Unlock() } return ok } var ( errExpectFieldItems = errors.New("no Items field in this object") errExpectSliceItems = errors.New("Items field must be a slice of objects") ) // GetItemsPtr returns a pointer to the list object's Items member. // If 'list' doesn't have an Items member, it's not really a list type // and an error will be returned. // This function will either return a pointer to a slice, or an error, but not both. // TODO: this will be replaced with an interface in the future func GetItemsPtr(list runtime.Object) (interface{}, error) { obj, err := getItemsPtr(list) if err != nil { return nil, fmt.Errorf("%T is not a list: %v", list, err) } return obj, nil } // getItemsPtr returns a pointer to the list object's Items member or an error. func getItemsPtr(list runtime.Object) (interface{}, error) { v, err := conversion.EnforcePtr(list) if err != nil { return nil, err } items := v.FieldByName("Items") if !items.IsValid() { return nil, errExpectFieldItems } switch items.Kind() { case reflect.Interface, reflect.Pointer: target := reflect.TypeOf(items.Interface()).Elem() if target.Kind() != reflect.Slice { return nil, errExpectSliceItems } return items.Interface(), nil case reflect.Slice: return items.Addr().Interface(), nil default: return nil, errExpectSliceItems } } // EachListItem invokes fn on each runtime.Object in the list. Any error immediately terminates // the loop. // // If items passed to fn are retained for different durations, and you want to avoid // retaining all items in obj as long as any item is referenced, use EachListItemWithAlloc instead. func EachListItem(obj runtime.Object, fn func(runtime.Object) error) error { return eachListItem(obj, fn, false) } // EachListItemWithAlloc works like EachListItem, but avoids retaining references to the items slice in obj. // It does this by making a shallow copy of non-pointer items in obj. // // If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency. func EachListItemWithAlloc(obj runtime.Object, fn func(runtime.Object) error) error { return eachListItem(obj, fn, true) } // allocNew: Whether shallow copy is required when the elements in Object.Items are struct func eachListItem(obj runtime.Object, fn func(runtime.Object) error, allocNew bool) error { if unstructured, ok := obj.(runtime.Unstructured); ok { if allocNew { return unstructured.EachListItemWithAlloc(fn) } return unstructured.EachListItem(fn) } // TODO: Change to an interface call? itemsPtr, err := GetItemsPtr(obj) if err != nil { return err } items, err := conversion.EnforcePtr(itemsPtr) if err != nil { return err } len := items.Len() if len == 0 { return nil } takeAddr := false if elemType := items.Type().Elem(); elemType.Kind() != reflect.Pointer && elemType.Kind() != reflect.Interface { if !items.Index(0).CanAddr() { return fmt.Errorf("unable to take address of items in %T for EachListItem", obj) } takeAddr = true } for i := 0; i < len; i++ { raw := items.Index(i) if takeAddr { if allocNew { // shallow copy to avoid retaining a reference to the original list item itemCopy := reflect.New(raw.Type()) // assign to itemCopy and type-assert itemCopy.Elem().Set(raw) // reflect.New will guarantee that itemCopy must be a pointer. raw = itemCopy } else { raw = raw.Addr() } } // raw must be a pointer or an interface // allocate a pointer is cheap switch item := raw.Interface().(type) { case *runtime.RawExtension: if err := fn(item.Object); err != nil { return err } case runtime.Object: if err := fn(item); err != nil { return err } default: obj, ok := item.(runtime.Object) if !ok { return fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind()) } if err := fn(obj); err != nil { return err } } } return nil } // ExtractList returns obj's Items element as an array of runtime.Objects. // Returns an error if obj is not a List type (does not have an Items member). // // If items in the returned list are retained for different durations, and you want to avoid // retaining all items in obj as long as any item is referenced, use ExtractListWithAlloc instead. func ExtractList(obj runtime.Object) ([]runtime.Object, error) { return extractList(obj, false) } // ExtractListWithAlloc works like ExtractList, but avoids retaining references to the items slice in obj. // It does this by making a shallow copy of non-pointer items in obj. // // If the items in the returned list are not retained, or are retained for the same duration, use ExtractList instead for memory efficiency. func ExtractListWithAlloc(obj runtime.Object) ([]runtime.Object, error) { return extractList(obj, true) } // allocNew: Whether shallow copy is required when the elements in Object.Items are struct func extractList(obj runtime.Object, allocNew bool) ([]runtime.Object, error) { itemsPtr, err := GetItemsPtr(obj) if err != nil { return nil, err } items, err := conversion.EnforcePtr(itemsPtr) if err != nil { return nil, err } list := make([]runtime.Object, items.Len()) if len(list) == 0 { return list, nil } elemType := items.Type().Elem() isRawExtension := elemType == rawExtensionObjectType implementsObject := elemType.Implements(objectType) for i := range list { raw := items.Index(i) switch { case isRawExtension: item := raw.Interface().(runtime.RawExtension) switch { case item.Object != nil: list[i] = item.Object case item.Raw != nil: // TODO: Set ContentEncoding and ContentType correctly. list[i] = &runtime.Unknown{Raw: item.Raw} default: list[i] = nil } case implementsObject: list[i] = raw.Interface().(runtime.Object) case allocNew: // shallow copy to avoid retaining a reference to the original list item itemCopy := reflect.New(raw.Type()) // assign to itemCopy and type-assert itemCopy.Elem().Set(raw) var ok bool // reflect.New will guarantee that itemCopy must be a pointer. if list[i], ok = itemCopy.Interface().(runtime.Object); !ok { return nil, fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind()) } default: var found bool if list[i], found = raw.Addr().Interface().(runtime.Object); !found { return nil, fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind()) } } } return list, nil } var ( // objectSliceType is the type of a slice of Objects objectSliceType = reflect.TypeOf([]runtime.Object{}) objectType = reflect.TypeOf((*runtime.Object)(nil)).Elem() rawExtensionObjectType = reflect.TypeOf(runtime.RawExtension{}) ) // LenList returns the length of this list or 0 if it is not a list. func LenList(list runtime.Object) int { itemsPtr, err := GetItemsPtr(list) if err != nil { return 0 } items, err := conversion.EnforcePtr(itemsPtr) if err != nil { return 0 } return items.Len() } // SetList sets the given list object's Items member have the elements given in // objects. // Returns an error if list is not a List type (does not have an Items member), // or if any of the objects are not of the right type. func SetList(list runtime.Object, objects []runtime.Object) error { itemsPtr, err := GetItemsPtr(list) if err != nil { return err } items, err := conversion.EnforcePtr(itemsPtr) if err != nil { return err } if items.Type() == objectSliceType { items.Set(reflect.ValueOf(objects)) return nil } slice := reflect.MakeSlice(items.Type(), len(objects), len(objects)) for i := range objects { dest := slice.Index(i) if dest.Type() == rawExtensionObjectType { dest = dest.FieldByName("Object") } // check to see if you're directly assignable if reflect.TypeOf(objects[i]).AssignableTo(dest.Type()) { dest.Set(reflect.ValueOf(objects[i])) continue } src, err := conversion.EnforcePtr(objects[i]) if err != nil { return err } if src.Type().AssignableTo(dest.Type()) { dest.Set(src) } else if src.Type().ConvertibleTo(dest.Type()) { dest.Set(src.Convert(dest.Type())) } else { return fmt.Errorf("item[%d]: can't assign or convert %v into %v", i, src.Type(), dest.Type()) } } items.Set(slice) return nil } golang-k8s-apimachinery-0.29.0/pkg/api/meta/help_test.go000066400000000000000000000300761453143165200230330ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package meta import ( "reflect" "strconv" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) const ( fakeObjectItemsNum = 1000 exemptObjectIndex = fakeObjectItemsNum / 4 ) type SampleSpec struct { Flied int } type FooSpec struct { Flied int } type FooList struct { metav1.TypeMeta metav1.ListMeta Items []Foo } func (s *FooList) DeepCopyObject() runtime.Object { panic("unimplemented") } type SampleList struct { metav1.TypeMeta metav1.ListMeta Items []Sample } func (s *SampleList) DeepCopyObject() runtime.Object { panic("unimplemented") } type RawExtensionList struct { metav1.TypeMeta metav1.ListMeta Items []runtime.RawExtension } func (l RawExtensionList) DeepCopyObject() runtime.Object { panic("unimplemented") } // NOTE: Foo struct itself is the implementer of runtime.Object. type Foo struct { metav1.TypeMeta metav1.ObjectMeta Spec FooSpec } func (f Foo) GetObjectKind() schema.ObjectKind { tm := f.TypeMeta return &tm } func (f Foo) DeepCopyObject() runtime.Object { panic("unimplemented") } // NOTE: the pointer of Sample that is the implementer of runtime.Object. // the behavior is similar to our corev1.Pod. corev1.Node type Sample struct { metav1.TypeMeta metav1.ObjectMeta Spec SampleSpec } func (s *Sample) GetObjectKind() schema.ObjectKind { tm := s.TypeMeta return &tm } func (s *Sample) DeepCopyObject() runtime.Object { panic("unimplemented") } func fakeSampleList(numItems int) *SampleList { out := &SampleList{ Items: make([]Sample, numItems), } for i := range out.Items { out.Items[i] = Sample{ TypeMeta: metav1.TypeMeta{ APIVersion: "sample.org/v1", Kind: "Sample", }, ObjectMeta: metav1.ObjectMeta{ Name: strconv.Itoa(i), Namespace: "default", Labels: map[string]string{ "label-key-1": "label-value-1", }, Annotations: map[string]string{ "annotations-key-1": "annotations-value-1", }, }, Spec: SampleSpec{ Flied: i, }, } } return out } func fakeExtensionList(numItems int) *RawExtensionList { out := &RawExtensionList{ Items: make([]runtime.RawExtension, numItems), } for i := range out.Items { out.Items[i] = runtime.RawExtension{ Object: &Foo{ TypeMeta: metav1.TypeMeta{ APIVersion: "sample.org/v2", Kind: "Sample", }, ObjectMeta: metav1.ObjectMeta{ Name: strconv.Itoa(i), Namespace: "default", Labels: map[string]string{ "label-key-1": "label-value-1", }, Annotations: map[string]string{ "annotations-key-1": "annotations-value-1", }, }, Spec: FooSpec{ Flied: i, }, }, } } return out } func fakeUnstructuredList(numItems int) runtime.Unstructured { out := &unstructured.UnstructuredList{ Items: make([]unstructured.Unstructured, numItems), } for i := range out.Items { out.Items[i] = unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Pod", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": strconv.Itoa(i), }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, } } return out } func fakeFooList(numItems int) *FooList { out := &FooList{ Items: make([]Foo, numItems), } for i := range out.Items { out.Items[i] = Foo{ TypeMeta: metav1.TypeMeta{ APIVersion: "foo.org/v1", Kind: "Foo", }, ObjectMeta: metav1.ObjectMeta{ Name: strconv.Itoa(i), Namespace: "default", Labels: map[string]string{ "label-key-1": "label-value-1", }, Annotations: map[string]string{ "annotations-key-1": "annotations-value-1", }, }, Spec: FooSpec{ Flied: i, }, } } return out } func TestEachList(t *testing.T) { tests := []struct { name string generateFunc func(num int) (list runtime.Object) expectObjectNum int }{ { name: "StructReceiverList", generateFunc: func(num int) (list runtime.Object) { return fakeFooList(num) }, expectObjectNum: fakeObjectItemsNum, }, { name: "PointerReceiverList", generateFunc: func(num int) (list runtime.Object) { return fakeSampleList(num) }, expectObjectNum: fakeObjectItemsNum, }, { name: "RawExtensionList", generateFunc: func(num int) (list runtime.Object) { return fakeExtensionList(num) }, expectObjectNum: fakeObjectItemsNum, }, { name: "UnstructuredList", generateFunc: func(num int) (list runtime.Object) { return fakeUnstructuredList(fakeObjectItemsNum) }, expectObjectNum: fakeObjectItemsNum, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Run("EachListItem", func(t *testing.T) { expectObjectNames := map[string]struct{}{} for i := 0; i < tc.expectObjectNum; i++ { expectObjectNames[strconv.Itoa(i)] = struct{}{} } list := tc.generateFunc(tc.expectObjectNum) err := EachListItem(list, func(object runtime.Object) error { o, err := Accessor(object) if err != nil { return err } delete(expectObjectNames, o.GetName()) return nil }) if err != nil { t.Errorf("each list item %#v: %v", list, err) } if len(expectObjectNames) != 0 { t.Fatal("expectObjectNames should be empty") } }) t.Run("EachListItemWithAlloc", func(t *testing.T) { expectObjectNames := map[string]struct{}{} for i := 0; i < tc.expectObjectNum; i++ { expectObjectNames[strconv.Itoa(i)] = struct{}{} } list := tc.generateFunc(tc.expectObjectNum) err := EachListItemWithAlloc(list, func(object runtime.Object) error { o, err := Accessor(object) if err != nil { return err } delete(expectObjectNames, o.GetName()) return nil }) if err != nil { t.Errorf("each list %#v with alloc: %v", list, err) } if len(expectObjectNames) != 0 { t.Fatal("expectObjectNames should be empty") } }) }) } } func TestExtractList(t *testing.T) { tests := []struct { name string generateFunc func(num int) (list runtime.Object) expectObjectNum int }{ { name: "StructReceiverList", generateFunc: func(num int) (list runtime.Object) { return fakeFooList(num) }, expectObjectNum: fakeObjectItemsNum, }, { name: "PointerReceiverList", generateFunc: func(num int) (list runtime.Object) { return fakeSampleList(num) }, expectObjectNum: fakeObjectItemsNum, }, { name: "RawExtensionList", generateFunc: func(num int) (list runtime.Object) { return fakeExtensionList(num) }, expectObjectNum: fakeObjectItemsNum, }, { name: "UnstructuredList", generateFunc: func(num int) (list runtime.Object) { return fakeUnstructuredList(fakeObjectItemsNum) }, expectObjectNum: fakeObjectItemsNum, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Run("ExtractList", func(t *testing.T) { expectObjectNames := map[string]struct{}{} for i := 0; i < tc.expectObjectNum; i++ { expectObjectNames[strconv.Itoa(i)] = struct{}{} } list := tc.generateFunc(tc.expectObjectNum) objs, err := ExtractList(list) if err != nil { t.Fatalf("extract list %#v: %v", list, err) } for i := range objs { var ( o metav1.Object err error obj = objs[i] ) if reflect.TypeOf(obj).Kind() == reflect.Struct { copy := reflect.New(reflect.TypeOf(obj)) copy.Elem().Set(reflect.ValueOf(obj)) o, err = Accessor(copy.Interface()) } else { o, err = Accessor(obj) } if err != nil { t.Fatalf("Accessor object %#v: %v", obj, err) } delete(expectObjectNames, o.GetName()) } if len(expectObjectNames) != 0 { t.Fatal("expectObjectNames should be empty") } }) t.Run("ExtractListWithAlloc", func(t *testing.T) { expectObjectNames := map[string]struct{}{} for i := 0; i < tc.expectObjectNum; i++ { expectObjectNames[strconv.Itoa(i)] = struct{}{} } list := tc.generateFunc(tc.expectObjectNum) objs, err := ExtractListWithAlloc(list) if err != nil { t.Fatalf("extract list with alloc: %v", err) } for i := range objs { var ( o metav1.Object err error obj = objs[i] ) if reflect.TypeOf(obj).Kind() == reflect.Struct { copy := reflect.New(reflect.TypeOf(obj)) copy.Elem().Set(reflect.ValueOf(obj)) o, err = Accessor(copy.Interface()) } else { o, err = Accessor(obj) } if err != nil { t.Fatalf("Accessor object %#v: %v", obj, err) } delete(expectObjectNames, o.GetName()) } if len(expectObjectNames) != 0 { t.Fatal("expectObjectNames should be empty") } }) }) } } func BenchmarkExtractListItem(b *testing.B) { tests := []struct { name string list runtime.Object }{ { name: "StructReceiverList", list: fakeFooList(fakeObjectItemsNum), }, { name: "PointerReceiverList", list: fakeSampleList(fakeObjectItemsNum), }, { name: "RawExtensionList", list: fakeExtensionList(fakeObjectItemsNum), }, } for _, tc := range tests { b.Run(tc.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, err := ExtractList(tc.list) if err != nil { b.Fatalf("ExtractList: %v", err) } } b.StopTimer() }) } } func BenchmarkEachListItem(b *testing.B) { tests := []struct { name string list runtime.Object }{ { name: "StructReceiverList", list: fakeFooList(fakeObjectItemsNum), }, { name: "PointerReceiverList", list: fakeSampleList(fakeObjectItemsNum), }, { name: "RawExtensionList", list: fakeExtensionList(fakeObjectItemsNum), }, { name: "UnstructuredList", list: fakeUnstructuredList(fakeObjectItemsNum), }, } for _, tc := range tests { b.Run(tc.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { err := EachListItem(tc.list, func(object runtime.Object) error { return nil }) if err != nil { b.Fatalf("EachListItem: %v", err) } } b.StopTimer() }) } } func BenchmarkExtractListItemWithAlloc(b *testing.B) { tests := []struct { name string list runtime.Object }{ { name: "StructReceiverList", list: fakeFooList(fakeObjectItemsNum), }, { name: "PointerReceiverList", list: fakeSampleList(fakeObjectItemsNum), }, { name: "RawExtensionList", list: fakeExtensionList(fakeObjectItemsNum), }, } for _, tc := range tests { b.Run(tc.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, err := ExtractListWithAlloc(tc.list) if err != nil { b.Fatalf("ExtractListWithAlloc: %v", err) } } b.StopTimer() }) } } func BenchmarkEachListItemWithAlloc(b *testing.B) { tests := []struct { name string list runtime.Object }{ { name: "StructReceiverList", list: fakeFooList(fakeObjectItemsNum), }, { name: "PointerReceiverList", list: fakeSampleList(fakeObjectItemsNum), }, { name: "RawExtensionList", list: fakeExtensionList(fakeObjectItemsNum), }, { name: "UnstructuredList", list: fakeUnstructuredList(fakeObjectItemsNum), }, } for _, tc := range tests { b.Run(tc.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { err := EachListItemWithAlloc(tc.list, func(object runtime.Object) error { return nil }) if err != nil { b.Fatalf("EachListItemWithAlloc: %v", err) } } b.StopTimer() }) } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/interfaces.go000066400000000000000000000127371453143165200231730ustar00rootroot00000000000000/* 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 meta import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ) type ListMetaAccessor interface { GetListMeta() List } // List lets you work with list metadata from any of the versioned or // internal API objects. Attempting to set or retrieve a field on an object that does // not support that field will be a no-op and return a default value. type List metav1.ListInterface // Type exposes the type and APIVersion of versioned or internal API objects. type Type metav1.Type // MetadataAccessor lets you work with object and list metadata from any of the versioned or // internal API objects. Attempting to set or retrieve a field on an object that does // not support that field (Name, UID, Namespace on lists) will be a no-op and return // a default value. // // MetadataAccessor exposes Interface in a way that can be used with multiple objects. type MetadataAccessor interface { APIVersion(obj runtime.Object) (string, error) SetAPIVersion(obj runtime.Object, version string) error Kind(obj runtime.Object) (string, error) SetKind(obj runtime.Object, kind string) error Namespace(obj runtime.Object) (string, error) SetNamespace(obj runtime.Object, namespace string) error Name(obj runtime.Object) (string, error) SetName(obj runtime.Object, name string) error GenerateName(obj runtime.Object) (string, error) SetGenerateName(obj runtime.Object, name string) error UID(obj runtime.Object) (types.UID, error) SetUID(obj runtime.Object, uid types.UID) error SelfLink(obj runtime.Object) (string, error) SetSelfLink(obj runtime.Object, selfLink string) error Labels(obj runtime.Object) (map[string]string, error) SetLabels(obj runtime.Object, labels map[string]string) error Annotations(obj runtime.Object) (map[string]string, error) SetAnnotations(obj runtime.Object, annotations map[string]string) error Continue(obj runtime.Object) (string, error) SetContinue(obj runtime.Object, c string) error runtime.ResourceVersioner } type RESTScopeName string const ( RESTScopeNameNamespace RESTScopeName = "namespace" RESTScopeNameRoot RESTScopeName = "root" ) // RESTScope contains the information needed to deal with REST resources that are in a resource hierarchy type RESTScope interface { // Name of the scope Name() RESTScopeName } // RESTMapping contains the information needed to deal with objects of a specific // resource and kind in a RESTful manner. type RESTMapping struct { // Resource is the GroupVersionResource (location) for this endpoint Resource schema.GroupVersionResource // GroupVersionKind is the GroupVersionKind (data format) to submit to this endpoint GroupVersionKind schema.GroupVersionKind // Scope contains the information needed to deal with REST Resources that are in a resource hierarchy Scope RESTScope } // RESTMapper allows clients to map resources to kind, and map kind and version // to interfaces for manipulating those objects. It is primarily intended for // consumers of Kubernetes compatible REST APIs as defined in docs/devel/api-conventions.md. // // The Kubernetes API provides versioned resources and object kinds which are scoped // to API groups. In other words, kinds and resources should not be assumed to be // unique across groups. // // TODO: split into sub-interfaces type RESTMapper interface { // KindFor takes a partial resource and returns the single match. Returns an error if there are multiple matches KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) // KindsFor takes a partial resource and returns the list of potential kinds in priority order KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) // ResourceFor takes a partial resource and returns the single match. Returns an error if there are multiple matches ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) // ResourcesFor takes a partial resource and returns the list of potential resource in priority order ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) // RESTMapping identifies a preferred resource mapping for the provided group kind. RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) // RESTMappings returns all resource mappings for the provided group kind if no // version search is provided. Otherwise identifies a preferred resource mapping for // the provided version(s). RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) ResourceSingularizer(resource string) (singular string, err error) } // ResettableRESTMapper is a RESTMapper which is capable of resetting itself // from discovery. // All rest mappers that delegate to other rest mappers must implement this interface and dynamically // check if the delegate mapper supports the Reset() operation. type ResettableRESTMapper interface { RESTMapper Reset() } golang-k8s-apimachinery-0.29.0/pkg/api/meta/lazy.go000066400000000000000000000062011453143165200220140ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package meta import ( "sync" "k8s.io/apimachinery/pkg/runtime/schema" ) // lazyObject defers loading the mapper and typer until necessary. type lazyObject struct { loader func() (RESTMapper, error) lock sync.Mutex loaded bool err error mapper RESTMapper } // NewLazyRESTMapperLoader handles unrecoverable errors when creating a RESTMapper / ObjectTyper by // returning those initialization errors when the interface methods are invoked. This defers the // initialization and any server calls until a client actually needs to perform the action. func NewLazyRESTMapperLoader(fn func() (RESTMapper, error)) RESTMapper { obj := &lazyObject{loader: fn} return obj } // init lazily loads the mapper and typer, returning an error if initialization has failed. func (o *lazyObject) init() error { o.lock.Lock() defer o.lock.Unlock() if o.loaded { return o.err } o.mapper, o.err = o.loader() o.loaded = true return o.err } var _ ResettableRESTMapper = &lazyObject{} func (o *lazyObject) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { if err := o.init(); err != nil { return schema.GroupVersionKind{}, err } return o.mapper.KindFor(resource) } func (o *lazyObject) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { if err := o.init(); err != nil { return []schema.GroupVersionKind{}, err } return o.mapper.KindsFor(resource) } func (o *lazyObject) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) { if err := o.init(); err != nil { return schema.GroupVersionResource{}, err } return o.mapper.ResourceFor(input) } func (o *lazyObject) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { if err := o.init(); err != nil { return []schema.GroupVersionResource{}, err } return o.mapper.ResourcesFor(input) } func (o *lazyObject) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { if err := o.init(); err != nil { return nil, err } return o.mapper.RESTMapping(gk, versions...) } func (o *lazyObject) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { if err := o.init(); err != nil { return nil, err } return o.mapper.RESTMappings(gk, versions...) } func (o *lazyObject) ResourceSingularizer(resource string) (singular string, err error) { if err := o.init(); err != nil { return "", err } return o.mapper.ResourceSingularizer(resource) } func (o *lazyObject) Reset() { o.lock.Lock() defer o.lock.Unlock() if o.loaded && o.err == nil { MaybeResetRESTMapper(o.mapper) } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/meta.go000066400000000000000000000407721453143165200217760ustar00rootroot00000000000000/* 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 meta import ( "fmt" "reflect" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" ) // errNotList is returned when an object implements the Object style interfaces but not the List style // interfaces. var errNotList = fmt.Errorf("object does not implement the List interfaces") var errNotCommon = fmt.Errorf("object does not implement the common interface for accessing the SelfLink") // CommonAccessor returns a Common interface for the provided object or an error if the object does // not provide List. func CommonAccessor(obj interface{}) (metav1.Common, error) { switch t := obj.(type) { case List: return t, nil case ListMetaAccessor: if m := t.GetListMeta(); m != nil { return m, nil } return nil, errNotCommon case metav1.ListMetaAccessor: if m := t.GetListMeta(); m != nil { return m, nil } return nil, errNotCommon case metav1.Object: return t, nil case metav1.ObjectMetaAccessor: if m := t.GetObjectMeta(); m != nil { return m, nil } return nil, errNotCommon default: return nil, errNotCommon } } // ListAccessor returns a List interface for the provided object or an error if the object does // not provide List. // IMPORTANT: Objects are NOT a superset of lists. Do not use this check to determine whether an // object *is* a List. func ListAccessor(obj interface{}) (List, error) { switch t := obj.(type) { case List: return t, nil case ListMetaAccessor: if m := t.GetListMeta(); m != nil { return m, nil } return nil, errNotList case metav1.ListMetaAccessor: if m := t.GetListMeta(); m != nil { return m, nil } return nil, errNotList default: return nil, errNotList } } // errNotObject is returned when an object implements the List style interfaces but not the Object style // interfaces. var errNotObject = fmt.Errorf("object does not implement the Object interfaces") // Accessor takes an arbitrary object pointer and returns meta.Interface. // obj must be a pointer to an API type. An error is returned if the minimum // required fields are missing. Fields that are not required return the default // value and are a no-op if set. func Accessor(obj interface{}) (metav1.Object, error) { switch t := obj.(type) { case metav1.Object: return t, nil case metav1.ObjectMetaAccessor: if m := t.GetObjectMeta(); m != nil { return m, nil } return nil, errNotObject default: return nil, errNotObject } } // AsPartialObjectMetadata takes the metav1 interface and returns a partial object. // TODO: consider making this solely a conversion action. func AsPartialObjectMetadata(m metav1.Object) *metav1.PartialObjectMetadata { switch t := m.(type) { case *metav1.ObjectMeta: return &metav1.PartialObjectMetadata{ObjectMeta: *t} default: return &metav1.PartialObjectMetadata{ ObjectMeta: metav1.ObjectMeta{ Name: m.GetName(), GenerateName: m.GetGenerateName(), Namespace: m.GetNamespace(), SelfLink: m.GetSelfLink(), UID: m.GetUID(), ResourceVersion: m.GetResourceVersion(), Generation: m.GetGeneration(), CreationTimestamp: m.GetCreationTimestamp(), DeletionTimestamp: m.GetDeletionTimestamp(), DeletionGracePeriodSeconds: m.GetDeletionGracePeriodSeconds(), Labels: m.GetLabels(), Annotations: m.GetAnnotations(), OwnerReferences: m.GetOwnerReferences(), Finalizers: m.GetFinalizers(), ManagedFields: m.GetManagedFields(), }, } } } // TypeAccessor returns an interface that allows retrieving and modifying the APIVersion // and Kind of an in-memory internal object. // TODO: this interface is used to test code that does not have ObjectMeta or ListMeta // in round tripping (objects which can use apiVersion/kind, but do not fit the Kube // api conventions). func TypeAccessor(obj interface{}) (Type, error) { if typed, ok := obj.(runtime.Object); ok { return objectAccessor{typed}, nil } v, err := conversion.EnforcePtr(obj) if err != nil { return nil, err } t := v.Type() if v.Kind() != reflect.Struct { return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), t, v.Interface()) } typeMeta := v.FieldByName("TypeMeta") if !typeMeta.IsValid() { return nil, fmt.Errorf("struct %v lacks embedded TypeMeta type", t) } a := &genericAccessor{} if err := extractFromTypeMeta(typeMeta, a); err != nil { return nil, fmt.Errorf("unable to find type fields on %#v: %v", typeMeta, err) } return a, nil } type objectAccessor struct { runtime.Object } func (obj objectAccessor) GetKind() string { return obj.GetObjectKind().GroupVersionKind().Kind } func (obj objectAccessor) SetKind(kind string) { gvk := obj.GetObjectKind().GroupVersionKind() gvk.Kind = kind obj.GetObjectKind().SetGroupVersionKind(gvk) } func (obj objectAccessor) GetAPIVersion() string { return obj.GetObjectKind().GroupVersionKind().GroupVersion().String() } func (obj objectAccessor) SetAPIVersion(version string) { gvk := obj.GetObjectKind().GroupVersionKind() gv, err := schema.ParseGroupVersion(version) if err != nil { gv = schema.GroupVersion{Version: version} } gvk.Group, gvk.Version = gv.Group, gv.Version obj.GetObjectKind().SetGroupVersionKind(gvk) } // NewAccessor returns a MetadataAccessor that can retrieve // or manipulate resource version on objects derived from core API // metadata concepts. func NewAccessor() MetadataAccessor { return resourceAccessor{} } // resourceAccessor implements ResourceVersioner and SelfLinker. type resourceAccessor struct{} func (resourceAccessor) Kind(obj runtime.Object) (string, error) { return objectAccessor{obj}.GetKind(), nil } func (resourceAccessor) SetKind(obj runtime.Object, kind string) error { objectAccessor{obj}.SetKind(kind) return nil } func (resourceAccessor) APIVersion(obj runtime.Object) (string, error) { return objectAccessor{obj}.GetAPIVersion(), nil } func (resourceAccessor) SetAPIVersion(obj runtime.Object, version string) error { objectAccessor{obj}.SetAPIVersion(version) return nil } func (resourceAccessor) Namespace(obj runtime.Object) (string, error) { accessor, err := Accessor(obj) if err != nil { return "", err } return accessor.GetNamespace(), nil } func (resourceAccessor) SetNamespace(obj runtime.Object, namespace string) error { accessor, err := Accessor(obj) if err != nil { return err } accessor.SetNamespace(namespace) return nil } func (resourceAccessor) Name(obj runtime.Object) (string, error) { accessor, err := Accessor(obj) if err != nil { return "", err } return accessor.GetName(), nil } func (resourceAccessor) SetName(obj runtime.Object, name string) error { accessor, err := Accessor(obj) if err != nil { return err } accessor.SetName(name) return nil } func (resourceAccessor) GenerateName(obj runtime.Object) (string, error) { accessor, err := Accessor(obj) if err != nil { return "", err } return accessor.GetGenerateName(), nil } func (resourceAccessor) SetGenerateName(obj runtime.Object, name string) error { accessor, err := Accessor(obj) if err != nil { return err } accessor.SetGenerateName(name) return nil } func (resourceAccessor) UID(obj runtime.Object) (types.UID, error) { accessor, err := Accessor(obj) if err != nil { return "", err } return accessor.GetUID(), nil } func (resourceAccessor) SetUID(obj runtime.Object, uid types.UID) error { accessor, err := Accessor(obj) if err != nil { return err } accessor.SetUID(uid) return nil } func (resourceAccessor) SelfLink(obj runtime.Object) (string, error) { accessor, err := CommonAccessor(obj) if err != nil { return "", err } return accessor.GetSelfLink(), nil } func (resourceAccessor) SetSelfLink(obj runtime.Object, selfLink string) error { accessor, err := CommonAccessor(obj) if err != nil { return err } accessor.SetSelfLink(selfLink) return nil } func (resourceAccessor) Labels(obj runtime.Object) (map[string]string, error) { accessor, err := Accessor(obj) if err != nil { return nil, err } return accessor.GetLabels(), nil } func (resourceAccessor) SetLabels(obj runtime.Object, labels map[string]string) error { accessor, err := Accessor(obj) if err != nil { return err } accessor.SetLabels(labels) return nil } func (resourceAccessor) Annotations(obj runtime.Object) (map[string]string, error) { accessor, err := Accessor(obj) if err != nil { return nil, err } return accessor.GetAnnotations(), nil } func (resourceAccessor) SetAnnotations(obj runtime.Object, annotations map[string]string) error { accessor, err := Accessor(obj) if err != nil { return err } accessor.SetAnnotations(annotations) return nil } func (resourceAccessor) ResourceVersion(obj runtime.Object) (string, error) { accessor, err := CommonAccessor(obj) if err != nil { return "", err } return accessor.GetResourceVersion(), nil } func (resourceAccessor) SetResourceVersion(obj runtime.Object, version string) error { accessor, err := CommonAccessor(obj) if err != nil { return err } accessor.SetResourceVersion(version) return nil } func (resourceAccessor) Continue(obj runtime.Object) (string, error) { accessor, err := ListAccessor(obj) if err != nil { return "", err } return accessor.GetContinue(), nil } func (resourceAccessor) SetContinue(obj runtime.Object, version string) error { accessor, err := ListAccessor(obj) if err != nil { return err } accessor.SetContinue(version) return nil } // extractFromOwnerReference extracts v to o. v is the OwnerReferences field of an object. func extractFromOwnerReference(v reflect.Value, o *metav1.OwnerReference) error { if err := runtime.Field(v, "APIVersion", &o.APIVersion); err != nil { return err } if err := runtime.Field(v, "Kind", &o.Kind); err != nil { return err } if err := runtime.Field(v, "Name", &o.Name); err != nil { return err } if err := runtime.Field(v, "UID", &o.UID); err != nil { return err } var controllerPtr *bool if err := runtime.Field(v, "Controller", &controllerPtr); err != nil { return err } if controllerPtr != nil { controller := *controllerPtr o.Controller = &controller } var blockOwnerDeletionPtr *bool if err := runtime.Field(v, "BlockOwnerDeletion", &blockOwnerDeletionPtr); err != nil { return err } if blockOwnerDeletionPtr != nil { block := *blockOwnerDeletionPtr o.BlockOwnerDeletion = &block } return nil } // setOwnerReference sets v to o. v is the OwnerReferences field of an object. func setOwnerReference(v reflect.Value, o *metav1.OwnerReference) error { if err := runtime.SetField(o.APIVersion, v, "APIVersion"); err != nil { return err } if err := runtime.SetField(o.Kind, v, "Kind"); err != nil { return err } if err := runtime.SetField(o.Name, v, "Name"); err != nil { return err } if err := runtime.SetField(o.UID, v, "UID"); err != nil { return err } if o.Controller != nil { controller := *(o.Controller) if err := runtime.SetField(&controller, v, "Controller"); err != nil { return err } } if o.BlockOwnerDeletion != nil { block := *(o.BlockOwnerDeletion) if err := runtime.SetField(&block, v, "BlockOwnerDeletion"); err != nil { return err } } return nil } // genericAccessor contains pointers to strings that can modify an arbitrary // struct and implements the Accessor interface. type genericAccessor struct { namespace *string name *string generateName *string uid *types.UID apiVersion *string kind *string resourceVersion *string selfLink *string creationTimestamp *metav1.Time deletionTimestamp **metav1.Time labels *map[string]string annotations *map[string]string ownerReferences reflect.Value finalizers *[]string } func (a genericAccessor) GetNamespace() string { if a.namespace == nil { return "" } return *a.namespace } func (a genericAccessor) SetNamespace(namespace string) { if a.namespace == nil { return } *a.namespace = namespace } func (a genericAccessor) GetName() string { if a.name == nil { return "" } return *a.name } func (a genericAccessor) SetName(name string) { if a.name == nil { return } *a.name = name } func (a genericAccessor) GetGenerateName() string { if a.generateName == nil { return "" } return *a.generateName } func (a genericAccessor) SetGenerateName(generateName string) { if a.generateName == nil { return } *a.generateName = generateName } func (a genericAccessor) GetUID() types.UID { if a.uid == nil { return "" } return *a.uid } func (a genericAccessor) SetUID(uid types.UID) { if a.uid == nil { return } *a.uid = uid } func (a genericAccessor) GetAPIVersion() string { return *a.apiVersion } func (a genericAccessor) SetAPIVersion(version string) { *a.apiVersion = version } func (a genericAccessor) GetKind() string { return *a.kind } func (a genericAccessor) SetKind(kind string) { *a.kind = kind } func (a genericAccessor) GetResourceVersion() string { return *a.resourceVersion } func (a genericAccessor) SetResourceVersion(version string) { *a.resourceVersion = version } func (a genericAccessor) GetSelfLink() string { return *a.selfLink } func (a genericAccessor) SetSelfLink(selfLink string) { *a.selfLink = selfLink } func (a genericAccessor) GetCreationTimestamp() metav1.Time { return *a.creationTimestamp } func (a genericAccessor) SetCreationTimestamp(timestamp metav1.Time) { *a.creationTimestamp = timestamp } func (a genericAccessor) GetDeletionTimestamp() *metav1.Time { return *a.deletionTimestamp } func (a genericAccessor) SetDeletionTimestamp(timestamp *metav1.Time) { *a.deletionTimestamp = timestamp } func (a genericAccessor) GetLabels() map[string]string { if a.labels == nil { return nil } return *a.labels } func (a genericAccessor) SetLabels(labels map[string]string) { *a.labels = labels } func (a genericAccessor) GetAnnotations() map[string]string { if a.annotations == nil { return nil } return *a.annotations } func (a genericAccessor) SetAnnotations(annotations map[string]string) { if a.annotations == nil { emptyAnnotations := make(map[string]string) a.annotations = &emptyAnnotations } *a.annotations = annotations } func (a genericAccessor) GetFinalizers() []string { if a.finalizers == nil { return nil } return *a.finalizers } func (a genericAccessor) SetFinalizers(finalizers []string) { *a.finalizers = finalizers } func (a genericAccessor) GetOwnerReferences() []metav1.OwnerReference { var ret []metav1.OwnerReference s := a.ownerReferences if s.Kind() != reflect.Pointer || s.Elem().Kind() != reflect.Slice { klog.Errorf("expect %v to be a pointer to slice", s) return ret } s = s.Elem() // Set the capacity to one element greater to avoid copy if the caller later append an element. ret = make([]metav1.OwnerReference, s.Len(), s.Len()+1) for i := 0; i < s.Len(); i++ { if err := extractFromOwnerReference(s.Index(i), &ret[i]); err != nil { klog.Errorf("extractFromOwnerReference failed: %v", err) return ret } } return ret } func (a genericAccessor) SetOwnerReferences(references []metav1.OwnerReference) { s := a.ownerReferences if s.Kind() != reflect.Pointer || s.Elem().Kind() != reflect.Slice { klog.Errorf("expect %v to be a pointer to slice", s) } s = s.Elem() newReferences := reflect.MakeSlice(s.Type(), len(references), len(references)) for i := 0; i < len(references); i++ { if err := setOwnerReference(newReferences.Index(i), &references[i]); err != nil { klog.Errorf("setOwnerReference failed: %v", err) return } } s.Set(newReferences) } // extractFromTypeMeta extracts pointers to version and kind fields from an object func extractFromTypeMeta(v reflect.Value, a *genericAccessor) error { if err := runtime.FieldPtr(v, "APIVersion", &a.apiVersion); err != nil { return err } if err := runtime.FieldPtr(v, "Kind", &a.kind); err != nil { return err } return nil } golang-k8s-apimachinery-0.29.0/pkg/api/meta/meta_test.go000066400000000000000000000027251453143165200230310ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package meta import ( "math/rand" "reflect" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "github.com/google/go-cmp/cmp" fuzz "github.com/google/gofuzz" ) func TestAsPartialObjectMetadata(t *testing.T) { f := fuzz.New().NilChance(.5).NumElements(0, 1).RandSource(rand.NewSource(1)) for i := 0; i < 100; i++ { m := &metav1.ObjectMeta{} f.Fuzz(m) partial := AsPartialObjectMetadata(m) if !reflect.DeepEqual(&partial.ObjectMeta, m) { t.Fatalf("incomplete partial object metadata: %s", cmp.Diff(&partial.ObjectMeta, m)) } } for i := 0; i < 100; i++ { m := &metav1beta1.PartialObjectMetadata{} f.Fuzz(&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)) } } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/multirestmapper.go000066400000000000000000000134211453143165200242740ustar00rootroot00000000000000/* 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 meta import ( "fmt" "strings" "k8s.io/apimachinery/pkg/runtime/schema" utilerrors "k8s.io/apimachinery/pkg/util/errors" ) var ( _ ResettableRESTMapper = MultiRESTMapper{} ) // MultiRESTMapper is a wrapper for multiple RESTMappers. type MultiRESTMapper []RESTMapper func (m MultiRESTMapper) String() string { nested := make([]string, 0, len(m)) for _, t := range m { currString := fmt.Sprintf("%v", t) splitStrings := strings.Split(currString, "\n") nested = append(nested, strings.Join(splitStrings, "\n\t")) } return fmt.Sprintf("MultiRESTMapper{\n\t%s\n}", strings.Join(nested, "\n\t")) } // ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod) // This implementation supports multiple REST schemas and return the first match. func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { for _, t := range m { singular, err = t.ResourceSingularizer(resource) if err == nil { return } } return } func (m MultiRESTMapper) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { allGVRs := []schema.GroupVersionResource{} for _, t := range m { gvrs, err := t.ResourcesFor(resource) // ignore "no match" errors, but any other error percolates back up if IsNoMatchError(err) { continue } if err != nil { return nil, err } // walk the existing values to de-dup for _, curr := range gvrs { found := false for _, existing := range allGVRs { if curr == existing { found = true break } } if !found { allGVRs = append(allGVRs, curr) } } } if len(allGVRs) == 0 { return nil, &NoResourceMatchError{PartialResource: resource} } return allGVRs, nil } func (m MultiRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { allGVKs := []schema.GroupVersionKind{} for _, t := range m { gvks, err := t.KindsFor(resource) // ignore "no match" errors, but any other error percolates back up if IsNoMatchError(err) { continue } if err != nil { return nil, err } // walk the existing values to de-dup for _, curr := range gvks { found := false for _, existing := range allGVKs { if curr == existing { found = true break } } if !found { allGVKs = append(allGVKs, curr) } } } if len(allGVKs) == 0 { return nil, &NoResourceMatchError{PartialResource: resource} } return allGVKs, nil } func (m MultiRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { resources, err := m.ResourcesFor(resource) if err != nil { return schema.GroupVersionResource{}, err } if len(resources) == 1 { return resources[0], nil } return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} } func (m MultiRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { kinds, err := m.KindsFor(resource) if err != nil { return schema.GroupVersionKind{}, err } if len(kinds) == 1 { return kinds[0], nil } return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} } // RESTMapping provides the REST mapping for the resource based on the // kind and version. This implementation supports multiple REST schemas and // return the first match. func (m MultiRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { allMappings := []*RESTMapping{} errors := []error{} for _, t := range m { currMapping, err := t.RESTMapping(gk, versions...) // ignore "no match" errors, but any other error percolates back up if IsNoMatchError(err) { continue } if err != nil { errors = append(errors, err) continue } allMappings = append(allMappings, currMapping) } // if we got exactly one mapping, then use it even if other requested failed if len(allMappings) == 1 { return allMappings[0], nil } if len(allMappings) > 1 { var kinds []schema.GroupVersionKind for _, m := range allMappings { kinds = append(kinds, m.GroupVersionKind) } return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds} } if len(errors) > 0 { return nil, utilerrors.NewAggregate(errors) } return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } // RESTMappings returns all possible RESTMappings for the provided group kind, or an error // if the type is not recognized. func (m MultiRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { var allMappings []*RESTMapping var errors []error for _, t := range m { currMappings, err := t.RESTMappings(gk, versions...) // ignore "no match" errors, but any other error percolates back up if IsNoMatchError(err) { continue } if err != nil { errors = append(errors, err) continue } allMappings = append(allMappings, currMappings...) } if len(errors) > 0 { return nil, utilerrors.NewAggregate(errors) } if len(allMappings) == 0 { return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } return allMappings, nil } func (m MultiRESTMapper) Reset() { for _, t := range m { MaybeResetRESTMapper(t) } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/multirestmapper_test.go000066400000000000000000000331541453143165200253400ustar00rootroot00000000000000/* 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 meta import ( "errors" "reflect" "testing" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestMultiRESTMapperResourceFor(t *testing.T) { tcs := []struct { name string mapper MultiRESTMapper input schema.GroupVersionResource result schema.GroupVersionResource err error }{ { name: "empty", mapper: MultiRESTMapper{}, input: schema.GroupVersionResource{Resource: "foo"}, result: schema.GroupVersionResource{}, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "ignore not found", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: schema.GroupVersionResource{}, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "accept first failure", mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "unused"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: schema.GroupVersionResource{}, err: errors.New("fail on this"), }, } for _, tc := range tcs { actualResult, actualErr := tc.mapper.ResourceFor(tc.input) if e, a := tc.result, actualResult; e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } switch { case tc.err == nil && actualErr == nil: case tc.err == nil: t.Errorf("%s: unexpected error: %v", tc.name, actualErr) case actualErr == nil: t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) case tc.err.Error() != actualErr.Error(): t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestMultiRESTMapperResourcesFor(t *testing.T) { tcs := []struct { name string mapper MultiRESTMapper input schema.GroupVersionResource result []schema.GroupVersionResource err error }{ { name: "empty", mapper: MultiRESTMapper{}, input: schema.GroupVersionResource{Resource: "foo"}, result: nil, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "ignore not found", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: nil, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "accept first failure", mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "unused"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: nil, err: errors.New("fail on this"), }, { name: "union and dedup", mapper: MultiRESTMapper{ fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "dupe"}, {Resource: "first"}}}, fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "dupe"}, {Resource: "second"}}}, }, input: schema.GroupVersionResource{Resource: "foo"}, result: []schema.GroupVersionResource{{Resource: "dupe"}, {Resource: "first"}, {Resource: "second"}}, }, { name: "skip not and continue", mapper: MultiRESTMapper{ fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}, fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "first"}, {Resource: "second"}}}, }, input: schema.GroupVersionResource{Resource: "foo"}, result: []schema.GroupVersionResource{{Resource: "first"}, {Resource: "second"}}, }, } for _, tc := range tcs { actualResult, actualErr := tc.mapper.ResourcesFor(tc.input) if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } switch { case tc.err == nil && actualErr == nil: case tc.err == nil: t.Errorf("%s: unexpected error: %v", tc.name, actualErr) case actualErr == nil: t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) case tc.err.Error() != actualErr.Error(): t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestMultiRESTMapperKindsFor(t *testing.T) { tcs := []struct { name string mapper MultiRESTMapper input schema.GroupVersionResource result []schema.GroupVersionKind err error }{ { name: "empty", mapper: MultiRESTMapper{}, input: schema.GroupVersionResource{Resource: "foo"}, result: nil, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "ignore not found", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: nil, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "accept first failure", mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "unused"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: nil, err: errors.New("fail on this"), }, { name: "union and dedup", mapper: MultiRESTMapper{ fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "dupe"}, {Kind: "first"}}}, fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "dupe"}, {Kind: "second"}}}, }, input: schema.GroupVersionResource{Resource: "foo"}, result: []schema.GroupVersionKind{{Kind: "dupe"}, {Kind: "first"}, {Kind: "second"}}, }, { name: "skip not and continue", mapper: MultiRESTMapper{ fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}, fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "first"}, {Kind: "second"}}}, }, input: schema.GroupVersionResource{Resource: "foo"}, result: []schema.GroupVersionKind{{Kind: "first"}, {Kind: "second"}}, }, } for _, tc := range tcs { actualResult, actualErr := tc.mapper.KindsFor(tc.input) if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } switch { case tc.err == nil && actualErr == nil: case tc.err == nil: t.Errorf("%s: unexpected error: %v", tc.name, actualErr) case actualErr == nil: t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) case tc.err.Error() != actualErr.Error(): t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestMultiRESTMapperKindFor(t *testing.T) { tcs := []struct { name string mapper MultiRESTMapper input schema.GroupVersionResource result schema.GroupVersionKind err error }{ { name: "empty", mapper: MultiRESTMapper{}, input: schema.GroupVersionResource{Resource: "foo"}, result: schema.GroupVersionKind{}, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "ignore not found", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: schema.GroupVersionKind{}, err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, }, { name: "accept first failure", mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "unused"}}}}, input: schema.GroupVersionResource{Resource: "foo"}, result: schema.GroupVersionKind{}, err: errors.New("fail on this"), }, } for _, tc := range tcs { actualResult, actualErr := tc.mapper.KindFor(tc.input) if e, a := tc.result, actualResult; e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } switch { case tc.err == nil && actualErr == nil: case tc.err == nil: t.Errorf("%s: unexpected error: %v", tc.name, actualErr) case actualErr == nil: t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) case tc.err.Error() != actualErr.Error(): t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestMultiRESTMapperRESTMappings(t *testing.T) { mapping1, mapping2 := &RESTMapping{}, &RESTMapping{} tcs := []struct { name string mapper MultiRESTMapper groupKind schema.GroupKind versions []string result []*RESTMapping err error }{ { name: "empty with no versions", mapper: MultiRESTMapper{}, groupKind: schema.GroupKind{Kind: "Foo"}, result: nil, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { name: "empty with one version", mapper: MultiRESTMapper{}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: []string{"v1beta"}, result: nil, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta"}}, }, { name: "empty with multi(two) vesions", mapper: MultiRESTMapper{}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: []string{"v1beta", "v2"}, result: nil, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta", "v2"}}, }, { name: "ignore not found with kind not exist", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "IGNORE_THIS"}}}}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: nil, result: nil, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { name: "ignore not found with version not exist", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1"}}}}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: []string{"v1beta"}, result: nil, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta"}}, }, { name: "ignore not found with multi versions not exist", mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1"}}}}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: []string{"v1beta", "v2"}, result: nil, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta", "v2"}}, }, { name: "accept first failure", mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{mappings: []*RESTMapping{mapping1}}}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: []string{"v1beta"}, result: nil, err: errors.New("fail on this"), }, { name: "return both", mapper: MultiRESTMapper{fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, fixedRESTMapper{mappings: []*RESTMapping{mapping2}}}, groupKind: schema.GroupKind{Kind: "Foo"}, versions: []string{"v1beta"}, result: []*RESTMapping{mapping1, mapping2}, }, } for _, tc := range tcs { actualResult, actualErr := tc.mapper.RESTMappings(tc.groupKind, tc.versions...) if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } switch { case tc.err == nil && actualErr == nil: case tc.err == nil: t.Errorf("%s: unexpected error: %v", tc.name, actualErr) case actualErr == nil: t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) case tc.err.Error() != actualErr.Error(): t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } type fixedRESTMapper struct { resourcesFor []schema.GroupVersionResource kindsFor []schema.GroupVersionKind resourceFor schema.GroupVersionResource kindFor schema.GroupVersionKind mappings []*RESTMapping err error } func (m fixedRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { return "", m.err } func (m fixedRESTMapper) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { return m.resourcesFor, m.err } func (m fixedRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { return m.kindsFor, m.err } func (m fixedRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { return m.resourceFor, m.err } func (m fixedRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { return m.kindFor, m.err } func (m fixedRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { return nil, m.err } func (m fixedRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (mappings []*RESTMapping, err error) { return m.mappings, m.err } func (m fixedRESTMapper) ResourceIsValid(resource schema.GroupVersionResource) bool { return false } golang-k8s-apimachinery-0.29.0/pkg/api/meta/priority.go000066400000000000000000000170711453143165200227250ustar00rootroot00000000000000/* Copyright 2016 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 meta import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" ) const ( AnyGroup = "*" AnyVersion = "*" AnyResource = "*" AnyKind = "*" ) var ( _ ResettableRESTMapper = PriorityRESTMapper{} ) // PriorityRESTMapper is a wrapper for automatically choosing a particular Resource or Kind // when multiple matches are possible type PriorityRESTMapper struct { // Delegate is the RESTMapper to use to locate all the Kind and Resource matches Delegate RESTMapper // ResourcePriority is a list of priority patterns to apply to matching resources. // The list of all matching resources is narrowed based on the patterns until only one remains. // A pattern with no matches is skipped. A pattern with more than one match uses its // matches as the list to continue matching against. ResourcePriority []schema.GroupVersionResource // KindPriority is a list of priority patterns to apply to matching kinds. // The list of all matching kinds is narrowed based on the patterns until only one remains. // A pattern with no matches is skipped. A pattern with more than one match uses its // matches as the list to continue matching against. KindPriority []schema.GroupVersionKind } func (m PriorityRESTMapper) String() string { return fmt.Sprintf("PriorityRESTMapper{\n\t%v\n\t%v\n\t%v\n}", m.ResourcePriority, m.KindPriority, m.Delegate) } // ResourceFor finds all resources, then passes them through the ResourcePriority patterns to find a single matching hit. func (m PriorityRESTMapper) ResourceFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionResource, error) { originalGVRs, originalErr := m.Delegate.ResourcesFor(partiallySpecifiedResource) if originalErr != nil && len(originalGVRs) == 0 { return schema.GroupVersionResource{}, originalErr } if len(originalGVRs) == 1 { return originalGVRs[0], originalErr } remainingGVRs := append([]schema.GroupVersionResource{}, originalGVRs...) for _, pattern := range m.ResourcePriority { matchedGVRs := []schema.GroupVersionResource{} for _, gvr := range remainingGVRs { if resourceMatches(pattern, gvr) { matchedGVRs = append(matchedGVRs, gvr) } } switch len(matchedGVRs) { case 0: // if you have no matches, then nothing matched this pattern just move to the next continue case 1: // one match, return return matchedGVRs[0], originalErr default: // more than one match, use the matched hits as the list moving to the next pattern. // this way you can have a series of selection criteria remainingGVRs = matchedGVRs } } return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingResources: originalGVRs} } // KindFor finds all kinds, then passes them through the KindPriority patterns to find a single matching hit. func (m PriorityRESTMapper) KindFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionKind, error) { originalGVKs, originalErr := m.Delegate.KindsFor(partiallySpecifiedResource) if originalErr != nil && len(originalGVKs) == 0 { return schema.GroupVersionKind{}, originalErr } if len(originalGVKs) == 1 { return originalGVKs[0], originalErr } remainingGVKs := append([]schema.GroupVersionKind{}, originalGVKs...) for _, pattern := range m.KindPriority { matchedGVKs := []schema.GroupVersionKind{} for _, gvr := range remainingGVKs { if kindMatches(pattern, gvr) { matchedGVKs = append(matchedGVKs, gvr) } } switch len(matchedGVKs) { case 0: // if you have no matches, then nothing matched this pattern just move to the next continue case 1: // one match, return return matchedGVKs[0], originalErr default: // more than one match, use the matched hits as the list moving to the next pattern. // this way you can have a series of selection criteria remainingGVKs = matchedGVKs } } return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingKinds: originalGVKs} } func resourceMatches(pattern schema.GroupVersionResource, resource schema.GroupVersionResource) bool { if pattern.Group != AnyGroup && pattern.Group != resource.Group { return false } if pattern.Version != AnyVersion && pattern.Version != resource.Version { return false } if pattern.Resource != AnyResource && pattern.Resource != resource.Resource { return false } return true } func kindMatches(pattern schema.GroupVersionKind, kind schema.GroupVersionKind) bool { if pattern.Group != AnyGroup && pattern.Group != kind.Group { return false } if pattern.Version != AnyVersion && pattern.Version != kind.Version { return false } if pattern.Kind != AnyKind && pattern.Kind != kind.Kind { return false } return true } func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { mappings, originalErr := m.Delegate.RESTMappings(gk, versions...) if originalErr != nil && len(mappings) == 0 { return nil, originalErr } // any versions the user provides take priority priorities := m.KindPriority if len(versions) > 0 { priorities = make([]schema.GroupVersionKind, 0, len(m.KindPriority)+len(versions)) for _, version := range versions { gv := schema.GroupVersion{ Version: version, Group: gk.Group, } priorities = append(priorities, gv.WithKind(AnyKind)) } priorities = append(priorities, m.KindPriority...) } remaining := append([]*RESTMapping{}, mappings...) for _, pattern := range priorities { var matching []*RESTMapping for _, m := range remaining { if kindMatches(pattern, m.GroupVersionKind) { matching = append(matching, m) } } switch len(matching) { case 0: // if you have no matches, then nothing matched this pattern just move to the next continue case 1: // one match, return return matching[0], originalErr default: // more than one match, use the matched hits as the list moving to the next pattern. // this way you can have a series of selection criteria remaining = matching } } if len(remaining) == 1 { return remaining[0], originalErr } var kinds []schema.GroupVersionKind for _, m := range mappings { kinds = append(kinds, m.GroupVersionKind) } return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds} } func (m PriorityRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { return m.Delegate.RESTMappings(gk, versions...) } func (m PriorityRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { return m.Delegate.ResourceSingularizer(resource) } func (m PriorityRESTMapper) ResourcesFor(partiallySpecifiedResource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { return m.Delegate.ResourcesFor(partiallySpecifiedResource) } func (m PriorityRESTMapper) KindsFor(partiallySpecifiedResource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { return m.Delegate.KindsFor(partiallySpecifiedResource) } func (m PriorityRESTMapper) Reset() { MaybeResetRESTMapper(m.Delegate) } golang-k8s-apimachinery-0.29.0/pkg/api/meta/priority_test.go000066400000000000000000000331301453143165200237560ustar00rootroot00000000000000/* Copyright 2016 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 meta import ( "errors" "reflect" "strings" "testing" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestPriorityRESTMapperResourceForErrorHandling(t *testing.T) { tcs := []struct { name string delegate RESTMapper resourcePatterns []schema.GroupVersionResource result schema.GroupVersionResource err string }{ { name: "error", delegate: fixedRESTMapper{err: errors.New("delegateError")}, err: "delegateError", }, { name: "single hit + error", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "single-hit"}}, err: errors.New("delegateError")}, result: schema.GroupVersionResource{Resource: "single-hit"}, err: "delegateError", }, { name: "group selection + error", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ {Group: "one", Version: "a", Resource: "first"}, {Group: "two", Version: "b", Resource: "second"}, }, err: errors.New("delegateError")}, resourcePatterns: []schema.GroupVersionResource{ {Group: "one", Version: AnyVersion, Resource: AnyResource}, }, result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, err: "delegateError", }, { name: "single hit", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "single-hit"}}}, result: schema.GroupVersionResource{Resource: "single-hit"}, }, { name: "ambiguous match", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ {Group: "one", Version: "a", Resource: "first"}, {Group: "two", Version: "b", Resource: "second"}, }}, err: "matches multiple resources", }, { name: "group selection", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ {Group: "one", Version: "a", Resource: "first"}, {Group: "two", Version: "b", Resource: "second"}, }}, resourcePatterns: []schema.GroupVersionResource{ {Group: "one", Version: AnyVersion, Resource: AnyResource}, }, result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, }, { name: "empty match continues", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ {Group: "one", Version: "a", Resource: "first"}, {Group: "two", Version: "b", Resource: "second"}, }}, resourcePatterns: []schema.GroupVersionResource{ {Group: "fail", Version: AnyVersion, Resource: AnyResource}, {Group: "one", Version: AnyVersion, Resource: AnyResource}, }, result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, }, { name: "group followed by version selection", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ {Group: "one", Version: "a", Resource: "first"}, {Group: "two", Version: "b", Resource: "second"}, {Group: "one", Version: "c", Resource: "third"}, }}, resourcePatterns: []schema.GroupVersionResource{ {Group: "one", Version: AnyVersion, Resource: AnyResource}, {Group: AnyGroup, Version: "a", Resource: AnyResource}, }, result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, }, { name: "resource selection", delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ {Group: "one", Version: "a", Resource: "first"}, {Group: "one", Version: "a", Resource: "second"}, }}, resourcePatterns: []schema.GroupVersionResource{ {Group: AnyGroup, Version: AnyVersion, Resource: "second"}, }, result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "second"}, }, } for _, tc := range tcs { mapper := PriorityRESTMapper{Delegate: tc.delegate, ResourcePriority: tc.resourcePatterns} actualResult, actualErr := mapper.ResourceFor(schema.GroupVersionResource{}) if e, a := tc.result, actualResult; e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } if len(tc.err) == 0 && actualErr == nil { continue } if len(tc.err) == 0 && actualErr != nil { t.Errorf("%s: unexpected err: %v", tc.name, actualErr) continue } if len(tc.err) > 0 && actualErr == nil { t.Errorf("%s: missing expected err: %v", tc.name, tc.err) continue } if !strings.Contains(actualErr.Error(), tc.err) { t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestPriorityRESTMapperKindForErrorHandling(t *testing.T) { tcs := []struct { name string delegate RESTMapper kindPatterns []schema.GroupVersionKind result schema.GroupVersionKind err string }{ { name: "error", delegate: fixedRESTMapper{err: errors.New("delegateErr")}, err: "delegateErr", }, { name: "single hit + error", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "single-hit"}}, err: errors.New("delegateErr")}, result: schema.GroupVersionKind{Kind: "single-hit"}, err: "delegateErr", }, { name: "group selection + error", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ {Group: "one", Version: "a", Kind: "first"}, {Group: "two", Version: "b", Kind: "second"}, }, err: errors.New("delegateErr")}, kindPatterns: []schema.GroupVersionKind{ {Group: "one", Version: AnyVersion, Kind: AnyKind}, }, result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, err: "delegateErr", }, { name: "single hit", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "single-hit"}}}, result: schema.GroupVersionKind{Kind: "single-hit"}, }, { name: "ambiguous match", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ {Group: "one", Version: "a", Kind: "first"}, {Group: "two", Version: "b", Kind: "second"}, }}, err: "matches multiple kinds", }, { name: "group selection", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ {Group: "one", Version: "a", Kind: "first"}, {Group: "two", Version: "b", Kind: "second"}, }}, kindPatterns: []schema.GroupVersionKind{ {Group: "one", Version: AnyVersion, Kind: AnyKind}, }, result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, }, { name: "empty match continues", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ {Group: "one", Version: "a", Kind: "first"}, {Group: "two", Version: "b", Kind: "second"}, }}, kindPatterns: []schema.GroupVersionKind{ {Group: "fail", Version: AnyVersion, Kind: AnyKind}, {Group: "one", Version: AnyVersion, Kind: AnyKind}, }, result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, }, { name: "group followed by version selection", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ {Group: "one", Version: "a", Kind: "first"}, {Group: "two", Version: "b", Kind: "second"}, {Group: "one", Version: "c", Kind: "third"}, }}, kindPatterns: []schema.GroupVersionKind{ {Group: "one", Version: AnyVersion, Kind: AnyKind}, {Group: AnyGroup, Version: "a", Kind: AnyKind}, }, result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, }, { name: "kind selection", delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ {Group: "one", Version: "a", Kind: "first"}, {Group: "one", Version: "a", Kind: "second"}, }}, kindPatterns: []schema.GroupVersionKind{ {Group: AnyGroup, Version: AnyVersion, Kind: "second"}, }, result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "second"}, }, } for _, tc := range tcs { mapper := PriorityRESTMapper{Delegate: tc.delegate, KindPriority: tc.kindPatterns} actualResult, actualErr := mapper.KindFor(schema.GroupVersionResource{}) if e, a := tc.result, actualResult; e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } if len(tc.err) == 0 && actualErr == nil { continue } if len(tc.err) == 0 && actualErr != nil { t.Errorf("%s: unexpected err: %v", tc.name, actualErr) continue } if len(tc.err) > 0 && actualErr == nil { t.Errorf("%s: missing expected err: %v", tc.name, tc.err) continue } if !strings.Contains(actualErr.Error(), tc.err) { t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestPriorityRESTMapperRESTMapping(t *testing.T) { mapping1 := &RESTMapping{ GroupVersionKind: schema.GroupVersionKind{Kind: "Foo", Version: "v1alpha1"}, } mapping2 := &RESTMapping{ GroupVersionKind: schema.GroupVersionKind{Kind: "Foo", Version: "v1"}, } mapping3 := &RESTMapping{ GroupVersionKind: schema.GroupVersionKind{Group: "other", Kind: "Foo", Version: "v1"}, } allMappers := MultiRESTMapper{ fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, fixedRESTMapper{mappings: []*RESTMapping{mapping2}}, fixedRESTMapper{mappings: []*RESTMapping{mapping3}}, } tcs := []struct { name string mapper PriorityRESTMapper input schema.GroupKind result *RESTMapping err error }{ { name: "empty", mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{}}, input: schema.GroupKind{Kind: "Foo"}, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { name: "ignore not found", mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "IGNORE_THIS"}}}}}, input: schema.GroupKind{Kind: "Foo"}, err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, }, { name: "accept first failure", mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{mappings: []*RESTMapping{mapping1}}}}, input: schema.GroupKind{Kind: "Foo"}, err: errors.New("fail on this"), }, { name: "result + error", mapper: PriorityRESTMapper{Delegate: fixedRESTMapper{mappings: []*RESTMapping{mapping1}, err: errors.New("fail on this")}}, input: schema.GroupKind{Kind: "Foo"}, result: mapping1, err: errors.New("fail on this"), }, { name: "return error for ambiguous", mapper: PriorityRESTMapper{ Delegate: allMappers, }, input: schema.GroupKind{Kind: "Foo"}, err: &AmbiguousKindError{ PartialKind: schema.GroupVersionKind{Kind: "Foo"}, MatchingKinds: []schema.GroupVersionKind{ {Kind: "Foo", Version: "v1alpha1"}, {Kind: "Foo", Version: "v1"}, {Group: "other", Kind: "Foo", Version: "v1"}, }, }, }, { name: "accept only item", mapper: PriorityRESTMapper{ Delegate: fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, }, input: schema.GroupKind{Kind: "Foo"}, result: mapping1, }, { name: "return single priority", mapper: PriorityRESTMapper{ Delegate: allMappers, KindPriority: []schema.GroupVersionKind{{Version: "v1", Kind: AnyKind}, {Version: "v1alpha1", Kind: AnyKind}}, }, input: schema.GroupKind{Kind: "Foo"}, result: mapping2, }, { name: "return out of group match", mapper: PriorityRESTMapper{ Delegate: allMappers, KindPriority: []schema.GroupVersionKind{{Group: AnyGroup, Version: "v1", Kind: AnyKind}, {Group: "other", Version: AnyVersion, Kind: AnyKind}}, }, input: schema.GroupKind{Kind: "Foo"}, result: mapping3, }, } for _, tc := range tcs { actualResult, actualErr := tc.mapper.RESTMapping(tc.input) if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } switch { case tc.err == nil && actualErr == nil: case tc.err == nil: t.Errorf("%s: unexpected error: %v", tc.name, actualErr) case actualErr == nil: t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) case tc.err.Error() != actualErr.Error(): t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } func TestPriorityRESTMapperRESTMappingHonorsUserVersion(t *testing.T) { mappingV2alpha1 := &RESTMapping{ GroupVersionKind: schema.GroupVersionKind{Group: "Bar", Kind: "Foo", Version: "v2alpha1"}, } mappingV1 := &RESTMapping{ GroupVersionKind: schema.GroupVersionKind{Group: "Bar", Kind: "Foo", Version: "v1"}, } allMappers := MultiRESTMapper{ fixedRESTMapper{mappings: []*RESTMapping{mappingV2alpha1}}, fixedRESTMapper{mappings: []*RESTMapping{mappingV1}}, } mapper := PriorityRESTMapper{ Delegate: allMappers, KindPriority: []schema.GroupVersionKind{{Group: "Bar", Version: "v2alpha1", Kind: AnyKind}, {Group: "Bar", Version: AnyVersion, Kind: AnyKind}}, } outMapping1, err := mapper.RESTMapping(schema.GroupKind{Group: "Bar", Kind: "Foo"}, "v1") if err != nil { t.Errorf("unexpected error: %v", err) } if outMapping1 != mappingV1 { t.Errorf("asked for version %v, expected mapping for %v, got mapping for %v", "v1", mappingV1.GroupVersionKind, outMapping1.GroupVersionKind) } outMapping2, err := mapper.RESTMapping(schema.GroupKind{Group: "Bar", Kind: "Foo"}, "v2alpha1") if err != nil { t.Errorf("unexpected error: %v", err) } if outMapping2 != mappingV2alpha1 { t.Errorf("asked for version %v, expected mapping for %v, got mapping for %v", "v2alpha1", mappingV2alpha1.GroupVersionKind, outMapping2.GroupVersionKind) } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/restmapper.go000066400000000000000000000400651453143165200232250ustar00rootroot00000000000000/* 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. */ // TODO: move everything in this file to pkg/api/rest package meta import ( "fmt" "sort" "strings" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // Implements RESTScope interface type restScope struct { name RESTScopeName } func (r *restScope) Name() RESTScopeName { return r.name } var RESTScopeNamespace = &restScope{ name: RESTScopeNameNamespace, } var RESTScopeRoot = &restScope{ name: RESTScopeNameRoot, } // DefaultRESTMapper exposes mappings between the types defined in a // runtime.Scheme. It assumes that all types defined the provided scheme // can be mapped with the provided MetadataAccessor and Codec interfaces. // // The resource name of a Kind is defined as the lowercase, // English-plural version of the Kind string. // When converting from resource to Kind, the singular version of the // resource name is also accepted for convenience. // // TODO: Only accept plural for some operations for increased control? // (`get pod bar` vs `get pods bar`) type DefaultRESTMapper struct { defaultGroupVersions []schema.GroupVersion resourceToKind map[schema.GroupVersionResource]schema.GroupVersionKind kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource kindToScope map[schema.GroupVersionKind]RESTScope singularToPlural map[schema.GroupVersionResource]schema.GroupVersionResource pluralToSingular map[schema.GroupVersionResource]schema.GroupVersionResource } func (m *DefaultRESTMapper) String() string { if m == nil { return "" } return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource) } var _ RESTMapper = &DefaultRESTMapper{} // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion // to a resource name and back based on the objects in a runtime.Scheme // and the Kubernetes API conventions. Takes a group name, a priority list of the versions // to search when an object has no default version (set empty to return an error), // and a function that retrieves the correct metadata for a given version. func NewDefaultRESTMapper(defaultGroupVersions []schema.GroupVersion) *DefaultRESTMapper { resourceToKind := make(map[schema.GroupVersionResource]schema.GroupVersionKind) kindToPluralResource := make(map[schema.GroupVersionKind]schema.GroupVersionResource) kindToScope := make(map[schema.GroupVersionKind]RESTScope) singularToPlural := make(map[schema.GroupVersionResource]schema.GroupVersionResource) pluralToSingular := make(map[schema.GroupVersionResource]schema.GroupVersionResource) // TODO: verify name mappings work correctly when versions differ return &DefaultRESTMapper{ resourceToKind: resourceToKind, kindToPluralResource: kindToPluralResource, kindToScope: kindToScope, defaultGroupVersions: defaultGroupVersions, singularToPlural: singularToPlural, pluralToSingular: pluralToSingular, } } func (m *DefaultRESTMapper) Add(kind schema.GroupVersionKind, scope RESTScope) { plural, singular := UnsafeGuessKindToResource(kind) m.AddSpecific(kind, plural, singular, scope) } func (m *DefaultRESTMapper) AddSpecific(kind schema.GroupVersionKind, plural, singular schema.GroupVersionResource, scope RESTScope) { m.singularToPlural[singular] = plural m.pluralToSingular[plural] = singular m.resourceToKind[singular] = kind m.resourceToKind[plural] = kind m.kindToPluralResource[kind] = plural m.kindToScope[kind] = scope } // unpluralizedSuffixes is a list of resource suffixes that are the same plural and singular // This is only is only necessary because some bits of code are lazy and don't actually use the RESTMapper like they should. // TODO eliminate this so that different callers can correctly map to resources. This probably means updating all // callers to use the RESTMapper they mean. var unpluralizedSuffixes = []string{ "endpoints", } // UnsafeGuessKindToResource converts Kind to a resource name. // Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match // and they aren't guaranteed to do so. func UnsafeGuessKindToResource(kind schema.GroupVersionKind) ( /*plural*/ schema.GroupVersionResource /*singular*/, schema.GroupVersionResource) { kindName := kind.Kind if len(kindName) == 0 { return schema.GroupVersionResource{}, schema.GroupVersionResource{} } singularName := strings.ToLower(kindName) singular := kind.GroupVersion().WithResource(singularName) for _, skip := range unpluralizedSuffixes { if strings.HasSuffix(singularName, skip) { return singular, singular } } switch string(singularName[len(singularName)-1]) { case "s": return kind.GroupVersion().WithResource(singularName + "es"), singular case "y": return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular } return kind.GroupVersion().WithResource(singularName + "s"), singular } // ResourceSingularizer implements RESTMapper // It converts a resource name from plural to singular (e.g., from pods to pod) func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) { partialResource := schema.GroupVersionResource{Resource: resourceType} resources, err := m.ResourcesFor(partialResource) if err != nil { return resourceType, err } singular := schema.GroupVersionResource{} for _, curr := range resources { currSingular, ok := m.pluralToSingular[curr] if !ok { continue } if singular.Empty() { singular = currSingular continue } if currSingular.Resource != singular.Resource { return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType) } } if singular.Empty() { return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType) } return singular.Resource, nil } // coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior) func coerceResourceForMatching(resource schema.GroupVersionResource) schema.GroupVersionResource { resource.Resource = strings.ToLower(resource.Resource) if resource.Version == runtime.APIVersionInternal { resource.Version = "" } return resource } func (m *DefaultRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { resource := coerceResourceForMatching(input) hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 if !hasResource { return nil, fmt.Errorf("a resource must be present, got: %v", resource) } ret := []schema.GroupVersionResource{} switch { case hasGroup && hasVersion: // fully qualified. Find the exact match for plural, singular := range m.pluralToSingular { if singular == resource { ret = append(ret, plural) break } if plural == resource { ret = append(ret, plural) break } } case hasGroup: // given a group, prefer an exact match. If you don't find one, resort to a prefix match on group foundExactMatch := false requestedGroupResource := resource.GroupResource() for plural, singular := range m.pluralToSingular { if singular.GroupResource() == requestedGroupResource { foundExactMatch = true ret = append(ret, plural) } if plural.GroupResource() == requestedGroupResource { foundExactMatch = true ret = append(ret, plural) } } // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match // storageclass.storage.k8s.io if !foundExactMatch { for plural, singular := range m.pluralToSingular { if !strings.HasPrefix(plural.Group, requestedGroupResource.Group) { continue } if singular.Resource == requestedGroupResource.Resource { ret = append(ret, plural) } if plural.Resource == requestedGroupResource.Resource { ret = append(ret, plural) } } } case hasVersion: for plural, singular := range m.pluralToSingular { if singular.Version == resource.Version && singular.Resource == resource.Resource { ret = append(ret, plural) } if plural.Version == resource.Version && plural.Resource == resource.Resource { ret = append(ret, plural) } } default: for plural, singular := range m.pluralToSingular { if singular.Resource == resource.Resource { ret = append(ret, plural) } if plural.Resource == resource.Resource { ret = append(ret, plural) } } } if len(ret) == 0 { return nil, &NoResourceMatchError{PartialResource: resource} } sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions}) return ret, nil } func (m *DefaultRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { resources, err := m.ResourcesFor(resource) if err != nil { return schema.GroupVersionResource{}, err } if len(resources) == 1 { return resources[0], nil } return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} } func (m *DefaultRESTMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { resource := coerceResourceForMatching(input) hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 if !hasResource { return nil, fmt.Errorf("a resource must be present, got: %v", resource) } ret := []schema.GroupVersionKind{} switch { // fully qualified. Find the exact match case hasGroup && hasVersion: kind, exists := m.resourceToKind[resource] if exists { ret = append(ret, kind) } case hasGroup: foundExactMatch := false requestedGroupResource := resource.GroupResource() for currResource, currKind := range m.resourceToKind { if currResource.GroupResource() == requestedGroupResource { foundExactMatch = true ret = append(ret, currKind) } } // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match // storageclass.storage.k8s.io if !foundExactMatch { for currResource, currKind := range m.resourceToKind { if !strings.HasPrefix(currResource.Group, requestedGroupResource.Group) { continue } if currResource.Resource == requestedGroupResource.Resource { ret = append(ret, currKind) } } } case hasVersion: for currResource, currKind := range m.resourceToKind { if currResource.Version == resource.Version && currResource.Resource == resource.Resource { ret = append(ret, currKind) } } default: for currResource, currKind := range m.resourceToKind { if currResource.Resource == resource.Resource { ret = append(ret, currKind) } } } if len(ret) == 0 { return nil, &NoResourceMatchError{PartialResource: input} } sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) return ret, nil } func (m *DefaultRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { kinds, err := m.KindsFor(resource) if err != nil { return schema.GroupVersionKind{}, err } if len(kinds) == 1 { return kinds[0], nil } return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} } type kindByPreferredGroupVersion struct { list []schema.GroupVersionKind sortOrder []schema.GroupVersion } func (o kindByPreferredGroupVersion) Len() int { return len(o.list) } func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } func (o kindByPreferredGroupVersion) Less(i, j int) bool { lhs := o.list[i] rhs := o.list[j] if lhs == rhs { return false } if lhs.GroupVersion() == rhs.GroupVersion() { return lhs.Kind < rhs.Kind } // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order lhsIndex := -1 rhsIndex := -1 for i := range o.sortOrder { if o.sortOrder[i] == lhs.GroupVersion() { lhsIndex = i } if o.sortOrder[i] == rhs.GroupVersion() { rhsIndex = i } } if rhsIndex == -1 { return true } return lhsIndex < rhsIndex } type resourceByPreferredGroupVersion struct { list []schema.GroupVersionResource sortOrder []schema.GroupVersion } func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) } func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } func (o resourceByPreferredGroupVersion) Less(i, j int) bool { lhs := o.list[i] rhs := o.list[j] if lhs == rhs { return false } if lhs.GroupVersion() == rhs.GroupVersion() { return lhs.Resource < rhs.Resource } // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order lhsIndex := -1 rhsIndex := -1 for i := range o.sortOrder { if o.sortOrder[i] == lhs.GroupVersion() { lhsIndex = i } if o.sortOrder[i] == rhs.GroupVersion() { rhsIndex = i } } if rhsIndex == -1 { return true } return lhsIndex < rhsIndex } // RESTMapping returns a struct representing the resource path and conversion interfaces a // RESTClient should use to operate on the provided group/kind in order of versions. If a version search // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which // version should be used to access the named group/kind. func (m *DefaultRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { mappings, err := m.RESTMappings(gk, versions...) if err != nil { return nil, err } if len(mappings) == 0 { return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } // since we rely on RESTMappings method // take the first match and return to the caller // as this was the existing behavior. return mappings[0], nil } // RESTMappings returns the RESTMappings for the provided group kind. If a version search order // is not provided, the search order provided to DefaultRESTMapper will be used. func (m *DefaultRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { mappings := make([]*RESTMapping, 0) potentialGVK := make([]schema.GroupVersionKind, 0) hadVersion := false // Pick an appropriate version for _, version := range versions { if len(version) == 0 || version == runtime.APIVersionInternal { continue } currGVK := gk.WithVersion(version) hadVersion = true if _, ok := m.kindToPluralResource[currGVK]; ok { potentialGVK = append(potentialGVK, currGVK) break } } // Use the default preferred versions if !hadVersion && len(potentialGVK) == 0 { for _, gv := range m.defaultGroupVersions { if gv.Group != gk.Group { continue } potentialGVK = append(potentialGVK, gk.WithVersion(gv.Version)) } } if len(potentialGVK) == 0 { return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} } for _, gvk := range potentialGVK { //Ensure we have a REST mapping res, ok := m.kindToPluralResource[gvk] if !ok { continue } // Ensure we have a REST scope scope, ok := m.kindToScope[gvk] if !ok { return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind) } mappings = append(mappings, &RESTMapping{ Resource: res, GroupVersionKind: gvk, Scope: scope, }) } if len(mappings) == 0 { return nil, &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}} } return mappings, nil } // MaybeResetRESTMapper calls Reset() on the mapper if it is a ResettableRESTMapper. func MaybeResetRESTMapper(mapper RESTMapper) { m, ok := mapper.(ResettableRESTMapper) if ok { m.Reset() } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/restmapper_test.go000066400000000000000000000716331453143165200242710ustar00rootroot00000000000000/* 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 meta import ( "reflect" "strings" "testing" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestRESTMapperVersionAndKindForResource(t *testing.T) { testGroup := "test.group" testVersion := "test" testGroupVersion := schema.GroupVersion{Group: testGroup, Version: testVersion} testCases := []struct { Resource schema.GroupVersionResource GroupVersionToRegister schema.GroupVersion ExpectedGVK schema.GroupVersionKind Err bool }{ {Resource: schema.GroupVersionResource{Resource: "internalobjec"}, Err: true}, {Resource: schema.GroupVersionResource{Resource: "internalObjec"}, Err: true}, {Resource: schema.GroupVersionResource{Resource: "internalobject"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, {Resource: schema.GroupVersionResource{Resource: "internalobjects"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]schema.GroupVersion{testGroupVersion}) if len(testCase.ExpectedGVK.Kind) != 0 { mapper.Add(testCase.ExpectedGVK, RESTScopeNamespace) } actualGVK, err := mapper.KindFor(testCase.Resource) hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) continue } if err != nil { continue } if actualGVK != testCase.ExpectedGVK { t.Errorf("%d: unexpected version and kind: e=%s a=%s", i, testCase.ExpectedGVK, actualGVK) } } } func TestRESTMapperGroupForResource(t *testing.T) { testCases := []struct { Resource schema.GroupVersionResource GroupVersionKind schema.GroupVersionKind Err bool }{ {Resource: schema.GroupVersionResource{Resource: "myObject"}, GroupVersionKind: schema.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, {Resource: schema.GroupVersionResource{Resource: "myobject"}, GroupVersionKind: schema.GroupVersionKind{Group: "testapi2", Version: "test", Kind: "MyObject"}}, {Resource: schema.GroupVersionResource{Resource: "myObje"}, Err: true, GroupVersionKind: schema.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, {Resource: schema.GroupVersionResource{Resource: "myobje"}, Err: true, GroupVersionKind: schema.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]schema.GroupVersion{testCase.GroupVersionKind.GroupVersion()}) mapper.Add(testCase.GroupVersionKind, RESTScopeNamespace) actualGVK, err := mapper.KindFor(testCase.Resource) if testCase.Err { if err == nil { t.Errorf("%d: expected error", i) } } else if err != nil { t.Errorf("%d: unexpected error: %v", i, err) } else if actualGVK != testCase.GroupVersionKind { t.Errorf("%d: expected group %q, got %q", i, testCase.GroupVersionKind, actualGVK) } } } func TestRESTMapperKindsFor(t *testing.T) { testCases := []struct { Name string PreferredOrder []schema.GroupVersion KindsToRegister []schema.GroupVersionKind PartialResourceToRequest schema.GroupVersionResource ExpectedKinds []schema.GroupVersionKind ExpectedKindErr string }{ { // exact matches are preferred Name: "groups, with group exact", PreferredOrder: []schema.GroupVersion{ {Group: "first-group-1", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, PartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kind"}, ExpectedKinds: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, }, { // group prefixes work Name: "groups, with group prefix", PreferredOrder: []schema.GroupVersion{ {Group: "second-group", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, }, PartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, ExpectedKinds: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, }, { // group prefixes can be ambiguous Name: "groups, with ambiguous group prefix", PreferredOrder: []schema.GroupVersion{ {Group: "first-group-1", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, PartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, ExpectedKinds: []schema.GroupVersionKind{ {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, ExpectedKindErr: " matches multiple kinds ", }, { Name: "ambiguous groups, with preference order", PreferredOrder: []schema.GroupVersion{ {Group: "second-group", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "your-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "your-kind"}, }, PartialResourceToRequest: schema.GroupVersionResource{Resource: "my-kinds"}, ExpectedKinds: []schema.GroupVersionKind{ {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, ExpectedKindErr: " matches multiple kinds ", }, { Name: "ambiguous groups, with explicit group match", PreferredOrder: []schema.GroupVersion{ {Group: "second-group", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "your-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "your-kind"}, }, PartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, ExpectedKinds: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, }, { Name: "ambiguous groups, with ambiguous version match", PreferredOrder: []schema.GroupVersion{ {Group: "first-group", Version: "first-version"}, {Group: "second-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "your-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "your-kind"}, }, PartialResourceToRequest: schema.GroupVersionResource{Version: "first-version", Resource: "my-kinds"}, ExpectedKinds: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, }, ExpectedKindErr: " matches multiple kinds ", }, } for _, testCase := range testCases { tcName := testCase.Name mapper := NewDefaultRESTMapper(testCase.PreferredOrder) for _, kind := range testCase.KindsToRegister { mapper.Add(kind, RESTScopeNamespace) } actualKinds, err := mapper.KindsFor(testCase.PartialResourceToRequest) if err != nil { t.Errorf("%s: unexpected error: %v", tcName, err) continue } if !reflect.DeepEqual(testCase.ExpectedKinds, actualKinds) { t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKinds, actualKinds) } singleKind, err := mapper.KindFor(testCase.PartialResourceToRequest) if err == nil && len(testCase.ExpectedKindErr) != 0 { t.Errorf("%s: expected error: %v", tcName, testCase.ExpectedKindErr) continue } if err != nil { if len(testCase.ExpectedKindErr) == 0 { t.Errorf("%s: unexpected error: %v", tcName, err) continue } else { if !strings.Contains(err.Error(), testCase.ExpectedKindErr) { t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKindErr, err) continue } } } else { if testCase.ExpectedKinds[0] != singleKind { t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKinds[0], singleKind) } } } } func TestRESTMapperResourcesFor(t *testing.T) { testCases := []struct { Name string PreferredOrder []schema.GroupVersion KindsToRegister []schema.GroupVersionKind PluralPartialResourceToRequest schema.GroupVersionResource SingularPartialResourceToRequest schema.GroupVersionResource ExpectedResources []schema.GroupVersionResource ExpectedResourceErr string }{ { // exact matches are preferred Name: "groups, with group exact", PreferredOrder: []schema.GroupVersion{ {Group: "first-group-1", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kind"}, ExpectedResources: []schema.GroupVersionResource{ {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, }, }, { // group prefixes work Name: "groups, with group prefix", PreferredOrder: []schema.GroupVersion{ {Group: "second-group", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, }, PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kinds"}, SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, ExpectedResources: []schema.GroupVersionResource{ {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, }, }, { // group prefixes can be ambiguous Name: "groups, with ambiguous group prefix", PreferredOrder: []schema.GroupVersion{ {Group: "first-group-1", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kinds"}, SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, ExpectedResources: []schema.GroupVersionResource{ {Group: "first-group-1", Version: "first-version", Resource: "my-kinds"}, {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, }, ExpectedResourceErr: " matches multiple resources ", }, { Name: "ambiguous groups, with preference order", PreferredOrder: []schema.GroupVersion{ {Group: "second-group", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "your-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "your-kind"}, }, PluralPartialResourceToRequest: schema.GroupVersionResource{Resource: "my-kinds"}, SingularPartialResourceToRequest: schema.GroupVersionResource{Resource: "my-kind"}, ExpectedResources: []schema.GroupVersionResource{ {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, }, ExpectedResourceErr: " matches multiple resources ", }, { Name: "ambiguous groups, with explicit group match", PreferredOrder: []schema.GroupVersion{ {Group: "second-group", Version: "first-version"}, {Group: "first-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "your-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "your-kind"}, }, PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kind"}, ExpectedResources: []schema.GroupVersionResource{ {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, }, }, { Name: "ambiguous groups, with ambiguous version match", PreferredOrder: []schema.GroupVersion{ {Group: "first-group", Version: "first-version"}, {Group: "second-group", Version: "first-version"}, }, KindsToRegister: []schema.GroupVersionKind{ {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "your-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "your-kind"}, }, PluralPartialResourceToRequest: schema.GroupVersionResource{Version: "first-version", Resource: "my-kinds"}, SingularPartialResourceToRequest: schema.GroupVersionResource{Version: "first-version", Resource: "my-kind"}, ExpectedResources: []schema.GroupVersionResource{ {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, }, ExpectedResourceErr: " matches multiple resources ", }, } for _, testCase := range testCases { tcName := testCase.Name for _, partialResource := range []schema.GroupVersionResource{testCase.PluralPartialResourceToRequest, testCase.SingularPartialResourceToRequest} { mapper := NewDefaultRESTMapper(testCase.PreferredOrder) for _, kind := range testCase.KindsToRegister { mapper.Add(kind, RESTScopeNamespace) } actualResources, err := mapper.ResourcesFor(partialResource) if err != nil { t.Errorf("%s: unexpected error: %v", tcName, err) continue } if !reflect.DeepEqual(testCase.ExpectedResources, actualResources) { t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResources, actualResources) } singleResource, err := mapper.ResourceFor(partialResource) if err == nil && len(testCase.ExpectedResourceErr) != 0 { t.Errorf("%s: expected error: %v", tcName, testCase.ExpectedResourceErr) continue } if err != nil { if len(testCase.ExpectedResourceErr) == 0 { t.Errorf("%s: unexpected error: %v", tcName, err) continue } else { if !strings.Contains(err.Error(), testCase.ExpectedResourceErr) { t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResourceErr, err) continue } } } else { if testCase.ExpectedResources[0] != singleResource { t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResources[0], singleResource) } } } } } func TestKindToResource(t *testing.T) { testCases := []struct { Kind string Plural, Singular string }{ {Kind: "Pod", Plural: "pods", Singular: "pod"}, {Kind: "ReplicationController", Plural: "replicationcontrollers", Singular: "replicationcontroller"}, // Add "ies" when ending with "y" {Kind: "ImageRepository", Plural: "imagerepositories", Singular: "imagerepository"}, // Add "es" when ending with "s" {Kind: "miss", Plural: "misses", Singular: "miss"}, // Add "s" otherwise {Kind: "lowercase", Plural: "lowercases", Singular: "lowercase"}, } for i, testCase := range testCases { version := schema.GroupVersion{} plural, singular := UnsafeGuessKindToResource(version.WithKind(testCase.Kind)) if singular != version.WithResource(testCase.Singular) || plural != version.WithResource(testCase.Plural) { t.Errorf("%d: unexpected plural and singular: %v %v", i, plural, singular) } } } func TestRESTMapperResourceSingularizer(t *testing.T) { testGroupVersion := schema.GroupVersion{Group: "tgroup", Version: "test"} testCases := []struct { Kind string Plural string Singular string }{ {Kind: "Pod", Plural: "pods", Singular: "pod"}, {Kind: "ReplicationController", Plural: "replicationcontrollers", Singular: "replicationcontroller"}, {Kind: "ImageRepository", Plural: "imagerepositories", Singular: "imagerepository"}, {Kind: "Status", Plural: "statuses", Singular: "status"}, {Kind: "lowercase", Plural: "lowercases", Singular: "lowercase"}, // TODO this test is broken. This updates to reflect actual behavior. Kinds are expected to be singular // old (incorrect), comment: Don't add extra s if the original object is already plural {Kind: "lowercases", Plural: "lowercaseses", Singular: "lowercases"}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]schema.GroupVersion{testGroupVersion}) // create singular/plural mapping mapper.Add(testGroupVersion.WithKind(testCase.Kind), RESTScopeNamespace) singular, err := mapper.ResourceSingularizer(testCase.Plural) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) } if singular != testCase.Singular { t.Errorf("%d: mismatched singular: got %v, expected %v", i, singular, testCase.Singular) } } } func TestRESTMapperRESTMapping(t *testing.T) { testGroup := "tgroup" testGroupVersion := schema.GroupVersion{Group: testGroup, Version: "test"} internalGroupVersion := schema.GroupVersion{Group: testGroup, Version: "test"} testCases := []struct { Kind string APIGroupVersions []schema.GroupVersion DefaultVersions []schema.GroupVersion Resource schema.GroupVersionResource ExpectedGroupVersion *schema.GroupVersion Err bool }{ {Kind: "Unknown", Err: true}, {Kind: "InternalObject", Err: true}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "Unknown", Err: true}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{}, Resource: internalGroupVersion.WithResource("internalobjects"), ExpectedGroupVersion: &schema.GroupVersion{Group: testGroup, Version: "test"}}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, // TODO: add test for a resource that exists in one version but not another } for i, testCase := range testCases { mapper := NewDefaultRESTMapper(testCase.DefaultVersions) mapper.Add(internalGroupVersion.WithKind("InternalObject"), RESTScopeNamespace) preferredVersions := []string{} for _, gv := range testCase.APIGroupVersions { preferredVersions = append(preferredVersions, gv.Version) } gk := schema.GroupKind{Group: testGroup, Kind: testCase.Kind} mapping, err := mapper.RESTMapping(gk, preferredVersions...) hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) } if hasErr { continue } if mapping.Resource != testCase.Resource { t.Errorf("%d: unexpected resource: %#v", i, mapping) } groupVersion := testCase.ExpectedGroupVersion if groupVersion == nil { groupVersion = &testCase.APIGroupVersions[0] } if mapping.GroupVersionKind.GroupVersion() != *groupVersion { t.Errorf("%d: unexpected version: %#v", i, mapping) } } } func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { expectedGroupVersion1 := schema.GroupVersion{Group: "tgroup", Version: "test1"} expectedGroupVersion2 := schema.GroupVersion{Group: "tgroup", Version: "test2"} expectedGroupVersion3 := schema.GroupVersion{Group: "tgroup", Version: "test3"} internalObjectGK := schema.GroupKind{Group: "tgroup", Kind: "InternalObject"} otherObjectGK := schema.GroupKind{Group: "tgroup", Kind: "OtherObject"} mapper := NewDefaultRESTMapper([]schema.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}) mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace) mapper.Add(expectedGroupVersion2.WithKind("OtherObject"), RESTScopeNamespace) // pick default matching object kind based on search order mapping, err := mapper.RESTMapping(otherObjectGK) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != expectedGroupVersion2.WithResource("otherobjects") || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { t.Errorf("unexpected mapping: %#v", mapping) } mapping, err = mapper.RESTMapping(internalObjectGK) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != expectedGroupVersion1.WithResource("internalobjects") || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion1 { t.Errorf("unexpected mapping: %#v", mapping) } // mismatch of version _, err = mapper.RESTMapping(internalObjectGK, expectedGroupVersion2.Version) if err == nil { t.Errorf("unexpected non-error") } _, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion1.Version) if err == nil { t.Errorf("unexpected non-error") } // not in the search versions _, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version) if err == nil { t.Errorf("unexpected non-error") } // explicit search order _, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version, expectedGroupVersion1.Version) if err == nil { t.Errorf("unexpected non-error") } mapping, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version, expectedGroupVersion2.Version) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != expectedGroupVersion2.WithResource("otherobjects") || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { t.Errorf("unexpected mapping: %#v", mapping) } } func TestRESTMapperRESTMappings(t *testing.T) { testGroup := "tgroup" testGroupVersion := schema.GroupVersion{Group: testGroup, Version: "v1"} testCases := []struct { Kind string APIGroupVersions []schema.GroupVersion DefaultVersions []schema.GroupVersion AddGroupVersionKind []schema.GroupVersionKind ExpectedRESTMappings []*RESTMapping Err bool }{ {Kind: "Unknown", Err: true}, {Kind: "InternalObject", Err: true}, {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "Unknown", Err: true}, // ask for specific version - not available - thus error {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "v2"}}, Err: true}, // ask for specific version - available - check ExpectedRESTMappings { DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "v2"}}, AddGroupVersionKind: []schema.GroupVersionKind{schema.GroupVersion{Group: testGroup, Version: "v2"}.WithKind("InternalObject")}, ExpectedRESTMappings: []*RESTMapping{{Resource: schema.GroupVersionResource{Group: testGroup, Version: "v2", Resource: "internalobjects"}, GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v2", Kind: "InternalObject"}}}, }, // ask for specific versions - only one available - check ExpectedRESTMappings { DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "v3"}, {Group: testGroup, Version: "v2"}}, AddGroupVersionKind: []schema.GroupVersionKind{schema.GroupVersion{Group: testGroup, Version: "v2"}.WithKind("InternalObject")}, ExpectedRESTMappings: []*RESTMapping{{Resource: schema.GroupVersionResource{Group: testGroup, Version: "v2", Resource: "internalobjects"}, GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v2", Kind: "InternalObject"}}}, }, // do not ask for specific version - search through default versions - check ExpectedRESTMappings { DefaultVersions: []schema.GroupVersion{testGroupVersion, {Group: testGroup, Version: "v2"}}, Kind: "InternalObject", AddGroupVersionKind: []schema.GroupVersionKind{schema.GroupVersion{Group: testGroup, Version: "v1"}.WithKind("InternalObject"), schema.GroupVersion{Group: testGroup, Version: "v2"}.WithKind("InternalObject")}, ExpectedRESTMappings: []*RESTMapping{ { Resource: schema.GroupVersionResource{Group: testGroup, Version: "v1", Resource: "internalobjects"}, GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v1", Kind: "InternalObject"}, }, { Resource: schema.GroupVersionResource{Group: testGroup, Version: "v2", Resource: "internalobjects"}, GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v2", Kind: "InternalObject"}, }, }, }, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper(testCase.DefaultVersions) for _, gvk := range testCase.AddGroupVersionKind { mapper.Add(gvk, RESTScopeNamespace) } preferredVersions := []string{} for _, gv := range testCase.APIGroupVersions { preferredVersions = append(preferredVersions, gv.Version) } gk := schema.GroupKind{Group: testGroup, Kind: testCase.Kind} mappings, err := mapper.RESTMappings(gk, preferredVersions...) hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) } if hasErr { continue } if len(mappings) != len(testCase.ExpectedRESTMappings) { t.Errorf("%d: unexpected number = %d of rest mappings was returned, expected = %d", i, len(mappings), len(testCase.ExpectedRESTMappings)) } for j, mapping := range mappings { exp := testCase.ExpectedRESTMappings[j] if mapping.Resource != exp.Resource { t.Errorf("%d - %d: unexpected resource: %#v", i, j, mapping) } if mapping.GroupVersionKind != exp.GroupVersionKind { t.Errorf("%d - %d: unexpected GroupVersionKind: %#v", i, j, mapping) } } } } func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { expectedGroupVersion1 := schema.GroupVersion{Group: "tgroup", Version: "test1"} expectedGroupVersion2 := schema.GroupVersion{Group: "tgroup", Version: "test2"} internalObjectGK := schema.GroupKind{Group: "tgroup", Kind: "InternalObject"} mapper := NewDefaultRESTMapper([]schema.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}) mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace) _, err := mapper.RESTMapping(internalObjectGK, "test3") if err == nil { t.Errorf("unexpected non-error") } } golang-k8s-apimachinery-0.29.0/pkg/api/meta/table/000077500000000000000000000000001453143165200215765ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/meta/table/table.go000066400000000000000000000040561453143165200232210ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package table import ( "time" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/duration" ) // MetaToTableRow converts a list or object into one or more table rows. The provided rowFn is invoked for // each accessed item, with name and age being passed to each. func MetaToTableRow(obj runtime.Object, rowFn func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error)) ([]metav1.TableRow, error) { if meta.IsListType(obj) { rows := make([]metav1.TableRow, 0, 16) err := meta.EachListItem(obj, func(obj runtime.Object) error { nestedRows, err := MetaToTableRow(obj, rowFn) if err != nil { return err } rows = append(rows, nestedRows...) return nil }) if err != nil { return nil, err } return rows, nil } rows := make([]metav1.TableRow, 0, 1) m, err := meta.Accessor(obj) if err != nil { return nil, err } row := metav1.TableRow{ Object: runtime.RawExtension{Object: obj}, } row.Cells, err = rowFn(obj, m, m.GetName(), ConvertToHumanReadableDateType(m.GetCreationTimestamp())) if err != nil { return nil, err } rows = append(rows, row) return rows, nil } // ConvertToHumanReadableDateType returns the elapsed time since timestamp in // human-readable approximation. func ConvertToHumanReadableDateType(timestamp metav1.Time) string { if timestamp.IsZero() { return "" } return duration.HumanDuration(time.Since(timestamp.Time)) } golang-k8s-apimachinery-0.29.0/pkg/api/meta/testrestmapper/000077500000000000000000000000001453143165200235715ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/meta/testrestmapper/test_restmapper.go000066400000000000000000000147611453143165200273520ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testrestmapper import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" ) // TestOnlyStaticRESTMapper returns a union RESTMapper of all known types with priorities chosen in the following order: // 1. legacy kube group preferred version, extensions preferred version, metrics preferred version, legacy // kube any version, extensions any version, metrics any version, all other groups alphabetical preferred version, // all other groups alphabetical. // // TODO callers of this method should be updated to build their own specific restmapper based on their scheme for their tests // TODO the things being tested are related to whether various cases are handled, not tied to the particular types being checked. func TestOnlyStaticRESTMapper(scheme *runtime.Scheme, versionPatterns ...schema.GroupVersion) meta.RESTMapper { unionMapper := meta.MultiRESTMapper{} unionedGroups := sets.NewString() for _, enabledVersion := range scheme.PrioritizedVersionsAllGroups() { if !unionedGroups.Has(enabledVersion.Group) { unionedGroups.Insert(enabledVersion.Group) unionMapper = append(unionMapper, newRESTMapper(enabledVersion.Group, scheme)) } } if len(versionPatterns) != 0 { resourcePriority := []schema.GroupVersionResource{} kindPriority := []schema.GroupVersionKind{} for _, versionPriority := range versionPatterns { resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource)) kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind)) } return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} } prioritizedGroups := []string{"", "extensions", "metrics"} resourcePriority, kindPriority := prioritiesForGroups(scheme, prioritizedGroups...) prioritizedGroupsSet := sets.NewString(prioritizedGroups...) remainingGroups := sets.String{} for _, enabledVersion := range scheme.PrioritizedVersionsAllGroups() { if !prioritizedGroupsSet.Has(enabledVersion.Group) { remainingGroups.Insert(enabledVersion.Group) } } remainingResourcePriority, remainingKindPriority := prioritiesForGroups(scheme, remainingGroups.List()...) resourcePriority = append(resourcePriority, remainingResourcePriority...) kindPriority = append(kindPriority, remainingKindPriority...) return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} } // prioritiesForGroups returns the resource and kind priorities for a PriorityRESTMapper, preferring the preferred version of each group first, // then any non-preferred version of the group second. func prioritiesForGroups(scheme *runtime.Scheme, groups ...string) ([]schema.GroupVersionResource, []schema.GroupVersionKind) { resourcePriority := []schema.GroupVersionResource{} kindPriority := []schema.GroupVersionKind{} for _, group := range groups { availableVersions := scheme.PrioritizedVersionsForGroup(group) if len(availableVersions) > 0 { resourcePriority = append(resourcePriority, availableVersions[0].WithResource(meta.AnyResource)) kindPriority = append(kindPriority, availableVersions[0].WithKind(meta.AnyKind)) } } for _, group := range groups { resourcePriority = append(resourcePriority, schema.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource}) kindPriority = append(kindPriority, schema.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind}) } return resourcePriority, kindPriority } func newRESTMapper(group string, scheme *runtime.Scheme) meta.RESTMapper { mapper := meta.NewDefaultRESTMapper(scheme.PrioritizedVersionsForGroup(group)) for _, gv := range scheme.PrioritizedVersionsForGroup(group) { for kind := range scheme.KnownTypes(gv) { if ignoredKinds.Has(kind) { continue } scope := meta.RESTScopeNamespace if rootScopedKinds[gv.WithKind(kind).GroupKind()] { scope = meta.RESTScopeRoot } mapper.Add(gv.WithKind(kind), scope) } } return mapper } // hardcoded is good enough for the test we're running var rootScopedKinds = map[schema.GroupKind]bool{ {Group: "admission.k8s.io", Kind: "AdmissionReview"}: true, {Group: "admissionregistration.k8s.io", Kind: "ValidatingWebhookConfiguration"}: true, {Group: "admissionregistration.k8s.io", Kind: "MutatingWebhookConfiguration"}: true, {Group: "authentication.k8s.io", Kind: "TokenReview"}: true, {Group: "authorization.k8s.io", Kind: "SubjectAccessReview"}: true, {Group: "authorization.k8s.io", Kind: "SelfSubjectAccessReview"}: true, {Group: "authorization.k8s.io", Kind: "SelfSubjectRulesReview"}: true, {Group: "certificates.k8s.io", Kind: "CertificateSigningRequest"}: true, {Group: "", Kind: "Node"}: true, {Group: "", Kind: "Namespace"}: true, {Group: "", Kind: "PersistentVolume"}: true, {Group: "", Kind: "ComponentStatus"}: true, {Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"}: true, {Group: "rbac.authorization.k8s.io", Kind: "ClusterRoleBinding"}: true, {Group: "scheduling.k8s.io", Kind: "PriorityClass"}: true, {Group: "storage.k8s.io", Kind: "StorageClass"}: true, {Group: "storage.k8s.io", Kind: "VolumeAttachment"}: true, {Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}: true, {Group: "apiserver.k8s.io", Kind: "AdmissionConfiguration"}: true, {Group: "audit.k8s.io", Kind: "Event"}: true, {Group: "audit.k8s.io", Kind: "Policy"}: true, {Group: "apiregistration.k8s.io", Kind: "APIService"}: true, {Group: "metrics.k8s.io", Kind: "NodeMetrics"}: true, {Group: "wardle.example.com", Kind: "Fischer"}: true, } // hardcoded is good enough for the test we're running var ignoredKinds = sets.NewString( "ListOptions", "DeleteOptions", "Status", "PodLogOptions", "PodExecOptions", "PodAttachOptions", "PodPortForwardOptions", "PodProxyOptions", "NodeProxyOptions", "ServiceProxyOptions", ) golang-k8s-apimachinery-0.29.0/pkg/api/resource/000077500000000000000000000000001453143165200214105ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/resource/OWNERS000066400000000000000000000002461453143165200223520ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - thockin - smarterclayton - wojtek-t - derekwaynecarr - mikedanese - saad-ali - janetkuo golang-k8s-apimachinery-0.29.0/pkg/api/resource/amount.go000066400000000000000000000225401453143165200232450ustar00rootroot00000000000000/* 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 resource import ( "math/big" "strconv" inf "gopkg.in/inf.v0" ) // Scale is used for getting and setting the base-10 scaled value. // Base-2 scales are omitted for mathematical simplicity. // See Quantity.ScaledValue for more details. type Scale int32 // infScale adapts a Scale value to an inf.Scale value. func (s Scale) infScale() inf.Scale { return inf.Scale(-s) // inf.Scale is upside-down } const ( Nano Scale = -9 Micro Scale = -6 Milli Scale = -3 Kilo Scale = 3 Mega Scale = 6 Giga Scale = 9 Tera Scale = 12 Peta Scale = 15 Exa Scale = 18 ) var ( Zero = int64Amount{} // Used by quantity strings - treat as read only zeroBytes = []byte("0") ) // int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster // than operations on inf.Dec for values that can be represented as int64. // +k8s:openapi-gen=true type int64Amount struct { value int64 scale Scale } // Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. func (a int64Amount) Sign() int { switch { case a.value == 0: return 0 case a.value > 0: return 1 default: return -1 } } // AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be // represented in an int64 OR would result in a loss of precision. This method is intended as // an optimization to avoid calling AsDec. func (a int64Amount) AsInt64() (int64, bool) { if a.scale == 0 { return a.value, true } if a.scale < 0 { // TODO: attempt to reduce factors, although it is assumed that factors are reduced prior // to the int64Amount being created. return 0, false } return positiveScaleInt64(a.value, a.scale) } // AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, // rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result // in overflow because 1e19 is not representable as an int64. Note that setting a scale larger // than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would // return 1, because 0.000001 is rounded up to 1. func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { if a.scale < scale { result, _ = negativeScaleInt64(a.value, scale-a.scale) return result, true } return positiveScaleInt64(a.value, a.scale-scale) } // AsDec returns an inf.Dec representation of this value. func (a int64Amount) AsDec() *inf.Dec { var base inf.Dec base.SetUnscaled(a.value) base.SetScale(inf.Scale(-a.scale)) return &base } // Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. func (a int64Amount) Cmp(b int64Amount) int { switch { case a.scale == b.scale: // compare only the unscaled portion case a.scale > b.scale: result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) if !exact { return a.AsDec().Cmp(b.AsDec()) } if result == a.value { switch { case remainder == 0: return 0 case remainder > 0: return -1 default: return 1 } } b.value = result default: result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) if !exact { return a.AsDec().Cmp(b.AsDec()) } if result == b.value { switch { case remainder == 0: return 0 case remainder > 0: return 1 default: return -1 } } a.value = result } switch { case a.value == b.value: return 0 case a.value < b.value: return -1 default: return 1 } } // Add adds two int64Amounts together, matching scales. It will return false and not mutate // a if overflow or underflow would result. func (a *int64Amount) Add(b int64Amount) bool { switch { case b.value == 0: return true case a.value == 0: a.value = b.value a.scale = b.scale return true case a.scale == b.scale: c, ok := int64Add(a.value, b.value) if !ok { return false } a.value = c case a.scale > b.scale: c, ok := positiveScaleInt64(a.value, a.scale-b.scale) if !ok { return false } c, ok = int64Add(c, b.value) if !ok { return false } a.scale = b.scale a.value = c default: c, ok := positiveScaleInt64(b.value, b.scale-a.scale) if !ok { return false } c, ok = int64Add(a.value, c) if !ok { return false } a.value = c } return true } // Sub removes the value of b from the current amount, or returns false if underflow would result. func (a *int64Amount) Sub(b int64Amount) bool { return a.Add(int64Amount{value: -b.value, scale: b.scale}) } // Mul multiplies the provided b to the current amount, or // returns false if overflow or underflow would result. func (a *int64Amount) Mul(b int64) bool { switch { case a.value == 0: return true case b == 0: a.value = 0 a.scale = 0 return true case a.scale == 0: c, ok := int64Multiply(a.value, b) if !ok { return false } a.value = c case a.scale > 0: c, ok := int64Multiply(a.value, b) if !ok { return false } if _, ok = positiveScaleInt64(c, a.scale); !ok { return false } a.value = c default: c, ok := int64Multiply(a.value, b) if !ok { return false } if _, ok = negativeScaleInt64(c, -a.scale); !ok { return false } a.value = c } return true } // AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision // was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { if a.scale >= scale { return a, true } result, exact := negativeScaleInt64(a.value, scale-a.scale) return int64Amount{value: result, scale: scale}, exact } // AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns // either that buffer or a larger buffer and the current exponent of the value. The value is adjusted // until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { mantissa := a.value exponent = int32(a.scale) amount, times := removeInt64Factors(mantissa, 10) exponent += int32(times) // make sure exponent is a multiple of 3 var ok bool switch exponent % 3 { case 1, -2: amount, ok = int64MultiplyScale10(amount) if !ok { return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) } exponent = exponent - 1 case 2, -1: amount, ok = int64MultiplyScale100(amount) if !ok { return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) } exponent = exponent - 2 } return strconv.AppendInt(out, amount, 10), exponent } // AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns // either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would // return []byte("2048"), 1. func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { value, ok := a.AsScaledInt64(0) if !ok { return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) } amount, exponent := removeInt64Factors(value, 1024) return strconv.AppendInt(out, amount, 10), exponent } // infDecAmount implements common operations over an inf.Dec that are specific to the quantity // representation. type infDecAmount struct { *inf.Dec } // AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision // was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { tmp := &inf.Dec{} tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 } // AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns // either that buffer or a larger buffer and the current exponent of the value. The value is adjusted // until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { mantissa := a.Dec.UnscaledBig() exponent = int32(-a.Dec.Scale()) amount := big.NewInt(0).Set(mantissa) // move all factors of 10 into the exponent for easy reasoning amount, times := removeBigIntFactors(amount, bigTen) exponent += times // make sure exponent is a multiple of 3 for exponent%3 != 0 { amount.Mul(amount, bigTen) exponent-- } return append(out, amount.String()...), exponent } // AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns // either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would // return []byte("2048"), 1. func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { tmp := &inf.Dec{} tmp.Round(a.Dec, 0, inf.RoundUp) amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) return append(out, amount.String()...), exponent } golang-k8s-apimachinery-0.29.0/pkg/api/resource/amount_test.go000066400000000000000000000160041453143165200243020ustar00rootroot00000000000000/* 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 resource import ( "testing" ) func TestInt64AmountAsInt64(t *testing.T) { for _, test := range []struct { value int64 scale Scale result int64 ok bool }{ {100, 0, 100, true}, {100, 1, 1000, true}, {100, -5, 0, false}, {100, 100, 0, false}, } { r, ok := int64Amount{value: test.value, scale: test.scale}.AsInt64() if r != test.result { t.Errorf("%v: unexpected result: %d", test, r) } if ok != test.ok { t.Errorf("%v: unexpected ok: %t", test, ok) } } } func TestInt64AmountAdd(t *testing.T) { for _, test := range []struct { a, b, c int64Amount ok bool }{ {int64Amount{value: 100, scale: 1}, int64Amount{value: 10, scale: 2}, int64Amount{value: 200, scale: 1}, true}, {int64Amount{value: 100, scale: 1}, int64Amount{value: 1, scale: 2}, int64Amount{value: 110, scale: 1}, true}, {int64Amount{value: 100, scale: 1}, int64Amount{value: 1, scale: 100}, int64Amount{value: 1, scale: 100}, false}, {int64Amount{value: -5, scale: 2}, int64Amount{value: 50, scale: 1}, int64Amount{value: 0, scale: 1}, true}, {int64Amount{value: -5, scale: 2}, int64Amount{value: 5, scale: 2}, int64Amount{value: 0, scale: 2}, true}, {int64Amount{value: mostPositive, scale: -1}, int64Amount{value: 1, scale: -1}, int64Amount{value: 0, scale: -1}, false}, {int64Amount{value: mostPositive, scale: -1}, int64Amount{value: 0, scale: -1}, int64Amount{value: mostPositive, scale: -1}, true}, {int64Amount{value: mostPositive / 10, scale: 1}, int64Amount{value: 10, scale: 0}, int64Amount{value: mostPositive, scale: -1}, false}, } { c := test.a ok := c.Add(test.b) if ok != test.ok { t.Errorf("%v: unexpected ok: %t", test, ok) } if ok { if c != test.c { t.Errorf("%v: unexpected result: %d", test, c) } } else { if c != test.a { t.Errorf("%v: overflow addition mutated source: %d", test, c) } } // addition is commutative c = test.b if ok := c.Add(test.a); ok != test.ok { t.Errorf("%v: unexpected ok: %t", test, ok) } if ok { if c != test.c { t.Errorf("%v: unexpected result: %d", test, c) } } else { if c != test.b { t.Errorf("%v: overflow addition mutated source: %d", test, c) } } } } func TestInt64AmountMul(t *testing.T) { for _, test := range []struct { a int64Amount b int64 c int64Amount ok bool }{ {int64Amount{value: 100, scale: 1}, 1000, int64Amount{value: 100000, scale: 1}, true}, {int64Amount{value: 100, scale: -1}, 1000, int64Amount{value: 100000, scale: -1}, true}, {int64Amount{value: 1, scale: 100}, 10, int64Amount{value: 1, scale: 100}, false}, {int64Amount{value: 1, scale: -100}, 10, int64Amount{value: 1, scale: -100}, false}, {int64Amount{value: -5, scale: 2}, 500, int64Amount{value: -2500, scale: 2}, true}, {int64Amount{value: -5, scale: -2}, 500, int64Amount{value: -2500, scale: -2}, true}, {int64Amount{value: 0, scale: 1}, 0, int64Amount{value: 0, scale: 1}, true}, {int64Amount{value: mostPositive, scale: -1}, 10, int64Amount{value: mostPositive, scale: -1}, false}, {int64Amount{value: mostPositive, scale: -1}, 0, int64Amount{value: 0, scale: 0}, true}, {int64Amount{value: mostPositive, scale: 0}, 1, int64Amount{value: mostPositive, scale: 0}, true}, {int64Amount{value: mostPositive / 10, scale: 1}, 10, int64Amount{value: mostPositive / 10, scale: 1}, false}, {int64Amount{value: mostPositive, scale: 0}, -1, int64Amount{value: -mostPositive, scale: 0}, true}, {int64Amount{value: mostNegative, scale: 0}, 1, int64Amount{value: mostNegative, scale: 0}, true}, {int64Amount{value: mostNegative, scale: 1}, 0, int64Amount{value: 0, scale: 0}, true}, {int64Amount{value: mostNegative, scale: 1}, 1, int64Amount{value: mostNegative, scale: 1}, false}, } { c := test.a ok := c.Mul(test.b) if ok && !test.ok { t.Errorf("unextected success: %v", c) } else if !ok && test.ok { t.Errorf("unexpeted failure: %v", c) } else if ok { if c != test.c { t.Errorf("%v: unexpected result: %d", test, c) } } else { if c != test.a { t.Errorf("%v: overflow multiplication mutated source: %d", test, c) } } } } func TestInt64AsCanonicalString(t *testing.T) { for _, test := range []struct { value int64 scale Scale result string exponent int32 }{ {100, 0, "100", 0}, {100, 1, "1", 3}, {100, -1, "10", 0}, {10800, -10, "1080", -9}, } { r, exp := int64Amount{value: test.value, scale: test.scale}.AsCanonicalBytes(nil) if string(r) != test.result { t.Errorf("%v: unexpected result: %s", test, r) } if exp != test.exponent { t.Errorf("%v: unexpected exponent: %d", test, exp) } } } func TestAmountSign(t *testing.T) { table := []struct { i int64Amount expect int }{ {int64Amount{value: -50, scale: 1}, -1}, {int64Amount{value: 0, scale: 1}, 0}, {int64Amount{value: 300, scale: 1}, 1}, {int64Amount{value: -50, scale: -8}, -1}, {int64Amount{value: 50, scale: -8}, 1}, {int64Amount{value: 0, scale: -8}, 0}, {int64Amount{value: -50, scale: 0}, -1}, {int64Amount{value: 50, scale: 0}, 1}, {int64Amount{value: 0, scale: 0}, 0}, } for _, testCase := range table { if result := testCase.i.Sign(); result != testCase.expect { t.Errorf("i: %v, Expected: %v, Actual: %v", testCase.i, testCase.expect, result) } } } func TestInt64AmountAsScaledInt64(t *testing.T) { for _, test := range []struct { name string i int64Amount scaled Scale result int64 ok bool }{ {"test when i.scale < scaled ", int64Amount{value: 100, scale: 0}, 5, 1, true}, {"test when i.scale = scaled", int64Amount{value: 100, scale: 1}, 1, 100, true}, {"test when i.scale > scaled and result doesn't overflow", int64Amount{value: 100, scale: 5}, 2, 100000, true}, {"test when i.scale > scaled and result overflows", int64Amount{value: 876, scale: 30}, 4, 0, false}, {"test when i.scale < 0 and fraction exists", int64Amount{value: 93, scale: -1}, 0, 10, true}, {"test when i.scale < 0 and fraction doesn't exist", int64Amount{value: 100, scale: -1}, 0, 10, true}, {"test when i.value < 0 and fraction exists", int64Amount{value: -1932, scale: 2}, 4, -20, true}, {"test when i.value < 0 and fraction doesn't exists", int64Amount{value: -1900, scale: 2}, 4, -19, true}, } { t.Run(test.name, func(t *testing.T) { r, ok := test.i.AsScaledInt64(test.scaled) if r != test.result { t.Errorf("%v: expected result: %d, got result: %d", test.name, test.result, r) } if ok != test.ok { t.Errorf("%v: expected ok: %t, got ok: %t", test.name, test.ok, ok) } }) } } golang-k8s-apimachinery-0.29.0/pkg/api/resource/generated.pb.go000066400000000000000000000113211453143165200242730ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto package resource import ( fmt "fmt" math "math" proto "github.com/gogo/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. 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} } func (m *Quantity) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Quantity.Unmarshal(m, b) } func (m *Quantity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Quantity.Marshal(b, m, deterministic) } func (m *Quantity) XXX_Merge(src proto.Message) { xxx_messageInfo_Quantity.Merge(m, src) } func (m *Quantity) XXX_Size() int { return xxx_messageInfo_Quantity.Size(m) } func (m *Quantity) XXX_DiscardUnknown() { xxx_messageInfo_Quantity.DiscardUnknown(m) } 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} } func (m *QuantityValue) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_QuantityValue.Unmarshal(m, b) } func (m *QuantityValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_QuantityValue.Marshal(b, m, deterministic) } func (m *QuantityValue) XXX_Merge(src proto.Message) { xxx_messageInfo_QuantityValue.Merge(m, src) } func (m *QuantityValue) XXX_Size() int { return xxx_messageInfo_QuantityValue.Size(m) } func (m *QuantityValue) XXX_DiscardUnknown() { xxx_messageInfo_QuantityValue.DiscardUnknown(m) } var xxx_messageInfo_QuantityValue proto.InternalMessageInfo func init() { proto.RegisterType((*Quantity)(nil), "k8s.io.apimachinery.pkg.api.resource.Quantity") proto.RegisterType((*QuantityValue)(nil), "k8s.io.apimachinery.pkg.api.resource.QuantityValue") } func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/api/resource/generated.proto", fileDescriptor_612bba87bd70906c) } 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, } golang-k8s-apimachinery-0.29.0/pkg/api/resource/generated.proto000066400000000000000000000075551453143165200244470ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.api.resource; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/api/resource"; // Quantity is a fixed-point representation of a number. // It provides convenient marshaling/unmarshaling in JSON and YAML, // in addition to String() and AsInt64() accessors. // // The serialization format is: // // ``` // ::= // // (Note that may be empty, from the "" case in .) // // ::= 0 | 1 | ... | 9 // ::= | // ::= | . | . | . // ::= "+" | "-" // ::= | // ::= | | // ::= Ki | Mi | Gi | Ti | Pi | Ei // // (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) // // ::= m | "" | k | M | G | T | P | E // // (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) // // ::= "e" | "E" // ``` // // No matter which of the three exponent forms is used, no quantity may represent // a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal // places. Numbers larger or more precise will be capped or rounded up. // (E.g.: 0.1m will rounded up to 1m.) // This may be extended in the future if we require larger or smaller quantities. // // When a Quantity is parsed from a string, it will remember the type of suffix // it had, and will use the same type again when it is serialized. // // Before serializing, Quantity will be put in "canonical form". // This means that Exponent/suffix will be adjusted up or down (with a // corresponding increase or decrease in Mantissa) such that: // // - No precision is lost // - No fractional digits will be emitted // - The exponent (or suffix) is as large as possible. // // The sign will be omitted unless the number is negative. // // Examples: // // - 1.5 will be serialized as "1500m" // - 1.5Gi will be serialized as "1536Mi" // // Note that the quantity will NEVER be internally represented by a // floating point number. That is the whole point of this exercise. // // Non-canonical values will still parse as long as they are well formed, // but will be re-emitted in their canonical form. (So always use canonical // form, or don't diff.) // // This format is intended to make it difficult to use these numbers without // writing some sort of special handling code in the hopes that that will // cause implementors to also use a fixed point implementation. // // +protobuf=true // +protobuf.embed=string // +protobuf.options.marshal=false // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true message Quantity { optional string string = 1; } // QuantityValue makes it possible to use a Quantity as value for a command // line parameter. // // +protobuf=true // +protobuf.embed=string // +protobuf.options.marshal=false // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:deepcopy-gen=true message QuantityValue { optional string string = 1; } golang-k8s-apimachinery-0.29.0/pkg/api/resource/math.go000066400000000000000000000165111453143165200226740ustar00rootroot00000000000000/* 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 resource import ( "math/big" inf "gopkg.in/inf.v0" ) const ( // maxInt64Factors is the highest value that will be checked when removing factors of 10 from an int64. // It is also the maximum decimal digits that can be represented with an int64. maxInt64Factors = 18 ) var ( // Commonly needed big.Int values-- treat as read only! bigTen = big.NewInt(10) bigZero = big.NewInt(0) bigOne = big.NewInt(1) bigThousand = big.NewInt(1000) big1024 = big.NewInt(1024) // Commonly needed inf.Dec values-- treat as read only! decZero = inf.NewDec(0, 0) decOne = inf.NewDec(1, 0) // Largest (in magnitude) number allowed. maxAllowed = infDecAmount{inf.NewDec((1<<63)-1, 0)} // == max int64 // The maximum value we can represent milli-units for. // Compare with the return value of Quantity.Value() to // see if it's safe to use Quantity.MilliValue(). MaxMilliValue = int64(((1 << 63) - 1) / 1000) ) const mostNegative = -(mostPositive + 1) const mostPositive = 1<<63 - 1 // int64Add returns a+b, or false if that would overflow int64. func int64Add(a, b int64) (int64, bool) { c := a + b switch { case a > 0 && b > 0: if c < 0 { return 0, false } case a < 0 && b < 0: if c > 0 { return 0, false } if a == mostNegative && b == mostNegative { return 0, false } } return c, true } // int64Multiply returns a*b, or false if that would overflow or underflow int64. func int64Multiply(a, b int64) (int64, bool) { if a == 0 || b == 0 || a == 1 || b == 1 { return a * b, true } if a == mostNegative || b == mostNegative { return 0, false } c := a * b return c, c/b == a } // int64MultiplyScale returns a*b, assuming b is greater than one, or false if that would overflow or underflow int64. // Use when b is known to be greater than one. func int64MultiplyScale(a int64, b int64) (int64, bool) { if a == 0 || a == 1 { return a * b, true } if a == mostNegative && b != 1 { return 0, false } c := a * b return c, c/b == a } // int64MultiplyScale10 multiplies a by 10, or returns false if that would overflow. This method is faster than // int64Multiply(a, 10) because the compiler can optimize constant factor multiplication. func int64MultiplyScale10(a int64) (int64, bool) { if a == 0 || a == 1 { return a * 10, true } if a == mostNegative { return 0, false } c := a * 10 return c, c/10 == a } // int64MultiplyScale100 multiplies a by 100, or returns false if that would overflow. This method is faster than // int64Multiply(a, 100) because the compiler can optimize constant factor multiplication. func int64MultiplyScale100(a int64) (int64, bool) { if a == 0 || a == 1 { return a * 100, true } if a == mostNegative { return 0, false } c := a * 100 return c, c/100 == a } // int64MultiplyScale1000 multiplies a by 1000, or returns false if that would overflow. This method is faster than // int64Multiply(a, 1000) because the compiler can optimize constant factor multiplication. func int64MultiplyScale1000(a int64) (int64, bool) { if a == 0 || a == 1 { return a * 1000, true } if a == mostNegative { return 0, false } c := a * 1000 return c, c/1000 == a } // positiveScaleInt64 multiplies base by 10^scale, returning false if the // value overflows. Passing a negative scale is undefined. func positiveScaleInt64(base int64, scale Scale) (int64, bool) { switch scale { case 0: return base, true case 1: return int64MultiplyScale10(base) case 2: return int64MultiplyScale100(base) case 3: return int64MultiplyScale1000(base) case 6: return int64MultiplyScale(base, 1000000) case 9: return int64MultiplyScale(base, 1000000000) default: value := base var ok bool for i := Scale(0); i < scale; i++ { if value, ok = int64MultiplyScale(value, 10); !ok { return 0, false } } return value, true } } // negativeScaleInt64 reduces base by the provided scale, rounding up, until the // value is zero or the scale is reached. Passing a negative scale is undefined. // The value returned, if not exact, is rounded away from zero. func negativeScaleInt64(base int64, scale Scale) (result int64, exact bool) { if scale == 0 { return base, true } value := base var fraction bool for i := Scale(0); i < scale; i++ { if !fraction && value%10 != 0 { fraction = true } value = value / 10 if value == 0 { if fraction { if base > 0 { return 1, false } return -1, false } return 0, true } } if fraction { if base > 0 { value++ } else { value-- } } return value, !fraction } func pow10Int64(b int64) int64 { switch b { case 0: return 1 case 1: return 10 case 2: return 100 case 3: return 1000 case 4: return 10000 case 5: return 100000 case 6: return 1000000 case 7: return 10000000 case 8: return 100000000 case 9: return 1000000000 case 10: return 10000000000 case 11: return 100000000000 case 12: return 1000000000000 case 13: return 10000000000000 case 14: return 100000000000000 case 15: return 1000000000000000 case 16: return 10000000000000000 case 17: return 100000000000000000 case 18: return 1000000000000000000 default: return 0 } } // negativeScaleInt64 returns the result of dividing base by scale * 10 and the remainder, or // false if no such division is possible. Dividing by negative scales is undefined. func divideByScaleInt64(base int64, scale Scale) (result, remainder int64, exact bool) { if scale == 0 { return base, 0, true } // the max scale representable in base 10 in an int64 is 18 decimal places if scale >= 18 { return 0, base, false } divisor := pow10Int64(int64(scale)) return base / divisor, base % divisor, true } // removeInt64Factors divides in a loop; the return values have the property that // value == result * base ^ scale func removeInt64Factors(value int64, base int64) (result int64, times int32) { times = 0 result = value negative := result < 0 if negative { result = -result } switch base { // allow the compiler to optimize the common cases case 10: for result >= 10 && result%10 == 0 { times++ result = result / 10 } // allow the compiler to optimize the common cases case 1024: for result >= 1024 && result%1024 == 0 { times++ result = result / 1024 } default: for result >= base && result%base == 0 { times++ result = result / base } } if negative { result = -result } return result, times } // removeBigIntFactors divides in a loop; the return values have the property that // d == result * factor ^ times // d may be modified in place. // If d == 0, then the return values will be (0, 0) func removeBigIntFactors(d, factor *big.Int) (result *big.Int, times int32) { q := big.NewInt(0) m := big.NewInt(0) for d.Cmp(bigZero) != 0 { q.DivMod(d, factor, m) if m.Cmp(bigZero) != 0 { break } times++ d, q = q, d } return d, times } golang-k8s-apimachinery-0.29.0/pkg/api/resource/math_test.go000066400000000000000000000121031453143165200237240ustar00rootroot00000000000000/* 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 resource import ( "testing" ) func TestDetectOverflowAdd(t *testing.T) { for _, test := range []struct { a, b int64 c int64 ok bool }{ {0, 0, 0, true}, {-1, 1, 0, true}, {0, 1, 1, true}, {2, 2, 4, true}, {2, -2, 0, true}, {-2, -2, -4, true}, {mostNegative, -1, 0, false}, {mostNegative, 1, mostNegative + 1, true}, {mostPositive, -1, mostPositive - 1, true}, {mostPositive, 1, 0, false}, {mostNegative, mostPositive, -1, true}, {mostPositive, mostNegative, -1, true}, {mostPositive, mostPositive, 0, false}, {mostNegative, mostNegative, 0, false}, {-mostPositive, mostNegative, 0, false}, {mostNegative, -mostPositive, 0, false}, {-mostPositive, -mostPositive, 0, false}, } { c, ok := int64Add(test.a, test.b) if c != test.c { t.Errorf("%v: unexpected result: %d", test, c) } if ok != test.ok { t.Errorf("%v: unexpected overflow: %t", test, ok) } // addition is commutative d, ok2 := int64Add(test.b, test.a) if c != d || ok != ok2 { t.Errorf("%v: not commutative: %d %t", test, d, ok2) } } } func TestDetectOverflowMultiply(t *testing.T) { for _, test := range []struct { a, b int64 c int64 ok bool }{ {0, 0, 0, true}, {-1, 1, -1, true}, {-1, -1, 1, true}, {1, 1, 1, true}, {0, 1, 0, true}, {1, 0, 0, true}, {2, 2, 4, true}, {2, -2, -4, true}, {-2, -2, 4, true}, {mostNegative, -1, 0, false}, {mostNegative, 1, mostNegative, true}, {mostPositive, -1, -mostPositive, true}, {mostPositive, 1, mostPositive, true}, {mostNegative, mostPositive, 0, false}, {mostPositive, mostNegative, 0, false}, {mostPositive, mostPositive, 1, false}, {mostNegative, mostNegative, 0, false}, {-mostPositive, mostNegative, 0, false}, {mostNegative, -mostPositive, 0, false}, {-mostPositive, -mostPositive, 1, false}, } { c, ok := int64Multiply(test.a, test.b) if c != test.c { t.Errorf("%v: unexpected result: %d", test, c) } if ok != test.ok { t.Errorf("%v: unexpected overflow: %t", test, ok) } // multiplication is commutative d, ok2 := int64Multiply(test.b, test.a) if c != d || ok != ok2 { t.Errorf("%v: not commutative: %d %t", test, d, ok2) } } } func TestDetectOverflowScale(t *testing.T) { for _, a := range []int64{0, -1, 1, 10, -10, mostPositive, mostNegative, -mostPositive} { for _, b := range []int64{1, 2, 10, 100, 1000, mostPositive} { expect, expectOk := int64Multiply(a, b) c, ok := int64MultiplyScale(a, b) if c != expect { t.Errorf("%d*%d: unexpected result: %d", a, b, c) } if ok != expectOk { t.Errorf("%d*%d: unexpected overflow: %t", a, b, ok) } } for _, test := range []struct { base int64 fn func(a int64) (int64, bool) }{ {10, int64MultiplyScale10}, {100, int64MultiplyScale100}, {1000, int64MultiplyScale1000}, } { expect, expectOk := int64Multiply(a, test.base) c, ok := test.fn(a) if c != expect { t.Errorf("%d*%d: unexpected result: %d", a, test.base, c) } if ok != expectOk { t.Errorf("%d*%d: unexpected overflow: %t", a, test.base, ok) } } } } func TestRemoveInt64Factors(t *testing.T) { for _, test := range []struct { value int64 max int64 result int64 scale int32 }{ {100, 10, 1, 2}, {100, 10, 1, 2}, {100, 100, 1, 1}, {1, 10, 1, 0}, } { r, s := removeInt64Factors(test.value, test.max) if r != test.result { t.Errorf("%v: unexpected result: %d", test, r) } if s != test.scale { t.Errorf("%v: unexpected scale: %d", test, s) } } } func TestNegativeScaleInt64(t *testing.T) { for _, test := range []struct { base int64 scale Scale result int64 exact bool }{ {1234567, 0, 1234567, true}, {1234567, 1, 123457, false}, {1234567, 2, 12346, false}, {1234567, 3, 1235, false}, {1234567, 4, 124, false}, {-1234567, 0, -1234567, true}, {-1234567, 1, -123457, false}, {-1234567, 2, -12346, false}, {-1234567, 3, -1235, false}, {-1234567, 4, -124, false}, {1000, 0, 1000, true}, {1000, 1, 100, true}, {1000, 2, 10, true}, {1000, 3, 1, true}, {1000, 4, 1, false}, {-1000, 0, -1000, true}, {-1000, 1, -100, true}, {-1000, 2, -10, true}, {-1000, 3, -1, true}, {-1000, 4, -1, false}, {0, 0, 0, true}, {0, 1, 0, true}, {0, 2, 0, true}, // negative scale is undefined behavior {1000, -1, 1000, true}, } { result, exact := negativeScaleInt64(test.base, test.scale) if result != test.result { t.Errorf("%v: unexpected result: %d", test, result) } if exact != test.exact { t.Errorf("%v: unexpected exact: %t", test, exact) } } } golang-k8s-apimachinery-0.29.0/pkg/api/resource/quantity.go000066400000000000000000000564241453143165200236300ustar00rootroot00000000000000/* 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 resource import ( "bytes" "errors" "fmt" "math" "math/big" "strconv" "strings" inf "gopkg.in/inf.v0" ) // Quantity is a fixed-point representation of a number. // It provides convenient marshaling/unmarshaling in JSON and YAML, // in addition to String() and AsInt64() accessors. // // The serialization format is: // // ``` // ::= // // (Note that may be empty, from the "" case in .) // // ::= 0 | 1 | ... | 9 // ::= | // ::= | . | . | . // ::= "+" | "-" // ::= | // ::= | | // ::= Ki | Mi | Gi | Ti | Pi | Ei // // (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) // // ::= m | "" | k | M | G | T | P | E // // (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) // // ::= "e" | "E" // ``` // // No matter which of the three exponent forms is used, no quantity may represent // a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal // places. Numbers larger or more precise will be capped or rounded up. // (E.g.: 0.1m will rounded up to 1m.) // This may be extended in the future if we require larger or smaller quantities. // // When a Quantity is parsed from a string, it will remember the type of suffix // it had, and will use the same type again when it is serialized. // // Before serializing, Quantity will be put in "canonical form". // This means that Exponent/suffix will be adjusted up or down (with a // corresponding increase or decrease in Mantissa) such that: // // - No precision is lost // - No fractional digits will be emitted // - The exponent (or suffix) is as large as possible. // // The sign will be omitted unless the number is negative. // // Examples: // // - 1.5 will be serialized as "1500m" // - 1.5Gi will be serialized as "1536Mi" // // Note that the quantity will NEVER be internally represented by a // floating point number. That is the whole point of this exercise. // // Non-canonical values will still parse as long as they are well formed, // but will be re-emitted in their canonical form. (So always use canonical // form, or don't diff.) // // This format is intended to make it difficult to use these numbers without // writing some sort of special handling code in the hopes that that will // cause implementors to also use a fixed point implementation. // // +protobuf=true // +protobuf.embed=string // +protobuf.options.marshal=false // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true type Quantity struct { // i is the quantity in int64 scaled form, if d.Dec == nil i int64Amount // d is the quantity in inf.Dec form if d.Dec != nil d infDecAmount // s is the generated value of this quantity to avoid recalculation s string // Change Format at will. See the comment for Canonicalize for // more details. Format } // CanonicalValue allows a quantity amount to be converted to a string. type CanonicalValue interface { // AsCanonicalBytes returns a byte array representing the string representation // of the value mantissa and an int32 representing its exponent in base-10. Callers may // pass a byte slice to the method to avoid allocations. AsCanonicalBytes(out []byte) ([]byte, int32) // AsCanonicalBase1024Bytes returns a byte array representing the string representation // of the value mantissa and an int32 representing its exponent in base-1024. Callers // may pass a byte slice to the method to avoid allocations. AsCanonicalBase1024Bytes(out []byte) ([]byte, int32) } // Format lists the three possible formattings of a quantity. type Format string const ( DecimalExponent = Format("DecimalExponent") // e.g., 12e6 BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20) DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) ) // MustParse turns the given string into a quantity or panics; for tests // or other cases where you know the string is valid. func MustParse(str string) Quantity { q, err := ParseQuantity(str) if err != nil { panic(fmt.Errorf("cannot parse '%v': %v", str, err)) } return q } const ( // splitREString is used to separate a number from its suffix; as such, // this is overly permissive, but that's OK-- it will be checked later. splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$" ) var ( // Errors that could happen while parsing a string. ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'") ErrNumeric = errors.New("unable to parse numeric part of quantity") ErrSuffix = errors.New("unable to parse quantity's suffix") ) // parseQuantityString is a fast scanner for quantity values. func parseQuantityString(str string) (positive bool, value, num, denom, suffix string, err error) { positive = true pos := 0 end := len(str) // handle leading sign if pos < end { switch str[0] { case '-': positive = false pos++ case '+': pos++ } } // strip leading zeros Zeroes: for i := pos; ; i++ { if i >= end { num = "0" value = num return } switch str[i] { case '0': pos++ default: break Zeroes } } // extract the numerator Num: for i := pos; ; i++ { if i >= end { num = str[pos:end] value = str[0:end] return } switch str[i] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': default: num = str[pos:i] pos = i break Num } } // if we stripped all numerator positions, always return 0 if len(num) == 0 { num = "0" } // handle a denominator if pos < end && str[pos] == '.' { pos++ Denom: for i := pos; ; i++ { if i >= end { denom = str[pos:end] value = str[0:end] return } switch str[i] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': default: denom = str[pos:i] pos = i break Denom } } // TODO: we currently allow 1.G, but we may not want to in the future. // if len(denom) == 0 { // err = ErrFormatWrong // return // } } value = str[0:pos] // grab the elements of the suffix suffixStart := pos for i := pos; ; i++ { if i >= end { suffix = str[suffixStart:end] return } if !strings.ContainsAny(str[i:i+1], "eEinumkKMGTP") { pos = i break } } if pos < end { switch str[pos] { case '-', '+': pos++ } } Suffix: for i := pos; ; i++ { if i >= end { suffix = str[suffixStart:end] return } switch str[i] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': default: break Suffix } } // we encountered a non decimal in the Suffix loop, but the last character // was not a valid exponent err = ErrFormatWrong return } // ParseQuantity turns str into a Quantity, or returns an error. func ParseQuantity(str string) (Quantity, error) { if len(str) == 0 { return Quantity{}, ErrFormatWrong } if str == "0" { return Quantity{Format: DecimalSI, s: str}, nil } positive, value, num, denom, suf, err := parseQuantityString(str) if err != nil { return Quantity{}, err } base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf)) if !ok { return Quantity{}, ErrSuffix } precision := int32(0) scale := int32(0) mantissa := int64(1) switch format { case DecimalExponent, DecimalSI: scale = exponent precision = maxInt64Factors - int32(len(num)+len(denom)) case BinarySI: scale = 0 switch { case exponent >= 0 && len(denom) == 0: // only handle positive binary numbers with the fast path mantissa = int64(int64(mantissa) << uint64(exponent)) // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1 default: precision = -1 } } if precision >= 0 { // if we have a denominator, shift the entire value to the left by the number of places in the // denominator scale -= int32(len(denom)) if scale >= int32(Nano) { shifted := num + denom var value int64 value, err := strconv.ParseInt(shifted, 10, 64) if err != nil { return Quantity{}, ErrNumeric } if result, ok := int64Multiply(value, int64(mantissa)); ok { if !positive { result = -result } // if the number is in canonical form, reuse the string switch format { case BinarySI: if exponent%10 == 0 && (value&0x07 != 0) { return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil } default: if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' { return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil } } return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil } } } amount := new(inf.Dec) if _, ok := amount.SetString(value); !ok { return Quantity{}, ErrNumeric } // So that no one but us has to think about suffixes, remove it. if base == 10 { amount.SetScale(amount.Scale() + Scale(exponent).infScale()) } else if base == 2 { // numericSuffix = 2 ** exponent numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) ub := amount.UnscaledBig() amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) } // Cap at min/max bounds. sign := amount.Sign() if sign == -1 { amount.Neg(amount) } // This rounds non-zero values up to the minimum representable value, under the theory that // if you want some resources, you should get some resources, even if you asked for way too small // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have // the side effect of rounding values < .5n to zero. if v, ok := amount.Unscaled(); v != int64(0) || !ok { amount.Round(amount, Nano.infScale(), inf.RoundUp) } // The max is just a simple cap. // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 { amount.Set(maxAllowed.Dec) } if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { // This avoids rounding and hopefully confusion, too. format = DecimalSI } if sign == -1 { amount.Neg(amount) } return Quantity{d: infDecAmount{amount}, Format: format}, nil } // DeepCopy returns a deep-copy of the Quantity value. Note that the method // receiver is a value, so we can mutate it in-place and return it. func (q Quantity) DeepCopy() Quantity { if q.d.Dec != nil { tmp := &inf.Dec{} q.d.Dec = tmp.Set(q.d.Dec) } return q } // OpenAPISchemaType is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. // // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators func (_ Quantity) OpenAPISchemaType() []string { return []string{"string"} } // OpenAPISchemaFormat is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. func (_ Quantity) OpenAPISchemaFormat() string { return "" } // OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing // the OpenAPI v3 spec of this type. func (Quantity) OpenAPIV3OneOfTypes() []string { return []string{"string", "number"} } // CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity). // // Note about BinarySI: // - If q.Format is set to BinarySI and q.Amount represents a non-zero value between // -1 and +1, it will be emitted as if q.Format were DecimalSI. // - Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be // rounded up. (1.1i becomes 2i.) func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) { if q.IsZero() { return zeroBytes, nil } var rounded CanonicalValue format := q.Format switch format { case DecimalExponent, DecimalSI: case BinarySI: if q.CmpInt64(-1024) > 0 && q.CmpInt64(1024) < 0 { // This avoids rounding and hopefully confusion, too. format = DecimalSI } else { var exact bool if rounded, exact = q.AsScale(0); !exact { // Don't lose precision-- show as DecimalSI format = DecimalSI } } default: format = DecimalExponent } // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to // one of the other formats. switch format { case DecimalExponent, DecimalSI: number, exponent := q.AsCanonicalBytes(out) suffix, _ := quantitySuffixer.constructBytes(10, exponent, format) return number, suffix default: // format must be BinarySI number, exponent := rounded.AsCanonicalBase1024Bytes(out) suffix, _ := quantitySuffixer.constructBytes(2, exponent*10, format) return number, suffix } } // 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. func (q *Quantity) AsApproximateFloat64() float64 { var base float64 var exponent int if q.d.Dec != nil { base, _ = big.NewFloat(0).SetInt(q.d.Dec.UnscaledBig()).Float64() exponent = int(-q.d.Dec.Scale()) } else { base = float64(q.i.value) exponent = int(q.i.scale) } if exponent == 0 { return base } return base * math.Pow10(exponent) } // 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) { if q.d.Dec != nil { return 0, false } return q.i.AsInt64() } // ToDec promotes the quantity in place to use an inf.Dec representation and returns itself. func (q *Quantity) ToDec() *Quantity { if q.d.Dec == nil { q.d.Dec = q.i.AsDec() q.i = int64Amount{} } return q } // AsDec returns the quantity as represented by a scaled inf.Dec. func (q *Quantity) AsDec() *inf.Dec { if q.d.Dec != nil { return q.d.Dec } q.d.Dec = q.i.AsDec() q.i = int64Amount{} return q.d.Dec } // AsCanonicalBytes returns the canonical byte representation of this quantity as a mantissa // and base 10 exponent. The out byte slice may be passed to the method to avoid an extra // allocation. func (q *Quantity) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { if q.d.Dec != nil { return q.d.AsCanonicalBytes(out) } return q.i.AsCanonicalBytes(out) } // IsZero returns true if the quantity is equal to zero. func (q *Quantity) IsZero() bool { if q.d.Dec != nil { return q.d.Dec.Sign() == 0 } return q.i.value == 0 } // Sign returns 0 if the quantity is zero, -1 if the quantity is less than zero, or 1 if the // quantity is greater than zero. func (q *Quantity) Sign() int { if q.d.Dec != nil { return q.d.Dec.Sign() } return q.i.Sign() } // AsScale returns the current value, rounded up to the provided scale, and returns // false if the scale resulted in a loss of precision. func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) { if q.d.Dec != nil { return q.d.AsScale(scale) } return q.i.AsScale(scale) } // RoundUp updates the quantity to the provided scale, ensuring that the value is at // least 1. False is returned if the rounding operation resulted in a loss of precision. // Negative numbers are rounded away from zero (-9 scale 1 rounds to -10). func (q *Quantity) RoundUp(scale Scale) bool { if q.d.Dec != nil { q.s = "" d, exact := q.d.AsScale(scale) q.d = d return exact } // avoid clearing the string value if we have already calculated it if q.i.scale >= scale { return true } q.s = "" i, exact := q.i.AsScale(scale) q.i = i return exact } // Add adds the provide y quantity to the current value. If the current value is zero, // the format of the quantity will be updated to the format of y. func (q *Quantity) Add(y Quantity) { q.s = "" if q.d.Dec == nil && y.d.Dec == nil { if q.i.value == 0 { q.Format = y.Format } if q.i.Add(y.i) { return } } else if q.IsZero() { q.Format = y.Format } q.ToDec().d.Dec.Add(q.d.Dec, y.AsDec()) } // Sub subtracts the provided quantity from the current value in place. If the current // value is zero, the format of the quantity will be updated to the format of y. func (q *Quantity) Sub(y Quantity) { q.s = "" if q.IsZero() { q.Format = y.Format } if q.d.Dec == nil && y.d.Dec == nil && q.i.Sub(y.i) { return } q.ToDec().d.Dec.Sub(q.d.Dec, y.AsDec()) } // Mul multiplies the provided y to the current value. // It will return false if the result is inexact. Otherwise, it will return true. func (q *Quantity) Mul(y int64) bool { q.s = "" if q.d.Dec == nil && q.i.Mul(y) { return true } return q.ToDec().d.Dec.Mul(q.d.Dec, inf.NewDec(y, inf.Scale(0))).UnscaledBig().IsInt64() } // Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the // quantity is greater than y. func (q *Quantity) Cmp(y Quantity) int { if q.d.Dec == nil && y.d.Dec == nil { return q.i.Cmp(y.i) } return q.AsDec().Cmp(y.AsDec()) } // CmpInt64 returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the // quantity is greater than y. func (q *Quantity) CmpInt64(y int64) int { if q.d.Dec != nil { return q.d.Dec.Cmp(inf.NewDec(y, inf.Scale(0))) } return q.i.Cmp(int64Amount{value: y}) } // Neg sets quantity to be the negative value of itself. func (q *Quantity) Neg() { q.s = "" if q.d.Dec == nil { q.i.value = -q.i.value return } q.d.Dec.Neg(q.d.Dec) } // Equal checks equality of two Quantities. This is useful for testing with // cmp.Equal. func (q Quantity) Equal(v Quantity) bool { return q.Cmp(v) == 0 } // int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation // of most Quantity values. const int64QuantityExpectedBytes = 18 // String formats the Quantity as a string, caching the result if not calculated. // String is an expensive operation and caching this result significantly reduces the cost of // normal parse / marshal operations on Quantity. func (q *Quantity) String() string { if q == nil { return "" } if len(q.s) == 0 { result := make([]byte, 0, int64QuantityExpectedBytes) number, suffix := q.CanonicalizeBytes(result) number = append(number, suffix...) q.s = string(number) } return q.s } // MarshalJSON implements the json.Marshaller interface. func (q Quantity) MarshalJSON() ([]byte, error) { if len(q.s) > 0 { out := make([]byte, len(q.s)+2) out[0], out[len(out)-1] = '"', '"' copy(out[1:], q.s) return out, nil } result := make([]byte, int64QuantityExpectedBytes) result[0] = '"' number, suffix := q.CanonicalizeBytes(result[1:1]) // if the same slice was returned to us that we passed in, avoid another allocation by copying number into // the source slice and returning that if len(number) > 0 && &number[0] == &result[1] && (len(number)+len(suffix)+2) <= int64QuantityExpectedBytes { number = append(number, suffix...) number = append(number, '"') return result[:1+len(number)], nil } // if CanonicalizeBytes needed more space than our slice provided, we may need to allocate again so use // append result = result[:1] result = append(result, number...) result = append(result, suffix...) result = append(result, '"') return result, nil } // ToUnstructured implements the value.UnstructuredConverter interface. func (q Quantity) ToUnstructured() interface{} { return q.String() } // UnmarshalJSON implements the json.Unmarshaller interface. // TODO: Remove support for leading/trailing whitespace func (q *Quantity) UnmarshalJSON(value []byte) error { l := len(value) if l == 4 && bytes.Equal(value, []byte("null")) { q.d.Dec = nil q.i = int64Amount{} return nil } if l >= 2 && value[0] == '"' && value[l-1] == '"' { value = value[1 : l-1] } parsed, err := ParseQuantity(strings.TrimSpace(string(value))) if err != nil { return err } // This copy is safe because parsed will not be referred to again. *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 { return &Quantity{ d: infDecAmount{&b}, Format: format, } } // NewQuantity returns a new Quantity representing the given // value in the given format. func NewQuantity(value int64, format Format) *Quantity { return &Quantity{ i: int64Amount{value: value}, Format: format, } } // NewMilliQuantity returns a new Quantity representing the given // value * 1/1000 in the given format. Note that BinarySI formatting // will round fractional values, and will be changed to DecimalSI for // values x where (-1 < x < 1) && (x != 0). func NewMilliQuantity(value int64, format Format) *Quantity { return &Quantity{ i: int64Amount{value: value, scale: -3}, Format: format, } } // NewScaledQuantity returns a new Quantity representing the given // value * 10^scale in DecimalSI format. func NewScaledQuantity(value int64, scale Scale) *Quantity { return &Quantity{ i: int64Amount{value: value, scale: scale}, Format: DecimalSI, } } // Value returns the unscaled value of q rounded up to the nearest integer away from 0. func (q *Quantity) Value() int64 { return q.ScaledValue(0) } // MilliValue returns the value of ceil(q * 1000); this could overflow an int64; // if that's a concern, call Value() first to verify the number is small enough. func (q *Quantity) MilliValue() int64 { return q.ScaledValue(Milli) } // ScaledValue returns the value of ceil(q / 10^scale). // For example, NewQuantity(1, DecimalSI).ScaledValue(Milli) returns 1000. // This could overflow an int64. // To detect overflow, call Value() first and verify the expected magnitude. func (q *Quantity) ScaledValue(scale Scale) int64 { if q.d.Dec == nil { i, _ := q.i.AsScaledInt64(scale) return i } dec := q.d.Dec return scaledValue(dec.UnscaledBig(), int(dec.Scale()), int(scale.infScale())) } // Set sets q's value to be value. func (q *Quantity) Set(value int64) { q.SetScaled(value, 0) } // SetMilli sets q's value to be value * 1/1000. func (q *Quantity) SetMilli(value int64) { q.SetScaled(value, Milli) } // SetScaled sets q's value to be value * 10^scale func (q *Quantity) SetScaled(value int64, scale Scale) { q.s = "" q.d.Dec = nil q.i = int64Amount{value: value, scale: scale} } // QuantityValue makes it possible to use a Quantity as value for a command // line parameter. // // +protobuf=true // +protobuf.embed=string // +protobuf.options.marshal=false // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:deepcopy-gen=true type QuantityValue struct { Quantity } // Set implements pflag.Value.Set and Go flag.Value.Set. func (q *QuantityValue) Set(s string) error { quantity, err := ParseQuantity(s) if err != nil { return err } q.Quantity = quantity return nil } // Type implements pflag.Value.Type. func (q QuantityValue) Type() string { return "quantity" } golang-k8s-apimachinery-0.29.0/pkg/api/resource/quantity_example_test.go000066400000000000000000000032651453143165200263750ustar00rootroot00000000000000/* 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 resource_test import ( "fmt" "k8s.io/apimachinery/pkg/api/resource" ) func ExampleFormat() { memorySize := resource.NewQuantity(5*1024*1024*1024, resource.BinarySI) fmt.Printf("memorySize = %v\n", memorySize) diskSize := resource.NewQuantity(5*1000*1000*1000, resource.DecimalSI) fmt.Printf("diskSize = %v\n", diskSize) cores := resource.NewMilliQuantity(5300, resource.DecimalSI) fmt.Printf("cores = %v\n", cores) // Output: // memorySize = 5Gi // diskSize = 5G // cores = 5300m } func ExampleMustParse() { memorySize := resource.MustParse("5Gi") fmt.Printf("memorySize = %v (%v)\n", memorySize.Value(), memorySize.Format) diskSize := resource.MustParse("5G") fmt.Printf("diskSize = %v (%v)\n", diskSize.Value(), diskSize.Format) cores := resource.MustParse("5300m") fmt.Printf("milliCores = %v (%v)\n", cores.MilliValue(), cores.Format) cores2 := resource.MustParse("5.4") fmt.Printf("milliCores = %v (%v)\n", cores2.MilliValue(), cores2.Format) // Output: // memorySize = 5368709120 (BinarySI) // diskSize = 5000000000 (DecimalSI) // milliCores = 5300 (DecimalSI) // milliCores = 5400 (DecimalSI) } golang-k8s-apimachinery-0.29.0/pkg/api/resource/quantity_proto.go000066400000000000000000000137661453143165200250550ustar00rootroot00000000000000/* Copyright 2015 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 resource import ( "fmt" "io" "math/bits" "github.com/gogo/protobuf/proto" ) var _ proto.Sizer = &Quantity{} func (m *Quantity) 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 } // MarshalTo is a customized version of the generated Protobuf unmarshaler for a struct // with a single string field. func (m *Quantity) MarshalTo(data []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(data[:size]) } // MarshalToSizedBuffer is a customized version of the generated // Protobuf unmarshaler for a struct with a single string field. func (m *Quantity) MarshalToSizedBuffer(data []byte) (int, error) { i := len(data) _ = i var l int _ = l // BEGIN CUSTOM MARSHAL out := m.String() i -= len(out) copy(data[i:], out) i = encodeVarintGenerated(data, i, uint64(len(out))) // END CUSTOM MARSHAL i-- data[i] = 0xa return len(data) - i, nil } func encodeVarintGenerated(data []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset for v >= 1<<7 { data[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } data[offset] = uint8(v) return base } func (m *Quantity) Size() (n int) { var l int _ = l // BEGIN CUSTOM SIZE l = len(m.String()) // END CUSTOM SIZE n += 1 + l + sovGenerated(uint64(l)) return n } func sovGenerated(x uint64) (n int) { return (bits.Len64(x|1) + 6) / 7 } // Unmarshal is a customized version of the generated Protobuf unmarshaler for a struct // with a single string field. func (m *Quantity) 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: Quantity: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Quantity: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field String_", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := data[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } s := string(data[iNdEx:postIndex]) // BEGIN CUSTOM DECODE p, err := ParseQuantity(s) if err != nil { return err } *m = p // END CUSTOM DECODE 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 skipGenerated(data []byte) (n int, err error) { l := len(data) iNdEx := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := data[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if data[iNdEx-1] < 0x80 { break } } return iNdEx, nil case 1: iNdEx += 8 return iNdEx, nil case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := data[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } iNdEx += length if length < 0 { return 0, ErrInvalidLengthGenerated } return iNdEx, nil case 3: for { var innerWire uint64 var start int = iNdEx for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := data[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } innerWireType := int(innerWire & 0x7) if innerWireType == 4 { break } next, err := skipGenerated(data[start:]) if err != nil { return 0, err } iNdEx = start + next } return iNdEx, nil case 4: return iNdEx, nil case 5: iNdEx += 4 return iNdEx, nil default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } } panic("unreachable") } var ( ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") ) golang-k8s-apimachinery-0.29.0/pkg/api/resource/quantity_proto_test.go000066400000000000000000000072521453143165200261050ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package resource import ( "testing" inf "gopkg.in/inf.v0" ) func TestQuantityProtoMarshal(t *testing.T) { // Test when d is nil table := []struct { quantity string expect Quantity }{ {"0", Quantity{i: int64Amount{value: 0, scale: 0}, s: "0", Format: DecimalSI}}, {"100m", Quantity{i: int64Amount{value: 100, scale: -3}, s: "100m", Format: DecimalSI}}, {"50m", Quantity{i: int64Amount{value: 50, scale: -3}, s: "50m", Format: DecimalSI}}, {"10000T", Quantity{i: int64Amount{value: 10000, scale: 12}, s: "10000T", Format: DecimalSI}}, } for _, testCase := range table { q := MustParse(testCase.quantity) // Won't currently get an error as MarshalTo can't return one result, _ := q.Marshal() q.MarshalTo(result) if q.Cmp(testCase.expect) != 0 { t.Errorf("Expected: %v, Actual: %v", testCase.expect, q) } } // Test when i is {0,0} table2 := []struct { dec *inf.Dec expect Quantity }{ {dec(0, 0).Dec, Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(0, 0).Dec}, s: "0", Format: DecimalSI}}, {dec(10, 0).Dec, Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(10, 0).Dec}, s: "10", Format: DecimalSI}}, {dec(-10, 0).Dec, Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(-10, 0).Dec}, s: "-10", Format: DecimalSI}}, } for _, testCase := range table2 { q := Quantity{d: infDecAmount{testCase.dec}, Format: DecimalSI} // Won't currently get an error as MarshalTo can't return one result, _ := q.Marshal() q.Unmarshal(result) if q.Cmp(testCase.expect) != 0 { t.Errorf("Expected: %v, Actual: %v", testCase.expect, q) } } } func TestQuantityProtoUnmarshal(t *testing.T) { // Test when d is nil table := []struct { input Quantity expect string }{ {Quantity{i: int64Amount{value: 0, scale: 0}, s: "0", Format: DecimalSI}, "0"}, {Quantity{i: int64Amount{value: 100, scale: -3}, s: "100m", Format: DecimalSI}, "100m"}, {Quantity{i: int64Amount{value: 50, scale: -3}, s: "50m", Format: DecimalSI}, "50m"}, {Quantity{i: int64Amount{value: 10000, scale: 12}, s: "10000T", Format: DecimalSI}, "10000T"}, } for _, testCase := range table { var inputQ Quantity expectQ := MustParse(testCase.expect) inputByteArray, _ := testCase.input.Marshal() inputQ.Unmarshal(inputByteArray) if inputQ.Cmp(expectQ) != 0 { t.Errorf("Expected: %v, Actual: %v", inputQ, expectQ) } } // Test when i is {0,0} table2 := []struct { input Quantity expect *inf.Dec }{ {Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(0, 0).Dec}, s: "0", Format: DecimalSI}, dec(0, 0).Dec}, {Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(10, 0).Dec}, s: "10", Format: DecimalSI}, dec(10, 0).Dec}, {Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(-10, 0).Dec}, s: "-10", Format: DecimalSI}, dec(-10, 0).Dec}, } for _, testCase := range table2 { var inputQ Quantity expectQ := Quantity{d: infDecAmount{testCase.expect}, Format: DecimalSI} inputByteArray, _ := testCase.input.Marshal() inputQ.Unmarshal(inputByteArray) if inputQ.Cmp(expectQ) != 0 { t.Errorf("Expected: %v, Actual: %v", inputQ, expectQ) } } } golang-k8s-apimachinery-0.29.0/pkg/api/resource/quantity_test.go000066400000000000000000001362311453143165200246620ustar00rootroot00000000000000/* 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 resource import ( "encoding/json" "fmt" "math" "math/big" "math/rand" "os" "strings" "testing" "unicode" fuzz "github.com/google/gofuzz" "github.com/spf13/pflag" inf "gopkg.in/inf.v0" ) var ( bigMostPositive = big.NewInt(mostPositive) bigMostNegative = big.NewInt(mostNegative) ) func dec(i int64, exponent int) infDecAmount { // See the below test-- scale is the negative of an exponent. return infDecAmount{inf.NewDec(i, inf.Scale(-exponent))} } func bigDec(i *big.Int, exponent int) infDecAmount { // See the below test-- scale is the negative of an exponent. return infDecAmount{inf.NewDecBig(i, inf.Scale(-exponent))} } func decQuantity(i int64, exponent int, format Format) Quantity { return Quantity{d: dec(i, exponent), Format: format} } func bigDecQuantity(i *big.Int, exponent int, format Format) Quantity { return Quantity{d: bigDec(i, exponent), Format: format} } func intQuantity(i int64, exponent Scale, format Format) Quantity { return Quantity{i: int64Amount{value: i, scale: exponent}, Format: format} } func TestDec(t *testing.T) { table := []struct { got infDecAmount expect string }{ {dec(1, 0), "1"}, {dec(1, 1), "10"}, {dec(5, 2), "500"}, {dec(8, 3), "8000"}, {dec(2, 0), "2"}, {dec(1, -1), "0.1"}, {dec(3, -2), "0.03"}, {dec(4, -3), "0.004"}, } for _, item := range table { if e, a := item.expect, item.got.Dec.String(); e != a { t.Errorf("expected %v, got %v", e, a) } } } func TestBigDec(t *testing.T) { table := []struct { got infDecAmount expect string }{ {bigDec(big.NewInt(1), 0), "1"}, {bigDec(big.NewInt(1), 1), "10"}, {bigDec(big.NewInt(5), 2), "500"}, {bigDec(big.NewInt(8), 3), "8000"}, {bigDec(big.NewInt(2), 0), "2"}, {bigDec(big.NewInt(1), -1), "0.1"}, {bigDec(big.NewInt(3), -2), "0.03"}, {bigDec(big.NewInt(4), -3), "0.004"}, {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 0), "9223372036854775808"}, {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 1), "92233720368547758080"}, {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 2), "922337203685477580800"}, {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), -1), "922337203685477580.8"}, {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), -2), "92233720368547758.08"}, {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), 0), "-9223372036854775809"}, {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), 1), "-92233720368547758090"}, {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), 2), "-922337203685477580900"}, {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), -1), "-922337203685477580.9"}, {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), -2), "-92233720368547758.09"}, } for _, item := range table { if e, a := item.expect, item.got.Dec.String(); e != a { t.Errorf("expected %v, got %v", e, a) } } } // TestQuantityParseZero ensures that when a 0 quantity is passed, its string value is 0 func TestQuantityParseZero(t *testing.T) { zero := MustParse("0") if expected, actual := "0", zero.String(); expected != actual { t.Errorf("Expected %v, actual %v", expected, actual) } } // TestQuantityParseNonNumericPanic ensures that when a non-numeric string is parsed // it panics func TestQuantityParseNonNumericPanic(t *testing.T) { defer func() { if r := recover(); r == nil { t.Errorf("MustParse did not panic") } }() _ = MustParse("Non-Numeric") } // TestQuantityAddZeroPreservesSuffix verifies that a suffix is preserved // independent of the order of operations when adding a zero and non-zero val func TestQuantityAddZeroPreservesSuffix(t *testing.T) { testValues := []string{"100m", "1Gi"} zero := MustParse("0") for _, testValue := range testValues { value := MustParse(testValue) v1 := value.DeepCopy() // ensure non-zero + zero = non-zero (suffix preserved) v1.Add(zero) // ensure zero + non-zero = non-zero (suffix preserved) v2 := zero.DeepCopy() v2.Add(value) if v1.String() != testValue { t.Errorf("Expected %v, actual %v", testValue, v1.String()) continue } if v2.String() != testValue { t.Errorf("Expected %v, actual %v", testValue, v2.String()) } } } // TestQuantitySubZeroPreservesSuffix verifies that a suffix is preserved // independent of the order of operations when subtracting a zero and non-zero val func TestQuantitySubZeroPreservesSuffix(t *testing.T) { testValues := []string{"100m", "1Gi"} zero := MustParse("0") for _, testValue := range testValues { value := MustParse(testValue) v1 := value.DeepCopy() // ensure non-zero - zero = non-zero (suffix preserved) v1.Sub(zero) // ensure we preserved the input value if v1.String() != testValue { t.Errorf("Expected %v, actual %v", testValue, v1.String()) } // ensure zero - non-zero = -non-zero (suffix preserved) v2 := zero.DeepCopy() v2.Sub(value) negVal := value.DeepCopy() negVal.Neg() if v2.String() != negVal.String() { t.Errorf("Expected %v, actual %v", negVal.String(), v2.String()) } } } // TestQuantityCanocicalizeZero verifies that you get 0 as canonical value if internal value is 0, and not 0 func TestQuantityCanocicalizeZero(t *testing.T) { val := MustParse("1000m") val.i.Sub(int64Amount{value: 1}) zero := Quantity{i: val.i, Format: DecimalSI} if expected, actual := "0", zero.String(); expected != actual { t.Errorf("Expected %v, actual %v", expected, actual) } } func TestQuantityCmp(t *testing.T) { // Test when d is nil table := []struct { x string y string expect int }{ {"0", "0", 0}, {"100m", "50m", 1}, {"50m", "100m", -1}, {"10000T", "100Gi", 1}, } for _, testCase := range table { q1 := MustParse(testCase.x) q2 := MustParse(testCase.y) if result := q1.Cmp(q2); result != testCase.expect { t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result) } } // Test when i is {0,0} table2 := []struct { x *inf.Dec y *inf.Dec expect int }{ {dec(0, 0).Dec, dec(0, 0).Dec, 0}, {nil, dec(0, 0).Dec, 0}, {dec(0, 0).Dec, nil, 0}, {nil, nil, 0}, {nil, dec(10, 0).Dec, -1}, {nil, dec(-10, 0).Dec, 1}, {dec(10, 0).Dec, nil, 1}, {dec(-10, 0).Dec, nil, -1}, } for _, testCase := range table2 { q1 := Quantity{d: infDecAmount{testCase.x}, Format: DecimalSI} q2 := Quantity{d: infDecAmount{testCase.y}, Format: DecimalSI} if result := q1.Cmp(q2); result != testCase.expect { t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result) } } } func TestParseQuantityString(t *testing.T) { table := []struct { input string positive bool value string num, denom, suffix string }{ {"0.025Ti", true, "0.025", "0", "025", "Ti"}, {"1.025Ti", true, "1.025", "1", "025", "Ti"}, {"-1.025Ti", false, "-1.025", "1", "025", "Ti"}, {".", true, ".", "0", "", ""}, {"-.", false, "-.", "0", "", ""}, {"1E-3", true, "1", "1", "", "E-3"}, } for _, test := range table { positive, value, num, denom, suffix, err := parseQuantityString(test.input) if err != nil { t.Errorf("%s: error: %v", test.input, err) continue } if positive != test.positive || value != test.value || num != test.num || denom != test.denom || suffix != test.suffix { t.Errorf("%s: unmatched: %t %q %q %q %q", test.input, positive, value, num, denom, suffix) } } } func TestQuantityParse(t *testing.T) { if _, err := ParseQuantity(""); err == nil { t.Errorf("expected empty string to return error") } table := []struct { input string expect Quantity }{ {"0", decQuantity(0, 0, DecimalSI)}, {"0n", decQuantity(0, 0, DecimalSI)}, {"0u", decQuantity(0, 0, DecimalSI)}, {"0m", decQuantity(0, 0, DecimalSI)}, {"0Ki", decQuantity(0, 0, BinarySI)}, {"0k", decQuantity(0, 0, DecimalSI)}, {"0Mi", decQuantity(0, 0, BinarySI)}, {"0M", decQuantity(0, 0, DecimalSI)}, {"0Gi", decQuantity(0, 0, BinarySI)}, {"0G", decQuantity(0, 0, DecimalSI)}, {"0Ti", decQuantity(0, 0, BinarySI)}, {"0T", decQuantity(0, 0, DecimalSI)}, // Quantity less numbers are allowed {"1", decQuantity(1, 0, DecimalSI)}, // Binary suffixes {"1Ki", decQuantity(1024, 0, BinarySI)}, {"8Ki", decQuantity(8*1024, 0, BinarySI)}, {"7Mi", decQuantity(7*1024*1024, 0, BinarySI)}, {"6Gi", decQuantity(6*1024*1024*1024, 0, BinarySI)}, {"5Ti", decQuantity(5*1024*1024*1024*1024, 0, BinarySI)}, {"4Pi", decQuantity(4*1024*1024*1024*1024*1024, 0, BinarySI)}, {"3Ei", decQuantity(3*1024*1024*1024*1024*1024*1024, 0, BinarySI)}, {"10Ti", decQuantity(10*1024*1024*1024*1024, 0, BinarySI)}, {"100Ti", decQuantity(100*1024*1024*1024*1024, 0, BinarySI)}, // Decimal suffixes {"5n", decQuantity(5, -9, DecimalSI)}, {"4u", decQuantity(4, -6, DecimalSI)}, {"3m", decQuantity(3, -3, DecimalSI)}, {"9", decQuantity(9, 0, DecimalSI)}, {"8k", decQuantity(8, 3, DecimalSI)}, {"50k", decQuantity(5, 4, DecimalSI)}, {"7M", decQuantity(7, 6, DecimalSI)}, {"6G", decQuantity(6, 9, DecimalSI)}, {"5T", decQuantity(5, 12, DecimalSI)}, {"40T", decQuantity(4, 13, DecimalSI)}, {"300T", decQuantity(3, 14, DecimalSI)}, {"2P", decQuantity(2, 15, DecimalSI)}, {"1E", decQuantity(1, 18, DecimalSI)}, // Decimal exponents {"1E-3", decQuantity(1, -3, DecimalExponent)}, {"1e3", decQuantity(1, 3, DecimalExponent)}, {"1E6", decQuantity(1, 6, DecimalExponent)}, {"1e9", decQuantity(1, 9, DecimalExponent)}, {"1E12", decQuantity(1, 12, DecimalExponent)}, {"1e15", decQuantity(1, 15, DecimalExponent)}, {"1E18", decQuantity(1, 18, DecimalExponent)}, // Nonstandard but still parsable {"1e14", decQuantity(1, 14, DecimalExponent)}, {"1e13", decQuantity(1, 13, DecimalExponent)}, {"1e3", decQuantity(1, 3, DecimalExponent)}, {"100.035k", decQuantity(100035, 0, DecimalSI)}, // Things that look like floating point {"0.001", decQuantity(1, -3, DecimalSI)}, {"0.0005k", decQuantity(5, -1, DecimalSI)}, {"0.005", decQuantity(5, -3, DecimalSI)}, {"0.05", decQuantity(5, -2, DecimalSI)}, {"0.5", decQuantity(5, -1, DecimalSI)}, {"0.00050k", decQuantity(5, -1, DecimalSI)}, {"0.00500", decQuantity(5, -3, DecimalSI)}, {"0.05000", decQuantity(5, -2, DecimalSI)}, {"0.50000", decQuantity(5, -1, DecimalSI)}, {"0.5e0", decQuantity(5, -1, DecimalExponent)}, {"0.5e-1", decQuantity(5, -2, DecimalExponent)}, {"0.5e-2", decQuantity(5, -3, DecimalExponent)}, {"0.5e0", decQuantity(5, -1, DecimalExponent)}, {"10.035M", decQuantity(10035, 3, DecimalSI)}, {"1.2e3", decQuantity(12, 2, DecimalExponent)}, {"1.3E+6", decQuantity(13, 5, DecimalExponent)}, {"1.40e9", decQuantity(14, 8, DecimalExponent)}, {"1.53E12", decQuantity(153, 10, DecimalExponent)}, {"1.6e15", decQuantity(16, 14, DecimalExponent)}, {"1.7E18", decQuantity(17, 17, DecimalExponent)}, {"9.01", decQuantity(901, -2, DecimalSI)}, {"8.1k", decQuantity(81, 2, DecimalSI)}, {"7.123456M", decQuantity(7123456, 0, DecimalSI)}, {"6.987654321G", decQuantity(6987654321, 0, DecimalSI)}, {"5.444T", decQuantity(5444, 9, DecimalSI)}, {"40.1T", decQuantity(401, 11, DecimalSI)}, {"300.2T", decQuantity(3002, 11, DecimalSI)}, {"2.5P", decQuantity(25, 14, DecimalSI)}, {"1.01E", decQuantity(101, 16, DecimalSI)}, // Things that saturate/round {"3.001n", decQuantity(4, -9, DecimalSI)}, {"1.1E-9", decQuantity(2, -9, DecimalExponent)}, {"0.0000000001", decQuantity(1, -9, DecimalSI)}, {"0.0000000005", decQuantity(1, -9, DecimalSI)}, {"0.00000000050", decQuantity(1, -9, DecimalSI)}, {"0.5e-9", decQuantity(1, -9, DecimalExponent)}, {"0.9n", decQuantity(1, -9, DecimalSI)}, {"0.00000012345", decQuantity(124, -9, DecimalSI)}, {"0.00000012354", decQuantity(124, -9, DecimalSI)}, {"9Ei", Quantity{d: maxAllowed, Format: BinarySI}}, {"9223372036854775807Ki", Quantity{d: maxAllowed, Format: BinarySI}}, {"12E", decQuantity(12, 18, DecimalSI)}, // We'll accept fractional binary stuff, too. {"100.035Ki", decQuantity(10243584, -2, BinarySI)}, {"0.5Mi", decQuantity(.5*1024*1024, 0, BinarySI)}, {"0.05Gi", decQuantity(536870912, -1, BinarySI)}, {"0.025Ti", decQuantity(274877906944, -1, BinarySI)}, // Things written by trolls {"0.000000000001Ki", decQuantity(2, -9, DecimalSI)}, // rounds up, changes format {".001", decQuantity(1, -3, DecimalSI)}, {".0001k", decQuantity(100, -3, DecimalSI)}, {"1.", decQuantity(1, 0, DecimalSI)}, {"1.G", decQuantity(1, 9, DecimalSI)}, } for _, asDec := range []bool{false, true} { for _, item := range table { got, err := ParseQuantity(item.input) if err != nil { t.Errorf("%v: unexpected error: %v", item.input, err) continue } if asDec { got.AsDec() } if e, a := item.expect, got; e.Cmp(a) != 0 { t.Errorf("%v: expected %v, got %v", item.input, e.String(), a.String()) } if e, a := item.expect.Format, got.Format; e != a { t.Errorf("%v: expected %#v, got %#v", item.input, e, a) } if asDec { if i, ok := got.AsInt64(); i != 0 || ok { t.Errorf("%v: expected inf.Dec to return false for AsInt64: %d", item.input, i) } continue } i, ok := item.expect.AsInt64() if !ok { continue } j, ok := got.AsInt64() if !ok { if got.d.Dec == nil && got.i.scale >= 0 { t.Errorf("%v: is an int64Amount, but can't return AsInt64: %v", item.input, got) } continue } if i != j { t.Errorf("%v: expected equivalent representation as int64: %d %d", item.input, i, j) } } for _, item := range table { got, err := ParseQuantity(item.input) if err != nil { t.Errorf("%v: unexpected error: %v", item.input, err) continue } if asDec { got.AsDec() } for _, format := range []Format{DecimalSI, BinarySI, DecimalExponent} { // ensure we are not simply checking pointer equality by creating a new inf.Dec var copied inf.Dec copied.Add(inf.NewDec(0, inf.Scale(0)), got.AsDec()) q := NewDecimalQuantity(copied, format) if c := q.Cmp(got); c != 0 { t.Errorf("%v: round trip from decimal back to quantity is not comparable: %d: %#v vs %#v", item.input, c, got, q) } } // verify that we can decompose the input and get the same result by building up from the base. positive, _, num, denom, suffix, err := parseQuantityString(item.input) if err != nil { t.Errorf("%v: unexpected error: %v", item.input, err) continue } if got.Sign() >= 0 && !positive || got.Sign() < 0 && positive { t.Errorf("%v: positive was incorrect: %t", item.input, positive) continue } var value string if !positive { value = "-" } value += num if len(denom) > 0 { value += "." + denom } value += suffix if len(value) == 0 { t.Errorf("%v: did not parse correctly, %q %q %q", item.input, num, denom, suffix) } expected, err := ParseQuantity(value) if err != nil { t.Errorf("%v: unexpected error for %s: %v", item.input, value, err) continue } if expected.Cmp(got) != 0 { t.Errorf("%v: not the same as %s", item.input, value) continue } } // Try the negative version of everything desired := &inf.Dec{} expect := Quantity{d: infDecAmount{Dec: desired}} for _, item := range table { got, err := ParseQuantity("-" + strings.TrimLeftFunc(item.input, unicode.IsSpace)) if err != nil { t.Errorf("-%v: unexpected error: %v", item.input, err) continue } if asDec { got.AsDec() } expected := item.expect desired.Neg(expected.AsDec()) if e, a := expect, got; e.Cmp(a) != 0 { t.Errorf("%v: expected %s, got %s", item.input, e.String(), a.String()) } if e, a := expected.Format, got.Format; e != a { t.Errorf("%v: expected %#v, got %#v", item.input, e, a) } } // Try everything with an explicit + for _, item := range table { got, err := ParseQuantity("+" + strings.TrimLeftFunc(item.input, unicode.IsSpace)) if err != nil { t.Errorf("-%v: unexpected error: %v", item.input, err) continue } if asDec { got.AsDec() } if e, a := item.expect, got; e.Cmp(a) != 0 { t.Errorf("%v(%t): expected %s, got %s", item.input, asDec, e.String(), a.String()) } if e, a := item.expect.Format, got.Format; e != a { t.Errorf("%v: expected %#v, got %#v", item.input, e, a) } } } invalid := []string{ "1.1.M", "1+1.0M", "0.1mi", "0.1am", "aoeu", ".5i", "1i", "-3.01i", "-3.01e-", // trailing whitespace is forbidden " 1", "1 ", } for _, item := range invalid { _, err := ParseQuantity(item) if err == nil { t.Errorf("%v parsed unexpectedly", item) } } } func TestQuantityRoundUp(t *testing.T) { table := []struct { in string scale Scale expect Quantity ok bool }{ {"9.01", -3, decQuantity(901, -2, DecimalSI), true}, {"9.01", -2, decQuantity(901, -2, DecimalSI), true}, {"9.01", -1, decQuantity(91, -1, DecimalSI), false}, {"9.01", 0, decQuantity(10, 0, DecimalSI), false}, {"9.01", 1, decQuantity(10, 0, DecimalSI), false}, {"9.01", 2, decQuantity(100, 0, DecimalSI), false}, {"-9.01", -3, decQuantity(-901, -2, DecimalSI), true}, {"-9.01", -2, decQuantity(-901, -2, DecimalSI), true}, {"-9.01", -1, decQuantity(-91, -1, DecimalSI), false}, {"-9.01", 0, decQuantity(-10, 0, DecimalSI), false}, {"-9.01", 1, decQuantity(-10, 0, DecimalSI), false}, {"-9.01", 2, decQuantity(-100, 0, DecimalSI), false}, } for _, asDec := range []bool{false, true} { for _, item := range table { got, err := ParseQuantity(item.in) if err != nil { t.Fatalf("unexpected error: %v", err) } expect := item.expect.DeepCopy() if asDec { got.AsDec() } if ok := got.RoundUp(item.scale); ok != item.ok { t.Errorf("%s(%d,%t): unexpected ok: %t", item.in, item.scale, asDec, ok) } if got.Cmp(expect) != 0 { t.Errorf("%s(%d,%t): unexpected round: %s vs %s", item.in, item.scale, asDec, got.String(), expect.String()) } } } } func TestQuantityCmpInt64AndDec(t *testing.T) { table := []struct { a, b Quantity cmp int }{ {intQuantity(901, -2, DecimalSI), intQuantity(901, -2, DecimalSI), 0}, {intQuantity(90, -1, DecimalSI), intQuantity(901, -2, DecimalSI), -1}, {intQuantity(901, -2, DecimalSI), intQuantity(900, -2, DecimalSI), 1}, {intQuantity(0, 0, DecimalSI), intQuantity(0, 0, DecimalSI), 0}, {intQuantity(0, 1, DecimalSI), intQuantity(0, -1, DecimalSI), 0}, {intQuantity(0, -1, DecimalSI), intQuantity(0, 1, DecimalSI), 0}, {intQuantity(800, -3, DecimalSI), intQuantity(1, 0, DecimalSI), -1}, {intQuantity(800, -3, DecimalSI), intQuantity(79, -2, DecimalSI), 1}, {intQuantity(mostPositive, 0, DecimalSI), intQuantity(1, -1, DecimalSI), 1}, {intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 0, DecimalSI), 1}, {intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 1, DecimalSI), 1}, {intQuantity(mostPositive, 1, DecimalSI), intQuantity(0, 1, DecimalSI), 1}, {intQuantity(mostPositive, -16, DecimalSI), intQuantity(1, 3, DecimalSI), -1}, {intQuantity(mostNegative, 0, DecimalSI), intQuantity(0, 0, DecimalSI), -1}, {intQuantity(mostNegative, -18, DecimalSI), intQuantity(-1, 0, DecimalSI), -1}, {intQuantity(mostNegative, -19, DecimalSI), intQuantity(-1, 0, DecimalSI), 1}, {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 0}, {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 1}, {intQuantity(-1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 0}, {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 0, DecimalSI), 1}, {intQuantity(1*1000000*1000000*1000000+1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 1}, {intQuantity(1*1000000*1000000*1000000-1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), -1}, } for _, item := range table { if cmp := item.a.Cmp(item.b); cmp != item.cmp { t.Errorf("%#v: unexpected Cmp: %d", item, cmp) } if cmp := item.b.Cmp(item.a); cmp != -item.cmp { t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) } } for _, item := range table { a, b := item.a.DeepCopy(), item.b.DeepCopy() a.AsDec() if cmp := a.Cmp(b); cmp != item.cmp { t.Errorf("%#v: unexpected Cmp: %d", item, cmp) } if cmp := b.Cmp(a); cmp != -item.cmp { t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) } } for _, item := range table { a, b := item.a.DeepCopy(), item.b.DeepCopy() b.AsDec() if cmp := a.Cmp(b); cmp != item.cmp { t.Errorf("%#v: unexpected Cmp: %d", item, cmp) } if cmp := b.Cmp(a); cmp != -item.cmp { t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) } } for _, item := range table { a, b := item.a.DeepCopy(), item.b.DeepCopy() a.AsDec() b.AsDec() if cmp := a.Cmp(b); cmp != item.cmp { t.Errorf("%#v: unexpected Cmp: %d", item, cmp) } if cmp := b.Cmp(a); cmp != -item.cmp { t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) } } } func TestQuantityNeg(t *testing.T) { table := []struct { a Quantity out string }{ {intQuantity(901, -2, DecimalSI), "-9010m"}, {decQuantity(901, -2, DecimalSI), "-9010m"}, } for i, item := range table { out := item.a.DeepCopy() out.Neg() if out.Cmp(item.a) == 0 { t.Errorf("%d: negating an item should not mutate the source: %s", i, out.String()) } if out.String() != item.out { t.Errorf("%d: negating did not equal exact value: %s", i, out.String()) } } } func TestQuantityString(t *testing.T) { table := []struct { in Quantity expect string alternate string }{ {decQuantity(1024*1024*1024, 0, BinarySI), "1Gi", "1024Mi"}, {decQuantity(300*1024*1024, 0, BinarySI), "300Mi", "307200Ki"}, {decQuantity(6*1024, 0, BinarySI), "6Ki", ""}, {decQuantity(1001*1024*1024*1024, 0, BinarySI), "1001Gi", "1025024Mi"}, {decQuantity(1024*1024*1024*1024, 0, BinarySI), "1Ti", "1024Gi"}, {decQuantity(5, 0, BinarySI), "5", "5000m"}, {decQuantity(500, -3, BinarySI), "500m", "0.5"}, {decQuantity(1, 9, DecimalSI), "1G", "1000M"}, {decQuantity(1000, 6, DecimalSI), "1G", "0.001T"}, {decQuantity(1000000, 3, DecimalSI), "1G", ""}, {decQuantity(1000000000, 0, DecimalSI), "1G", ""}, {decQuantity(1, -3, DecimalSI), "1m", "1000u"}, {decQuantity(80, -3, DecimalSI), "80m", ""}, {decQuantity(1080, -3, DecimalSI), "1080m", "1.08"}, {decQuantity(108, -2, DecimalSI), "1080m", "1080000000n"}, {decQuantity(10800, -4, DecimalSI), "1080m", ""}, {decQuantity(300, 6, DecimalSI), "300M", ""}, {decQuantity(1, 12, DecimalSI), "1T", ""}, {decQuantity(1234567, 6, DecimalSI), "1234567M", ""}, {decQuantity(1234567, -3, BinarySI), "1234567m", ""}, {decQuantity(3, 3, DecimalSI), "3k", ""}, {decQuantity(1025, 0, BinarySI), "1025", ""}, {decQuantity(0, 0, DecimalSI), "0", ""}, {decQuantity(0, 0, BinarySI), "0", ""}, {decQuantity(1, 9, DecimalExponent), "1e9", ".001e12"}, {decQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"}, {decQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"}, {decQuantity(80, -3, DecimalExponent), "80e-3", ""}, {decQuantity(300, 6, DecimalExponent), "300e6", ""}, {decQuantity(1, 12, DecimalExponent), "1e12", ""}, {decQuantity(1, 3, DecimalExponent), "1e3", ""}, {decQuantity(3, 3, DecimalExponent), "3e3", ""}, {decQuantity(3, 3, DecimalSI), "3k", ""}, {decQuantity(0, 0, DecimalExponent), "0", "00"}, {decQuantity(1, -9, DecimalSI), "1n", ""}, {decQuantity(80, -9, DecimalSI), "80n", ""}, {decQuantity(1080, -9, DecimalSI), "1080n", ""}, {decQuantity(108, -8, DecimalSI), "1080n", ""}, {decQuantity(10800, -10, DecimalSI), "1080n", ""}, {decQuantity(1, -6, DecimalSI), "1u", ""}, {decQuantity(80, -6, DecimalSI), "80u", ""}, {decQuantity(1080, -6, DecimalSI), "1080u", ""}, } for _, item := range table { got := item.in.String() if e, a := item.expect, got; e != a { t.Errorf("%#v: expected %v, got %v", item.in, e, a) } q, err := ParseQuantity(item.expect) if err != nil { t.Errorf("%#v: unexpected error: %v", item.expect, err) } if len(q.s) == 0 || q.s != item.expect { t.Errorf("%#v: did not copy canonical string on parse: %s", item.expect, q.s) } if len(item.alternate) == 0 { continue } q, err = ParseQuantity(item.alternate) if err != nil { t.Errorf("%#v: unexpected error: %v", item.expect, err) continue } if len(q.s) != 0 { t.Errorf("%#v: unexpected nested string: %v", item.expect, q.s) } if q.String() != item.expect { t.Errorf("%#v: unexpected alternate canonical: %v", item.expect, q.String()) } if len(q.s) == 0 || q.s != item.expect { t.Errorf("%#v: did not set canonical string on ToString: %s", item.expect, q.s) } } desired := &inf.Dec{} // Avoid modifying the values in the table. for _, item := range table { if item.in.Cmp(Quantity{}) == 0 { // Don't expect it to print "-0" ever continue } q := item.in q.d = infDecAmount{desired.Neg(q.AsDec())} if e, a := "-"+item.expect, q.String(); e != a { t.Errorf("%#v: expected %v, got %v", item.in, e, a) } } } func TestQuantityParseEmit(t *testing.T) { table := []struct { in string expect string }{ {"1Ki", "1Ki"}, {"1Mi", "1Mi"}, {"1Gi", "1Gi"}, {"1024Mi", "1Gi"}, {"1000M", "1G"}, {".001Ki", "1024m"}, {".000001Ki", "1024u"}, {".000000001Ki", "1024n"}, {".000000000001Ki", "2n"}, } for _, item := range table { q, err := ParseQuantity(item.in) if err != nil { t.Errorf("Couldn't parse %v", item.in) continue } if e, a := item.expect, q.String(); e != a { t.Errorf("%#v: expected %v, got %v", item.in, e, a) } } for _, item := range table { q, err := ParseQuantity("-" + item.in) if err != nil { t.Errorf("Couldn't parse %v", item.in) continue } if q.Cmp(Quantity{}) == 0 { continue } if e, a := "-"+item.expect, q.String(); e != a { t.Errorf("%#v: expected %v, got %v (%#v)", item.in, e, a, q.i) } } } var fuzzer = fuzz.New().Funcs( func(q *Quantity, c fuzz.Continue) { q.i = Zero if c.RandBool() { q.Format = BinarySI if c.RandBool() { dec := &inf.Dec{} q.d = infDecAmount{Dec: dec} dec.SetScale(0) dec.SetUnscaled(c.Int63()) return } // Be sure to test cases like 1Mi dec := &inf.Dec{} q.d = infDecAmount{Dec: dec} dec.SetScale(0) dec.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5))) return } if c.RandBool() { q.Format = DecimalSI } else { q.Format = DecimalExponent } if c.RandBool() { dec := &inf.Dec{} q.d = infDecAmount{Dec: dec} dec.SetScale(inf.Scale(c.Intn(4))) dec.SetUnscaled(c.Int63()) return } // Be sure to test cases like 1M dec := &inf.Dec{} q.d = infDecAmount{Dec: dec} dec.SetScale(inf.Scale(3 - c.Intn(15))) dec.SetUnscaled(c.Int63n(1000)) }, ) func TestQuantityDeepCopy(t *testing.T) { // Test when d is nil slice := []string{"0", "100m", "50m", "10000T"} for _, testCase := range slice { q := MustParse(testCase) if result := q.DeepCopy(); result != q { t.Errorf("Expected: %v, Actual: %v", q, result) } } table := []*inf.Dec{ dec(0, 0).Dec, dec(10, 0).Dec, dec(-10, 0).Dec, } // Test when i is {0,0} for _, testCase := range table { q := Quantity{d: infDecAmount{testCase}, Format: DecimalSI} result := q.DeepCopy() if q.d.Cmp(result.AsDec()) != 0 { t.Errorf("Expected: %v, Actual: %v", q.String(), result.String()) } result = Quantity{d: infDecAmount{dec(2, 0).Dec}, Format: DecimalSI} if q.d.Cmp(result.AsDec()) == 0 { t.Errorf("Modifying result has affected q") } } } func TestJSON(t *testing.T) { for i := 0; i < 500; i++ { q := &Quantity{} fuzzer.Fuzz(q) b, err := json.Marshal(q) if err != nil { t.Errorf("error encoding %v: %v", q, err) continue } q2 := &Quantity{} err = json.Unmarshal(b, q2) if err != nil { t.Logf("%d: %s", i, string(b)) t.Errorf("%v: error decoding %v: %v", q, string(b), err) } if q2.Cmp(*q) != 0 { t.Errorf("Expected equal: %v, %v (json was '%v')", q, q2, string(b)) } } } func TestJSONWhitespace(t *testing.T) { q := Quantity{} testCases := []struct { in string expect string }{ {`" 1"`, "1"}, {`"1 "`, "1"}, {`1`, "1"}, {` 1`, "1"}, {`1 `, "1"}, {`10`, "10"}, {`-1`, "-1"}, {` -1`, "-1"}, } for _, test := range testCases { if err := json.Unmarshal([]byte(test.in), &q); err != nil { t.Errorf("%q: %v", test.in, err) } if q.String() != test.expect { t.Errorf("unexpected string: %q", q.String()) } } } func TestMilliNewSet(t *testing.T) { table := []struct { value int64 format Format expect string exact bool }{ {1, DecimalSI, "1m", true}, {1000, DecimalSI, "1", true}, {1234000, DecimalSI, "1234", true}, {1024, BinarySI, "1024m", false}, // Format changes {1000000, "invalidFormatDefaultsToExponent", "1e3", true}, {1024 * 1024, BinarySI, "1048576m", false}, // Format changes } for _, item := range table { q := NewMilliQuantity(item.value, item.format) if e, a := item.expect, q.String(); e != a { t.Errorf("Expected %v, got %v; %#v", e, a, q) } if !item.exact { continue } q2, err := ParseQuantity(q.String()) if err != nil { t.Errorf("Round trip failed on %v", q) } if e, a := item.value, q2.MilliValue(); e != a { t.Errorf("Expected %v, got %v", e, a) } } for _, item := range table { q := NewQuantity(0, item.format) q.SetMilli(item.value) if e, a := item.expect, q.String(); e != a { t.Errorf("Set: Expected %v, got %v; %#v", e, a, q) } } } func TestNewSet(t *testing.T) { table := []struct { value int64 format Format expect string }{ {1, DecimalSI, "1"}, {1000, DecimalSI, "1k"}, {1234000, DecimalSI, "1234k"}, {1024, BinarySI, "1Ki"}, {1000000, "invalidFormatDefaultsToExponent", "1e6"}, {1024 * 1024, BinarySI, "1Mi"}, } for _, asDec := range []bool{false, true} { for _, item := range table { q := NewQuantity(item.value, item.format) if asDec { q.ToDec() } if e, a := item.expect, q.String(); e != a { t.Errorf("Expected %v, got %v; %#v", e, a, q) } q2, err := ParseQuantity(q.String()) if err != nil { t.Errorf("Round trip failed on %v", q) } if e, a := item.value, q2.Value(); e != a { t.Errorf("Expected %v, got %v", e, a) } } for _, item := range table { q := NewQuantity(0, item.format) q.Set(item.value) if asDec { q.ToDec() } if e, a := item.expect, q.String(); e != a { t.Errorf("Set: Expected %v, got %v; %#v", e, a, q) } } } } func TestNewScaledSet(t *testing.T) { table := []struct { value int64 scale Scale expect string }{ {1, Nano, "1n"}, {1000, Nano, "1u"}, {1, Micro, "1u"}, {1000, Micro, "1m"}, {1, Milli, "1m"}, {1000, Milli, "1"}, {1, 0, "1"}, {0, Nano, "0"}, {0, Micro, "0"}, {0, Milli, "0"}, {0, 0, "0"}, } for _, item := range table { q := NewScaledQuantity(item.value, item.scale) if e, a := item.expect, q.String(); e != a { t.Errorf("Expected %v, got %v; %#v", e, a, q) } q2, err := ParseQuantity(q.String()) if err != nil { t.Errorf("Round trip failed on %v", q) } if e, a := item.value, q2.ScaledValue(item.scale); e != a { t.Errorf("Expected %v, got %v", e, a) } q3 := NewQuantity(0, DecimalSI) q3.SetScaled(item.value, item.scale) if q.Cmp(*q3) != 0 { t.Errorf("Expected %v and %v to be equal", q, q3) } } } func TestScaledValue(t *testing.T) { table := []struct { fromScale Scale toScale Scale expected int64 }{ {Nano, Nano, 1}, {Nano, Micro, 1}, {Nano, Milli, 1}, {Nano, 0, 1}, {Micro, Nano, 1000}, {Micro, Micro, 1}, {Micro, Milli, 1}, {Micro, 0, 1}, {Milli, Nano, 1000 * 1000}, {Milli, Micro, 1000}, {Milli, Milli, 1}, {Milli, 0, 1}, {0, Nano, 1000 * 1000 * 1000}, {0, Micro, 1000 * 1000}, {0, Milli, 1000}, {0, 0, 1}, {2, -2, 100 * 100}, } for _, item := range table { q := NewScaledQuantity(1, item.fromScale) if e, a := item.expected, q.ScaledValue(item.toScale); e != a { t.Errorf("%v to %v: Expected %v, got %v", item.fromScale, item.toScale, e, a) } } } func TestUninitializedNoCrash(t *testing.T) { var q Quantity q.Value() q.MilliValue() q.DeepCopy() _ = q.String() q.MarshalJSON() } func TestDeepCopy(t *testing.T) { q := NewQuantity(5, DecimalSI) c := q.DeepCopy() c.Set(6) if q.Value() == 6 { t.Errorf("Copy didn't") } } func TestSub(t *testing.T) { tests := []struct { a Quantity b Quantity expected Quantity }{ {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(0, 0, DecimalSI)}, {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(9, 0, DecimalSI)}, {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(9, 0, BinarySI)}, {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(-50, 0, DecimalSI)}, {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)}, {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, } for i, test := range tests { test.a.Sub(test.b) if test.a.Cmp(test.expected) != 0 { t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) } } } func TestNeg(t *testing.T) { tests := []struct { a Quantity b Quantity expected Quantity }{ {a: intQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)}, {a: Quantity{}, expected: Quantity{}}, {a: intQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)}, {a: intQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)}, {a: decQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)}, {a: decQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)}, {a: decQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)}, } for i, test := range tests { a := test.a.DeepCopy() a.Neg() // ensure value is same if a.Cmp(test.expected) != 0 { t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), a.String()) } } } func TestAdd(t *testing.T) { tests := []struct { a Quantity b Quantity expected Quantity }{ {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(20, 0, DecimalSI)}, {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(11, 0, DecimalSI)}, {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(11, 0, BinarySI)}, {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(50, 0, DecimalSI)}, {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)}, {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, } for i, test := range tests { test.a.Add(test.b) if test.a.Cmp(test.expected) != 0 { t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) } } } func TestMul(t *testing.T) { tests := []struct { a Quantity b int64 expected Quantity ok bool }{ {decQuantity(10, 0, DecimalSI), 10, decQuantity(100, 0, DecimalSI), true}, {decQuantity(10, 0, DecimalSI), 1, decQuantity(10, 0, DecimalSI), true}, {decQuantity(10, 0, BinarySI), 1, decQuantity(10, 0, BinarySI), true}, {Quantity{Format: DecimalSI}, 50, decQuantity(0, 0, DecimalSI), true}, {decQuantity(50, 0, DecimalSI), 0, decQuantity(0, 0, DecimalSI), true}, {Quantity{Format: DecimalSI}, 0, decQuantity(0, 0, DecimalSI), true}, {decQuantity(10, 0, DecimalSI), -10, decQuantity(-100, 0, DecimalSI), true}, {decQuantity(-10, 0, DecimalSI), 1, decQuantity(-10, 0, DecimalSI), true}, {decQuantity(10, 0, BinarySI), -1, decQuantity(-10, 0, BinarySI), true}, {decQuantity(-50, 0, DecimalSI), 0, decQuantity(0, 0, DecimalSI), true}, {decQuantity(-50, 0, DecimalSI), -50, decQuantity(2500, 0, DecimalSI), true}, {Quantity{Format: DecimalSI}, -50, decQuantity(0, 0, DecimalSI), true}, {decQuantity(mostPositive, 0, DecimalSI), 0, decQuantity(0, 1, DecimalSI), true}, {decQuantity(mostPositive, 0, DecimalSI), 1, decQuantity(mostPositive, 0, DecimalSI), true}, {decQuantity(mostPositive, 0, DecimalSI), -1, decQuantity(-mostPositive, 0, DecimalSI), true}, {decQuantity(mostPositive/2, 0, DecimalSI), 2, decQuantity((mostPositive/2)*2, 0, DecimalSI), true}, {decQuantity(mostPositive/-2, 0, DecimalSI), -2, decQuantity((mostPositive/2)*2, 0, DecimalSI), true}, {decQuantity(mostPositive, 0, DecimalSI), 2, bigDecQuantity(big.NewInt(0).Mul(bigMostPositive, big.NewInt(2)), 0, DecimalSI), false}, {decQuantity(mostPositive, 0, DecimalSI), 10, decQuantity(mostPositive, 1, DecimalSI), false}, {decQuantity(mostPositive, 0, DecimalSI), -10, decQuantity(-mostPositive, 1, DecimalSI), false}, {decQuantity(mostNegative, 0, DecimalSI), 0, decQuantity(0, 1, DecimalSI), true}, {decQuantity(mostNegative, 0, DecimalSI), 1, decQuantity(mostNegative, 0, DecimalSI), true}, {decQuantity(mostNegative, 0, DecimalSI), -1, bigDecQuantity(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 0, DecimalSI), false}, {decQuantity(mostNegative/2, 0, DecimalSI), 2, decQuantity(mostNegative, 0, DecimalSI), true}, {decQuantity(mostNegative/-2, 0, DecimalSI), -2, decQuantity(mostNegative, 0, DecimalSI), true}, {decQuantity(mostNegative, 0, DecimalSI), 2, bigDecQuantity(big.NewInt(0).Mul(bigMostNegative, big.NewInt(2)), 0, DecimalSI), false}, {decQuantity(mostNegative, 0, DecimalSI), 10, decQuantity(mostNegative, 1, DecimalSI), false}, {decQuantity(mostNegative, 0, DecimalSI), -10, bigDecQuantity(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 1, DecimalSI), false}, } for i, test := range tests { if ok := test.a.Mul(test.b); test.ok != ok { t.Errorf("[%d] Expected ok: %t, got ok: %t", i, test.ok, ok) } if test.a.Cmp(test.expected) != 0 { t.Errorf("[%d] Expected %q, got %q", i, test.expected.AsDec().String(), test.a.AsDec().String()) } } } func TestAddSubRoundTrip(t *testing.T) { for k := -10; k <= 10; k++ { q := Quantity{Format: DecimalSI} var order []int64 for i := 0; i < 100; i++ { j := rand.Int63() order = append(order, j) q.Add(*NewScaledQuantity(j, Scale(k))) } for _, j := range order { q.Sub(*NewScaledQuantity(j, Scale(k))) } if !q.IsZero() { t.Errorf("addition and subtraction did not cancel: %s", &q) } } } func TestAddSubRoundTripAcrossScales(t *testing.T) { q := Quantity{Format: DecimalSI} var order []int64 for i := 0; i < 100; i++ { j := rand.Int63() order = append(order, j) q.Add(*NewScaledQuantity(j, Scale(j%20-10))) } for _, j := range order { q.Sub(*NewScaledQuantity(j, Scale(j%20-10))) } if !q.IsZero() { t.Errorf("addition and subtraction did not cancel: %s", &q) } } func TestNegateRoundTrip(t *testing.T) { for _, asDec := range []bool{false, true} { for k := -10; k <= 10; k++ { for i := 0; i < 100; i++ { j := rand.Int63() q := *NewScaledQuantity(j, Scale(k)) if asDec { q.AsDec() } b := q.DeepCopy() b.Neg() b.Neg() if b.Cmp(q) != 0 { t.Errorf("double negation did not cancel: %s", &q) } } } } } func TestQuantityAsApproximateFloat64(t *testing.T) { 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) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way {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) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way {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) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way {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 _, 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) } if item.in.d.Dec != nil { if i, ok := item.in.AsInt64(); ok { q := intQuantity(i, 0, item.in.Format) out := q.AsApproximateFloat64() 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 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.AsApproximateFloat64() 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.AsApproximateFloat64() 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), intQuantity(1024*1024*1024*1024, 0, BinarySI), intQuantity(1000000, 3, DecimalSI), intQuantity(1000000000, 0, DecimalSI), intQuantity(1, -3, DecimalSI), intQuantity(80, -3, DecimalSI), intQuantity(1080, -3, DecimalSI), intQuantity(0, 0, BinarySI), intQuantity(1, 9, DecimalExponent), intQuantity(1, -9, DecimalSI), intQuantity(1000000, 10, DecimalSI), } } func BenchmarkQuantityString(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() var s string for i := 0; i < b.N; i++ { q := values[i%len(values)] q.s = "" s = q.String() } b.StopTimer() if len(s) == 0 { b.Fatal(s) } } func BenchmarkQuantityStringPrecalc(b *testing.B) { values := benchmarkQuantities() for i := range values { _ = values[i].String() } b.ResetTimer() var s string for i := 0; i < b.N; i++ { q := values[i%len(values)] s = q.String() } b.StopTimer() if len(s) == 0 { b.Fatal(s) } } func BenchmarkQuantityStringBinarySI(b *testing.B) { values := benchmarkQuantities() for i := range values { values[i].Format = BinarySI } b.ResetTimer() var s string for i := 0; i < b.N; i++ { q := values[i%len(values)] q.s = "" s = q.String() } b.StopTimer() if len(s) == 0 { b.Fatal(s) } } func BenchmarkQuantityMarshalJSON(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() for i := 0; i < b.N; i++ { q := values[i%len(values)] q.s = "" if _, err := q.MarshalJSON(); err != nil { b.Fatal(err) } } b.StopTimer() } func BenchmarkQuantityUnmarshalJSON(b *testing.B) { values := benchmarkQuantities() var json [][]byte for _, v := range values { data, _ := v.MarshalJSON() json = append(json, data) } b.ResetTimer() for i := 0; i < b.N; i++ { var q Quantity if err := q.UnmarshalJSON(json[i%len(values)]); err != nil { b.Fatal(err) } } b.StopTimer() } func BenchmarkParseQuantity(b *testing.B) { values := benchmarkQuantities() var strings []string for _, v := range values { strings = append(strings, v.String()) } b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := ParseQuantity(strings[i%len(values)]); err != nil { b.Fatal(err) } } b.StopTimer() } func BenchmarkCanonicalize(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() buffer := make([]byte, 0, 100) for i := 0; i < b.N; i++ { s, _ := values[i%len(values)].CanonicalizeBytes(buffer) if len(s) == 0 { b.Fatal(s) } } b.StopTimer() } func BenchmarkQuantityRoundUp(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() for i := 0; i < b.N; i++ { q := values[i%len(values)] copied := q copied.RoundUp(-3) } b.StopTimer() } func BenchmarkQuantityCopy(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() for i := 0; i < b.N; i++ { values[i%len(values)].DeepCopy() } b.StopTimer() } func BenchmarkQuantityAdd(b *testing.B) { values := benchmarkQuantities() base := &Quantity{} b.ResetTimer() for i := 0; i < b.N; i++ { q := values[i%len(values)] base.d.Dec = nil base.i = int64Amount{value: 100} base.Add(q) } b.StopTimer() } func BenchmarkQuantityCmp(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() for i := 0; i < b.N; i++ { q := values[i%len(values)] if q.Cmp(q) != 0 { b.Fatal(q) } } b.StopTimer() } func BenchmarkQuantityAsApproximateFloat64(b *testing.B) { values := benchmarkQuantities() b.ResetTimer() for i := 0; i < b.N; i++ { q := values[i%len(values)] if q.AsApproximateFloat64() == -1 { b.Fatal(q) } } b.StopTimer() } var _ pflag.Value = &QuantityValue{} func TestQuantityValueSet(t *testing.T) { q := QuantityValue{} if err := q.Set("invalid"); err == nil { t.Error("'invalid' did not trigger a parse error") } if err := q.Set("1Mi"); err != nil { t.Errorf("parsing 1Mi should have worked, got: %v", err) } if q.Value() != 1024*1024 { t.Errorf("quantity should have been set to 1Mi, got: %v", q) } data, err := json.Marshal(q) if err != nil { t.Errorf("unexpected encoding error: %v", err) } expected := `"1Mi"` if string(data) != expected { t.Errorf("expected 1Mi value to be encoded as %q, got: %q", expected, string(data)) } } func ExampleQuantityValue() { q := QuantityValue{ Quantity: MustParse("1Mi"), } fs := pflag.FlagSet{} fs.SetOutput(os.Stdout) fs.Var(&q, "mem", "sets amount of memory") fs.PrintDefaults() // Output: // --mem quantity sets amount of memory (default 1Mi) } golang-k8s-apimachinery-0.29.0/pkg/api/resource/scale_int.go000066400000000000000000000047331453143165200237070ustar00rootroot00000000000000/* Copyright 2015 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 resource import ( "math" "math/big" "sync" ) var ( // A sync pool to reduce allocation. intPool sync.Pool maxInt64 = big.NewInt(math.MaxInt64) ) func init() { intPool.New = func() interface{} { return &big.Int{} } } // scaledValue scales given unscaled value from scale to new Scale and returns // an int64. It ALWAYS rounds up the result when scale down. The final result might // overflow. // // scale, newScale represents the scale of the unscaled decimal. // The mathematical value of the decimal is unscaled * 10**(-scale). func scaledValue(unscaled *big.Int, scale, newScale int) int64 { dif := scale - newScale if dif == 0 { return unscaled.Int64() } // Handle scale up // This is an easy case, we do not need to care about rounding and overflow. // If any intermediate operation causes overflow, the result will overflow. if dif < 0 { return unscaled.Int64() * int64(math.Pow10(-dif)) } // Handle scale down // We have to be careful about the intermediate operations. // fast path when unscaled < max.Int64 and exp(10,dif) < max.Int64 const log10MaxInt64 = 19 if unscaled.Cmp(maxInt64) < 0 && dif < log10MaxInt64 { divide := int64(math.Pow10(dif)) result := unscaled.Int64() / divide mod := unscaled.Int64() % divide if mod != 0 { return result + 1 } return result } // We should only convert back to int64 when getting the result. divisor := intPool.Get().(*big.Int) exp := intPool.Get().(*big.Int) result := intPool.Get().(*big.Int) defer func() { intPool.Put(divisor) intPool.Put(exp) intPool.Put(result) }() // divisor = 10^(dif) // TODO: create loop up table if exp costs too much. divisor.Exp(bigTen, exp.SetInt64(int64(dif)), nil) // reuse exp remainder := exp // result = unscaled / divisor // remainder = unscaled % divisor result.DivMod(unscaled, divisor, remainder) if remainder.Sign() != 0 { return result.Int64() + 1 } return result.Int64() } golang-k8s-apimachinery-0.29.0/pkg/api/resource/scale_int_test.go000066400000000000000000000041431453143165200247410ustar00rootroot00000000000000/* Copyright 2015 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 resource import ( "math" "math/big" "testing" ) func TestScaledValueInternal(t *testing.T) { tests := []struct { unscaled *big.Int scale int newScale int want int64 }{ // remain scale {big.NewInt(1000), 0, 0, 1000}, // scale down {big.NewInt(1000), 0, -3, 1}, {big.NewInt(1000), 3, 0, 1}, {big.NewInt(0), 3, 0, 0}, // always round up {big.NewInt(999), 3, 0, 1}, {big.NewInt(500), 3, 0, 1}, {big.NewInt(499), 3, 0, 1}, {big.NewInt(1), 3, 0, 1}, // large scaled value does not lose precision {big.NewInt(0).Sub(maxInt64, bigOne), 1, 0, (math.MaxInt64-1)/10 + 1}, // large intermediate result. {big.NewInt(1).Exp(big.NewInt(10), big.NewInt(100), nil), 100, 0, 1}, // scale up {big.NewInt(0), 0, 3, 0}, {big.NewInt(1), 0, 3, 1000}, {big.NewInt(1), -3, 0, 1000}, {big.NewInt(1000), -3, 2, 100000000}, {big.NewInt(0).Div(big.NewInt(math.MaxInt64), bigThousand), 0, 3, (math.MaxInt64 / 1000) * 1000}, } for i, tt := range tests { old := (&big.Int{}).Set(tt.unscaled) got := scaledValue(tt.unscaled, tt.scale, tt.newScale) if got != tt.want { t.Errorf("#%d: got = %v, want %v", i, got, tt.want) } if tt.unscaled.Cmp(old) != 0 { t.Errorf("#%d: unscaled = %v, want %v", i, tt.unscaled, old) } } } func BenchmarkScaledValueSmall(b *testing.B) { s := big.NewInt(1000) for i := 0; i < b.N; i++ { scaledValue(s, 3, 0) } } func BenchmarkScaledValueLarge(b *testing.B) { s := big.NewInt(math.MaxInt64) s.Mul(s, big.NewInt(1000)) for i := 0; i < b.N; i++ { scaledValue(s, 10, 0) } } golang-k8s-apimachinery-0.29.0/pkg/api/resource/suffix.go000066400000000000000000000122501453143165200232430ustar00rootroot00000000000000/* 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 resource import ( "strconv" ) type suffix string // suffixer can interpret and construct suffixes. type suffixer interface { interpret(suffix) (base, exponent int32, fmt Format, ok bool) construct(base, exponent int32, fmt Format) (s suffix, ok bool) constructBytes(base, exponent int32, fmt Format) (s []byte, ok bool) } // quantitySuffixer handles suffixes for all three formats that quantity // can handle. var quantitySuffixer = newSuffixer() type bePair struct { base, exponent int32 } type listSuffixer struct { suffixToBE map[suffix]bePair beToSuffix map[bePair]suffix beToSuffixBytes map[bePair][]byte } func (ls *listSuffixer) addSuffix(s suffix, pair bePair) { if ls.suffixToBE == nil { ls.suffixToBE = map[suffix]bePair{} } if ls.beToSuffix == nil { ls.beToSuffix = map[bePair]suffix{} } if ls.beToSuffixBytes == nil { ls.beToSuffixBytes = map[bePair][]byte{} } ls.suffixToBE[s] = pair ls.beToSuffix[pair] = s ls.beToSuffixBytes[pair] = []byte(s) } func (ls *listSuffixer) lookup(s suffix) (base, exponent int32, ok bool) { pair, ok := ls.suffixToBE[s] if !ok { return 0, 0, false } return pair.base, pair.exponent, true } func (ls *listSuffixer) construct(base, exponent int32) (s suffix, ok bool) { s, ok = ls.beToSuffix[bePair{base, exponent}] return } func (ls *listSuffixer) constructBytes(base, exponent int32) (s []byte, ok bool) { s, ok = ls.beToSuffixBytes[bePair{base, exponent}] return } type suffixHandler struct { decSuffixes listSuffixer binSuffixes listSuffixer } type fastLookup struct { *suffixHandler } func (l fastLookup) interpret(s suffix) (base, exponent int32, format Format, ok bool) { switch s { case "": return 10, 0, DecimalSI, true case "n": return 10, -9, DecimalSI, true case "u": return 10, -6, DecimalSI, true case "m": return 10, -3, DecimalSI, true case "k": return 10, 3, DecimalSI, true case "M": return 10, 6, DecimalSI, true case "G": return 10, 9, DecimalSI, true } return l.suffixHandler.interpret(s) } func newSuffixer() suffixer { sh := &suffixHandler{} // IMPORTANT: if you change this section you must change fastLookup sh.binSuffixes.addSuffix("Ki", bePair{2, 10}) sh.binSuffixes.addSuffix("Mi", bePair{2, 20}) sh.binSuffixes.addSuffix("Gi", bePair{2, 30}) sh.binSuffixes.addSuffix("Ti", bePair{2, 40}) sh.binSuffixes.addSuffix("Pi", bePair{2, 50}) sh.binSuffixes.addSuffix("Ei", bePair{2, 60}) // Don't emit an error when trying to produce // a suffix for 2^0. sh.decSuffixes.addSuffix("", bePair{2, 0}) sh.decSuffixes.addSuffix("n", bePair{10, -9}) sh.decSuffixes.addSuffix("u", bePair{10, -6}) sh.decSuffixes.addSuffix("m", bePair{10, -3}) sh.decSuffixes.addSuffix("", bePair{10, 0}) sh.decSuffixes.addSuffix("k", bePair{10, 3}) sh.decSuffixes.addSuffix("M", bePair{10, 6}) sh.decSuffixes.addSuffix("G", bePair{10, 9}) sh.decSuffixes.addSuffix("T", bePair{10, 12}) sh.decSuffixes.addSuffix("P", bePair{10, 15}) sh.decSuffixes.addSuffix("E", bePair{10, 18}) return fastLookup{sh} } func (sh *suffixHandler) construct(base, exponent int32, fmt Format) (s suffix, ok bool) { switch fmt { case DecimalSI: return sh.decSuffixes.construct(base, exponent) case BinarySI: return sh.binSuffixes.construct(base, exponent) case DecimalExponent: if base != 10 { return "", false } if exponent == 0 { return "", true } return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true } return "", false } func (sh *suffixHandler) constructBytes(base, exponent int32, format Format) (s []byte, ok bool) { switch format { case DecimalSI: return sh.decSuffixes.constructBytes(base, exponent) case BinarySI: return sh.binSuffixes.constructBytes(base, exponent) case DecimalExponent: if base != 10 { return nil, false } if exponent == 0 { return nil, true } result := make([]byte, 8) result[0] = 'e' number := strconv.AppendInt(result[1:1], int64(exponent), 10) if &result[1] == &number[0] { return result[:1+len(number)], true } result = append(result[:1], number...) return result, true } return nil, false } func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int32, fmt Format, ok bool) { // Try lookup tables first if b, e, ok := sh.decSuffixes.lookup(suffix); ok { return b, e, DecimalSI, true } if b, e, ok := sh.binSuffixes.lookup(suffix); ok { return b, e, BinarySI, true } if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') { parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64) if err != nil { return 0, 0, DecimalExponent, false } return 10, int32(parsed), DecimalExponent, true } return 0, 0, DecimalExponent, false } golang-k8s-apimachinery-0.29.0/pkg/api/resource/zz_generated.deepcopy.go000066400000000000000000000025511453143165200262320ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package resource // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Quantity) DeepCopyInto(out *Quantity) { *out = in.DeepCopy() return } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *QuantityValue) DeepCopyInto(out *QuantityValue) { *out = *in out.Quantity = in.Quantity.DeepCopy() return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuantityValue. func (in *QuantityValue) DeepCopy() *QuantityValue { if in == nil { return nil } out := new(QuantityValue) in.DeepCopyInto(out) return out } golang-k8s-apimachinery-0.29.0/pkg/api/validation/000077500000000000000000000000001453143165200217135ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/validation/doc.go000066400000000000000000000013071453143165200230100ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package validation contains generic api type validation functions. package validation // import "k8s.io/apimachinery/pkg/api/validation" golang-k8s-apimachinery-0.29.0/pkg/api/validation/generic.go000066400000000000000000000062731453143165200236660ustar00rootroot00000000000000/* 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 validation import ( "strings" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) // IsNegativeErrorMsg is a error message for value must be greater than or equal to 0. const IsNegativeErrorMsg string = `must be greater than or equal to 0` // ValidateNameFunc validates that the provided name is valid for a given resource type. // Not all resources have the same validation rules for names. Prefix is true // if the name will have a value appended to it. If the name is not valid, // this returns a list of descriptions of individual characteristics of the // value that were not valid. Otherwise this returns an empty list or nil. type ValidateNameFunc func(name string, prefix bool) []string // NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. func NameIsDNSSubdomain(name string, prefix bool) []string { if prefix { name = maskTrailingDash(name) } return validation.IsDNS1123Subdomain(name) } // NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. func NameIsDNSLabel(name string, prefix bool) []string { if prefix { name = maskTrailingDash(name) } return validation.IsDNS1123Label(name) } // NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. func NameIsDNS1035Label(name string, prefix bool) []string { if prefix { name = maskTrailingDash(name) } return validation.IsDNS1035Label(name) } // ValidateNamespaceName can be used to check whether the given namespace name is valid. // Prefix indicates this name will be used as part of generation, in which case // trailing dashes are allowed. var ValidateNamespaceName = NameIsDNSLabel // ValidateServiceAccountName can be used to check whether the given service account name is valid. // Prefix indicates this name will be used as part of generation, in which case // trailing dashes are allowed. var ValidateServiceAccountName = NameIsDNSSubdomain // maskTrailingDash replaces the final character of a string with a subdomain safe // value if it is a dash and if the length of this string is greater than 1. Note that // this is used when a value could be appended to the string, see ValidateNameFunc // for more info. func maskTrailingDash(name string) string { if len(name) > 1 && strings.HasSuffix(name, "-") { return name[:len(name)-2] + "a" } return name } // ValidateNonnegativeField validates that given value is not negative. func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if value < 0 { allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg)) } return allErrs } golang-k8s-apimachinery-0.29.0/pkg/api/validation/generic_test.go000066400000000000000000000033561453143165200247240ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import "testing" func TestMaskTrailingDash(t *testing.T) { testCases := []struct { beforeMasking string expectedAfterMasking string description string }{ { beforeMasking: "", expectedAfterMasking: "", description: "empty string", }, { beforeMasking: "-", expectedAfterMasking: "-", description: "only a single dash", }, { beforeMasking: "-foo", expectedAfterMasking: "-foo", description: "has leading dash", }, { beforeMasking: "-foo-", expectedAfterMasking: "-foa", description: "has both leading and trailing dashes", }, { beforeMasking: "b-", expectedAfterMasking: "a", description: "has trailing dash", }, { beforeMasking: "ab", expectedAfterMasking: "ab", description: "has neither leading nor trailing dashes", }, } for _, tc := range testCases { afterMasking := maskTrailingDash(tc.beforeMasking) if afterMasking != tc.expectedAfterMasking { t.Errorf("error in test case: %s. expected: %s, actual: %s", tc.description, tc.expectedAfterMasking, afterMasking) } } } golang-k8s-apimachinery-0.29.0/pkg/api/validation/objectmeta.go000066400000000000000000000300401453143165200243540ustar00rootroot00000000000000/* 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 validation import ( "fmt" "strings" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) // FieldImmutableErrorMsg is a error message for field is immutable. const FieldImmutableErrorMsg string = `field is immutable` const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB // BannedOwners is a black list of object that are not allowed to be owners. var BannedOwners = map[schema.GroupVersionKind]struct{}{ {Group: "", Version: "v1", Kind: "Event"}: {}, } // ValidateAnnotations validates that a set of annotations are correctly defined. func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for k := range annotations { // The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking. for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { allErrs = append(allErrs, field.Invalid(fldPath, k, msg)) } } if err := ValidateAnnotationsSize(annotations); err != nil { allErrs = append(allErrs, field.TooLong(fldPath, "", TotalAnnotationSizeLimitB)) } return allErrs } func ValidateAnnotationsSize(annotations map[string]string) error { var totalSize int64 for k, v := range annotations { totalSize += (int64)(len(k)) + (int64)(len(v)) } if totalSize > (int64)(TotalAnnotationSizeLimitB) { return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB) } return nil } func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind) // gvk.Group is empty for the legacy group. if len(gvk.Version) == 0 { 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")) } if len(ownerReference.Name) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "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")) } if _, ok := BannedOwners[gvk]; ok { allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk))) } return allErrs } // ValidateOwnerReferences validates that a set of owner references are correctly defined. func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} firstControllerName := "" for _, ref := range ownerReferences { allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...) if ref.Controller != nil && *ref.Controller { curControllerName := ref.Kind + "/" + ref.Name if firstControllerName != "" { allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences, fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", firstControllerName, curControllerName))) } else { firstControllerName = curControllerName } } } return allErrs } // ValidateFinalizerName validates finalizer names. func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, msg := range validation.IsQualifiedName(stringValue) { allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg)) } return allErrs } // ValidateNoNewFinalizers validates the new finalizers has no new finalizers compare to old finalizers. func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...)) if len(extra) != 0 { allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List()))) } return allErrs } // ValidateImmutableField validates the new value and the old value are deeply equal. func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if !apiequality.Semantic.DeepEqual(oldVal, newVal) { allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg)) } return allErrs } // ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already // been performed. // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { metadata, err := meta.Accessor(objMeta) if err != nil { var allErrs field.ErrorList allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error())) return allErrs } return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath) } // ValidateObjectMetaAccessor validates an object's metadata on creation. It expects that name generation has already // been performed. // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList if len(meta.GetGenerateName()) != 0 { for _, msg := range nameFn(meta.GetGenerateName(), true) { allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg)) } } // If the generated name validates, but the calculated value does not, it's a problem with generation, and we // report it here. This may confuse users, but indicates a programming bug and still must be validated. // If there are multiple fields out of which one is required then add an or as a separator if len(meta.GetName()) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) } else { for _, msg := range nameFn(meta.GetName(), false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg)) } } if requiresNamespace { if len(meta.GetNamespace()) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) } else { for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg)) } } } else { if len(meta.GetNamespace()) != 0 { allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) } } allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...) allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...) allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...) allErrs = append(allErrs, v1validation.ValidateManagedFields(meta.GetManagedFields(), fldPath.Child("managedFields"))...) return allErrs } // ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} hasFinalizerOrphanDependents := false hasFinalizerDeleteDependents := false for _, finalizer := range finalizers { allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...) if finalizer == metav1.FinalizerOrphanDependents { hasFinalizerOrphanDependents = true } if finalizer == metav1.FinalizerDeleteDependents { hasFinalizerDeleteDependents = true } } if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents { allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents))) } return allErrs } // ValidateObjectMetaUpdate validates an object's metadata when updated. func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { newMetadata, err := meta.Accessor(newMeta) if err != nil { allErrs := field.ErrorList{} allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error())) return allErrs } oldMetadata, err := meta.Accessor(oldMeta) if err != nil { allErrs := field.ErrorList{} allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error())) return allErrs } return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath) } // ValidateObjectMetaAccessorUpdate validates an object's metadata when updated. func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList // Finalizers cannot be added if the object is already being deleted. if oldMeta.GetDeletionTimestamp() != nil { allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...) } // Reject updates that don't specify a resource version if len(newMeta.GetResourceVersion()) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update")) } // Generation shouldn't be decremented if newMeta.GetGeneration() < oldMeta.GetGeneration() { allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented")) } allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp"))...) allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds"))...) allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...) allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) allErrs = append(allErrs, v1validation.ValidateManagedFields(newMeta.GetManagedFields(), fldPath.Child("managedFields"))...) return allErrs } golang-k8s-apimachinery-0.29.0/pkg/api/validation/objectmeta_test.go000066400000000000000000000434321453143165200254240ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import ( "math/rand" "reflect" "strings" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" ) const ( maxLengthErrMsg = "must be no more than" namePartErrMsg = "name part must consist of" nameErrMsg = "a qualified name must consist of" ) // Ensure custom name functions are allowed func TestValidateObjectMetaCustomName(t *testing.T) { errs := ValidateObjectMeta( &metav1.ObjectMeta{Name: "test", GenerateName: "foo"}, false, func(s string, prefix bool) []string { if s == "test" { return nil } return []string{"name-gen"} }, field.NewPath("field")) if len(errs) != 1 { t.Fatalf("unexpected errors: %v", errs) } if !strings.Contains(errs[0].Error(), "name-gen") { t.Errorf("unexpected error message: %v", errs) } } // Ensure namespace names follow dns label format func TestValidateObjectMetaNamespaces(t *testing.T) { errs := ValidateObjectMeta( &metav1.ObjectMeta{Name: "test", Namespace: "foo.bar"}, true, func(s string, prefix bool) []string { return nil }, field.NewPath("field")) if len(errs) != 1 { t.Fatalf("unexpected errors: %v", errs) } if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) { t.Errorf("unexpected error message: %v", errs) } maxLength := 63 letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b := make([]rune, maxLength+1) for i := range b { b[i] = letters[rand.Intn(len(letters))] } errs = ValidateObjectMeta( &metav1.ObjectMeta{Name: "test", Namespace: string(b)}, true, func(s string, prefix bool) []string { return nil }, field.NewPath("field")) if len(errs) != 2 { t.Fatalf("unexpected errors: %v", errs) } if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") { t.Errorf("unexpected error message: %v", errs) } } func TestValidateObjectMetaOwnerReferences(t *testing.T) { trueVar := true falseVar := false testCases := []struct { description string ownerReferences []metav1.OwnerReference expectError bool expectedErrorMessage string }{ { description: "simple success - third party extension.", ownerReferences: []metav1.OwnerReference{ { APIVersion: "customresourceVersion", Kind: "customresourceKind", Name: "name", UID: "1", }, }, expectError: false, expectedErrorMessage: "", }, { description: "simple failures - event shouldn't be set as an owner", ownerReferences: []metav1.OwnerReference{ { APIVersion: "v1", Kind: "Event", Name: "name", UID: "1", }, }, expectError: true, expectedErrorMessage: "is disallowed from being an owner", }, { description: "simple controller ref success - one reference with Controller set", ownerReferences: []metav1.OwnerReference{ { APIVersion: "customresourceVersion", Kind: "customresourceKind", Name: "name", UID: "1", Controller: &falseVar, }, { APIVersion: "customresourceVersion", Kind: "customresourceKind", Name: "name", UID: "2", Controller: &trueVar, }, { APIVersion: "customresourceVersion", Kind: "customresourceKind", Name: "name", UID: "3", Controller: &falseVar, }, { APIVersion: "customresourceVersion", Kind: "customresourceKind", Name: "name", UID: "4", }, }, expectError: false, expectedErrorMessage: "", }, { description: "simple controller ref failure - two references with Controller set", ownerReferences: []metav1.OwnerReference{ { APIVersion: "customresourceVersion", Kind: "customresourceKind1", Name: "name", UID: "1", Controller: &falseVar, }, { APIVersion: "customresourceVersion", Kind: "customresourceKind2", Name: "name", UID: "2", Controller: &trueVar, }, { APIVersion: "customresourceVersion", Kind: "customresourceKind3", Name: "name", UID: "3", Controller: &trueVar, }, { APIVersion: "customresourceVersion", Kind: "customresourceKind4", Name: "name", UID: "4", }, }, expectError: true, expectedErrorMessage: "Only one reference can have Controller set to true. Found \"true\" in references for customresourceKind2/name and customresourceKind3/name", }, } for _, tc := range testCases { errs := ValidateObjectMeta( &metav1.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences}, true, func(s string, prefix bool) []string { return nil }, field.NewPath("field")) if len(errs) != 0 && !tc.expectError { t.Errorf("unexpected error: %v in test case %v", errs, tc.description) } if len(errs) == 0 && tc.expectError { t.Errorf("expect error in test case %v", tc.description) } if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) { t.Errorf("unexpected error message: %v in test case %v", errs, tc.description) } } } func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) { if errs := ValidateObjectMetaUpdate( &metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, field.NewPath("field"), ); len(errs) != 1 { t.Fatalf("unexpected errors: %v", errs) } if errs := ValidateObjectMetaUpdate( &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, &metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, field.NewPath("field"), ); len(errs) != 1 { t.Fatalf("unexpected errors: %v", errs) } if errs := ValidateObjectMetaUpdate( &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))}, field.NewPath("field"), ); len(errs) != 1 { t.Fatalf("unexpected errors: %v", errs) } } func TestValidateFinalizersUpdate(t *testing.T) { testcases := map[string]struct { Old metav1.ObjectMeta New metav1.ObjectMeta ExpectedErr string }{ "invalid adding finalizers": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, ExpectedErr: "y/b", }, "invalid changing finalizers": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}}, ExpectedErr: "x/b", }, "valid removing finalizers": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, ExpectedErr: "", }, "valid adding finalizers for objects not being deleted": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}}, ExpectedErr: "", }, } for name, tc := range testcases { errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) if len(errs) == 0 { if len(tc.ExpectedErr) != 0 { t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) } } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) } } } func TestValidateFinalizersPreventConflictingFinalizers(t *testing.T) { testcases := map[string]struct { ObjectMeta metav1.ObjectMeta ExpectedErr string }{ "conflicting finalizers": { ObjectMeta: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents}}, ExpectedErr: "cannot be both set", }, } for name, tc := range testcases { errs := ValidateObjectMeta(&tc.ObjectMeta, false, NameIsDNSSubdomain, field.NewPath("field")) if len(errs) == 0 { if len(tc.ExpectedErr) != 0 { t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) } } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) } } } func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) { now := metav1.NewTime(time.Unix(1000, 0).UTC()) later := metav1.NewTime(time.Unix(2000, 0).UTC()) gracePeriodShort := int64(30) gracePeriodLong := int64(40) testcases := map[string]struct { Old metav1.ObjectMeta New metav1.ObjectMeta ExpectedNew metav1.ObjectMeta ExpectedErrs []string }{ "valid without deletion fields": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, ExpectedErrs: []string{}, }, "valid with deletion fields": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedErrs: []string{}, }, "invalid set deletionTimestamp": { 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"}, }, "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"}, }, "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"}, }, "invalid set deletionGracePeriodSeconds": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable"}, }, "invalid clear deletionGracePeriodSeconds": { 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"}, }, "invalid change deletionGracePeriodSeconds": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable"}, }, } for k, tc := range testcases { errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) if len(errs) != len(tc.ExpectedErrs) { t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) t.Logf("%s: Got: %#v", k, errs) t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) continue } 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()) } } if !reflect.DeepEqual(tc.New, tc.ExpectedNew) { t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New) } } } func TestObjectMetaGenerationUpdate(t *testing.T) { testcases := map[string]struct { Old metav1.ObjectMeta New metav1.ObjectMeta ExpectedErrs []string }{ "invalid generation change - decremented": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4}, ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"}, }, "valid generation change - incremented by one": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2}, ExpectedErrs: []string{}, }, "valid generation field - not updated": { Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, ExpectedErrs: []string{}, }, } for k, tc := range testcases { errList := []string{} errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) if len(errs) != len(tc.ExpectedErrs) { t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) for _, err := range errs { errList = append(errList, err.Error()) } t.Logf("%s: Got: %#v", k, errList) t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) continue } 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]) } } } } // Ensure trailing slash is allowed in generate name func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) { errs := ValidateObjectMeta( &metav1.ObjectMeta{Name: "test", GenerateName: "foo-"}, false, NameIsDNSSubdomain, field.NewPath("field")) if len(errs) != 0 { t.Fatalf("unexpected errors: %v", errs) } } func TestValidateAnnotations(t *testing.T) { successCases := []map[string]string{ {"simple": "bar"}, {"now-with-dashes": "bar"}, {"1-starts-with-num": "bar"}, {"1234": "bar"}, {"simple/simple": "bar"}, {"now-with-dashes/simple": "bar"}, {"now-with-dashes/now-with-dashes": "bar"}, {"now.with.dots/simple": "bar"}, {"now-with.dashes-and.dots/simple": "bar"}, {"1-num.2-num/3-num": "bar"}, {"1234/5678": "bar"}, {"1.2.3.4/5678": "bar"}, {"UpperCase123": "bar"}, {"a": strings.Repeat("b", TotalAnnotationSizeLimitB-1)}, { "a": strings.Repeat("b", TotalAnnotationSizeLimitB/2-1), "c": strings.Repeat("d", TotalAnnotationSizeLimitB/2-1), }, } for i := range successCases { errs := ValidateAnnotations(successCases[i], field.NewPath("field")) if len(errs) != 0 { t.Errorf("case[%d] expected success, got %#v", i, errs) } } nameErrorCases := []struct { annotations map[string]string expect string }{ {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, } for i := range nameErrorCases { errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field")) if len(errs) != 1 { t.Errorf("case[%d]: expected failure", i) } else { if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) { t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail) } } } totalSizeErrorCases := []map[string]string{ {"a": strings.Repeat("b", TotalAnnotationSizeLimitB)}, { "a": strings.Repeat("b", TotalAnnotationSizeLimitB/2), "c": strings.Repeat("d", TotalAnnotationSizeLimitB/2), }, } for i := range totalSizeErrorCases { errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field")) if len(errs) != 1 { t.Errorf("case[%d] expected failure", i) } } } golang-k8s-apimachinery-0.29.0/pkg/api/validation/path/000077500000000000000000000000001453143165200226475ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/api/validation/path/name.go000066400000000000000000000042571453143165200241260ustar00rootroot00000000000000/* Copyright 2015 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 path import ( "fmt" "strings" ) // NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store) var NameMayNotBe = []string{".", ".."} // NameMayNotContain specifies substrings that cannot be used in names specified as path segments (like the REST API or etcd store) var NameMayNotContain = []string{"/", "%"} // IsValidPathSegmentName validates the name can be safely encoded as a path segment func IsValidPathSegmentName(name string) []string { for _, illegalName := range NameMayNotBe { if name == illegalName { return []string{fmt.Sprintf(`may not be '%s'`, illegalName)} } } var errors []string for _, illegalContent := range NameMayNotContain { if strings.Contains(name, illegalContent) { errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) } } return errors } // IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment // It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid func IsValidPathSegmentPrefix(name string) []string { var errors []string for _, illegalContent := range NameMayNotContain { if strings.Contains(name, illegalContent) { errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) } } return errors } // ValidatePathSegmentName validates the name can be safely encoded as a path segment func ValidatePathSegmentName(name string, prefix bool) []string { if prefix { return IsValidPathSegmentPrefix(name) } return IsValidPathSegmentName(name) } golang-k8s-apimachinery-0.29.0/pkg/api/validation/path/name_test.go000066400000000000000000000074401453143165200251620ustar00rootroot00000000000000/* Copyright 2015 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 path import ( "strings" "testing" ) func TestValidatePathSegmentName(t *testing.T) { testcases := map[string]struct { Name string Prefix bool ExpectedMsg string }{ "empty": { Name: "", Prefix: false, ExpectedMsg: "", }, "empty,prefix": { Name: "", Prefix: true, ExpectedMsg: "", }, "valid": { Name: "foo.bar.baz", Prefix: false, ExpectedMsg: "", }, "valid,prefix": { Name: "foo.bar.baz", Prefix: true, ExpectedMsg: "", }, // Make sure mixed case, non DNS subdomain characters are tolerated "valid complex": { Name: "sha256:ABCDEF012345@ABCDEF012345", Prefix: false, ExpectedMsg: "", }, // Make sure non-ascii characters are tolerated "valid extended charset": { Name: "Iñtërnâtiônàlizætiøn", Prefix: false, ExpectedMsg: "", }, "dot": { Name: ".", Prefix: false, ExpectedMsg: ".", }, "dot leading": { Name: ".test", Prefix: false, ExpectedMsg: "", }, "dot,prefix": { Name: ".", Prefix: true, ExpectedMsg: "", }, "dot dot": { Name: "..", Prefix: false, ExpectedMsg: "..", }, "dot dot leading": { Name: "..test", Prefix: false, ExpectedMsg: "", }, "dot dot,prefix": { Name: "..", Prefix: true, ExpectedMsg: "", }, "slash": { Name: "foo/bar", Prefix: false, ExpectedMsg: "/", }, "slash,prefix": { Name: "foo/bar", Prefix: true, ExpectedMsg: "/", }, "percent": { Name: "foo%bar", Prefix: false, ExpectedMsg: "%", }, "percent,prefix": { Name: "foo%bar", Prefix: true, ExpectedMsg: "%", }, } for k, tc := range testcases { msgs := ValidatePathSegmentName(tc.Name, tc.Prefix) if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { t.Errorf("%s: expected no message, got %v", k, msgs) } if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { t.Errorf("%s: expected error message, got none", k) } if len(tc.ExpectedMsg) > 0 && !strings.Contains(msgs[0], tc.ExpectedMsg) { t.Errorf("%s: expected message containing %q, got %v", k, tc.ExpectedMsg, msgs[0]) } } } func TestValidateWithMultiErrors(t *testing.T) { testcases := map[string]struct { Name string Prefix bool ExpectedMsg []string }{ "slash,percent": { Name: "foo//bar%", Prefix: false, ExpectedMsg: []string{"may not contain '/'", "may not contain '%'"}, }, "slash,percent,prefix": { Name: "foo//bar%", Prefix: true, ExpectedMsg: []string{"may not contain '/'", "may not contain '%'"}, }, } for k, tc := range testcases { msgs := ValidatePathSegmentName(tc.Name, tc.Prefix) if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { t.Errorf("%s: expected no message, got %v", k, msgs) } if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { t.Errorf("%s: expected error message, got none", k) } if len(tc.ExpectedMsg) > 0 { for i := 0; i < len(tc.ExpectedMsg); i++ { if msgs[i] != tc.ExpectedMsg[i] { t.Errorf("%s: expected message containing %q, got %v", k, tc.ExpectedMsg[i], msgs[i]) } } } } } golang-k8s-apimachinery-0.29.0/pkg/apis/000077500000000000000000000000001453143165200177445ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/OWNERS000066400000000000000000000003371453143165200207070ustar00rootroot00000000000000# 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 golang-k8s-apimachinery-0.29.0/pkg/apis/meta/000077500000000000000000000000001453143165200206725ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/fuzzer/000077500000000000000000000000001453143165200222175ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/fuzzer/fuzzer.go000066400000000000000000000234121453143165200240750ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package fuzzer import ( "fmt" "math/rand" "sort" "strconv" "strings" fuzz "github.com/google/gofuzz" apitesting "k8s.io/apimachinery/pkg/api/apitesting" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" ) func genericFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ func(q *resource.Quantity, c fuzz.Continue) { *q = *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) }, func(j *int, c fuzz.Continue) { *j = int(c.Int31()) }, func(j **int, c fuzz.Continue) { if c.RandBool() { i := int(c.Int31()) *j = &i } else { *j = nil } }, func(j *runtime.TypeMeta, c fuzz.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) { // TODO: uncomment when round trip starts from a versioned object if true { //c.RandBool() { *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"}`), ContentType: runtime.ContentTypeJSON, } } else { types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}} t := types[c.Rand.Intn(len(types))] c.Fuzz(t) *j = t } }, func(r *runtime.RawExtension, c fuzz.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) // 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. var codec = apitesting.TestCodec(codecs, metav1.SchemeGroupVersion) // Convert the object to raw bytes bytes, err := runtime.Encode(codec, obj) if err != nil { panic(fmt.Sprintf("Failed to encode object: %v", err)) } // strip trailing newlines which do not survive roundtrips for len(bytes) >= 1 && bytes[len(bytes)-1] == 10 { bytes = bytes[:len(bytes)-1] } // Set the bytes field on the RawExtension r.Raw = bytes }, } } // taken from gofuzz internals for RandString type charRange struct { first, last rune } func (c *charRange) choose(r *rand.Rand) rune { count := int64(c.last - c.first + 1) ch := c.first + rune(r.Int63n(count)) return ch } // randomLabelPart produces a valid random label value or name-part // of a label key. func randomLabelPart(c fuzz.Continue, canBeEmpty bool) string { validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}} validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}, {'.', '.'}, {'-', '-'}, {'_', '_'}} partLen := c.Rand.Intn(64) // len is [0, 63] if !canBeEmpty { partLen = c.Rand.Intn(63) + 1 // len is [1, 63] } runes := make([]rune, partLen) if partLen == 0 { return string(runes) } runes[0] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) for i := range runes[1:] { runes[i+1] = validMiddle[c.Rand.Intn(len(validMiddle))].choose(c.Rand) } runes[len(runes)-1] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) return string(runes) } func randomDNSLabel(c fuzz.Continue) string { validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}} validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'-', '-'}} partLen := c.Rand.Intn(63) + 1 // len is [1, 63] runes := make([]rune, partLen) runes[0] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) for i := range runes[1:] { runes[i+1] = validMiddle[c.Rand.Intn(len(validMiddle))].choose(c.Rand) } runes[len(runes)-1] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) return string(runes) } func randomLabelKey(c fuzz.Continue) string { namePart := randomLabelPart(c, false) prefixPart := "" usePrefix := c.RandBool() if usePrefix { // we can fit, with dots, at most 3 labels in the 253 allotted characters prefixPartsLen := c.Rand.Intn(2) + 1 prefixParts := make([]string, prefixPartsLen) for i := range prefixParts { prefixParts[i] = randomDNSLabel(c) } prefixPart = strings.Join(prefixParts, ".") + "/" } return prefixPart + namePart } func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ func(j *metav1.TypeMeta, c fuzz.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) j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) j.UID = types.UID(c.RandString()) // 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) j.CreationTimestamp = metav1.Unix(int64(sec), int64(nsec)).Rfc3339Copy() if j.DeletionTimestamp != nil { c.Fuzz(&sec) c.Fuzz(&nsec) t := metav1.Unix(int64(sec), int64(nsec)).Rfc3339Copy() j.DeletionTimestamp = &t } if len(j.Labels) == 0 { j.Labels = nil } else { delete(j.Labels, "") } if len(j.Annotations) == 0 { j.Annotations = nil } else { delete(j.Annotations, "") } if len(j.OwnerReferences) == 0 { j.OwnerReferences = nil } if len(j.Finalizers) == 0 { j.Finalizers = nil } }, func(j *metav1.ResourceVersionMatch, c fuzz.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.LabelSelector, c fuzz.Continue) { c.FuzzNoCustom(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 { j.MatchExpressions = make([]metav1.LabelSelectorRequirement, c.Rand.Intn(2)+1) } if j.MatchLabels != nil { fuzzedMatchLabels := make(map[string]string, len(j.MatchLabels)) for i := 0; i < len(j.MatchLabels); i++ { fuzzedMatchLabels[randomLabelKey(c)] = randomLabelPart(c, true) } j.MatchLabels = fuzzedMatchLabels } validOperators := []metav1.LabelSelectorOperator{ metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn, metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist, } 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. // In practice, not sorting doesn't hurt anything... for i := range j.MatchExpressions { req := metav1.LabelSelectorRequirement{} c.Fuzz(&req) req.Key = randomLabelKey(c) req.Operator = validOperators[c.Rand.Intn(len(validOperators))] if req.Operator == metav1.LabelSelectorOpIn || req.Operator == metav1.LabelSelectorOpNotIn { if len(req.Values) == 0 { // we must have some values here, so randomly choose a short length req.Values = make([]string, c.Rand.Intn(2)+1) } for i := range req.Values { req.Values[i] = randomLabelPart(c, true) } sort.Strings(req.Values) } else { req.Values = nil } j.MatchExpressions[i] = req } 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) j.FieldsV1 = nil }, } } func v1beta1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ func(r *metav1beta1.TableOptions, c fuzz.Continue) { c.FuzzNoCustom(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) if len(r.Conditions) == 0 { r.Conditions = nil } n := c.Intn(10) if n > 0 { r.Cells = make([]interface{}, n) } for i := range r.Cells { t := c.Intn(6) switch t { case 0: r.Cells[i] = c.RandString() case 1: r.Cells[i] = c.Int63() case 2: r.Cells[i] = c.RandBool() case 3: x := map[string]interface{}{} for j := c.Intn(10) + 1; j >= 0; j-- { x[c.RandString()] = c.RandString() } r.Cells[i] = x case 4: x := make([]interface{}, c.Intn(10)) for i := range x { x[i] = c.Int63() } r.Cells[i] = x default: r.Cells[i] = nil } } }, } } var Funcs = fuzzer.MergeFuzzerFuncs( genericFuzzerFuncs, v1FuzzerFuncs, v1beta1FuzzerFuncs, ) golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/000077500000000000000000000000001453143165200241145ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/defaults.go000066400000000000000000000026041453143165200262540ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internalversion import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // SetListOptionsDefaults sets defaults on the provided ListOptions if applicable. // // TODO(#115478): once the watch-list fg is always on we register this function in the scheme (via AddTypeDefaultingFunc). // TODO(#115478): when the function is registered in the scheme remove all callers of this method. func SetListOptionsDefaults(obj *ListOptions, isWatchListFeatureEnabled bool) { if !isWatchListFeatureEnabled { return } if obj.SendInitialEvents != nil || len(obj.ResourceVersionMatch) != 0 { return } legacy := obj.ResourceVersion == "" || obj.ResourceVersion == "0" if obj.Watch && legacy { turnOnInitialEvents := true obj.SendInitialEvents = &turnOnInitialEvents obj.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/defaults_test.go000066400000000000000000000130011453143165200273040ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internalversion import ( "testing" "github.com/google/go-cmp/cmp" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" ) func TestSetListOptionsDefaults(t *testing.T) { boolPtrFn := func(b bool) *bool { return &b } scenarios := []struct { name string watchListFeatureEnabled bool targetObj ListOptions expectedObj ListOptions }{ { name: "no-op, RV doesn't match", watchListFeatureEnabled: true, targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "1"}, expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "1"}, }, { 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)}, }, { name: "no-op, ResourceVersionMatch set", watchListFeatureEnabled: true, targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, }, { name: "no-op, Watch=false", watchListFeatureEnabled: true, targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything()}, expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything()}, }, { 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}, }, { 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}, }, { name: "no-op, match on empty RV but watch-list fg is off", targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true}, expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true}, }, { 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)}, }, { 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)}, }, { name: "no-op, match on empty RV but ResourceVersionMatch set", watchListFeatureEnabled: true, targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, }, } for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { SetListOptionsDefaults(&scenario.targetObj, scenario.watchListFeatureEnabled) if !apiequality.Semantic.DeepEqual(&scenario.expectedObj, &scenario.targetObj) { t.Errorf("expected and defaulted objects are different:\n%s", cmp.Diff(&scenario.expectedObj, &scenario.targetObj)) } }) } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/doc.go000066400000000000000000000013531453143165200252120ustar00rootroot00000000000000/* Copyright 2016 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. */ // +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/meta/v1 package internalversion // import "k8s.io/apimachinery/pkg/apis/meta/internalversion" golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/register.go000066400000000000000000000056721453143165200263010ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internalversion import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // GroupName is the group name for this API. const GroupName = "meta.k8s.io" var ( // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = &SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} // Kind takes an unqualified kind and returns a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // addToGroupVersion registers common meta types into schemas. func addToGroupVersion(scheme *runtime.Scheme) error { if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil { return err } // ListOptions is the only options struct which needs conversion (it exposes labels and fields // as selectors for convenience). The other types have only a single representation today. scheme.AddKnownTypes(SchemeGroupVersion, &ListOptions{}, &metav1.GetOptions{}, &metav1.DeleteOptions{}, &metav1.CreateOptions{}, &metav1.UpdateOptions{}, ) scheme.AddKnownTypes(SchemeGroupVersion, &metav1.Table{}, &metav1.TableOptions{}, &metav1beta1.PartialObjectMetadata{}, &metav1beta1.PartialObjectMetadataList{}, ) if err := metav1beta1.AddMetaToScheme(scheme); err != nil { return err } if err := metav1.AddMetaToScheme(scheme); err != nil { return err } // Allow delete options to be decoded across all version in this scheme (we may want to be more clever than this) scheme.AddUnversionedTypes(SchemeGroupVersion, &metav1.DeleteOptions{}, &metav1.CreateOptions{}, &metav1.UpdateOptions{}) metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) if err := metav1beta1.RegisterConversions(scheme); err != nil { return err } return nil } // Unlike other API groups, meta internal knows about all meta external versions, but keeps // the logic for conversion private. func init() { localSchemeBuilder.Register(addToGroupVersion) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/scheme/000077500000000000000000000000001453143165200253605ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/scheme/doc.go000066400000000000000000000012171453143165200264550ustar00rootroot00000000000000/* Copyright 2016 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 scheme // import "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/scheme/register.go000066400000000000000000000025111453143165200275320ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package scheme import ( "k8s.io/apimachinery/pkg/apis/meta/internalversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // Scheme is the registry for any type that adheres to the meta API spec. var scheme = runtime.NewScheme() // Codecs provides access to encoding and decoding for the scheme. var Codecs = serializer.NewCodecFactory(scheme) // ParameterCodec handles versioning of objects that are converted to query parameters. 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)) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/scheme/register_test.go000066400000000000000000000053101453143165200305710ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package scheme import ( "net/url" "reflect" "testing" "github.com/google/go-cmp/cmp" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestListOptions(t *testing.T) { // verify round trip conversion ten := int64(10) in := &metav1.ListOptions{ LabelSelector: "a=1", FieldSelector: "b=1", ResourceVersion: "10", TimeoutSeconds: &ten, Watch: true, } out := &metainternalversion.ListOptions{} if err := scheme.Convert(in, out, nil); err != nil { t.Fatal(err) } actual := &metav1.ListOptions{} if err := scheme.Convert(out, actual, nil); err != nil { t.Fatal(err) } if !reflect.DeepEqual(in, actual) { t.Errorf("unexpected: %s", cmp.Diff(in, actual)) } // verify failing conversion for i, failingObject := range []*metav1.ListOptions{ {LabelSelector: "a!!!"}, {FieldSelector: "a!!!"}, } { out = &metainternalversion.ListOptions{} 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") { 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") { t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err) } actual = &metav1.ListOptions{} if err := ParameterCodec.DecodeParameters(url.Values{"watch": []string{"1"}}, metav1.SchemeGroupVersion, actual); err != nil { t.Fatal(err) } if !actual.Watch { t.Errorf("unexpected watch decode: %#v", actual) } // check ParameterCodec query, err := ParameterCodec.EncodeParameters(in, metav1.SchemeGroupVersion) if err != nil { t.Fatal(err) } actual = &metav1.ListOptions{} if err := ParameterCodec.DecodeParameters(query, metav1.SchemeGroupVersion, actual); err != nil { t.Fatal(err) } if !reflect.DeepEqual(in, actual) { t.Errorf("unexpected: %s", cmp.Diff(in, actual)) } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/scheme/roundtrip_test.go000066400000000000000000000014431453143165200307760ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package scheme import ( "testing" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" "k8s.io/apimachinery/pkg/apis/meta/fuzzer" ) func TestRoundTrip(t *testing.T) { roundtrip.RoundTripTestForScheme(t, scheme, fuzzer.Funcs) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/types.go000066400000000000000000000107551453143165200256170ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internalversion import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ListOptions is the query options to a standard REST list call. type ListOptions struct { metav1.TypeMeta // A selector based on labels LabelSelector labels.Selector // A selector based on fields FieldSelector fields.Selector // If true, watch for changes to this list Watch bool // allowWatchBookmarks requests watch events with type "BOOKMARK". // Servers that do not implement bookmarks may ignore this flag and // bookmarks are sent at the server's discretion. Clients should not // 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 // details. ResourceVersion string // resourceVersionMatch determines how resourceVersion is applied to list calls. // It is highly recommended that resourceVersionMatch be set for list calls where // resourceVersion is set. // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for // details. ResourceVersionMatch metav1.ResourceVersionMatch // Timeout for the list/watch call. TimeoutSeconds *int64 // Limit specifies the maximum number of results to return from the server. The server may // not support this field on all resource types, but if it does and more results remain it // will set the continue field on the returned list object. Limit int64 // Continue is a token returned by the server that lets a client retrieve chunks of results // from the server by specifying limit. The server may reject requests for continuation tokens // it does not recognize and will return a 410 error if the token can no longer be used because // it has expired. Continue string // `sendInitialEvents=true` may be set together with `watch=true`. // In that case, the watch stream will begin with synthetic events to // produce the current state of objects in the collection. Once all such // events have been sent, a synthetic "Bookmark" event will be sent. // The bookmark will report the ResourceVersion (RV) corresponding to the // set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. // Afterwards, the watch stream will proceed as usual, sending watch events // corresponding to changes (subsequent to the RV) to objects watched. // // When `sendInitialEvents` option is set, we require `resourceVersionMatch` // option to also be set. The semantic of the watch request is as following: // - `resourceVersionMatch` = NotOlderThan // is interpreted as "data at least as new as the provided `resourceVersion`" // and the bookmark event is send when the state is synced // to a `resourceVersion` at least as fresh as the one provided by the ListOptions. // If `resourceVersion` is unset, this is interpreted as "consistent read" and the // bookmark event is send when the state is synced at least to the moment // when request started being processed. // - `resourceVersionMatch` set to any other value or unset // Invalid error is returned. // // Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward // compatibility reasons) and to false otherwise. SendInitialEvents *bool } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // List holds a list of objects, which may not be known by the server. type List struct { metav1.TypeMeta // +optional metav1.ListMeta Items []runtime.Object } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/validation/000077500000000000000000000000001453143165200262465ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/validation/validation.go000066400000000000000000000070461453143165200307360ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import ( "fmt" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" ) // ValidateListOptions returns all validation errors found while validating the ListOptions. func ValidateListOptions(options *internalversion.ListOptions, isWatchListFeatureEnabled bool) field.ErrorList { if options.Watch { return validateWatchOptions(options, isWatchListFeatureEnabled) } allErrs := field.ErrorList{} if match := options.ResourceVersionMatch; len(match) > 0 { if len(options.ResourceVersion) == 0 { allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden unless resourceVersion is provided")) } if len(options.Continue) > 0 { allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden when continue is provided")) } if match != metav1.ResourceVersionMatchExact && match != metav1.ResourceVersionMatchNotOlderThan { allErrs = append(allErrs, field.NotSupported(field.NewPath("resourceVersionMatch"), match, []string{string(metav1.ResourceVersionMatchExact), string(metav1.ResourceVersionMatchNotOlderThan), ""})) } if match == metav1.ResourceVersionMatchExact && options.ResourceVersion == "0" { allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\"")) } } if options.SendInitialEvents != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("sendInitialEvents"), "sendInitialEvents is forbidden for list")) } return allErrs } func validateWatchOptions(options *internalversion.ListOptions, isWatchListFeatureEnabled bool) field.ErrorList { allErrs := field.ErrorList{} match := options.ResourceVersionMatch if options.SendInitialEvents != nil { if match != metav1.ResourceVersionMatchNotOlderThan { allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), fmt.Sprintf("sendInitialEvents requires setting resourceVersionMatch to %s", metav1.ResourceVersionMatchNotOlderThan))) } if !isWatchListFeatureEnabled { allErrs = append(allErrs, field.Forbidden(field.NewPath("sendInitialEvents"), "sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled")) } } if len(match) > 0 { if options.SendInitialEvents == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden for watch unless sendInitialEvents is provided")) } if match != metav1.ResourceVersionMatchNotOlderThan { allErrs = append(allErrs, field.NotSupported(field.NewPath("resourceVersionMatch"), match, []string{string(metav1.ResourceVersionMatchNotOlderThan)})) } if len(options.Continue) > 0 { allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden when continue is provided")) } } return allErrs } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/validation/validation_test.go000066400000000000000000000171451453143165200317760ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import ( "testing" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestValidateListOptions(t *testing.T) { boolPtrFn := func(b bool) *bool { return &b } cases := []struct { name string opts internalversion.ListOptions watchListFeatureEnabled bool expectErrors []string }{{ name: "valid-default", opts: internalversion.ListOptions{}, }, { name: "valid-resourceversionmatch-exact", opts: internalversion.ListOptions{ ResourceVersion: "1", ResourceVersionMatch: metav1.ResourceVersionMatchExact, }, }, { name: "invalid-resourceversionmatch-exact", opts: internalversion.ListOptions{ ResourceVersion: "0", ResourceVersionMatch: metav1.ResourceVersionMatchExact, }, expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\""}, }, { name: "valid-resourceversionmatch-notolderthan", opts: internalversion.ListOptions{ ResourceVersion: "0", ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, }, { name: "invalid-resourceversionmatch", opts: internalversion.ListOptions{ ResourceVersion: "0", ResourceVersionMatch: "foo", }, expectErrors: []string{"resourceVersionMatch: Unsupported value: \"foo\": supported values: \"Exact\", \"NotOlderThan\", \"\""}, }, { name: "list-sendInitialEvents-forbidden", opts: internalversion.ListOptions{ SendInitialEvents: boolPtrFn(true), }, expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for list"}, }, { name: "valid-watch-default", opts: internalversion.ListOptions{ Watch: true, }, }, { name: "valid-watch-sendInitialEvents-on", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, AllowWatchBookmarks: true, }, watchListFeatureEnabled: true, }, { name: "valid-watch-sendInitialEvents-off", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(false), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, AllowWatchBookmarks: true, }, watchListFeatureEnabled: true, }, { name: "watch-resourceversionmatch-without-sendInitialEvents-forbidden", opts: internalversion.ListOptions{ Watch: true, ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden for watch unless sendInitialEvents is provided"}, }, { name: "watch-sendInitialEvents-without-resourceversionmatch-forbidden", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(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), ResourceVersionMatch: metav1.ResourceVersionMatchExact, AllowWatchBookmarks: true, }, watchListFeatureEnabled: true, expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"Exact\": supported values: \"NotOlderThan\""}, }, { name: "watch-sendInitialEvents-on-with-empty-resourceversionmatch-forbidden", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(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"}, }, { name: "watch-sendInitialEvents-off-with-empty-resourceversionmatch-forbidden", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(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"}, }, { name: "watch-sendInitialEvents-with-incorrect-resourceversionmatch-forbidden", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: "incorrect", AllowWatchBookmarks: true, }, watchListFeatureEnabled: true, expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"incorrect\": supported values: \"NotOlderThan\""}, }, { // note that validating allowWatchBookmarks would break backward compatibility // because it was possible to request initial events via resourceVersion=0 before this change name: "watch-sendInitialEvents-no-allowWatchBookmark", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, watchListFeatureEnabled: true, }, { name: "watch-sendInitialEvents-no-watchlist-fg-disabled", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, AllowWatchBookmarks: true, }, expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, }, { name: "watch-sendInitialEvents-no-watchlist-fg-disabled", opts: internalversion.ListOptions{ Watch: true, SendInitialEvents: boolPtrFn(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, AllowWatchBookmarks: true, Continue: "123", }, watchListFeatureEnabled: true, expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden when continue is provided"}, }} for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { errs := ValidateListOptions(&tc.opts, tc.watchListFeatureEnabled) if len(tc.expectErrors) > 0 { if len(errs) != len(tc.expectErrors) { t.Errorf("expected %d errors but got %d errors", len(tc.expectErrors), len(errs)) return } for i, expectedErr := range tc.expectErrors { if expectedErr != errs[i].Error() { t.Errorf("expected error '%s' but got '%s'", expectedErr, errs[i].Error()) } } return } if len(errs) != 0 { t.Errorf("expected no errors, but got: %v", errs) } }) } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/zz_generated.conversion.go000066400000000000000000000130371453143165200313140ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by conversion-gen. DO NOT EDIT. package internalversion import ( unsafe "unsafe" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) func init() { localSchemeBuilder.Register(RegisterConversions) } // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { if err := s.AddGeneratedConversionFunc((*List)(nil), (*v1.List)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_internalversion_List_To_v1_List(a.(*List), b.(*v1.List), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*v1.List)(nil), (*List)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_List_To_internalversion_List(a.(*v1.List), b.(*List), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*ListOptions)(nil), (*v1.ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_internalversion_ListOptions_To_v1_ListOptions(a.(*ListOptions), b.(*v1.ListOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*v1.ListOptions)(nil), (*ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_ListOptions_To_internalversion_ListOptions(a.(*v1.ListOptions), b.(*ListOptions), scope) }); err != nil { return err } return nil } func autoConvert_internalversion_List_To_v1_List(in *List, out *v1.List, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.RawExtension, len(*in)) for i := range *in { if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&(*in)[i], &(*out)[i], s); err != nil { return err } } } else { out.Items = nil } return nil } // Convert_internalversion_List_To_v1_List is an autogenerated conversion function. func Convert_internalversion_List_To_v1_List(in *List, out *v1.List, s conversion.Scope) error { return autoConvert_internalversion_List_To_v1_List(in, out, s) } func autoConvert_v1_List_To_internalversion_List(in *v1.List, out *List, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.Object, len(*in)) for i := range *in { if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&(*in)[i], &(*out)[i], s); err != nil { return err } } } else { out.Items = nil } return nil } // Convert_v1_List_To_internalversion_List is an autogenerated conversion function. func Convert_v1_List_To_internalversion_List(in *v1.List, out *List, s conversion.Scope) error { return autoConvert_v1_List_To_internalversion_List(in, out, s) } func autoConvert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error { if err := v1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { return err } if err := v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { return err } out.Watch = in.Watch out.AllowWatchBookmarks = in.AllowWatchBookmarks out.ResourceVersion = in.ResourceVersion out.ResourceVersionMatch = v1.ResourceVersionMatch(in.ResourceVersionMatch) out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) out.Limit = in.Limit out.Continue = in.Continue out.SendInitialEvents = (*bool)(unsafe.Pointer(in.SendInitialEvents)) return nil } // Convert_internalversion_ListOptions_To_v1_ListOptions is an autogenerated conversion function. func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error { return autoConvert_internalversion_ListOptions_To_v1_ListOptions(in, out, s) } func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { if err := v1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { return err } if err := v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil { return err } out.Watch = in.Watch out.AllowWatchBookmarks = in.AllowWatchBookmarks out.ResourceVersion = in.ResourceVersion out.ResourceVersionMatch = v1.ResourceVersionMatch(in.ResourceVersionMatch) out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) out.Limit = in.Limit out.Continue = in.Continue out.SendInitialEvents = (*bool)(unsafe.Pointer(in.SendInitialEvents)) return nil } // Convert_v1_ListOptions_To_internalversion_ListOptions is an autogenerated conversion function. func Convert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { return autoConvert_v1_ListOptions_To_internalversion_ListOptions(in, out, s) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/internalversion/zz_generated.deepcopy.go000066400000000000000000000053341453143165200307400ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package internalversion import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *List) DeepCopyInto(out *List) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.Object, len(*in)) for i := range *in { if (*in)[i] != nil { (*out)[i] = (*in)[i].DeepCopyObject() } } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. func (in *List) DeepCopy() *List { if in == nil { return nil } out := new(List) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *List) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListOptions) DeepCopyInto(out *ListOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.LabelSelector != nil { out.LabelSelector = in.LabelSelector.DeepCopySelector() } if in.FieldSelector != nil { out.FieldSelector = in.FieldSelector.DeepCopySelector() } if in.TimeoutSeconds != nil { in, out := &in.TimeoutSeconds, &out.TimeoutSeconds *out = new(int64) **out = **in } if in.SendInitialEvents != nil { in, out := &in.SendInitialEvents, &out.SendInitialEvents *out = new(bool) **out = **in } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions. func (in *ListOptions) DeepCopy() *ListOptions { if in == nil { return nil } out := new(ListOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ListOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/000077500000000000000000000000001453143165200212205ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/OWNERS000066400000000000000000000003371453143165200221630ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - thockin - smarterclayton - wojtek-t - deads2k - caesarxuchao - liggitt - sttts - luxas - janetkuo - justinsb - ncdc - soltysh - dims golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/controller_ref.go000066400000000000000000000036131453143165200245710ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "k8s.io/apimachinery/pkg/runtime/schema" ) // IsControlledBy checks if the object has a controllerRef set to the given owner func IsControlledBy(obj Object, owner Object) bool { ref := GetControllerOfNoCopy(obj) if ref == nil { return false } return ref.UID == owner.GetUID() } // GetControllerOf returns a pointer to a copy of the controllerRef if controllee has a controller func GetControllerOf(controllee Object) *OwnerReference { ref := GetControllerOfNoCopy(controllee) if ref == nil { return nil } cp := *ref return &cp } // GetControllerOf returns a pointer to the controllerRef if controllee has a controller func GetControllerOfNoCopy(controllee Object) *OwnerReference { refs := controllee.GetOwnerReferences() for i := range refs { if refs[i].Controller != nil && *refs[i].Controller { return &refs[i] } } return nil } // 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, } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/controller_ref_test.go000066400000000000000000000073451453143165200256360ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "testing" "k8s.io/apimachinery/pkg/runtime/schema" ) type metaObj struct { ObjectMeta TypeMeta } func TestNewControllerRef(t *testing.T) { gvk := schema.GroupVersionKind{ Group: "group", Version: "v1", Kind: "Kind", } obj1 := &metaObj{ ObjectMeta: ObjectMeta{ Name: "name", UID: "uid1", }, } controllerRef := NewControllerRef(obj1, gvk) if controllerRef.UID != obj1.UID { t.Errorf("Incorrect UID: %s", controllerRef.UID) } if controllerRef.Controller == nil || *controllerRef.Controller != true { t.Error("Controller must be set to true") } if controllerRef.BlockOwnerDeletion == nil || *controllerRef.BlockOwnerDeletion != true { t.Error("BlockOwnerDeletion must be set to true") } if controllerRef.APIVersion == "" || controllerRef.Kind == "" || controllerRef.Name == "" { t.Errorf("All controllerRef fields must be set: %v", controllerRef) } } func TestGetControllerOf(t *testing.T) { gvk := schema.GroupVersionKind{ Group: "group", Version: "v1", Kind: "Kind", } obj1 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "uid1", Name: "name1", }, } controllerRef := NewControllerRef(obj1, gvk) var falseRef = false obj2 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "uid2", Name: "name1", OwnerReferences: []OwnerReference{ { Name: "owner1", Controller: &falseRef, }, *controllerRef, { Name: "owner2", Controller: &falseRef, }, }, }, } if GetControllerOf(obj1) != nil { t.Error("GetControllerOf must return null") } c := GetControllerOf(obj2) if c.Name != controllerRef.Name || c.UID != controllerRef.UID { t.Errorf("Incorrect result of GetControllerOf: %v", c) } } func BenchmarkGetControllerOf(b *testing.B) { gvk := schema.GroupVersionKind{ Group: "group", Version: "v1", Kind: "Kind", } obj1 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "9d0cdf8a-dedc-11e9-bf91-42010a800167", Name: "my-object", }, } controllerRef := NewControllerRef(obj1, gvk) controllerRef2 := *controllerRef controllerRef2.Controller = nil obj2 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "uid2", Name: "name1", OwnerReferences: []OwnerReference{controllerRef2, controllerRef2, *controllerRef}, }, } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { c := GetControllerOf(obj2) if c.Name != controllerRef.Name || c.UID != controllerRef.UID { b.Errorf("Incorrect result of GetControllerOf: %v", c) } } } func TestIsControlledBy(t *testing.T) { gvk := schema.GroupVersionKind{ Group: "group", Version: "v1", Kind: "Kind", } obj1 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "uid1", }, } obj2 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "uid2", OwnerReferences: []OwnerReference{ *NewControllerRef(obj1, gvk), }, }, } obj3 := &metaObj{ ObjectMeta: ObjectMeta{ UID: "uid3", OwnerReferences: []OwnerReference{ *NewControllerRef(obj2, gvk), }, }, } if !IsControlledBy(obj2, obj1) || !IsControlledBy(obj3, obj2) { t.Error("Incorrect IsControlledBy result: false") } if IsControlledBy(obj3, obj1) { t.Error("Incorrect IsControlledBy result: true") } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/conversion.go000066400000000000000000000215431453143165200237410ustar00rootroot00000000000000/* 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 v1 import ( "fmt" "net/url" "strconv" "strings" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" ) func Convert_Pointer_float64_To_float64(in **float64, out *float64, s conversion.Scope) error { if *in == nil { *out = 0 return nil } *out = float64(**in) return nil } func Convert_float64_To_Pointer_float64(in *float64, out **float64, s conversion.Scope) error { temp := float64(*in) *out = &temp return nil } func Convert_Pointer_int32_To_int32(in **int32, out *int32, s conversion.Scope) error { if *in == nil { *out = 0 return nil } *out = int32(**in) return nil } func Convert_int32_To_Pointer_int32(in *int32, out **int32, s conversion.Scope) error { temp := int32(*in) *out = &temp return nil } func Convert_Pointer_int64_To_int64(in **int64, out *int64, s conversion.Scope) error { if *in == nil { *out = 0 return nil } *out = int64(**in) return nil } func Convert_int64_To_Pointer_int64(in *int64, out **int64, s conversion.Scope) error { temp := int64(*in) *out = &temp return nil } func Convert_Pointer_int64_To_int(in **int64, out *int, s conversion.Scope) error { if *in == nil { *out = 0 return nil } *out = int(**in) return nil } func Convert_int_To_Pointer_int64(in *int, out **int64, s conversion.Scope) error { temp := int64(*in) *out = &temp return nil } func Convert_Pointer_string_To_string(in **string, out *string, s conversion.Scope) error { if *in == nil { *out = "" return nil } *out = **in return nil } func Convert_string_To_Pointer_string(in *string, out **string, s conversion.Scope) error { if in == nil { stringVar := "" *out = &stringVar return nil } *out = in return nil } func Convert_Pointer_bool_To_bool(in **bool, out *bool, s conversion.Scope) error { if *in == nil { *out = false return nil } *out = **in return nil } func Convert_bool_To_Pointer_bool(in *bool, out **bool, s conversion.Scope) error { if in == nil { boolVar := false *out = &boolVar return nil } *out = in return nil } // +k8s:conversion-fn=drop func Convert_v1_TypeMeta_To_v1_TypeMeta(in, out *TypeMeta, s conversion.Scope) error { // These values are explicitly not copied //out.APIVersion = in.APIVersion //out.Kind = in.Kind return nil } // +k8s:conversion-fn=copy-only func Convert_v1_ListMeta_To_v1_ListMeta(in, out *ListMeta, s conversion.Scope) error { *out = *in return nil } // +k8s:conversion-fn=copy-only func Convert_v1_DeleteOptions_To_v1_DeleteOptions(in, out *DeleteOptions, s conversion.Scope) error { *out = *in return nil } // +k8s:conversion-fn=copy-only func Convert_intstr_IntOrString_To_intstr_IntOrString(in, out *intstr.IntOrString, s conversion.Scope) error { *out = *in return nil } func Convert_Pointer_intstr_IntOrString_To_intstr_IntOrString(in **intstr.IntOrString, out *intstr.IntOrString, s conversion.Scope) error { if *in == nil { *out = intstr.IntOrString{} // zero value return nil } *out = **in // copy return nil } func Convert_intstr_IntOrString_To_Pointer_intstr_IntOrString(in *intstr.IntOrString, out **intstr.IntOrString, s conversion.Scope) error { temp := *in // copy *out = &temp return nil } // +k8s:conversion-fn=copy-only func Convert_v1_Time_To_v1_Time(in *Time, out *Time, s conversion.Scope) error { // Cannot deep copy these, because time.Time has unexported fields. *out = *in return nil } // +k8s:conversion-fn=copy-only func Convert_v1_MicroTime_To_v1_MicroTime(in *MicroTime, out *MicroTime, s conversion.Scope) error { // Cannot deep copy these, because time.Time has unexported fields. *out = *in return nil } func Convert_Pointer_v1_Duration_To_v1_Duration(in **Duration, out *Duration, s conversion.Scope) error { if *in == nil { *out = Duration{} // zero duration return nil } *out = **in // copy return nil } func Convert_v1_Duration_To_Pointer_v1_Duration(in *Duration, out **Duration, s conversion.Scope) error { temp := *in //copy *out = &temp return nil } // Convert_Slice_string_To_v1_Time allows converting a URL query parameter value func Convert_Slice_string_To_v1_Time(in *[]string, out *Time, s conversion.Scope) error { str := "" if len(*in) > 0 { str = (*in)[0] } return out.UnmarshalQueryParameter(str) } func Convert_Slice_string_To_Pointer_v1_Time(in *[]string, out **Time, s conversion.Scope) error { if in == nil { return nil } str := "" if len(*in) > 0 { str = (*in)[0] } temp := Time{} if err := temp.UnmarshalQueryParameter(str); err != nil { return err } *out = &temp return nil } func Convert_string_To_labels_Selector(in *string, out *labels.Selector, s conversion.Scope) error { selector, err := labels.Parse(*in) if err != nil { return err } *out = selector return nil } func Convert_string_To_fields_Selector(in *string, out *fields.Selector, s conversion.Scope) error { selector, err := fields.ParseSelector(*in) if err != nil { return err } *out = selector return nil } func Convert_labels_Selector_To_string(in *labels.Selector, out *string, s conversion.Scope) error { if *in == nil { return nil } *out = (*in).String() return nil } func Convert_fields_Selector_To_string(in *fields.Selector, out *string, s conversion.Scope) error { if *in == nil { return nil } *out = (*in).String() return nil } // +k8s:conversion-fn=copy-only func Convert_resource_Quantity_To_resource_Quantity(in *resource.Quantity, out *resource.Quantity, s conversion.Scope) error { *out = *in return nil } func Convert_Map_string_To_string_To_v1_LabelSelector(in *map[string]string, out *LabelSelector, s conversion.Scope) error { if in == nil { return nil } for labelKey, labelValue := range *in { AddLabelToSelector(out, labelKey, labelValue) } return nil } func Convert_v1_LabelSelector_To_Map_string_To_string(in *LabelSelector, out *map[string]string, s conversion.Scope) error { var err error *out, err = LabelSelectorAsMap(in) return err } // Convert_Slice_string_To_Slice_int32 converts multiple query parameters or // a single query parameter with a comma delimited value to multiple int32. // This is used for port forwarding which needs the ports as int32. func Convert_Slice_string_To_Slice_int32(in *[]string, out *[]int32, s conversion.Scope) error { for _, s := range *in { for _, v := range strings.Split(s, ",") { x, err := strconv.ParseUint(v, 10, 16) if err != nil { return fmt.Errorf("cannot convert to []int32: %v", err) } *out = append(*out, int32(x)) } } return nil } // Convert_Slice_string_To_Pointer_v1_DeletionPropagation allows converting a URL query parameter propagationPolicy func Convert_Slice_string_To_Pointer_v1_DeletionPropagation(in *[]string, out **DeletionPropagation, s conversion.Scope) error { var str string if len(*in) > 0 { str = (*in)[0] } else { str = "" } temp := DeletionPropagation(str) *out = &temp return nil } // Convert_Slice_string_To_v1_IncludeObjectPolicy allows converting a URL query parameter value func Convert_Slice_string_To_v1_IncludeObjectPolicy(in *[]string, out *IncludeObjectPolicy, s conversion.Scope) error { if len(*in) > 0 { *out = IncludeObjectPolicy((*in)[0]) } return nil } // Convert_url_Values_To_v1_DeleteOptions allows converting a URL to DeleteOptions. func Convert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptions, s conversion.Scope) error { if err := autoConvert_url_Values_To_v1_DeleteOptions(in, out, s); err != nil { return err } uid := types.UID("") if values, ok := (*in)["uid"]; ok && len(values) > 0 { uid = types.UID(values[0]) } resourceVersion := "" if values, ok := (*in)["resourceVersion"]; ok && len(values) > 0 { resourceVersion = values[0] } if len(uid) > 0 || len(resourceVersion) > 0 { if out.Preconditions == nil { out.Preconditions = &Preconditions{} } if len(uid) > 0 { out.Preconditions.UID = &uid } if len(resourceVersion) > 0 { out.Preconditions.ResourceVersion = &resourceVersion } } return nil } // Convert_Slice_string_To_v1_ResourceVersionMatch allows converting a URL query parameter to ResourceVersionMatch func Convert_Slice_string_To_v1_ResourceVersionMatch(in *[]string, out *ResourceVersionMatch, s conversion.Scope) error { if len(*in) > 0 { *out = ResourceVersionMatch((*in)[0]) } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/conversion_test.go000066400000000000000000000063371453143165200250040ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1_test import ( "testing" "time" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestMapToLabelSelectorRoundTrip(t *testing.T) { // We should be able to round-trip a map-only selector through LabelSelector. inputs := []map[string]string{ nil, {}, {"one": "foo"}, {"one": "foo", "two": "bar"}, } for _, in := range inputs { ls := &v1.LabelSelector{} if err := v1.Convert_Map_string_To_string_To_v1_LabelSelector(&in, ls, nil); err != nil { t.Errorf("Convert_Map_string_To_string_To_v1_LabelSelector(%#v): %v", in, err) continue } out := map[string]string{} if err := v1.Convert_v1_LabelSelector_To_Map_string_To_string(ls, &out, nil); err != nil { t.Errorf("Convert_v1_LabelSelector_To_Map_string_To_string(%#v): %v", ls, err) continue } if !apiequality.Semantic.DeepEqual(in, out) { t.Errorf("map-selector conversion round-trip failed: got %v; want %v", out, in) } } } func TestConvertSliceStringToDeletionPropagation(t *testing.T) { tcs := []struct { Input []string Output v1.DeletionPropagation }{ { Input: nil, Output: "", }, { Input: []string{}, Output: "", }, { Input: []string{"foo"}, Output: "foo", }, { Input: []string{"bar", "foo"}, Output: "bar", }, } for _, tc := range tcs { var dpPtr *v1.DeletionPropagation if err := v1.Convert_Slice_string_To_Pointer_v1_DeletionPropagation(&tc.Input, &dpPtr, nil); err != nil { t.Errorf("Convert_Slice_string_To_Pointer_v1_DeletionPropagation(%#v): %v", tc.Input, err) continue } if !apiequality.Semantic.DeepEqual(dpPtr, &tc.Output) { t.Errorf("slice string to DeletionPropagation conversion failed: got %v; want %v", *dpPtr, tc.Output) } } } func TestConvertSliceStringToPointerTime(t *testing.T) { t1 := v1.Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC) t1String := t1.Format(time.RFC3339) t2 := v1.Date(2000, time.June, 6, 6, 6, 6, 0, time.UTC) t2String := t2.Format(time.RFC3339) tcs := []struct { Input []string Output *v1.Time }{ { Input: []string{}, Output: &v1.Time{}, }, { Input: []string{""}, Output: &v1.Time{}, }, { Input: []string{t1String}, Output: &t1, }, { Input: []string{t1String, t2String}, Output: &t1, }, } for _, tc := range tcs { var timePtr *v1.Time if err := v1.Convert_Slice_string_To_Pointer_v1_Time(&tc.Input, &timePtr, nil); err != nil { t.Errorf("Convert_Slice_string_To_Pointer_v1_Time(%#v): %v", tc.Input, err) continue } if !apiequality.Semantic.DeepEqual(timePtr, tc.Output) { t.Errorf("slice string to *v1.Time conversion failed: got %#v; want %#v", timePtr, tc.Output) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/deepcopy.go000066400000000000000000000021521453143165200233570ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "k8s.io/apimachinery/pkg/runtime" ) func (in *TableRow) DeepCopy() *TableRow { if in == nil { return nil } out := new(TableRow) if in.Cells != nil { out.Cells = make([]interface{}, len(in.Cells)) for i := range in.Cells { out.Cells[i] = runtime.DeepCopyJSONValue(in.Cells[i]) } } if in.Conditions != nil { out.Conditions = make([]TableRowCondition, len(in.Conditions)) for i := range in.Conditions { in.Conditions[i].DeepCopyInto(&out.Conditions[i]) } } in.Object.DeepCopyInto(&out.Object) return out } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/doc.go000066400000000000000000000014051453143165200223140ustar00rootroot00000000000000/* Copyright 2016 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. */ // +k8s:conversion-gen=false // +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta // +groupName=meta.k8s.io package v1 // import "k8s.io/apimachinery/pkg/apis/meta/v1" golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/duration.go000066400000000000000000000036361453143165200234040ustar00rootroot00000000000000/* 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 v1 import ( "encoding/json" "time" ) // Duration is a wrapper around time.Duration which supports correct // marshaling to YAML and JSON. In particular, it marshals into strings, which // can be used as map keys in json. type Duration struct { time.Duration `protobuf:"varint,1,opt,name=duration,casttype=time.Duration"` } // UnmarshalJSON implements the json.Unmarshaller interface. func (d *Duration) UnmarshalJSON(b []byte) error { var str string err := json.Unmarshal(b, &str) if err != nil { return err } pd, err := time.ParseDuration(str) if err != nil { return err } d.Duration = pd return nil } // MarshalJSON implements the json.Marshaler interface. func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(d.Duration.String()) } // ToUnstructured implements the value.UnstructuredConverter interface. func (d Duration) ToUnstructured() interface{} { return d.Duration.String() } // OpenAPISchemaType is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. // // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators func (_ Duration) OpenAPISchemaType() []string { return []string{"string"} } // OpenAPISchemaFormat is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. func (_ Duration) OpenAPISchemaFormat() string { return "" } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/duration_test.go000066400000000000000000000100711453143165200244320ustar00rootroot00000000000000/* 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 v1 import ( "encoding/json" "testing" "time" "sigs.k8s.io/yaml" ) type DurationHolder struct { D Duration `json:"d"` } func TestDurationMarshalYAML(t *testing.T) { cases := []struct { input Duration result string }{ {Duration{5 * time.Second}, "d: 5s\n"}, {Duration{2 * time.Minute}, "d: 2m0s\n"}, {Duration{time.Hour + 3*time.Millisecond}, "d: 1h0m0.003s\n"}, } for _, c := range cases { input := DurationHolder{c.input} result, err := yaml.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input: %q: %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input: %q: expected %q, got %q", input, c.result, string(result)) } } } func TestDurationUnmarshalYAML(t *testing.T) { cases := []struct { input string result Duration }{ {"d: 0s\n", Duration{}}, {"d: 5s\n", Duration{5 * time.Second}}, {"d: 2m0s\n", Duration{2 * time.Minute}}, {"d: 1h0m0.003s\n", Duration{time.Hour + 3*time.Millisecond}}, // Units with zero values can optionally be dropped {"d: 2m\n", Duration{2 * time.Minute}}, {"d: 1h0.003s\n", Duration{time.Hour + 3*time.Millisecond}}, } for _, c := range cases { var result DurationHolder if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input %q: %v", c.input, err) } if result.D != c.result { t.Errorf("Failed to unmarshal input %q: expected %q, got %q", c.input, c.result, result) } } } func TestDurationMarshalJSON(t *testing.T) { cases := []struct { input Duration result string }{ {Duration{5 * time.Second}, `{"d":"5s"}`}, {Duration{2 * time.Minute}, `{"d":"2m0s"}`}, {Duration{time.Hour + 3*time.Millisecond}, `{"d":"1h0m0.003s"}`}, } for _, c := range cases { input := DurationHolder{c.input} result, err := json.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input: %q: %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input: %q: expected %q, got %q", input, c.result, string(result)) } } } func TestDurationUnmarshalJSON(t *testing.T) { cases := []struct { input string result Duration }{ {`{"d":"0s"}`, Duration{}}, {`{"d":"5s"}`, Duration{5 * time.Second}}, {`{"d":"2m0s"}`, Duration{2 * time.Minute}}, {`{"d":"1h0m0.003s"}`, Duration{time.Hour + 3*time.Millisecond}}, // Units with zero values can optionally be dropped {`{"d":"2m"}`, Duration{2 * time.Minute}}, {`{"d":"1h0.003s"}`, Duration{time.Hour + 3*time.Millisecond}}, } for _, c := range cases { var result DurationHolder if err := json.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input %q: %v", c.input, err) } if result.D != c.result { t.Errorf("Failed to unmarshal input %q: expected %q, got %q", c.input, c.result, result) } } } func TestDurationMarshalJSONUnmarshalYAML(t *testing.T) { cases := []struct { input Duration }{ {Duration{}}, {Duration{5 * time.Second}}, {Duration{2 * time.Minute}}, {Duration{time.Hour + 3*time.Millisecond}}, } for i, c := range cases { input := DurationHolder{c.input} jsonMarshalled, err := json.Marshal(&input) if err != nil { t.Errorf("%d-1: Failed to marshal input: '%v': %v", i, input, err) } var result DurationHolder if err := yaml.Unmarshal(jsonMarshalled, &result); err != nil { t.Errorf("%d-2: Failed to unmarshal '%+v': %v", i, string(jsonMarshalled), err) } if input.D != result.D { t.Errorf("%d-4: Failed to marshal input '%#v': got %#v", i, input, result) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/generated.pb.go000066400000000000000000010630731453143165200241170ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto package v1 import ( fmt "fmt" io "io" proto "github.com/gogo/protobuf/proto" github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" runtime "k8s.io/apimachinery/pkg/runtime" math "math" math_bits "math/bits" reflect "reflect" strings "strings" time "time" k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package func (m *APIGroup) Reset() { *m = APIGroup{} } func (*APIGroup) ProtoMessage() {} func (*APIGroup) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{0} } func (m *APIGroup) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *APIGroup) 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 *APIGroup) XXX_Merge(src proto.Message) { xxx_messageInfo_APIGroup.Merge(m, src) } func (m *APIGroup) XXX_Size() int { return m.Size() } func (m *APIGroup) XXX_DiscardUnknown() { xxx_messageInfo_APIGroup.DiscardUnknown(m) } var xxx_messageInfo_APIGroup proto.InternalMessageInfo func (m *APIGroupList) Reset() { *m = APIGroupList{} } func (*APIGroupList) ProtoMessage() {} func (*APIGroupList) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{1} } func (m *APIGroupList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *APIGroupList) 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 *APIGroupList) XXX_Merge(src proto.Message) { xxx_messageInfo_APIGroupList.Merge(m, src) } func (m *APIGroupList) XXX_Size() int { return m.Size() } func (m *APIGroupList) XXX_DiscardUnknown() { xxx_messageInfo_APIGroupList.DiscardUnknown(m) } var xxx_messageInfo_APIGroupList proto.InternalMessageInfo func (m *APIResource) Reset() { *m = APIResource{} } func (*APIResource) ProtoMessage() {} func (*APIResource) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{2} } func (m *APIResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *APIResource) 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 *APIResource) XXX_Merge(src proto.Message) { xxx_messageInfo_APIResource.Merge(m, src) } func (m *APIResource) XXX_Size() int { return m.Size() } func (m *APIResource) XXX_DiscardUnknown() { xxx_messageInfo_APIResource.DiscardUnknown(m) } var xxx_messageInfo_APIResource proto.InternalMessageInfo func (m *APIResourceList) Reset() { *m = APIResourceList{} } func (*APIResourceList) ProtoMessage() {} func (*APIResourceList) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{3} } func (m *APIResourceList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *APIResourceList) 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 *APIResourceList) XXX_Merge(src proto.Message) { xxx_messageInfo_APIResourceList.Merge(m, src) } func (m *APIResourceList) XXX_Size() int { return m.Size() } func (m *APIResourceList) XXX_DiscardUnknown() { xxx_messageInfo_APIResourceList.DiscardUnknown(m) } var xxx_messageInfo_APIResourceList proto.InternalMessageInfo func (m *APIVersions) Reset() { *m = APIVersions{} } func (*APIVersions) ProtoMessage() {} func (*APIVersions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{4} } func (m *APIVersions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *APIVersions) 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 *APIVersions) XXX_Merge(src proto.Message) { xxx_messageInfo_APIVersions.Merge(m, src) } func (m *APIVersions) XXX_Size() int { return m.Size() } func (m *APIVersions) XXX_DiscardUnknown() { xxx_messageInfo_APIVersions.DiscardUnknown(m) } var xxx_messageInfo_APIVersions proto.InternalMessageInfo func (m *ApplyOptions) Reset() { *m = ApplyOptions{} } func (*ApplyOptions) ProtoMessage() {} func (*ApplyOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{5} } func (m *ApplyOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ApplyOptions) 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 *ApplyOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_ApplyOptions.Merge(m, src) } func (m *ApplyOptions) XXX_Size() int { return m.Size() } func (m *ApplyOptions) XXX_DiscardUnknown() { xxx_messageInfo_ApplyOptions.DiscardUnknown(m) } var xxx_messageInfo_ApplyOptions proto.InternalMessageInfo func (m *Condition) Reset() { *m = Condition{} } func (*Condition) ProtoMessage() {} func (*Condition) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{6} } func (m *Condition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Condition) 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 *Condition) XXX_Merge(src proto.Message) { xxx_messageInfo_Condition.Merge(m, src) } func (m *Condition) XXX_Size() int { return m.Size() } func (m *Condition) XXX_DiscardUnknown() { xxx_messageInfo_Condition.DiscardUnknown(m) } var xxx_messageInfo_Condition proto.InternalMessageInfo func (m *CreateOptions) Reset() { *m = CreateOptions{} } func (*CreateOptions) ProtoMessage() {} func (*CreateOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{7} } func (m *CreateOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *CreateOptions) 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 *CreateOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_CreateOptions.Merge(m, src) } func (m *CreateOptions) XXX_Size() int { return m.Size() } func (m *CreateOptions) XXX_DiscardUnknown() { xxx_messageInfo_CreateOptions.DiscardUnknown(m) } var xxx_messageInfo_CreateOptions proto.InternalMessageInfo func (m *DeleteOptions) Reset() { *m = DeleteOptions{} } func (*DeleteOptions) ProtoMessage() {} func (*DeleteOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{8} } func (m *DeleteOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *DeleteOptions) 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 *DeleteOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_DeleteOptions.Merge(m, src) } func (m *DeleteOptions) XXX_Size() int { return m.Size() } func (m *DeleteOptions) XXX_DiscardUnknown() { xxx_messageInfo_DeleteOptions.DiscardUnknown(m) } var xxx_messageInfo_DeleteOptions proto.InternalMessageInfo func (m *Duration) Reset() { *m = Duration{} } func (*Duration) ProtoMessage() {} func (*Duration) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{9} } func (m *Duration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Duration) 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 *Duration) XXX_Merge(src proto.Message) { xxx_messageInfo_Duration.Merge(m, src) } func (m *Duration) XXX_Size() int { return m.Size() } func (m *Duration) XXX_DiscardUnknown() { xxx_messageInfo_Duration.DiscardUnknown(m) } var xxx_messageInfo_Duration proto.InternalMessageInfo func (m *FieldsV1) Reset() { *m = FieldsV1{} } func (*FieldsV1) ProtoMessage() {} func (*FieldsV1) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{10} } func (m *FieldsV1) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *FieldsV1) 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 *FieldsV1) XXX_Merge(src proto.Message) { xxx_messageInfo_FieldsV1.Merge(m, src) } func (m *FieldsV1) XXX_Size() int { return m.Size() } func (m *FieldsV1) XXX_DiscardUnknown() { xxx_messageInfo_FieldsV1.DiscardUnknown(m) } var xxx_messageInfo_FieldsV1 proto.InternalMessageInfo func (m *GetOptions) Reset() { *m = GetOptions{} } func (*GetOptions) ProtoMessage() {} func (*GetOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{11} } func (m *GetOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GetOptions) 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 *GetOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_GetOptions.Merge(m, src) } func (m *GetOptions) XXX_Size() int { return m.Size() } func (m *GetOptions) XXX_DiscardUnknown() { xxx_messageInfo_GetOptions.DiscardUnknown(m) } var xxx_messageInfo_GetOptions proto.InternalMessageInfo func (m *GroupKind) Reset() { *m = GroupKind{} } func (*GroupKind) ProtoMessage() {} func (*GroupKind) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{12} } func (m *GroupKind) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GroupKind) 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 *GroupKind) XXX_Merge(src proto.Message) { xxx_messageInfo_GroupKind.Merge(m, src) } func (m *GroupKind) XXX_Size() int { return m.Size() } func (m *GroupKind) XXX_DiscardUnknown() { xxx_messageInfo_GroupKind.DiscardUnknown(m) } var xxx_messageInfo_GroupKind proto.InternalMessageInfo func (m *GroupResource) Reset() { *m = GroupResource{} } func (*GroupResource) ProtoMessage() {} func (*GroupResource) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{13} } func (m *GroupResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GroupResource) 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 *GroupResource) XXX_Merge(src proto.Message) { xxx_messageInfo_GroupResource.Merge(m, src) } func (m *GroupResource) XXX_Size() int { return m.Size() } func (m *GroupResource) XXX_DiscardUnknown() { xxx_messageInfo_GroupResource.DiscardUnknown(m) } var xxx_messageInfo_GroupResource proto.InternalMessageInfo func (m *GroupVersion) Reset() { *m = GroupVersion{} } func (*GroupVersion) ProtoMessage() {} func (*GroupVersion) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{14} } func (m *GroupVersion) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GroupVersion) 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 *GroupVersion) XXX_Merge(src proto.Message) { xxx_messageInfo_GroupVersion.Merge(m, src) } func (m *GroupVersion) XXX_Size() int { return m.Size() } func (m *GroupVersion) XXX_DiscardUnknown() { xxx_messageInfo_GroupVersion.DiscardUnknown(m) } var xxx_messageInfo_GroupVersion proto.InternalMessageInfo func (m *GroupVersionForDiscovery) Reset() { *m = GroupVersionForDiscovery{} } func (*GroupVersionForDiscovery) ProtoMessage() {} func (*GroupVersionForDiscovery) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{15} } func (m *GroupVersionForDiscovery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GroupVersionForDiscovery) 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 *GroupVersionForDiscovery) XXX_Merge(src proto.Message) { xxx_messageInfo_GroupVersionForDiscovery.Merge(m, src) } func (m *GroupVersionForDiscovery) XXX_Size() int { return m.Size() } func (m *GroupVersionForDiscovery) XXX_DiscardUnknown() { xxx_messageInfo_GroupVersionForDiscovery.DiscardUnknown(m) } var xxx_messageInfo_GroupVersionForDiscovery proto.InternalMessageInfo func (m *GroupVersionKind) Reset() { *m = GroupVersionKind{} } func (*GroupVersionKind) ProtoMessage() {} func (*GroupVersionKind) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{16} } func (m *GroupVersionKind) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GroupVersionKind) 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 *GroupVersionKind) XXX_Merge(src proto.Message) { xxx_messageInfo_GroupVersionKind.Merge(m, src) } func (m *GroupVersionKind) XXX_Size() int { return m.Size() } func (m *GroupVersionKind) XXX_DiscardUnknown() { xxx_messageInfo_GroupVersionKind.DiscardUnknown(m) } var xxx_messageInfo_GroupVersionKind proto.InternalMessageInfo func (m *GroupVersionResource) Reset() { *m = GroupVersionResource{} } func (*GroupVersionResource) ProtoMessage() {} func (*GroupVersionResource) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{17} } func (m *GroupVersionResource) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *GroupVersionResource) 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 *GroupVersionResource) XXX_Merge(src proto.Message) { xxx_messageInfo_GroupVersionResource.Merge(m, src) } func (m *GroupVersionResource) XXX_Size() int { return m.Size() } func (m *GroupVersionResource) XXX_DiscardUnknown() { xxx_messageInfo_GroupVersionResource.DiscardUnknown(m) } var xxx_messageInfo_GroupVersionResource proto.InternalMessageInfo func (m *LabelSelector) Reset() { *m = LabelSelector{} } func (*LabelSelector) ProtoMessage() {} func (*LabelSelector) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{18} } func (m *LabelSelector) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *LabelSelector) 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 *LabelSelector) XXX_Merge(src proto.Message) { xxx_messageInfo_LabelSelector.Merge(m, src) } func (m *LabelSelector) XXX_Size() int { return m.Size() } func (m *LabelSelector) XXX_DiscardUnknown() { xxx_messageInfo_LabelSelector.DiscardUnknown(m) } var xxx_messageInfo_LabelSelector proto.InternalMessageInfo func (m *LabelSelectorRequirement) Reset() { *m = LabelSelectorRequirement{} } func (*LabelSelectorRequirement) ProtoMessage() {} func (*LabelSelectorRequirement) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{19} } func (m *LabelSelectorRequirement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *LabelSelectorRequirement) 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 *LabelSelectorRequirement) XXX_Merge(src proto.Message) { xxx_messageInfo_LabelSelectorRequirement.Merge(m, src) } func (m *LabelSelectorRequirement) XXX_Size() int { return m.Size() } func (m *LabelSelectorRequirement) XXX_DiscardUnknown() { xxx_messageInfo_LabelSelectorRequirement.DiscardUnknown(m) } var xxx_messageInfo_LabelSelectorRequirement proto.InternalMessageInfo func (m *List) Reset() { *m = List{} } func (*List) ProtoMessage() {} func (*List) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{20} } func (m *List) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *List) 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 *List) XXX_Merge(src proto.Message) { xxx_messageInfo_List.Merge(m, src) } func (m *List) XXX_Size() int { return m.Size() } func (m *List) XXX_DiscardUnknown() { xxx_messageInfo_List.DiscardUnknown(m) } var xxx_messageInfo_List proto.InternalMessageInfo func (m *ListMeta) Reset() { *m = ListMeta{} } func (*ListMeta) ProtoMessage() {} func (*ListMeta) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{21} } func (m *ListMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ListMeta) 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 *ListMeta) XXX_Merge(src proto.Message) { xxx_messageInfo_ListMeta.Merge(m, src) } func (m *ListMeta) XXX_Size() int { return m.Size() } func (m *ListMeta) XXX_DiscardUnknown() { xxx_messageInfo_ListMeta.DiscardUnknown(m) } var xxx_messageInfo_ListMeta proto.InternalMessageInfo func (m *ListOptions) Reset() { *m = ListOptions{} } func (*ListOptions) ProtoMessage() {} func (*ListOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{22} } func (m *ListOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ListOptions) 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 *ListOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_ListOptions.Merge(m, src) } func (m *ListOptions) XXX_Size() int { return m.Size() } func (m *ListOptions) XXX_DiscardUnknown() { xxx_messageInfo_ListOptions.DiscardUnknown(m) } var xxx_messageInfo_ListOptions proto.InternalMessageInfo func (m *ManagedFieldsEntry) Reset() { *m = ManagedFieldsEntry{} } func (*ManagedFieldsEntry) ProtoMessage() {} func (*ManagedFieldsEntry) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{23} } func (m *ManagedFieldsEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ManagedFieldsEntry) 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 *ManagedFieldsEntry) XXX_Merge(src proto.Message) { xxx_messageInfo_ManagedFieldsEntry.Merge(m, src) } func (m *ManagedFieldsEntry) XXX_Size() int { return m.Size() } func (m *ManagedFieldsEntry) XXX_DiscardUnknown() { xxx_messageInfo_ManagedFieldsEntry.DiscardUnknown(m) } var xxx_messageInfo_ManagedFieldsEntry proto.InternalMessageInfo func (m *MicroTime) Reset() { *m = MicroTime{} } func (*MicroTime) ProtoMessage() {} func (*MicroTime) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{24} } func (m *MicroTime) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_MicroTime.Unmarshal(m, b) } func (m *MicroTime) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_MicroTime.Marshal(b, m, deterministic) } func (m *MicroTime) XXX_Merge(src proto.Message) { xxx_messageInfo_MicroTime.Merge(m, src) } func (m *MicroTime) XXX_Size() int { return xxx_messageInfo_MicroTime.Size(m) } func (m *MicroTime) XXX_DiscardUnknown() { xxx_messageInfo_MicroTime.DiscardUnknown(m) } var xxx_messageInfo_MicroTime proto.InternalMessageInfo func (m *ObjectMeta) Reset() { *m = ObjectMeta{} } func (*ObjectMeta) ProtoMessage() {} func (*ObjectMeta) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{25} } func (m *ObjectMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ObjectMeta) 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 *ObjectMeta) XXX_Merge(src proto.Message) { xxx_messageInfo_ObjectMeta.Merge(m, src) } func (m *ObjectMeta) XXX_Size() int { return m.Size() } func (m *ObjectMeta) XXX_DiscardUnknown() { xxx_messageInfo_ObjectMeta.DiscardUnknown(m) } var xxx_messageInfo_ObjectMeta proto.InternalMessageInfo func (m *OwnerReference) Reset() { *m = OwnerReference{} } func (*OwnerReference) ProtoMessage() {} func (*OwnerReference) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{26} } func (m *OwnerReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *OwnerReference) 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 *OwnerReference) XXX_Merge(src proto.Message) { xxx_messageInfo_OwnerReference.Merge(m, src) } func (m *OwnerReference) XXX_Size() int { return m.Size() } func (m *OwnerReference) XXX_DiscardUnknown() { xxx_messageInfo_OwnerReference.DiscardUnknown(m) } var xxx_messageInfo_OwnerReference proto.InternalMessageInfo func (m *PartialObjectMetadata) Reset() { *m = PartialObjectMetadata{} } func (*PartialObjectMetadata) ProtoMessage() {} func (*PartialObjectMetadata) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{27} } func (m *PartialObjectMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PartialObjectMetadata) 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 *PartialObjectMetadata) XXX_Merge(src proto.Message) { xxx_messageInfo_PartialObjectMetadata.Merge(m, src) } func (m *PartialObjectMetadata) XXX_Size() int { return m.Size() } func (m *PartialObjectMetadata) XXX_DiscardUnknown() { xxx_messageInfo_PartialObjectMetadata.DiscardUnknown(m) } var xxx_messageInfo_PartialObjectMetadata proto.InternalMessageInfo func (m *PartialObjectMetadataList) Reset() { *m = PartialObjectMetadataList{} } func (*PartialObjectMetadataList) ProtoMessage() {} func (*PartialObjectMetadataList) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{28} } func (m *PartialObjectMetadataList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PartialObjectMetadataList) 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 *PartialObjectMetadataList) XXX_Merge(src proto.Message) { xxx_messageInfo_PartialObjectMetadataList.Merge(m, src) } func (m *PartialObjectMetadataList) XXX_Size() int { return m.Size() } func (m *PartialObjectMetadataList) XXX_DiscardUnknown() { xxx_messageInfo_PartialObjectMetadataList.DiscardUnknown(m) } var xxx_messageInfo_PartialObjectMetadataList proto.InternalMessageInfo func (m *Patch) Reset() { *m = Patch{} } func (*Patch) ProtoMessage() {} func (*Patch) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{29} } func (m *Patch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Patch) 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 *Patch) XXX_Merge(src proto.Message) { xxx_messageInfo_Patch.Merge(m, src) } func (m *Patch) XXX_Size() int { return m.Size() } func (m *Patch) XXX_DiscardUnknown() { xxx_messageInfo_Patch.DiscardUnknown(m) } var xxx_messageInfo_Patch proto.InternalMessageInfo func (m *PatchOptions) Reset() { *m = PatchOptions{} } func (*PatchOptions) ProtoMessage() {} func (*PatchOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{30} } func (m *PatchOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PatchOptions) 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 *PatchOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_PatchOptions.Merge(m, src) } func (m *PatchOptions) XXX_Size() int { return m.Size() } func (m *PatchOptions) XXX_DiscardUnknown() { xxx_messageInfo_PatchOptions.DiscardUnknown(m) } var xxx_messageInfo_PatchOptions proto.InternalMessageInfo func (m *Preconditions) Reset() { *m = Preconditions{} } func (*Preconditions) ProtoMessage() {} func (*Preconditions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{31} } func (m *Preconditions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Preconditions) 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 *Preconditions) XXX_Merge(src proto.Message) { xxx_messageInfo_Preconditions.Merge(m, src) } func (m *Preconditions) XXX_Size() int { return m.Size() } func (m *Preconditions) XXX_DiscardUnknown() { xxx_messageInfo_Preconditions.DiscardUnknown(m) } var xxx_messageInfo_Preconditions proto.InternalMessageInfo func (m *RootPaths) Reset() { *m = RootPaths{} } func (*RootPaths) ProtoMessage() {} func (*RootPaths) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{32} } func (m *RootPaths) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *RootPaths) 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 *RootPaths) XXX_Merge(src proto.Message) { xxx_messageInfo_RootPaths.Merge(m, src) } func (m *RootPaths) XXX_Size() int { return m.Size() } func (m *RootPaths) XXX_DiscardUnknown() { xxx_messageInfo_RootPaths.DiscardUnknown(m) } var xxx_messageInfo_RootPaths proto.InternalMessageInfo func (m *ServerAddressByClientCIDR) Reset() { *m = ServerAddressByClientCIDR{} } func (*ServerAddressByClientCIDR) ProtoMessage() {} func (*ServerAddressByClientCIDR) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{33} } func (m *ServerAddressByClientCIDR) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ServerAddressByClientCIDR) 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 *ServerAddressByClientCIDR) XXX_Merge(src proto.Message) { xxx_messageInfo_ServerAddressByClientCIDR.Merge(m, src) } func (m *ServerAddressByClientCIDR) XXX_Size() int { return m.Size() } func (m *ServerAddressByClientCIDR) XXX_DiscardUnknown() { xxx_messageInfo_ServerAddressByClientCIDR.DiscardUnknown(m) } var xxx_messageInfo_ServerAddressByClientCIDR proto.InternalMessageInfo func (m *Status) Reset() { *m = Status{} } func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{34} } func (m *Status) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Status) 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 *Status) XXX_Merge(src proto.Message) { xxx_messageInfo_Status.Merge(m, src) } func (m *Status) XXX_Size() int { return m.Size() } func (m *Status) XXX_DiscardUnknown() { xxx_messageInfo_Status.DiscardUnknown(m) } var xxx_messageInfo_Status proto.InternalMessageInfo func (m *StatusCause) Reset() { *m = StatusCause{} } func (*StatusCause) ProtoMessage() {} func (*StatusCause) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{35} } func (m *StatusCause) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *StatusCause) 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 *StatusCause) XXX_Merge(src proto.Message) { xxx_messageInfo_StatusCause.Merge(m, src) } func (m *StatusCause) XXX_Size() int { return m.Size() } func (m *StatusCause) XXX_DiscardUnknown() { xxx_messageInfo_StatusCause.DiscardUnknown(m) } var xxx_messageInfo_StatusCause proto.InternalMessageInfo func (m *StatusDetails) Reset() { *m = StatusDetails{} } func (*StatusDetails) ProtoMessage() {} func (*StatusDetails) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{36} } func (m *StatusDetails) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *StatusDetails) 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 *StatusDetails) XXX_Merge(src proto.Message) { xxx_messageInfo_StatusDetails.Merge(m, src) } func (m *StatusDetails) XXX_Size() int { return m.Size() } func (m *StatusDetails) XXX_DiscardUnknown() { xxx_messageInfo_StatusDetails.DiscardUnknown(m) } var xxx_messageInfo_StatusDetails proto.InternalMessageInfo func (m *TableOptions) Reset() { *m = TableOptions{} } func (*TableOptions) ProtoMessage() {} func (*TableOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{37} } func (m *TableOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *TableOptions) 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 *TableOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_TableOptions.Merge(m, src) } func (m *TableOptions) XXX_Size() int { return m.Size() } func (m *TableOptions) XXX_DiscardUnknown() { xxx_messageInfo_TableOptions.DiscardUnknown(m) } var xxx_messageInfo_TableOptions proto.InternalMessageInfo func (m *Time) Reset() { *m = Time{} } func (*Time) ProtoMessage() {} func (*Time) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{38} } func (m *Time) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Time.Unmarshal(m, b) } func (m *Time) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Time.Marshal(b, m, deterministic) } func (m *Time) XXX_Merge(src proto.Message) { xxx_messageInfo_Time.Merge(m, src) } func (m *Time) XXX_Size() int { return xxx_messageInfo_Time.Size(m) } func (m *Time) XXX_DiscardUnknown() { xxx_messageInfo_Time.DiscardUnknown(m) } var xxx_messageInfo_Time proto.InternalMessageInfo func (m *Timestamp) Reset() { *m = Timestamp{} } func (*Timestamp) ProtoMessage() {} func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{39} } func (m *Timestamp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Timestamp) 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 *Timestamp) XXX_Merge(src proto.Message) { xxx_messageInfo_Timestamp.Merge(m, src) } func (m *Timestamp) XXX_Size() int { return m.Size() } func (m *Timestamp) XXX_DiscardUnknown() { xxx_messageInfo_Timestamp.DiscardUnknown(m) } var xxx_messageInfo_Timestamp proto.InternalMessageInfo func (m *TypeMeta) Reset() { *m = TypeMeta{} } func (*TypeMeta) ProtoMessage() {} func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{40} } func (m *TypeMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *TypeMeta) 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 *TypeMeta) XXX_Merge(src proto.Message) { xxx_messageInfo_TypeMeta.Merge(m, src) } func (m *TypeMeta) XXX_Size() int { return m.Size() } func (m *TypeMeta) XXX_DiscardUnknown() { xxx_messageInfo_TypeMeta.DiscardUnknown(m) } var xxx_messageInfo_TypeMeta proto.InternalMessageInfo func (m *UpdateOptions) Reset() { *m = UpdateOptions{} } func (*UpdateOptions) ProtoMessage() {} func (*UpdateOptions) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{41} } func (m *UpdateOptions) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *UpdateOptions) 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 *UpdateOptions) XXX_Merge(src proto.Message) { xxx_messageInfo_UpdateOptions.Merge(m, src) } func (m *UpdateOptions) XXX_Size() int { return m.Size() } func (m *UpdateOptions) XXX_DiscardUnknown() { xxx_messageInfo_UpdateOptions.DiscardUnknown(m) } var xxx_messageInfo_UpdateOptions proto.InternalMessageInfo func (m *Verbs) Reset() { *m = Verbs{} } func (*Verbs) ProtoMessage() {} func (*Verbs) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{42} } func (m *Verbs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Verbs) 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 *Verbs) XXX_Merge(src proto.Message) { xxx_messageInfo_Verbs.Merge(m, src) } func (m *Verbs) XXX_Size() int { return m.Size() } func (m *Verbs) XXX_DiscardUnknown() { xxx_messageInfo_Verbs.DiscardUnknown(m) } var xxx_messageInfo_Verbs proto.InternalMessageInfo func (m *WatchEvent) Reset() { *m = WatchEvent{} } func (*WatchEvent) ProtoMessage() {} func (*WatchEvent) Descriptor() ([]byte, []int) { return fileDescriptor_cf52fa777ced5367, []int{43} } func (m *WatchEvent) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *WatchEvent) 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 *WatchEvent) XXX_Merge(src proto.Message) { xxx_messageInfo_WatchEvent.Merge(m, src) } func (m *WatchEvent) XXX_Size() int { return m.Size() } func (m *WatchEvent) XXX_DiscardUnknown() { xxx_messageInfo_WatchEvent.DiscardUnknown(m) } var xxx_messageInfo_WatchEvent proto.InternalMessageInfo func init() { proto.RegisterType((*APIGroup)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIGroup") proto.RegisterType((*APIGroupList)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIGroupList") proto.RegisterType((*APIResource)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIResource") proto.RegisterType((*APIResourceList)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIResourceList") proto.RegisterType((*APIVersions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.APIVersions") proto.RegisterType((*ApplyOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ApplyOptions") proto.RegisterType((*Condition)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Condition") proto.RegisterType((*CreateOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.CreateOptions") proto.RegisterType((*DeleteOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.DeleteOptions") proto.RegisterType((*Duration)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Duration") proto.RegisterType((*FieldsV1)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.FieldsV1") proto.RegisterType((*GetOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GetOptions") proto.RegisterType((*GroupKind)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupKind") proto.RegisterType((*GroupResource)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupResource") proto.RegisterType((*GroupVersion)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersion") proto.RegisterType((*GroupVersionForDiscovery)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery") proto.RegisterType((*GroupVersionKind)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind") proto.RegisterType((*GroupVersionResource)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource") proto.RegisterType((*LabelSelector)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector") proto.RegisterMapType((map[string]string)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector.MatchLabelsEntry") proto.RegisterType((*LabelSelectorRequirement)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement") proto.RegisterType((*List)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.List") proto.RegisterType((*ListMeta)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta") proto.RegisterType((*ListOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ListOptions") proto.RegisterType((*ManagedFieldsEntry)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry") proto.RegisterType((*MicroTime)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.MicroTime") proto.RegisterType((*ObjectMeta)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta") proto.RegisterMapType((map[string]string)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta.AnnotationsEntry") proto.RegisterMapType((map[string]string)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta.LabelsEntry") proto.RegisterType((*OwnerReference)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.OwnerReference") proto.RegisterType((*PartialObjectMetadata)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.PartialObjectMetadata") proto.RegisterType((*PartialObjectMetadataList)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.PartialObjectMetadataList") proto.RegisterType((*Patch)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Patch") proto.RegisterType((*PatchOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.PatchOptions") proto.RegisterType((*Preconditions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Preconditions") proto.RegisterType((*RootPaths)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.RootPaths") proto.RegisterType((*ServerAddressByClientCIDR)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR") proto.RegisterType((*Status)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Status") proto.RegisterType((*StatusCause)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.StatusCause") proto.RegisterType((*StatusDetails)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.StatusDetails") proto.RegisterType((*TableOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.TableOptions") proto.RegisterType((*Time)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Time") proto.RegisterType((*Timestamp)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Timestamp") proto.RegisterType((*TypeMeta)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.TypeMeta") proto.RegisterType((*UpdateOptions)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.UpdateOptions") proto.RegisterType((*Verbs)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.Verbs") proto.RegisterType((*WatchEvent)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1.WatchEvent") } func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto", fileDescriptor_cf52fa777ced5367) } var fileDescriptor_cf52fa777ced5367 = []byte{ // 2867 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x1a, 0x4b, 0x6f, 0x24, 0x47, 0xd9, 0x3d, 0x0f, 0x7b, 0xe6, 0x9b, 0x19, 0x3f, 0x6a, 0xbd, 0x30, 0x6b, 0x84, 0xc7, 0xe9, 0x44, 0xd1, 0x06, 0x92, 0x71, 0x76, 0x09, 0xd1, 0x66, 0x43, 0x02, 0x1e, 0xcf, 0x7a, 0xe3, 0x64, 0x1d, 0x5b, 0xe5, 0xdd, 0x05, 0x42, 0x84, 0xd2, 0x9e, 0x2e, 0x8f, 0x1b, 0xf7, 0x74, 0x4f, 0xaa, 0x7a, 0xbc, 0x19, 0x38, 0x90, 0x03, 0x08, 0x90, 0x50, 0x14, 0x6e, 0x9c, 0x50, 0x22, 0xf8, 0x01, 0x88, 0x13, 0x77, 0x90, 0xc8, 0x31, 0x88, 0x4b, 0x24, 0xd0, 0x28, 0x31, 0x07, 0x8e, 0x88, 0xab, 0x85, 0x04, 0xaa, 0x47, 0x77, 0x57, 0xcf, 0x63, 0xdd, 0x93, 0x5d, 0x22, 0x6e, 0xd3, 0xdf, 0xbb, 0xaa, 0xbe, 0xfa, 0xea, 0x7b, 0x0c, 0xec, 0x1c, 0x5f, 0x63, 0x75, 0xc7, 0x5f, 0x3f, 0xee, 0x1d, 0x10, 0xea, 0x91, 0x80, 0xb0, 0xf5, 0x13, 0xe2, 0xd9, 0x3e, 0x5d, 0x57, 0x08, 0xab, 0xeb, 0x74, 0xac, 0xd6, 0x91, 0xe3, 0x11, 0xda, 0x5f, 0xef, 0x1e, 0xb7, 0x39, 0x80, 0xad, 0x77, 0x48, 0x60, 0xad, 0x9f, 0x5c, 0x59, 0x6f, 0x13, 0x8f, 0x50, 0x2b, 0x20, 0x76, 0xbd, 0x4b, 0xfd, 0xc0, 0x47, 0x8f, 0x49, 0xae, 0xba, 0xce, 0x55, 0xef, 0x1e, 0xb7, 0x39, 0x80, 0xd5, 0x39, 0x57, 0xfd, 0xe4, 0xca, 0xca, 0x53, 0x6d, 0x27, 0x38, 0xea, 0x1d, 0xd4, 0x5b, 0x7e, 0x67, 0xbd, 0xed, 0xb7, 0xfd, 0x75, 0xc1, 0x7c, 0xd0, 0x3b, 0x14, 0x5f, 0xe2, 0x43, 0xfc, 0x92, 0x42, 0x57, 0x26, 0x9a, 0x42, 0x7b, 0x5e, 0xe0, 0x74, 0xc8, 0xb0, 0x15, 0x2b, 0xcf, 0x9e, 0xc7, 0xc0, 0x5a, 0x47, 0xa4, 0x63, 0x0d, 0xf3, 0x99, 0x7f, 0xca, 0x42, 0x61, 0x63, 0x6f, 0xfb, 0x26, 0xf5, 0x7b, 0x5d, 0xb4, 0x06, 0x39, 0xcf, 0xea, 0x90, 0xaa, 0xb1, 0x66, 0x5c, 0x2e, 0x36, 0xca, 0x1f, 0x0c, 0x6a, 0x33, 0xa7, 0x83, 0x5a, 0xee, 0x55, 0xab, 0x43, 0xb0, 0xc0, 0x20, 0x17, 0x0a, 0x27, 0x84, 0x32, 0xc7, 0xf7, 0x58, 0x35, 0xb3, 0x96, 0xbd, 0x5c, 0xba, 0xfa, 0x62, 0x3d, 0xcd, 0xfa, 0xeb, 0x42, 0xc1, 0x5d, 0xc9, 0xba, 0xe5, 0xd3, 0xa6, 0xc3, 0x5a, 0xfe, 0x09, 0xa1, 0xfd, 0xc6, 0xa2, 0xd2, 0x52, 0x50, 0x48, 0x86, 0x23, 0x0d, 0xe8, 0x47, 0x06, 0x2c, 0x76, 0x29, 0x39, 0x24, 0x94, 0x12, 0x5b, 0xe1, 0xab, 0xd9, 0x35, 0xe3, 0x21, 0xa8, 0xad, 0x2a, 0xb5, 0x8b, 0x7b, 0x43, 0xf2, 0xf1, 0x88, 0x46, 0xf4, 0x6b, 0x03, 0x56, 0x18, 0xa1, 0x27, 0x84, 0x6e, 0xd8, 0x36, 0x25, 0x8c, 0x35, 0xfa, 0x9b, 0xae, 0x43, 0xbc, 0x60, 0x73, 0xbb, 0x89, 0x59, 0x35, 0x27, 0xf6, 0xe1, 0xeb, 0xe9, 0x0c, 0xda, 0x9f, 0x24, 0xa7, 0x61, 0x2a, 0x8b, 0x56, 0x26, 0x92, 0x30, 0x7c, 0x1f, 0x33, 0xcc, 0x43, 0x28, 0x87, 0x07, 0x79, 0xcb, 0x61, 0x01, 0xba, 0x0b, 0xb3, 0x6d, 0xfe, 0xc1, 0xaa, 0x86, 0x30, 0xb0, 0x9e, 0xce, 0xc0, 0x50, 0x46, 0x63, 0x5e, 0xd9, 0x33, 0x2b, 0x3e, 0x19, 0x56, 0xd2, 0xcc, 0x9f, 0xe5, 0xa0, 0xb4, 0xb1, 0xb7, 0x8d, 0x09, 0xf3, 0x7b, 0xb4, 0x45, 0x52, 0x38, 0xcd, 0x35, 0x28, 0x33, 0xc7, 0x6b, 0xf7, 0x5c, 0x8b, 0x72, 0x68, 0x75, 0x56, 0x50, 0x2e, 0x2b, 0xca, 0xf2, 0xbe, 0x86, 0xc3, 0x09, 0x4a, 0x74, 0x15, 0x80, 0x4b, 0x60, 0x5d, 0xab, 0x45, 0xec, 0x6a, 0x66, 0xcd, 0xb8, 0x5c, 0x68, 0x20, 0xc5, 0x07, 0xaf, 0x46, 0x18, 0xac, 0x51, 0xa1, 0x47, 0x21, 0x2f, 0x2c, 0xad, 0x16, 0x84, 0x9a, 0x8a, 0x22, 0xcf, 0x8b, 0x65, 0x60, 0x89, 0x43, 0x4f, 0xc0, 0x9c, 0xf2, 0xb2, 0x6a, 0x51, 0x90, 0x2d, 0x28, 0xb2, 0xb9, 0xd0, 0x0d, 0x42, 0x3c, 0x5f, 0xdf, 0xb1, 0xe3, 0xd9, 0xc2, 0xef, 0xb4, 0xf5, 0xbd, 0xe2, 0x78, 0x36, 0x16, 0x18, 0x74, 0x0b, 0xf2, 0x27, 0x84, 0x1e, 0x70, 0x4f, 0xe0, 0xae, 0xf9, 0xe5, 0x74, 0x1b, 0x7d, 0x97, 0xb3, 0x34, 0x8a, 0xdc, 0x34, 0xf1, 0x13, 0x4b, 0x21, 0xa8, 0x0e, 0xc0, 0x8e, 0x7c, 0x1a, 0x88, 0xe5, 0x55, 0xf3, 0x6b, 0xd9, 0xcb, 0xc5, 0xc6, 0x3c, 0x5f, 0xef, 0x7e, 0x04, 0xc5, 0x1a, 0x05, 0xa7, 0x6f, 0x59, 0x01, 0x69, 0xfb, 0xd4, 0x21, 0xac, 0x3a, 0x17, 0xd3, 0x6f, 0x46, 0x50, 0xac, 0x51, 0xa0, 0x97, 0x01, 0xb1, 0xc0, 0xa7, 0x56, 0x9b, 0xa8, 0xa5, 0xbe, 0x64, 0xb1, 0xa3, 0x2a, 0x88, 0xd5, 0xad, 0xa8, 0xd5, 0xa1, 0xfd, 0x11, 0x0a, 0x3c, 0x86, 0xcb, 0xfc, 0x9d, 0x01, 0x0b, 0x9a, 0x2f, 0x08, 0xbf, 0xbb, 0x06, 0xe5, 0xb6, 0x76, 0xeb, 0x94, 0x5f, 0x44, 0xa7, 0xad, 0xdf, 0x48, 0x9c, 0xa0, 0x44, 0x04, 0x8a, 0x54, 0x49, 0x0a, 0xa3, 0xcb, 0x95, 0xd4, 0x4e, 0x1b, 0xda, 0x10, 0x6b, 0xd2, 0x80, 0x0c, 0xc7, 0x92, 0xcd, 0x7f, 0x18, 0xc2, 0x81, 0xc3, 0x78, 0x83, 0x2e, 0x6b, 0x31, 0xcd, 0x10, 0xdb, 0x57, 0x9e, 0x10, 0x8f, 0xce, 0x09, 0x04, 0x99, 0xff, 0x8b, 0x40, 0x70, 0xbd, 0xf0, 0xcb, 0xf7, 0x6a, 0x33, 0x6f, 0xff, 0x6d, 0x6d, 0xc6, 0xfc, 0x85, 0x01, 0xe5, 0x8d, 0x6e, 0xd7, 0xed, 0xef, 0x76, 0x03, 0xb1, 0x00, 0x13, 0x66, 0x6d, 0xda, 0xc7, 0x3d, 0x4f, 0x2d, 0x14, 0xf8, 0xfd, 0x6e, 0x0a, 0x08, 0x56, 0x18, 0x7e, 0x7f, 0x0e, 0x7d, 0xda, 0x22, 0xea, 0xba, 0x45, 0xf7, 0x67, 0x8b, 0x03, 0xb1, 0xc4, 0xf1, 0x43, 0x3e, 0x74, 0x88, 0x6b, 0xef, 0x58, 0x9e, 0xd5, 0x26, 0x54, 0x5d, 0x8e, 0x68, 0xeb, 0xb7, 0x34, 0x1c, 0x4e, 0x50, 0x9a, 0xff, 0xc9, 0x40, 0x71, 0xd3, 0xf7, 0x6c, 0x27, 0x50, 0x97, 0x2b, 0xe8, 0x77, 0x47, 0x82, 0xc7, 0xed, 0x7e, 0x97, 0x60, 0x81, 0x41, 0xcf, 0xc1, 0x2c, 0x0b, 0xac, 0xa0, 0xc7, 0x84, 0x3d, 0xc5, 0xc6, 0x23, 0x61, 0x58, 0xda, 0x17, 0xd0, 0xb3, 0x41, 0x6d, 0x21, 0x12, 0x27, 0x41, 0x58, 0x31, 0x70, 0x4f, 0xf7, 0x0f, 0xc4, 0x46, 0xd9, 0x37, 0xe5, 0xb3, 0x17, 0xbe, 0x1f, 0xd9, 0xd8, 0xd3, 0x77, 0x47, 0x28, 0xf0, 0x18, 0x2e, 0x74, 0x02, 0xc8, 0xb5, 0x58, 0x70, 0x9b, 0x5a, 0x1e, 0x13, 0xba, 0x6e, 0x3b, 0x1d, 0xa2, 0x2e, 0xfc, 0x97, 0xd2, 0x9d, 0x38, 0xe7, 0x88, 0xf5, 0xde, 0x1a, 0x91, 0x86, 0xc7, 0x68, 0x40, 0x8f, 0xc3, 0x2c, 0x25, 0x16, 0xf3, 0xbd, 0x6a, 0x5e, 0x2c, 0x3f, 0x8a, 0xca, 0x58, 0x40, 0xb1, 0xc2, 0xf2, 0x80, 0xd6, 0x21, 0x8c, 0x59, 0xed, 0x30, 0xbc, 0x46, 0x01, 0x6d, 0x47, 0x82, 0x71, 0x88, 0x37, 0x7f, 0x6b, 0x40, 0x65, 0x93, 0x12, 0x2b, 0x20, 0xd3, 0xb8, 0xc5, 0xa7, 0x3e, 0x71, 0xb4, 0x01, 0x0b, 0xe2, 0xfb, 0xae, 0xe5, 0x3a, 0xb6, 0x3c, 0x83, 0x9c, 0x60, 0xfe, 0xbc, 0x62, 0x5e, 0xd8, 0x4a, 0xa2, 0xf1, 0x30, 0xbd, 0xf9, 0x93, 0x2c, 0x54, 0x9a, 0xc4, 0x25, 0xb1, 0xc9, 0x5b, 0x80, 0xda, 0xd4, 0x6a, 0x91, 0x3d, 0x42, 0x1d, 0xdf, 0xde, 0x27, 0x2d, 0xdf, 0xb3, 0x99, 0x70, 0xa3, 0x6c, 0xe3, 0x73, 0x7c, 0x7f, 0x6f, 0x8e, 0x60, 0xf1, 0x18, 0x0e, 0xe4, 0x42, 0xa5, 0x4b, 0xc5, 0x6f, 0xb1, 0xe7, 0xd2, 0xcb, 0x4a, 0x57, 0xbf, 0x92, 0xee, 0x48, 0xf7, 0x74, 0xd6, 0xc6, 0xd2, 0xe9, 0xa0, 0x56, 0x49, 0x80, 0x70, 0x52, 0x38, 0xfa, 0x06, 0x2c, 0xfa, 0xb4, 0x7b, 0x64, 0x79, 0x4d, 0xd2, 0x25, 0x9e, 0x4d, 0xbc, 0x80, 0x89, 0x8d, 0x2c, 0x34, 0x96, 0x79, 0x2e, 0xb2, 0x3b, 0x84, 0xc3, 0x23, 0xd4, 0xe8, 0x35, 0x58, 0xea, 0x52, 0xbf, 0x6b, 0xb5, 0xc5, 0xc6, 0xec, 0xf9, 0xae, 0xd3, 0xea, 0xab, 0xed, 0x7c, 0xf2, 0x74, 0x50, 0x5b, 0xda, 0x1b, 0x46, 0x9e, 0x0d, 0x6a, 0x17, 0xc4, 0xd6, 0x71, 0x48, 0x8c, 0xc4, 0xa3, 0x62, 0x34, 0x37, 0xc8, 0x4f, 0x72, 0x03, 0x73, 0x1b, 0x0a, 0xcd, 0x9e, 0xba, 0x13, 0x2f, 0x40, 0xc1, 0x56, 0xbf, 0xd5, 0xce, 0x87, 0x97, 0x33, 0xa2, 0x39, 0x1b, 0xd4, 0x2a, 0x3c, 0xfd, 0xac, 0x87, 0x00, 0x1c, 0xb1, 0x98, 0x8f, 0x43, 0x41, 0x1c, 0x3c, 0xbb, 0x7b, 0x05, 0x2d, 0x42, 0x16, 0x5b, 0xf7, 0x84, 0x94, 0x32, 0xe6, 0x3f, 0xb5, 0x28, 0xb6, 0x0b, 0x70, 0x93, 0x04, 0xe1, 0xc1, 0x6f, 0xc0, 0x42, 0x18, 0xca, 0x93, 0x2f, 0x4c, 0xe4, 0x4d, 0x38, 0x89, 0xc6, 0xc3, 0xf4, 0xe6, 0xeb, 0x50, 0x14, 0xaf, 0x10, 0x7f, 0xc2, 0xe3, 0x74, 0xc1, 0xb8, 0x4f, 0xba, 0x10, 0xe6, 0x00, 0x99, 0x49, 0x39, 0x80, 0x66, 0xae, 0x0b, 0x15, 0xc9, 0x1b, 0x26, 0x48, 0xa9, 0x34, 0x3c, 0x09, 0x85, 0xd0, 0x4c, 0xa5, 0x25, 0x4a, 0x8c, 0x43, 0x41, 0x38, 0xa2, 0xd0, 0xb4, 0x1d, 0x41, 0xe2, 0x45, 0x4d, 0xa7, 0x4c, 0xcb, 0x7e, 0x32, 0xf7, 0xcf, 0x7e, 0x34, 0x4d, 0x3f, 0x84, 0xea, 0xa4, 0x6c, 0xfa, 0x01, 0xde, 0xfc, 0xf4, 0xa6, 0x98, 0xef, 0x18, 0xb0, 0xa8, 0x4b, 0x4a, 0x7f, 0x7c, 0xe9, 0x95, 0x9c, 0x9f, 0xed, 0x69, 0x3b, 0xf2, 0x2b, 0x03, 0x96, 0x13, 0x4b, 0x9b, 0xea, 0xc4, 0xa7, 0x30, 0x4a, 0x77, 0x8e, 0xec, 0x14, 0xce, 0xf1, 0x97, 0x0c, 0x54, 0x6e, 0x59, 0x07, 0xc4, 0xdd, 0x27, 0x2e, 0x69, 0x05, 0x3e, 0x45, 0x3f, 0x80, 0x52, 0xc7, 0x0a, 0x5a, 0x47, 0x02, 0x1a, 0x56, 0x06, 0xcd, 0x74, 0xc1, 0x2e, 0x21, 0xa9, 0xbe, 0x13, 0x8b, 0xb9, 0xe1, 0x05, 0xb4, 0xdf, 0xb8, 0xa0, 0x4c, 0x2a, 0x69, 0x18, 0xac, 0x6b, 0x13, 0xe5, 0x9c, 0xf8, 0xbe, 0xf1, 0x56, 0x97, 0xa7, 0x2d, 0xd3, 0x57, 0x91, 0x09, 0x13, 0x30, 0x79, 0xb3, 0xe7, 0x50, 0xd2, 0x21, 0x5e, 0x10, 0x97, 0x73, 0x3b, 0x43, 0xf2, 0xf1, 0x88, 0xc6, 0x95, 0x17, 0x61, 0x71, 0xd8, 0x78, 0x1e, 0x7f, 0x8e, 0x49, 0x5f, 0x9e, 0x17, 0xe6, 0x3f, 0xd1, 0x32, 0xe4, 0x4f, 0x2c, 0xb7, 0xa7, 0x6e, 0x23, 0x96, 0x1f, 0xd7, 0x33, 0xd7, 0x0c, 0xf3, 0x37, 0x06, 0x54, 0x27, 0x19, 0x82, 0xbe, 0xa8, 0x09, 0x6a, 0x94, 0x94, 0x55, 0xd9, 0x57, 0x48, 0x5f, 0x4a, 0xbd, 0x01, 0x05, 0xbf, 0xcb, 0x73, 0x0a, 0x9f, 0xaa, 0x53, 0x7f, 0x22, 0x3c, 0xc9, 0x5d, 0x05, 0x3f, 0x1b, 0xd4, 0x2e, 0x26, 0xc4, 0x87, 0x08, 0x1c, 0xb1, 0xf2, 0x48, 0x2d, 0xec, 0xe1, 0xaf, 0x47, 0x14, 0xa9, 0xef, 0x0a, 0x08, 0x56, 0x18, 0xf3, 0xf7, 0x06, 0xe4, 0x44, 0x42, 0xfe, 0x3a, 0x14, 0xf8, 0xfe, 0xd9, 0x56, 0x60, 0x09, 0xbb, 0x52, 0x97, 0x82, 0x9c, 0x7b, 0x87, 0x04, 0x56, 0xec, 0x6d, 0x21, 0x04, 0x47, 0x12, 0x11, 0x86, 0xbc, 0x13, 0x90, 0x4e, 0x78, 0x90, 0x4f, 0x4d, 0x14, 0xad, 0x1a, 0x11, 0x75, 0x6c, 0xdd, 0xbb, 0xf1, 0x56, 0x40, 0x3c, 0x7e, 0x18, 0xf1, 0xd5, 0xd8, 0xe6, 0x32, 0xb0, 0x14, 0x65, 0xfe, 0xcb, 0x80, 0x48, 0x15, 0x77, 0x7e, 0x46, 0xdc, 0xc3, 0x5b, 0x8e, 0x77, 0xac, 0xb6, 0x35, 0x32, 0x67, 0x5f, 0xc1, 0x71, 0x44, 0x31, 0xee, 0x79, 0xc8, 0x4c, 0xf7, 0x3c, 0x70, 0x85, 0x2d, 0xdf, 0x0b, 0x1c, 0xaf, 0x37, 0x72, 0xdb, 0x36, 0x15, 0x1c, 0x47, 0x14, 0x3c, 0x11, 0xa1, 0xa4, 0x63, 0x39, 0x9e, 0xe3, 0xb5, 0xf9, 0x22, 0x36, 0xfd, 0x9e, 0x17, 0x88, 0x17, 0x59, 0x25, 0x22, 0x78, 0x04, 0x8b, 0xc7, 0x70, 0x98, 0xff, 0xce, 0x41, 0x89, 0xaf, 0x39, 0x7c, 0xe7, 0x9e, 0x87, 0x8a, 0xab, 0x7b, 0x81, 0x5a, 0xfb, 0x45, 0x65, 0x4a, 0xf2, 0x5e, 0xe3, 0x24, 0x2d, 0x67, 0x16, 0x29, 0x54, 0xc4, 0x9c, 0x49, 0x32, 0x6f, 0xe9, 0x48, 0x9c, 0xa4, 0xe5, 0xd1, 0xeb, 0x1e, 0xbf, 0x1f, 0x2a, 0x33, 0x89, 0x8e, 0xe8, 0x9b, 0x1c, 0x88, 0x25, 0x0e, 0xed, 0xc0, 0x05, 0xcb, 0x75, 0xfd, 0x7b, 0x02, 0xd8, 0xf0, 0xfd, 0xe3, 0x8e, 0x45, 0x8f, 0x99, 0x28, 0xa6, 0x0b, 0x8d, 0x2f, 0x28, 0x96, 0x0b, 0x1b, 0xa3, 0x24, 0x78, 0x1c, 0xdf, 0xb8, 0x63, 0xcb, 0x4d, 0x79, 0x6c, 0x47, 0xb0, 0x3c, 0x04, 0x12, 0xb7, 0x5c, 0x55, 0xb6, 0xcf, 0x28, 0x39, 0xcb, 0x78, 0x0c, 0xcd, 0xd9, 0x04, 0x38, 0x1e, 0x2b, 0x11, 0x5d, 0x87, 0x79, 0xee, 0xc9, 0x7e, 0x2f, 0x08, 0xf3, 0xce, 0xbc, 0x38, 0x6e, 0x74, 0x3a, 0xa8, 0xcd, 0xdf, 0x4e, 0x60, 0xf0, 0x10, 0x25, 0xdf, 0x5c, 0xd7, 0xe9, 0x38, 0x41, 0x75, 0x4e, 0xb0, 0x44, 0x9b, 0x7b, 0x8b, 0x03, 0xb1, 0xc4, 0x25, 0x3c, 0xb0, 0x70, 0xae, 0x07, 0x6e, 0xc2, 0x12, 0x23, 0x9e, 0xbd, 0xed, 0x39, 0x81, 0x63, 0xb9, 0x37, 0x4e, 0x44, 0x56, 0x59, 0x12, 0x07, 0x71, 0x91, 0xa7, 0x84, 0xfb, 0xc3, 0x48, 0x3c, 0x4a, 0x6f, 0xfe, 0x39, 0x0b, 0x48, 0x26, 0xec, 0xb6, 0x4c, 0xca, 0x64, 0x5c, 0xe4, 0x65, 0x85, 0x4a, 0xf8, 0x8d, 0xa1, 0xb2, 0x42, 0xe5, 0xfa, 0x21, 0x1e, 0xed, 0x40, 0x51, 0xc6, 0xa7, 0xf8, 0xce, 0xad, 0x2b, 0xe2, 0xe2, 0x6e, 0x88, 0x38, 0x1b, 0xd4, 0x56, 0x12, 0x6a, 0x22, 0x8c, 0x28, 0xf9, 0x62, 0x09, 0xe8, 0x2a, 0x80, 0xd5, 0x75, 0xf4, 0xa6, 0x5f, 0x31, 0x6e, 0xfd, 0xc4, 0xe5, 0x3b, 0xd6, 0xa8, 0xd0, 0x4b, 0x90, 0x0b, 0x3e, 0x5d, 0x59, 0x56, 0x10, 0x55, 0x27, 0x2f, 0xc2, 0x84, 0x04, 0xae, 0x5d, 0x5c, 0x0a, 0xc6, 0xcd, 0x52, 0x15, 0x55, 0xa4, 0x7d, 0x2b, 0xc2, 0x60, 0x8d, 0x0a, 0x7d, 0x0b, 0x0a, 0x87, 0x2a, 0x9f, 0x15, 0xa7, 0x9b, 0x3a, 0xce, 0x86, 0x59, 0xb0, 0xec, 0x3b, 0x84, 0x5f, 0x38, 0x92, 0x86, 0xbe, 0x0a, 0x25, 0xd6, 0x3b, 0x88, 0x52, 0x00, 0xe9, 0x12, 0xd1, 0x7b, 0xbb, 0x1f, 0xa3, 0xb0, 0x4e, 0x67, 0xbe, 0x09, 0xc5, 0x1d, 0xa7, 0x45, 0x7d, 0x51, 0x48, 0x3e, 0x01, 0x73, 0x2c, 0x51, 0x25, 0x45, 0x27, 0x19, 0xba, 0x6a, 0x88, 0xe7, 0x3e, 0xea, 0x59, 0x9e, 0x2f, 0x6b, 0xa1, 0x7c, 0xec, 0xa3, 0xaf, 0x72, 0x20, 0x96, 0xb8, 0xeb, 0xcb, 0x3c, 0xcb, 0xf8, 0xe9, 0xfb, 0xb5, 0x99, 0x77, 0xdf, 0xaf, 0xcd, 0xbc, 0xf7, 0xbe, 0xca, 0x38, 0xfe, 0x00, 0x00, 0xbb, 0x07, 0xdf, 0x23, 0x2d, 0x19, 0xbb, 0x53, 0xf5, 0x06, 0xc3, 0x96, 0xb4, 0xe8, 0x0d, 0x66, 0x86, 0x32, 0x47, 0x0d, 0x87, 0x13, 0x94, 0x68, 0x1d, 0x8a, 0x51, 0xd7, 0x4f, 0xf9, 0xc7, 0x52, 0xe8, 0x6f, 0x51, 0x6b, 0x10, 0xc7, 0x34, 0x89, 0x87, 0x24, 0x77, 0xee, 0x43, 0xd2, 0x80, 0x6c, 0xcf, 0xb1, 0x55, 0xd5, 0xfd, 0x74, 0xf8, 0x90, 0xdf, 0xd9, 0x6e, 0x9e, 0x0d, 0x6a, 0x8f, 0x4c, 0x6a, 0xb6, 0x07, 0xfd, 0x2e, 0x61, 0xf5, 0x3b, 0xdb, 0x4d, 0xcc, 0x99, 0xc7, 0x45, 0xb5, 0xd9, 0x29, 0xa3, 0xda, 0x55, 0x80, 0x76, 0xdc, 0xbb, 0x90, 0x41, 0x23, 0x72, 0x44, 0xad, 0x67, 0xa1, 0x51, 0x21, 0x06, 0x4b, 0x2d, 0x5e, 0xdf, 0xab, 0x1e, 0x02, 0x0b, 0xac, 0x8e, 0xec, 0x86, 0x4e, 0x77, 0x27, 0x2e, 0x29, 0x35, 0x4b, 0x9b, 0xc3, 0xc2, 0xf0, 0xa8, 0x7c, 0xe4, 0xc3, 0x92, 0xad, 0xca, 0xcc, 0x58, 0x69, 0x71, 0x6a, 0xa5, 0x22, 0x62, 0x35, 0x87, 0x05, 0xe1, 0x51, 0xd9, 0xe8, 0xbb, 0xb0, 0x12, 0x02, 0x47, 0x6b, 0x7d, 0x11, 0xf5, 0xb3, 0x8d, 0xd5, 0xd3, 0x41, 0x6d, 0xa5, 0x39, 0x91, 0x0a, 0xdf, 0x47, 0x02, 0xb2, 0x61, 0xd6, 0x95, 0x59, 0x72, 0x49, 0x64, 0x36, 0x5f, 0x4b, 0xb7, 0x8a, 0xd8, 0xfb, 0xeb, 0x7a, 0x76, 0x1c, 0xf5, 0x6d, 0x54, 0x62, 0xac, 0x64, 0xa3, 0xb7, 0xa0, 0x64, 0x79, 0x9e, 0x1f, 0x58, 0xb2, 0xfb, 0x50, 0x16, 0xaa, 0x36, 0xa6, 0x56, 0xb5, 0x11, 0xcb, 0x18, 0xca, 0xc6, 0x35, 0x0c, 0xd6, 0x55, 0xa1, 0x7b, 0xb0, 0xe0, 0xdf, 0xf3, 0x08, 0xc5, 0xe4, 0x90, 0x50, 0xe2, 0xb5, 0x08, 0xab, 0x56, 0x84, 0xf6, 0x67, 0x52, 0x6a, 0x4f, 0x30, 0xc7, 0x2e, 0x9d, 0x84, 0x33, 0x3c, 0xac, 0x05, 0xd5, 0x79, 0x6c, 0xf5, 0x2c, 0xd7, 0xf9, 0x3e, 0xa1, 0xac, 0x3a, 0x1f, 0x37, 0xac, 0xb7, 0x22, 0x28, 0xd6, 0x28, 0x50, 0x0f, 0x2a, 0x1d, 0xfd, 0xc9, 0xa8, 0x2e, 0x09, 0x33, 0xaf, 0xa5, 0x33, 0x73, 0xf4, 0x51, 0x8b, 0xd3, 0xa0, 0x04, 0x0e, 0x27, 0xb5, 0xac, 0x3c, 0x07, 0xa5, 0x4f, 0x59, 0x21, 0xf0, 0x0a, 0x63, 0xf8, 0x40, 0xa6, 0xaa, 0x30, 0xfe, 0x98, 0x81, 0xf9, 0xe4, 0x36, 0x0e, 0x3d, 0x87, 0xf9, 0x54, 0xcf, 0x61, 0x58, 0xcb, 0x1a, 0x13, 0x27, 0x17, 0x61, 0x7c, 0xce, 0x4e, 0x8c, 0xcf, 0x2a, 0x0c, 0xe6, 0x1e, 0x24, 0x0c, 0xd6, 0x01, 0x78, 0xb2, 0x42, 0x7d, 0xd7, 0x25, 0x54, 0x44, 0xc0, 0x82, 0x9a, 0x50, 0x44, 0x50, 0xac, 0x51, 0xf0, 0x94, 0xfa, 0xc0, 0xf5, 0x5b, 0xc7, 0x62, 0x0b, 0xc2, 0xdb, 0x2b, 0x62, 0x5f, 0x41, 0xa6, 0xd4, 0x8d, 0x11, 0x2c, 0x1e, 0xc3, 0x61, 0xf6, 0xe1, 0xe2, 0x9e, 0x45, 0x79, 0x92, 0x13, 0xdf, 0x14, 0x51, 0xb3, 0xbc, 0x31, 0x52, 0x11, 0x3d, 0x3d, 0xed, 0x8d, 0x8b, 0x37, 0x3f, 0x86, 0xc5, 0x55, 0x91, 0xf9, 0x57, 0x03, 0x2e, 0x8d, 0xd5, 0xfd, 0x19, 0x54, 0x64, 0x6f, 0x24, 0x2b, 0xb2, 0xe7, 0x53, 0xb6, 0x32, 0xc7, 0x59, 0x3b, 0xa1, 0x3e, 0x9b, 0x83, 0xfc, 0x1e, 0xcf, 0x84, 0xcd, 0x0f, 0x0d, 0x28, 0x8b, 0x5f, 0xd3, 0x74, 0x92, 0x6b, 0xc9, 0x01, 0x43, 0xf1, 0xe1, 0x0d, 0x17, 0x1e, 0x46, 0xab, 0xf9, 0x1d, 0x03, 0x92, 0x3d, 0x5c, 0xf4, 0xa2, 0xbc, 0x02, 0x46, 0xd4, 0x64, 0x9d, 0xd2, 0xfd, 0x5f, 0x98, 0x54, 0x92, 0x5e, 0x48, 0xd5, 0xad, 0x7c, 0x12, 0x8a, 0xd8, 0xf7, 0x83, 0x3d, 0x2b, 0x38, 0x62, 0x7c, 0xef, 0xba, 0xfc, 0x87, 0xda, 0x5e, 0xb1, 0x77, 0x02, 0x83, 0x25, 0xdc, 0xfc, 0xb9, 0x01, 0x97, 0x26, 0xce, 0x8d, 0x78, 0x14, 0x69, 0x45, 0x5f, 0x6a, 0x45, 0x91, 0x23, 0xc7, 0x74, 0x58, 0xa3, 0xe2, 0xb5, 0x64, 0x62, 0xd8, 0x34, 0x5c, 0x4b, 0x26, 0xb4, 0xe1, 0x24, 0xad, 0xf9, 0xcf, 0x0c, 0xa8, 0x41, 0xcd, 0xff, 0xd8, 0xe9, 0x1f, 0x1f, 0x1a, 0x13, 0xcd, 0x27, 0xc7, 0x44, 0xd1, 0x4c, 0x48, 0x9b, 0x93, 0x64, 0xef, 0x3f, 0x27, 0x41, 0xcf, 0x46, 0xa3, 0x17, 0xe9, 0x43, 0xab, 0xc9, 0xd1, 0xcb, 0xd9, 0xa0, 0x56, 0x56, 0xc2, 0x93, 0xa3, 0x98, 0xd7, 0x60, 0xce, 0x26, 0x81, 0xe5, 0xb8, 0xb2, 0x2e, 0x4c, 0x3d, 0x4c, 0x90, 0xc2, 0x9a, 0x92, 0xb5, 0x51, 0xe2, 0x36, 0xa9, 0x0f, 0x1c, 0x0a, 0xe4, 0x01, 0xbb, 0xe5, 0xdb, 0xb2, 0x22, 0xc9, 0xc7, 0x01, 0x7b, 0xd3, 0xb7, 0x09, 0x16, 0x18, 0xf3, 0x5d, 0x03, 0x4a, 0x52, 0xd2, 0xa6, 0xd5, 0x63, 0x04, 0x5d, 0x89, 0x56, 0x21, 0x8f, 0xfb, 0x92, 0x3e, 0x63, 0x3b, 0x1b, 0xd4, 0x8a, 0x82, 0x4c, 0x14, 0x33, 0x63, 0x66, 0x49, 0x99, 0x73, 0xf6, 0xe8, 0x51, 0xc8, 0x8b, 0x0b, 0xa4, 0x36, 0x33, 0x1e, 0x16, 0x72, 0x20, 0x96, 0x38, 0xf3, 0xe3, 0x0c, 0x54, 0x12, 0x8b, 0x4b, 0x51, 0x17, 0x44, 0x2d, 0xd4, 0x4c, 0x8a, 0xb6, 0xfc, 0xe4, 0xd1, 0xbc, 0x7a, 0xbe, 0x66, 0x1f, 0xe4, 0xf9, 0xfa, 0x36, 0xcc, 0xb6, 0xf8, 0x1e, 0x85, 0xff, 0xf4, 0xb8, 0x32, 0xcd, 0x71, 0x8a, 0xdd, 0x8d, 0xbd, 0x51, 0x7c, 0x32, 0xac, 0x04, 0xa2, 0x9b, 0xb0, 0x44, 0x49, 0x40, 0xfb, 0x1b, 0x87, 0x01, 0xa1, 0x7a, 0x33, 0x21, 0x1f, 0x67, 0xdf, 0x78, 0x98, 0x00, 0x8f, 0xf2, 0x98, 0x07, 0x50, 0xbe, 0x6d, 0x1d, 0xb8, 0xd1, 0x78, 0x0c, 0x43, 0xc5, 0xf1, 0x5a, 0x6e, 0xcf, 0x26, 0x32, 0xa0, 0x87, 0xd1, 0x2b, 0xbc, 0xb4, 0xdb, 0x3a, 0xf2, 0x6c, 0x50, 0xbb, 0x90, 0x00, 0xc8, 0x79, 0x10, 0x4e, 0x8a, 0x30, 0x5d, 0xc8, 0x7d, 0x86, 0x95, 0xe4, 0x77, 0xa0, 0x18, 0xe7, 0xfa, 0x0f, 0x59, 0xa5, 0xf9, 0x06, 0x14, 0xb8, 0xc7, 0x87, 0x35, 0xea, 0x39, 0x59, 0x52, 0x32, 0xf7, 0xca, 0xa4, 0xc9, 0xbd, 0xc4, 0x90, 0xf5, 0x4e, 0xd7, 0x7e, 0xc0, 0x21, 0x6b, 0xe6, 0x41, 0x5e, 0xbe, 0xec, 0x94, 0x2f, 0xdf, 0x55, 0x90, 0x7f, 0x44, 0xe1, 0x8f, 0x8c, 0x4c, 0x20, 0xb4, 0x47, 0x46, 0x7f, 0xff, 0xb5, 0x09, 0xc3, 0x8f, 0x0d, 0x00, 0xd1, 0xca, 0x13, 0x6d, 0xa4, 0x14, 0xe3, 0xfc, 0x3b, 0x30, 0xeb, 0x4b, 0x8f, 0x94, 0x83, 0xd6, 0x29, 0xfb, 0xc5, 0xd1, 0x45, 0x92, 0x3e, 0x89, 0x95, 0xb0, 0xc6, 0xcb, 0x1f, 0x7c, 0xb2, 0x3a, 0xf3, 0xe1, 0x27, 0xab, 0x33, 0x1f, 0x7d, 0xb2, 0x3a, 0xf3, 0xf6, 0xe9, 0xaa, 0xf1, 0xc1, 0xe9, 0xaa, 0xf1, 0xe1, 0xe9, 0xaa, 0xf1, 0xd1, 0xe9, 0xaa, 0xf1, 0xf1, 0xe9, 0xaa, 0xf1, 0xee, 0xdf, 0x57, 0x67, 0x5e, 0x7b, 0x2c, 0xcd, 0x1f, 0xfc, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x28, 0x27, 0x65, 0xab, 0x20, 0x28, 0x00, 0x00, } func (m *APIGroup) 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 *APIGroup) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *APIGroup) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.ServerAddressByClientCIDRs) > 0 { for iNdEx := len(m.ServerAddressByClientCIDRs) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.ServerAddressByClientCIDRs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 } } { size, err := m.PreferredVersion.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a if len(m.Versions) > 0 { for iNdEx := len(m.Versions) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Versions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *APIGroupList) 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 *APIGroupList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *APIGroupList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Groups) > 0 { for iNdEx := len(m.Groups) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Groups[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *APIResource) 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 *APIResource) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *APIResource) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.StorageVersionHash) copy(dAtA[i:], m.StorageVersionHash) i = encodeVarintGenerated(dAtA, i, uint64(len(m.StorageVersionHash))) i-- dAtA[i] = 0x52 i -= len(m.Version) copy(dAtA[i:], m.Version) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) i-- dAtA[i] = 0x4a i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0x42 if len(m.Categories) > 0 { for iNdEx := len(m.Categories) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Categories[iNdEx]) copy(dAtA[i:], m.Categories[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Categories[iNdEx]))) i-- dAtA[i] = 0x3a } } i -= len(m.SingularName) copy(dAtA[i:], m.SingularName) i = encodeVarintGenerated(dAtA, i, uint64(len(m.SingularName))) i-- dAtA[i] = 0x32 if len(m.ShortNames) > 0 { for iNdEx := len(m.ShortNames) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.ShortNames[iNdEx]) copy(dAtA[i:], m.ShortNames[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ShortNames[iNdEx]))) i-- dAtA[i] = 0x2a } } if m.Verbs != nil { { size, err := m.Verbs.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 } i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0x1a i-- if m.Namespaced { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x10 i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *APIResourceList) 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 *APIResourceList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *APIResourceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.APIResources) > 0 { for iNdEx := len(m.APIResources) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.APIResources[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } i -= len(m.GroupVersion) copy(dAtA[i:], m.GroupVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.GroupVersion))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *APIVersions) 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 *APIVersions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *APIVersions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.ServerAddressByClientCIDRs) > 0 { for iNdEx := len(m.ServerAddressByClientCIDRs) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.ServerAddressByClientCIDRs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } if len(m.Versions) > 0 { for iNdEx := len(m.Versions) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Versions[iNdEx]) copy(dAtA[i:], m.Versions[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Versions[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *ApplyOptions) 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 *ApplyOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ApplyOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.FieldManager) copy(dAtA[i:], m.FieldManager) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) i-- dAtA[i] = 0x1a i-- if m.Force { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x10 if len(m.DryRun) > 0 { for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.DryRun[iNdEx]) copy(dAtA[i:], m.DryRun[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *Condition) 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 *Condition) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Condition) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) i-- dAtA[i] = 0x32 i -= len(m.Reason) copy(dAtA[i:], m.Reason) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) i-- dAtA[i] = 0x2a { size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) i-- dAtA[i] = 0x18 i -= len(m.Status) copy(dAtA[i:], m.Status) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) i-- dAtA[i] = 0x12 i -= len(m.Type) copy(dAtA[i:], m.Type) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *CreateOptions) 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 *CreateOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *CreateOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.FieldValidation) copy(dAtA[i:], m.FieldValidation) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldValidation))) i-- dAtA[i] = 0x22 i -= len(m.FieldManager) copy(dAtA[i:], m.FieldManager) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) i-- dAtA[i] = 0x1a if len(m.DryRun) > 0 { for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.DryRun[iNdEx]) copy(dAtA[i:], m.DryRun[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *DeleteOptions) 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 *DeleteOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *DeleteOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.DryRun) > 0 { for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.DryRun[iNdEx]) copy(dAtA[i:], m.DryRun[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) i-- dAtA[i] = 0x2a } } if m.PropagationPolicy != nil { i -= len(*m.PropagationPolicy) copy(dAtA[i:], *m.PropagationPolicy) i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PropagationPolicy))) i-- dAtA[i] = 0x22 } if m.OrphanDependents != nil { i-- if *m.OrphanDependents { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x18 } if m.Preconditions != nil { { size, err := m.Preconditions.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } if m.GracePeriodSeconds != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.GracePeriodSeconds)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *Duration) 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 *Duration) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Duration) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i = encodeVarintGenerated(dAtA, i, uint64(m.Duration)) i-- dAtA[i] = 0x8 return len(dAtA) - i, nil } func (m *FieldsV1) 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 *FieldsV1) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *FieldsV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.Raw != nil { i -= len(m.Raw) copy(dAtA[i:], m.Raw) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Raw))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *GetOptions) 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 *GetOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GetOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.ResourceVersion) copy(dAtA[i:], m.ResourceVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *GroupKind) 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 *GroupKind) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GroupKind) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0x12 i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *GroupResource) 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 *GroupResource) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GroupResource) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Resource) copy(dAtA[i:], m.Resource) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) i-- dAtA[i] = 0x12 i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *GroupVersion) 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 *GroupVersion) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GroupVersion) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Version) copy(dAtA[i:], m.Version) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) i-- dAtA[i] = 0x12 i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *GroupVersionForDiscovery) 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 *GroupVersionForDiscovery) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GroupVersionForDiscovery) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Version) copy(dAtA[i:], m.Version) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) i-- dAtA[i] = 0x12 i -= len(m.GroupVersion) copy(dAtA[i:], m.GroupVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.GroupVersion))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *GroupVersionKind) 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 *GroupVersionKind) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GroupVersionKind) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0x1a i -= len(m.Version) copy(dAtA[i:], m.Version) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) i-- dAtA[i] = 0x12 i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *GroupVersionResource) 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 *GroupVersionResource) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *GroupVersionResource) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Resource) copy(dAtA[i:], m.Resource) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) i-- dAtA[i] = 0x1a i -= len(m.Version) copy(dAtA[i:], m.Version) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) i-- dAtA[i] = 0x12 i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *LabelSelector) 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 *LabelSelector) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *LabelSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.MatchExpressions) > 0 { for iNdEx := len(m.MatchExpressions) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.MatchExpressions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } if len(m.MatchLabels) > 0 { keysForMatchLabels := make([]string, 0, len(m.MatchLabels)) for k := range m.MatchLabels { keysForMatchLabels = append(keysForMatchLabels, string(k)) } github_com_gogo_protobuf_sortkeys.Strings(keysForMatchLabels) for iNdEx := len(keysForMatchLabels) - 1; iNdEx >= 0; iNdEx-- { v := m.MatchLabels[string(keysForMatchLabels[iNdEx])] baseI := i i -= len(v) copy(dAtA[i:], v) i = encodeVarintGenerated(dAtA, i, uint64(len(v))) i-- dAtA[i] = 0x12 i -= len(keysForMatchLabels[iNdEx]) copy(dAtA[i:], keysForMatchLabels[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(keysForMatchLabels[iNdEx]))) i-- dAtA[i] = 0xa i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *LabelSelectorRequirement) 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 *LabelSelectorRequirement) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *LabelSelectorRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Values) > 0 { for iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Values[iNdEx]) copy(dAtA[i:], m.Values[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Values[iNdEx]))) i-- dAtA[i] = 0x1a } } i -= len(m.Operator) copy(dAtA[i:], m.Operator) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operator))) i-- dAtA[i] = 0x12 i -= len(m.Key) copy(dAtA[i:], m.Key) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *List) 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 *List) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *List) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Items) > 0 { for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } { size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *ListMeta) 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 *ListMeta) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ListMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.RemainingItemCount != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.RemainingItemCount)) i-- dAtA[i] = 0x20 } i -= len(m.Continue) copy(dAtA[i:], m.Continue) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Continue))) i-- dAtA[i] = 0x1a i -= len(m.ResourceVersion) copy(dAtA[i:], m.ResourceVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) i-- dAtA[i] = 0x12 i -= len(m.SelfLink) copy(dAtA[i:], m.SelfLink) i = encodeVarintGenerated(dAtA, i, uint64(len(m.SelfLink))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *ListOptions) 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 *ListOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ListOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.SendInitialEvents != nil { i-- if *m.SendInitialEvents { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x58 } i -= len(m.ResourceVersionMatch) copy(dAtA[i:], m.ResourceVersionMatch) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersionMatch))) i-- dAtA[i] = 0x52 i-- if m.AllowWatchBookmarks { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x48 i -= len(m.Continue) copy(dAtA[i:], m.Continue) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Continue))) i-- dAtA[i] = 0x42 i = encodeVarintGenerated(dAtA, i, uint64(m.Limit)) i-- dAtA[i] = 0x38 if m.TimeoutSeconds != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) i-- dAtA[i] = 0x28 } i -= len(m.ResourceVersion) copy(dAtA[i:], m.ResourceVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) i-- dAtA[i] = 0x22 i-- if m.Watch { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x18 i -= len(m.FieldSelector) copy(dAtA[i:], m.FieldSelector) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldSelector))) i-- dAtA[i] = 0x12 i -= len(m.LabelSelector) copy(dAtA[i:], m.LabelSelector) i = encodeVarintGenerated(dAtA, i, uint64(len(m.LabelSelector))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *ManagedFieldsEntry) 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 *ManagedFieldsEntry) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ManagedFieldsEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Subresource) copy(dAtA[i:], m.Subresource) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Subresource))) i-- dAtA[i] = 0x42 if m.FieldsV1 != nil { { size, err := m.FieldsV1.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x3a } i -= len(m.FieldsType) copy(dAtA[i:], m.FieldsType) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldsType))) i-- dAtA[i] = 0x32 if m.Time != nil { { size, err := m.Time.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 } i -= len(m.APIVersion) copy(dAtA[i:], m.APIVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) i-- dAtA[i] = 0x1a i -= len(m.Operation) copy(dAtA[i:], m.Operation) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) i-- dAtA[i] = 0x12 i -= len(m.Manager) copy(dAtA[i:], m.Manager) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Manager))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *ObjectMeta) 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 *ObjectMeta) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ObjectMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.ManagedFields) > 0 { for iNdEx := len(m.ManagedFields) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.ManagedFields[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1 i-- dAtA[i] = 0x8a } } if len(m.Finalizers) > 0 { for iNdEx := len(m.Finalizers) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Finalizers[iNdEx]) copy(dAtA[i:], m.Finalizers[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Finalizers[iNdEx]))) i-- dAtA[i] = 0x72 } } if len(m.OwnerReferences) > 0 { for iNdEx := len(m.OwnerReferences) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.OwnerReferences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x6a } } if len(m.Annotations) > 0 { keysForAnnotations := make([]string, 0, len(m.Annotations)) for k := range m.Annotations { keysForAnnotations = append(keysForAnnotations, string(k)) } github_com_gogo_protobuf_sortkeys.Strings(keysForAnnotations) for iNdEx := len(keysForAnnotations) - 1; iNdEx >= 0; iNdEx-- { v := m.Annotations[string(keysForAnnotations[iNdEx])] baseI := i i -= len(v) copy(dAtA[i:], v) i = encodeVarintGenerated(dAtA, i, uint64(len(v))) i-- dAtA[i] = 0x12 i -= len(keysForAnnotations[iNdEx]) copy(dAtA[i:], keysForAnnotations[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(keysForAnnotations[iNdEx]))) i-- dAtA[i] = 0xa i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) i-- dAtA[i] = 0x62 } } if len(m.Labels) > 0 { keysForLabels := make([]string, 0, len(m.Labels)) for k := range m.Labels { keysForLabels = append(keysForLabels, string(k)) } github_com_gogo_protobuf_sortkeys.Strings(keysForLabels) for iNdEx := len(keysForLabels) - 1; iNdEx >= 0; iNdEx-- { v := m.Labels[string(keysForLabels[iNdEx])] baseI := i i -= len(v) copy(dAtA[i:], v) i = encodeVarintGenerated(dAtA, i, uint64(len(v))) i-- dAtA[i] = 0x12 i -= len(keysForLabels[iNdEx]) copy(dAtA[i:], keysForLabels[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(keysForLabels[iNdEx]))) i-- dAtA[i] = 0xa i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) i-- dAtA[i] = 0x5a } } if m.DeletionGracePeriodSeconds != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.DeletionGracePeriodSeconds)) i-- dAtA[i] = 0x50 } if m.DeletionTimestamp != nil { { size, err := m.DeletionTimestamp.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x4a } { size, err := m.CreationTimestamp.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x42 i = encodeVarintGenerated(dAtA, i, uint64(m.Generation)) i-- dAtA[i] = 0x38 i -= len(m.ResourceVersion) copy(dAtA[i:], m.ResourceVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) i-- dAtA[i] = 0x32 i -= len(m.UID) copy(dAtA[i:], m.UID) i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) i-- dAtA[i] = 0x2a i -= len(m.SelfLink) copy(dAtA[i:], m.SelfLink) i = encodeVarintGenerated(dAtA, i, uint64(len(m.SelfLink))) i-- dAtA[i] = 0x22 i -= len(m.Namespace) copy(dAtA[i:], m.Namespace) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) i-- dAtA[i] = 0x1a i -= len(m.GenerateName) copy(dAtA[i:], m.GenerateName) i = encodeVarintGenerated(dAtA, i, uint64(len(m.GenerateName))) i-- dAtA[i] = 0x12 i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *OwnerReference) 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 *OwnerReference) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *OwnerReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.BlockOwnerDeletion != nil { i-- if *m.BlockOwnerDeletion { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x38 } if m.Controller != nil { i-- if *m.Controller { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x30 } i -= len(m.APIVersion) copy(dAtA[i:], m.APIVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) i-- dAtA[i] = 0x2a i -= len(m.UID) copy(dAtA[i:], m.UID) i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) i-- dAtA[i] = 0x22 i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0x1a i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *PartialObjectMetadata) 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 *PartialObjectMetadata) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PartialObjectMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *PartialObjectMetadataList) 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 *PartialObjectMetadataList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PartialObjectMetadataList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Items) > 0 { for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } { size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *Patch) 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 *Patch) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Patch) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l return len(dAtA) - i, nil } func (m *PatchOptions) 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 *PatchOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PatchOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.FieldValidation) copy(dAtA[i:], m.FieldValidation) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldValidation))) i-- dAtA[i] = 0x22 i -= len(m.FieldManager) copy(dAtA[i:], m.FieldManager) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) i-- dAtA[i] = 0x1a if m.Force != nil { i-- if *m.Force { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x10 } if len(m.DryRun) > 0 { for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.DryRun[iNdEx]) copy(dAtA[i:], m.DryRun[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *Preconditions) 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 *Preconditions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Preconditions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.ResourceVersion != nil { i -= len(*m.ResourceVersion) copy(dAtA[i:], *m.ResourceVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ResourceVersion))) i-- dAtA[i] = 0x12 } if m.UID != nil { i -= len(*m.UID) copy(dAtA[i:], *m.UID) i = encodeVarintGenerated(dAtA, i, uint64(len(*m.UID))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *RootPaths) 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 *RootPaths) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *RootPaths) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Paths) > 0 { for iNdEx := len(m.Paths) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Paths[iNdEx]) copy(dAtA[i:], m.Paths[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Paths[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *ServerAddressByClientCIDR) 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 *ServerAddressByClientCIDR) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ServerAddressByClientCIDR) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.ServerAddress) copy(dAtA[i:], m.ServerAddress) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ServerAddress))) i-- dAtA[i] = 0x12 i -= len(m.ClientCIDR) copy(dAtA[i:], m.ClientCIDR) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ClientCIDR))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *Status) 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 *Status) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i = encodeVarintGenerated(dAtA, i, uint64(m.Code)) i-- dAtA[i] = 0x30 if m.Details != nil { { size, err := m.Details.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x2a } i -= len(m.Reason) copy(dAtA[i:], m.Reason) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) i-- dAtA[i] = 0x22 i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) i-- dAtA[i] = 0x1a i -= len(m.Status) copy(dAtA[i:], m.Status) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) i-- dAtA[i] = 0x12 { size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *StatusCause) 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 *StatusCause) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *StatusCause) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Field) copy(dAtA[i:], m.Field) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Field))) i-- dAtA[i] = 0x1a i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) i-- dAtA[i] = 0x12 i -= len(m.Type) copy(dAtA[i:], m.Type) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *StatusDetails) 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 *StatusDetails) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *StatusDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.UID) copy(dAtA[i:], m.UID) i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) i-- dAtA[i] = 0x32 i = encodeVarintGenerated(dAtA, i, uint64(m.RetryAfterSeconds)) i-- dAtA[i] = 0x28 if len(m.Causes) > 0 { for iNdEx := len(m.Causes) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Causes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 } } i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0x1a i -= len(m.Group) copy(dAtA[i:], m.Group) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) i-- dAtA[i] = 0x12 i -= len(m.Name) copy(dAtA[i:], m.Name) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *TableOptions) 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 *TableOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *TableOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.IncludeObject) copy(dAtA[i:], m.IncludeObject) i = encodeVarintGenerated(dAtA, i, uint64(len(m.IncludeObject))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *Timestamp) 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 *Timestamp) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Timestamp) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i = encodeVarintGenerated(dAtA, i, uint64(m.Nanos)) i-- dAtA[i] = 0x10 i = encodeVarintGenerated(dAtA, i, uint64(m.Seconds)) i-- dAtA[i] = 0x8 return len(dAtA) - i, nil } func (m *TypeMeta) 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 *TypeMeta) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *TypeMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.APIVersion) copy(dAtA[i:], m.APIVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) i-- dAtA[i] = 0x12 i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *UpdateOptions) 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 *UpdateOptions) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *UpdateOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.FieldValidation) copy(dAtA[i:], m.FieldValidation) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldValidation))) i-- dAtA[i] = 0x1a i -= len(m.FieldManager) copy(dAtA[i:], m.FieldManager) i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) i-- dAtA[i] = 0x12 if len(m.DryRun) > 0 { for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.DryRun[iNdEx]) copy(dAtA[i:], m.DryRun[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m Verbs) 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 Verbs) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m Verbs) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m) > 0 { for iNdEx := len(m) - 1; iNdEx >= 0; iNdEx-- { i -= len(m[iNdEx]) copy(dAtA[i:], m[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(m[iNdEx]))) i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *WatchEvent) 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 *WatchEvent) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *WatchEvent) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { size, err := m.Object.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 i -= len(m.Type) copy(dAtA[i:], m.Type) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *APIGroup) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) if len(m.Versions) > 0 { for _, e := range m.Versions { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } l = m.PreferredVersion.Size() n += 1 + l + sovGenerated(uint64(l)) if len(m.ServerAddressByClientCIDRs) > 0 { for _, e := range m.ServerAddressByClientCIDRs { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *APIGroupList) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.Groups) > 0 { for _, e := range m.Groups { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *APIResource) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) n += 2 l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) if m.Verbs != nil { l = m.Verbs.Size() n += 1 + l + sovGenerated(uint64(l)) } if len(m.ShortNames) > 0 { for _, s := range m.ShortNames { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } l = len(m.SingularName) n += 1 + l + sovGenerated(uint64(l)) if len(m.Categories) > 0 { for _, s := range m.Categories { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Version) n += 1 + l + sovGenerated(uint64(l)) l = len(m.StorageVersionHash) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *APIResourceList) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.GroupVersion) n += 1 + l + sovGenerated(uint64(l)) if len(m.APIResources) > 0 { for _, e := range m.APIResources { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *APIVersions) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.Versions) > 0 { for _, s := range m.Versions { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } if len(m.ServerAddressByClientCIDRs) > 0 { for _, e := range m.ServerAddressByClientCIDRs { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *ApplyOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.DryRun) > 0 { for _, s := range m.DryRun { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } n += 2 l = len(m.FieldManager) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *Condition) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Type) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Status) n += 1 + l + sovGenerated(uint64(l)) n += 1 + sovGenerated(uint64(m.ObservedGeneration)) l = m.LastTransitionTime.Size() n += 1 + l + sovGenerated(uint64(l)) l = len(m.Reason) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *CreateOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.DryRun) > 0 { for _, s := range m.DryRun { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } l = len(m.FieldManager) n += 1 + l + sovGenerated(uint64(l)) l = len(m.FieldValidation) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *DeleteOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.GracePeriodSeconds != nil { n += 1 + sovGenerated(uint64(*m.GracePeriodSeconds)) } if m.Preconditions != nil { l = m.Preconditions.Size() n += 1 + l + sovGenerated(uint64(l)) } if m.OrphanDependents != nil { n += 2 } if m.PropagationPolicy != nil { l = len(*m.PropagationPolicy) n += 1 + l + sovGenerated(uint64(l)) } if len(m.DryRun) > 0 { for _, s := range m.DryRun { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *Duration) Size() (n int) { if m == nil { return 0 } var l int _ = l n += 1 + sovGenerated(uint64(m.Duration)) return n } func (m *FieldsV1) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.Raw != nil { l = len(m.Raw) n += 1 + l + sovGenerated(uint64(l)) } return n } func (m *GetOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.ResourceVersion) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *GroupKind) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *GroupResource) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Resource) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *GroupVersion) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Version) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *GroupVersionForDiscovery) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.GroupVersion) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Version) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *GroupVersionKind) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Version) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *GroupVersionResource) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Version) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Resource) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *LabelSelector) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.MatchLabels) > 0 { for k, v := range m.MatchLabels { _ = k _ = v mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } if len(m.MatchExpressions) > 0 { for _, e := range m.MatchExpressions { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *LabelSelectorRequirement) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Key) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Operator) n += 1 + l + sovGenerated(uint64(l)) if len(m.Values) > 0 { for _, s := range m.Values { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *List) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.ListMeta.Size() n += 1 + l + sovGenerated(uint64(l)) if len(m.Items) > 0 { for _, e := range m.Items { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *ListMeta) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.SelfLink) n += 1 + l + sovGenerated(uint64(l)) l = len(m.ResourceVersion) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Continue) n += 1 + l + sovGenerated(uint64(l)) if m.RemainingItemCount != nil { n += 1 + sovGenerated(uint64(*m.RemainingItemCount)) } return n } func (m *ListOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.LabelSelector) n += 1 + l + sovGenerated(uint64(l)) l = len(m.FieldSelector) n += 1 + l + sovGenerated(uint64(l)) n += 2 l = len(m.ResourceVersion) n += 1 + l + sovGenerated(uint64(l)) if m.TimeoutSeconds != nil { n += 1 + sovGenerated(uint64(*m.TimeoutSeconds)) } n += 1 + sovGenerated(uint64(m.Limit)) l = len(m.Continue) n += 1 + l + sovGenerated(uint64(l)) n += 2 l = len(m.ResourceVersionMatch) n += 1 + l + sovGenerated(uint64(l)) if m.SendInitialEvents != nil { n += 2 } return n } func (m *ManagedFieldsEntry) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Manager) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Operation) n += 1 + l + sovGenerated(uint64(l)) l = len(m.APIVersion) n += 1 + l + sovGenerated(uint64(l)) if m.Time != nil { l = m.Time.Size() n += 1 + l + sovGenerated(uint64(l)) } l = len(m.FieldsType) n += 1 + l + sovGenerated(uint64(l)) if m.FieldsV1 != nil { l = m.FieldsV1.Size() n += 1 + l + sovGenerated(uint64(l)) } l = len(m.Subresource) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *ObjectMeta) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) l = len(m.GenerateName) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Namespace) n += 1 + l + sovGenerated(uint64(l)) l = len(m.SelfLink) n += 1 + l + sovGenerated(uint64(l)) l = len(m.UID) n += 1 + l + sovGenerated(uint64(l)) l = len(m.ResourceVersion) n += 1 + l + sovGenerated(uint64(l)) n += 1 + sovGenerated(uint64(m.Generation)) l = m.CreationTimestamp.Size() n += 1 + l + sovGenerated(uint64(l)) if m.DeletionTimestamp != nil { l = m.DeletionTimestamp.Size() n += 1 + l + sovGenerated(uint64(l)) } if m.DeletionGracePeriodSeconds != nil { n += 1 + sovGenerated(uint64(*m.DeletionGracePeriodSeconds)) } if len(m.Labels) > 0 { for k, v := range m.Labels { _ = k _ = v mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } if len(m.Annotations) > 0 { for k, v := range m.Annotations { _ = k _ = v mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } if len(m.OwnerReferences) > 0 { for _, e := range m.OwnerReferences { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } if len(m.Finalizers) > 0 { for _, s := range m.Finalizers { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } if len(m.ManagedFields) > 0 { for _, e := range m.ManagedFields { l = e.Size() n += 2 + l + sovGenerated(uint64(l)) } } return n } func (m *OwnerReference) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) l = len(m.UID) n += 1 + l + sovGenerated(uint64(l)) l = len(m.APIVersion) n += 1 + l + sovGenerated(uint64(l)) if m.Controller != nil { n += 2 } if m.BlockOwnerDeletion != nil { n += 2 } return n } func (m *PartialObjectMetadata) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.ObjectMeta.Size() n += 1 + l + sovGenerated(uint64(l)) return n } func (m *PartialObjectMetadataList) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.ListMeta.Size() n += 1 + l + sovGenerated(uint64(l)) if len(m.Items) > 0 { for _, e := range m.Items { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *Patch) Size() (n int) { if m == nil { return 0 } var l int _ = l return n } func (m *PatchOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.DryRun) > 0 { for _, s := range m.DryRun { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } if m.Force != nil { n += 2 } l = len(m.FieldManager) n += 1 + l + sovGenerated(uint64(l)) l = len(m.FieldValidation) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *Preconditions) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.UID != nil { l = len(*m.UID) n += 1 + l + sovGenerated(uint64(l)) } if m.ResourceVersion != nil { l = len(*m.ResourceVersion) n += 1 + l + sovGenerated(uint64(l)) } return n } func (m *RootPaths) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.Paths) > 0 { for _, s := range m.Paths { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *ServerAddressByClientCIDR) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.ClientCIDR) n += 1 + l + sovGenerated(uint64(l)) l = len(m.ServerAddress) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *Status) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.ListMeta.Size() n += 1 + l + sovGenerated(uint64(l)) l = len(m.Status) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Reason) n += 1 + l + sovGenerated(uint64(l)) if m.Details != nil { l = m.Details.Size() n += 1 + l + sovGenerated(uint64(l)) } n += 1 + sovGenerated(uint64(m.Code)) return n } func (m *StatusCause) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Type) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Field) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *StatusDetails) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Name) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Group) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) if len(m.Causes) > 0 { for _, e := range m.Causes { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } n += 1 + sovGenerated(uint64(m.RetryAfterSeconds)) l = len(m.UID) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *TableOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.IncludeObject) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *Timestamp) Size() (n int) { if m == nil { return 0 } var l int _ = l n += 1 + sovGenerated(uint64(m.Seconds)) n += 1 + sovGenerated(uint64(m.Nanos)) return n } func (m *TypeMeta) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) l = len(m.APIVersion) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *UpdateOptions) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.DryRun) > 0 { for _, s := range m.DryRun { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } l = len(m.FieldManager) n += 1 + l + sovGenerated(uint64(l)) l = len(m.FieldValidation) n += 1 + l + sovGenerated(uint64(l)) return n } func (m Verbs) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m) > 0 { for _, s := range m { l = len(s) n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *WatchEvent) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Type) n += 1 + l + sovGenerated(uint64(l)) l = m.Object.Size() n += 1 + l + sovGenerated(uint64(l)) return n } func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (this *APIGroup) String() string { if this == nil { return "nil" } repeatedStringForVersions := "[]GroupVersionForDiscovery{" for _, f := range this.Versions { repeatedStringForVersions += strings.Replace(strings.Replace(f.String(), "GroupVersionForDiscovery", "GroupVersionForDiscovery", 1), `&`, ``, 1) + "," } repeatedStringForVersions += "}" repeatedStringForServerAddressByClientCIDRs := "[]ServerAddressByClientCIDR{" for _, f := range this.ServerAddressByClientCIDRs { repeatedStringForServerAddressByClientCIDRs += strings.Replace(strings.Replace(f.String(), "ServerAddressByClientCIDR", "ServerAddressByClientCIDR", 1), `&`, ``, 1) + "," } repeatedStringForServerAddressByClientCIDRs += "}" s := strings.Join([]string{`&APIGroup{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Versions:` + repeatedStringForVersions + `,`, `PreferredVersion:` + strings.Replace(strings.Replace(this.PreferredVersion.String(), "GroupVersionForDiscovery", "GroupVersionForDiscovery", 1), `&`, ``, 1) + `,`, `ServerAddressByClientCIDRs:` + repeatedStringForServerAddressByClientCIDRs + `,`, `}`, }, "") return s } func (this *APIGroupList) String() string { if this == nil { return "nil" } repeatedStringForGroups := "[]APIGroup{" for _, f := range this.Groups { repeatedStringForGroups += strings.Replace(strings.Replace(f.String(), "APIGroup", "APIGroup", 1), `&`, ``, 1) + "," } repeatedStringForGroups += "}" s := strings.Join([]string{`&APIGroupList{`, `Groups:` + repeatedStringForGroups + `,`, `}`, }, "") return s } func (this *APIResource) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&APIResource{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Namespaced:` + fmt.Sprintf("%v", this.Namespaced) + `,`, `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, `Verbs:` + strings.Replace(fmt.Sprintf("%v", this.Verbs), "Verbs", "Verbs", 1) + `,`, `ShortNames:` + fmt.Sprintf("%v", this.ShortNames) + `,`, `SingularName:` + fmt.Sprintf("%v", this.SingularName) + `,`, `Categories:` + fmt.Sprintf("%v", this.Categories) + `,`, `Group:` + fmt.Sprintf("%v", this.Group) + `,`, `Version:` + fmt.Sprintf("%v", this.Version) + `,`, `StorageVersionHash:` + fmt.Sprintf("%v", this.StorageVersionHash) + `,`, `}`, }, "") return s } func (this *APIResourceList) String() string { if this == nil { return "nil" } repeatedStringForAPIResources := "[]APIResource{" for _, f := range this.APIResources { repeatedStringForAPIResources += strings.Replace(strings.Replace(f.String(), "APIResource", "APIResource", 1), `&`, ``, 1) + "," } repeatedStringForAPIResources += "}" s := strings.Join([]string{`&APIResourceList{`, `GroupVersion:` + fmt.Sprintf("%v", this.GroupVersion) + `,`, `APIResources:` + repeatedStringForAPIResources + `,`, `}`, }, "") return s } func (this *ApplyOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&ApplyOptions{`, `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, `Force:` + fmt.Sprintf("%v", this.Force) + `,`, `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, `}`, }, "") return s } func (this *Condition) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Condition{`, `Type:` + fmt.Sprintf("%v", this.Type) + `,`, `Status:` + fmt.Sprintf("%v", this.Status) + `,`, `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "Time", 1), `&`, ``, 1) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `}`, }, "") return s } func (this *CreateOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&CreateOptions{`, `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, `FieldValidation:` + fmt.Sprintf("%v", this.FieldValidation) + `,`, `}`, }, "") return s } func (this *DeleteOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&DeleteOptions{`, `GracePeriodSeconds:` + valueToStringGenerated(this.GracePeriodSeconds) + `,`, `Preconditions:` + strings.Replace(this.Preconditions.String(), "Preconditions", "Preconditions", 1) + `,`, `OrphanDependents:` + valueToStringGenerated(this.OrphanDependents) + `,`, `PropagationPolicy:` + valueToStringGenerated(this.PropagationPolicy) + `,`, `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, `}`, }, "") return s } func (this *Duration) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Duration{`, `Duration:` + fmt.Sprintf("%v", this.Duration) + `,`, `}`, }, "") return s } func (this *GetOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&GetOptions{`, `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, `}`, }, "") return s } func (this *GroupVersionForDiscovery) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&GroupVersionForDiscovery{`, `GroupVersion:` + fmt.Sprintf("%v", this.GroupVersion) + `,`, `Version:` + fmt.Sprintf("%v", this.Version) + `,`, `}`, }, "") return s } func (this *LabelSelector) String() string { if this == nil { return "nil" } repeatedStringForMatchExpressions := "[]LabelSelectorRequirement{" for _, f := range this.MatchExpressions { repeatedStringForMatchExpressions += strings.Replace(strings.Replace(f.String(), "LabelSelectorRequirement", "LabelSelectorRequirement", 1), `&`, ``, 1) + "," } repeatedStringForMatchExpressions += "}" keysForMatchLabels := make([]string, 0, len(this.MatchLabels)) for k := range this.MatchLabels { keysForMatchLabels = append(keysForMatchLabels, k) } github_com_gogo_protobuf_sortkeys.Strings(keysForMatchLabels) mapStringForMatchLabels := "map[string]string{" for _, k := range keysForMatchLabels { mapStringForMatchLabels += fmt.Sprintf("%v: %v,", k, this.MatchLabels[k]) } mapStringForMatchLabels += "}" s := strings.Join([]string{`&LabelSelector{`, `MatchLabels:` + mapStringForMatchLabels + `,`, `MatchExpressions:` + repeatedStringForMatchExpressions + `,`, `}`, }, "") return s } func (this *LabelSelectorRequirement) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&LabelSelectorRequirement{`, `Key:` + fmt.Sprintf("%v", this.Key) + `,`, `Operator:` + fmt.Sprintf("%v", this.Operator) + `,`, `Values:` + fmt.Sprintf("%v", this.Values) + `,`, `}`, }, "") return s } func (this *List) String() string { if this == nil { return "nil" } repeatedStringForItems := "[]RawExtension{" for _, f := range this.Items { repeatedStringForItems += fmt.Sprintf("%v", f) + "," } repeatedStringForItems += "}" s := strings.Join([]string{`&List{`, `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "ListMeta", 1), `&`, ``, 1) + `,`, `Items:` + repeatedStringForItems + `,`, `}`, }, "") return s } func (this *ListMeta) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&ListMeta{`, `SelfLink:` + fmt.Sprintf("%v", this.SelfLink) + `,`, `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, `Continue:` + fmt.Sprintf("%v", this.Continue) + `,`, `RemainingItemCount:` + valueToStringGenerated(this.RemainingItemCount) + `,`, `}`, }, "") return s } func (this *ListOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&ListOptions{`, `LabelSelector:` + fmt.Sprintf("%v", this.LabelSelector) + `,`, `FieldSelector:` + fmt.Sprintf("%v", this.FieldSelector) + `,`, `Watch:` + fmt.Sprintf("%v", this.Watch) + `,`, `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, `Limit:` + fmt.Sprintf("%v", this.Limit) + `,`, `Continue:` + fmt.Sprintf("%v", this.Continue) + `,`, `AllowWatchBookmarks:` + fmt.Sprintf("%v", this.AllowWatchBookmarks) + `,`, `ResourceVersionMatch:` + fmt.Sprintf("%v", this.ResourceVersionMatch) + `,`, `SendInitialEvents:` + valueToStringGenerated(this.SendInitialEvents) + `,`, `}`, }, "") return s } func (this *ManagedFieldsEntry) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&ManagedFieldsEntry{`, `Manager:` + fmt.Sprintf("%v", this.Manager) + `,`, `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, `Time:` + strings.Replace(fmt.Sprintf("%v", this.Time), "Time", "Time", 1) + `,`, `FieldsType:` + fmt.Sprintf("%v", this.FieldsType) + `,`, `FieldsV1:` + strings.Replace(fmt.Sprintf("%v", this.FieldsV1), "FieldsV1", "FieldsV1", 1) + `,`, `Subresource:` + fmt.Sprintf("%v", this.Subresource) + `,`, `}`, }, "") return s } func (this *ObjectMeta) String() string { if this == nil { return "nil" } repeatedStringForOwnerReferences := "[]OwnerReference{" for _, f := range this.OwnerReferences { repeatedStringForOwnerReferences += strings.Replace(strings.Replace(f.String(), "OwnerReference", "OwnerReference", 1), `&`, ``, 1) + "," } repeatedStringForOwnerReferences += "}" repeatedStringForManagedFields := "[]ManagedFieldsEntry{" for _, f := range this.ManagedFields { repeatedStringForManagedFields += strings.Replace(strings.Replace(f.String(), "ManagedFieldsEntry", "ManagedFieldsEntry", 1), `&`, ``, 1) + "," } repeatedStringForManagedFields += "}" keysForLabels := make([]string, 0, len(this.Labels)) for k := range this.Labels { keysForLabels = append(keysForLabels, k) } github_com_gogo_protobuf_sortkeys.Strings(keysForLabels) mapStringForLabels := "map[string]string{" for _, k := range keysForLabels { mapStringForLabels += fmt.Sprintf("%v: %v,", k, this.Labels[k]) } mapStringForLabels += "}" keysForAnnotations := make([]string, 0, len(this.Annotations)) for k := range this.Annotations { keysForAnnotations = append(keysForAnnotations, k) } github_com_gogo_protobuf_sortkeys.Strings(keysForAnnotations) mapStringForAnnotations := "map[string]string{" for _, k := range keysForAnnotations { mapStringForAnnotations += fmt.Sprintf("%v: %v,", k, this.Annotations[k]) } mapStringForAnnotations += "}" s := strings.Join([]string{`&ObjectMeta{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `GenerateName:` + fmt.Sprintf("%v", this.GenerateName) + `,`, `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, `SelfLink:` + fmt.Sprintf("%v", this.SelfLink) + `,`, `UID:` + fmt.Sprintf("%v", this.UID) + `,`, `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, `Generation:` + fmt.Sprintf("%v", this.Generation) + `,`, `CreationTimestamp:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CreationTimestamp), "Time", "Time", 1), `&`, ``, 1) + `,`, `DeletionTimestamp:` + strings.Replace(fmt.Sprintf("%v", this.DeletionTimestamp), "Time", "Time", 1) + `,`, `DeletionGracePeriodSeconds:` + valueToStringGenerated(this.DeletionGracePeriodSeconds) + `,`, `Labels:` + mapStringForLabels + `,`, `Annotations:` + mapStringForAnnotations + `,`, `OwnerReferences:` + repeatedStringForOwnerReferences + `,`, `Finalizers:` + fmt.Sprintf("%v", this.Finalizers) + `,`, `ManagedFields:` + repeatedStringForManagedFields + `,`, `}`, }, "") return s } func (this *OwnerReference) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&OwnerReference{`, `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `UID:` + fmt.Sprintf("%v", this.UID) + `,`, `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, `Controller:` + valueToStringGenerated(this.Controller) + `,`, `BlockOwnerDeletion:` + valueToStringGenerated(this.BlockOwnerDeletion) + `,`, `}`, }, "") return s } func (this *PartialObjectMetadata) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&PartialObjectMetadata{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "ObjectMeta", 1), `&`, ``, 1) + `,`, `}`, }, "") return s } func (this *PartialObjectMetadataList) String() string { if this == nil { return "nil" } repeatedStringForItems := "[]PartialObjectMetadata{" for _, f := range this.Items { repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "PartialObjectMetadata", "PartialObjectMetadata", 1), `&`, ``, 1) + "," } repeatedStringForItems += "}" s := strings.Join([]string{`&PartialObjectMetadataList{`, `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "ListMeta", 1), `&`, ``, 1) + `,`, `Items:` + repeatedStringForItems + `,`, `}`, }, "") return s } func (this *Patch) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Patch{`, `}`, }, "") return s } func (this *PatchOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&PatchOptions{`, `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, `Force:` + valueToStringGenerated(this.Force) + `,`, `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, `FieldValidation:` + fmt.Sprintf("%v", this.FieldValidation) + `,`, `}`, }, "") return s } func (this *Preconditions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Preconditions{`, `UID:` + valueToStringGenerated(this.UID) + `,`, `ResourceVersion:` + valueToStringGenerated(this.ResourceVersion) + `,`, `}`, }, "") return s } func (this *RootPaths) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&RootPaths{`, `Paths:` + fmt.Sprintf("%v", this.Paths) + `,`, `}`, }, "") return s } func (this *ServerAddressByClientCIDR) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&ServerAddressByClientCIDR{`, `ClientCIDR:` + fmt.Sprintf("%v", this.ClientCIDR) + `,`, `ServerAddress:` + fmt.Sprintf("%v", this.ServerAddress) + `,`, `}`, }, "") return s } func (this *Status) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Status{`, `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "ListMeta", 1), `&`, ``, 1) + `,`, `Status:` + fmt.Sprintf("%v", this.Status) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `Details:` + strings.Replace(this.Details.String(), "StatusDetails", "StatusDetails", 1) + `,`, `Code:` + fmt.Sprintf("%v", this.Code) + `,`, `}`, }, "") return s } func (this *StatusCause) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&StatusCause{`, `Type:` + fmt.Sprintf("%v", this.Type) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `Field:` + fmt.Sprintf("%v", this.Field) + `,`, `}`, }, "") return s } func (this *StatusDetails) String() string { if this == nil { return "nil" } repeatedStringForCauses := "[]StatusCause{" for _, f := range this.Causes { repeatedStringForCauses += strings.Replace(strings.Replace(f.String(), "StatusCause", "StatusCause", 1), `&`, ``, 1) + "," } repeatedStringForCauses += "}" s := strings.Join([]string{`&StatusDetails{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Group:` + fmt.Sprintf("%v", this.Group) + `,`, `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, `Causes:` + repeatedStringForCauses + `,`, `RetryAfterSeconds:` + fmt.Sprintf("%v", this.RetryAfterSeconds) + `,`, `UID:` + fmt.Sprintf("%v", this.UID) + `,`, `}`, }, "") return s } func (this *TableOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&TableOptions{`, `IncludeObject:` + fmt.Sprintf("%v", this.IncludeObject) + `,`, `}`, }, "") return s } func (this *Timestamp) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Timestamp{`, `Seconds:` + fmt.Sprintf("%v", this.Seconds) + `,`, `Nanos:` + fmt.Sprintf("%v", this.Nanos) + `,`, `}`, }, "") return s } func (this *TypeMeta) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&TypeMeta{`, `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, `}`, }, "") return s } func (this *UpdateOptions) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&UpdateOptions{`, `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, `FieldValidation:` + fmt.Sprintf("%v", this.FieldValidation) + `,`, `}`, }, "") return s } func (this *WatchEvent) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&WatchEvent{`, `Type:` + fmt.Sprintf("%v", this.Type) + `,`, `Object:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Object), "RawExtension", "runtime.RawExtension", 1), `&`, ``, 1) + `,`, `}`, }, "") return s } func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { return "nil" } pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } func (m *APIGroup) 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: APIGroup: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: APIGroup: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Versions", 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.Versions = append(m.Versions, GroupVersionForDiscovery{}) if err := m.Versions[len(m.Versions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PreferredVersion", 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 } if err := m.PreferredVersion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ServerAddressByClientCIDRs", 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.ServerAddressByClientCIDRs = append(m.ServerAddressByClientCIDRs, ServerAddressByClientCIDR{}) if err := m.ServerAddressByClientCIDRs[len(m.ServerAddressByClientCIDRs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *APIGroupList) 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: APIGroupList: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: APIGroupList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Groups", 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.Groups = append(m.Groups, APIGroup{}) if err := m.Groups[len(m.Groups)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *APIResource) 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: APIResource: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: APIResource: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Namespaced", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.Namespaced = bool(v != 0) case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Verbs", 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 } if m.Verbs == nil { m.Verbs = Verbs{} } if err := m.Verbs.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ShortNames", 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.ShortNames = append(m.ShortNames, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SingularName", 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.SingularName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Categories", 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.Categories = append(m.Categories, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Version", 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.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StorageVersionHash", 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.StorageVersionHash = string(dAtA[iNdEx:postIndex]) 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 *APIResourceList) 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: APIResourceList: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: APIResourceList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field GroupVersion", 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.GroupVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field APIResources", 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.APIResources = append(m.APIResources, APIResource{}) if err := m.APIResources[len(m.APIResources)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *APIVersions) 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: APIVersions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: APIVersions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Versions", 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.Versions = append(m.Versions, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ServerAddressByClientCIDRs", 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.ServerAddressByClientCIDRs = append(m.ServerAddressByClientCIDRs, ServerAddressByClientCIDR{}) if err := m.ServerAddressByClientCIDRs[len(m.ServerAddressByClientCIDRs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *ApplyOptions) 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: ApplyOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ApplyOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.Force = bool(v != 0) case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", 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.FieldManager = string(dAtA[iNdEx:postIndex]) 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 *Condition) 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: Condition: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Condition: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Type", 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.Type = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Status", 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.Status = ConditionStatus(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) } m.ObservedGeneration = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.ObservedGeneration |= int64(b&0x7F) << shift if b < 0x80 { break } } case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", 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 } if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Reason", 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.Reason = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Message", 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.Message = string(dAtA[iNdEx:postIndex]) 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 *CreateOptions) 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: CreateOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: CreateOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", 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.FieldManager = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldValidation", 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.FieldValidation = string(dAtA[iNdEx:postIndex]) 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 *DeleteOptions) 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: DeleteOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: DeleteOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field GracePeriodSeconds", wireType) } var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int64(b&0x7F) << shift if b < 0x80 { break } } m.GracePeriodSeconds = &v case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Preconditions", 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 } if m.Preconditions == nil { m.Preconditions = &Preconditions{} } if err := m.Preconditions.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field OrphanDependents", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } b := bool(v != 0) m.OrphanDependents = &b case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PropagationPolicy", 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 := DeletionPropagation(dAtA[iNdEx:postIndex]) m.PropagationPolicy = &s iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) 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 *Duration) 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: Duration: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Duration: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType) } m.Duration = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Duration |= time.Duration(b&0x7F) << shift if b < 0x80 { break } } 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 *FieldsV1) 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: FieldsV1: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: FieldsV1: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...) if m.Raw == nil { m.Raw = []byte{} } 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 *GetOptions) 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: GetOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GetOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", 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.ResourceVersion = string(dAtA[iNdEx:postIndex]) 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 *GroupKind) 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: GroupKind: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GroupKind: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) 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 *GroupResource) 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: GroupResource: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GroupResource: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Resource", 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.Resource = string(dAtA[iNdEx:postIndex]) 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 *GroupVersion) 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: GroupVersion: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GroupVersion: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Version", 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.Version = string(dAtA[iNdEx:postIndex]) 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 *GroupVersionForDiscovery) 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: GroupVersionForDiscovery: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GroupVersionForDiscovery: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field GroupVersion", 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.GroupVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Version", 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.Version = string(dAtA[iNdEx:postIndex]) 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 *GroupVersionKind) 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: GroupVersionKind: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GroupVersionKind: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Version", 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.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) 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 *GroupVersionResource) 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: GroupVersionResource: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: GroupVersionResource: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Version", 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.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Resource", 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.Resource = string(dAtA[iNdEx:postIndex]) 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 *LabelSelector) 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: LabelSelector: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: LabelSelector: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field MatchLabels", 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 } if m.MatchLabels == nil { m.MatchLabels = make(map[string]string) } var mapkey string var mapvalue string for iNdEx < postIndex { entryPreIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) if fieldNum == 1 { var stringLenmapkey uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapkey |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapkey := int(stringLenmapkey) if intStringLenmapkey < 0 { return ErrInvalidLengthGenerated } postStringIndexmapkey := iNdEx + intStringLenmapkey if postStringIndexmapkey < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapkey > l { return io.ErrUnexpectedEOF } mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) iNdEx = postStringIndexmapkey } else if fieldNum == 2 { var stringLenmapvalue uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapvalue |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapvalue := int(stringLenmapvalue) if intStringLenmapvalue < 0 { return ErrInvalidLengthGenerated } postStringIndexmapvalue := iNdEx + intStringLenmapvalue if postStringIndexmapvalue < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapvalue > l { return io.ErrUnexpectedEOF } mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) iNdEx = postStringIndexmapvalue } else { iNdEx = entryPreIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > postIndex { return io.ErrUnexpectedEOF } iNdEx += skippy } } m.MatchLabels[mapkey] = mapvalue iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field MatchExpressions", 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.MatchExpressions = append(m.MatchExpressions, LabelSelectorRequirement{}) if err := m.MatchExpressions[len(m.MatchExpressions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *LabelSelectorRequirement) 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: LabelSelectorRequirement: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: LabelSelectorRequirement: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Key", 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.Key = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Operator", 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.Operator = LabelSelectorOperator(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Values", 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.Values = append(m.Values, string(dAtA[iNdEx:postIndex])) 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 *List) 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: List: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: List: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 } if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Items", 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.Items = append(m.Items, runtime.RawExtension{}) if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *ListMeta) 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: ListMeta: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ListMeta: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SelfLink", 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.SelfLink = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", 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.ResourceVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Continue", 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.Continue = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field RemainingItemCount", wireType) } var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int64(b&0x7F) << shift if b < 0x80 { break } } m.RemainingItemCount = &v 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 *ListOptions) 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: ListOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ListOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LabelSelector", 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.LabelSelector = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldSelector", 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.FieldSelector = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Watch", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.Watch = bool(v != 0) case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", 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.ResourceVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field TimeoutSeconds", wireType) } var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int64(b&0x7F) << shift if b < 0x80 { break } } m.TimeoutSeconds = &v case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) } m.Limit = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Limit |= int64(b&0x7F) << shift if b < 0x80 { break } } case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Continue", 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.Continue = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 9: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field AllowWatchBookmarks", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.AllowWatchBookmarks = bool(v != 0) case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersionMatch", 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.ResourceVersionMatch = ResourceVersionMatch(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field SendInitialEvents", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } b := bool(v != 0) m.SendInitialEvents = &b 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 *ManagedFieldsEntry) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: ManagedFieldsEntry: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ManagedFieldsEntry: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Manager", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.Manager = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.Operation = ManagedFieldsOperationType(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.APIVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } if m.Time == nil { m.Time = &Time{} } if err := m.Time.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldsType", 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.FieldsType = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldsV1", 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 } if m.FieldsV1 == nil { m.FieldsV1 = &FieldsV1{} } if err := m.FieldsV1.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Subresource", 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.Subresource = string(dAtA[iNdEx:postIndex]) 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 *ObjectMeta) 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: ObjectMeta: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ObjectMeta: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field GenerateName", 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.GenerateName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Namespace", 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.Namespace = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SelfLink", 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.SelfLink = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UID", 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.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", 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.ResourceVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) } m.Generation = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Generation |= int64(b&0x7F) << shift if b < 0x80 { break } } case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CreationTimestamp", 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 } if err := m.CreationTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DeletionTimestamp", 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 } if m.DeletionTimestamp == nil { m.DeletionTimestamp = &Time{} } if err := m.DeletionTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 10: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field DeletionGracePeriodSeconds", wireType) } var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int64(b&0x7F) << shift if b < 0x80 { break } } m.DeletionGracePeriodSeconds = &v case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Labels", 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 } if m.Labels == nil { m.Labels = make(map[string]string) } var mapkey string var mapvalue string for iNdEx < postIndex { entryPreIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) if fieldNum == 1 { var stringLenmapkey uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapkey |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapkey := int(stringLenmapkey) if intStringLenmapkey < 0 { return ErrInvalidLengthGenerated } postStringIndexmapkey := iNdEx + intStringLenmapkey if postStringIndexmapkey < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapkey > l { return io.ErrUnexpectedEOF } mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) iNdEx = postStringIndexmapkey } else if fieldNum == 2 { var stringLenmapvalue uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapvalue |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapvalue := int(stringLenmapvalue) if intStringLenmapvalue < 0 { return ErrInvalidLengthGenerated } postStringIndexmapvalue := iNdEx + intStringLenmapvalue if postStringIndexmapvalue < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapvalue > l { return io.ErrUnexpectedEOF } mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) iNdEx = postStringIndexmapvalue } else { iNdEx = entryPreIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > postIndex { return io.ErrUnexpectedEOF } iNdEx += skippy } } m.Labels[mapkey] = mapvalue iNdEx = postIndex case 12: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Annotations", 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 } if m.Annotations == nil { m.Annotations = make(map[string]string) } var mapkey string var mapvalue string for iNdEx < postIndex { entryPreIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) if fieldNum == 1 { var stringLenmapkey uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapkey |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapkey := int(stringLenmapkey) if intStringLenmapkey < 0 { return ErrInvalidLengthGenerated } postStringIndexmapkey := iNdEx + intStringLenmapkey if postStringIndexmapkey < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapkey > l { return io.ErrUnexpectedEOF } mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) iNdEx = postStringIndexmapkey } else if fieldNum == 2 { var stringLenmapvalue uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapvalue |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapvalue := int(stringLenmapvalue) if intStringLenmapvalue < 0 { return ErrInvalidLengthGenerated } postStringIndexmapvalue := iNdEx + intStringLenmapvalue if postStringIndexmapvalue < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapvalue > l { return io.ErrUnexpectedEOF } mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) iNdEx = postStringIndexmapvalue } else { iNdEx = entryPreIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > postIndex { return io.ErrUnexpectedEOF } iNdEx += skippy } } m.Annotations[mapkey] = mapvalue iNdEx = postIndex case 13: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field OwnerReferences", 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.OwnerReferences = append(m.OwnerReferences, OwnerReference{}) if err := m.OwnerReferences[len(m.OwnerReferences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 14: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Finalizers", 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.Finalizers = append(m.Finalizers, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 17: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ManagedFields", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.ManagedFields = append(m.ManagedFields, ManagedFieldsEntry{}) if err := m.ManagedFields[len(m.ManagedFields)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) 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 *OwnerReference) 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: OwnerReference: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: OwnerReference: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UID", 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.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.APIVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Controller", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } b := bool(v != 0) m.Controller = &b case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field BlockOwnerDeletion", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } b := bool(v != 0) m.BlockOwnerDeletion = &b 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 *PartialObjectMetadata) 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: PartialObjectMetadata: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PartialObjectMetadata: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", 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 } if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *PartialObjectMetadataList) 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: PartialObjectMetadataList: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PartialObjectMetadataList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 } if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Items", 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.Items = append(m.Items, PartialObjectMetadata{}) if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *Patch) 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: Patch: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Patch: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { 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 *PatchOptions) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: PatchOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PatchOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } b := bool(v != 0) m.Force = &b case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", 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.FieldManager = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldValidation", 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.FieldValidation = string(dAtA[iNdEx:postIndex]) 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 *Preconditions) 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: Preconditions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Preconditions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UID", 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 := k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) m.UID = &s iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", 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.ResourceVersion = &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 *RootPaths) 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: RootPaths: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: RootPaths: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Paths", 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.Paths = append(m.Paths, string(dAtA[iNdEx:postIndex])) 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 *ServerAddressByClientCIDR) 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: ServerAddressByClientCIDR: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ServerAddressByClientCIDR: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ClientCIDR", 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.ClientCIDR = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ServerAddress", 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.ServerAddress = string(dAtA[iNdEx:postIndex]) 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 *Status) 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: Status: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 } if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Status", 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.Status = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Message", 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.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Reason", 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.Reason = StatusReason(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Details", 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 } if m.Details == nil { m.Details = &StatusDetails{} } if err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) } m.Code = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Code |= int32(b&0x7F) << shift if b < 0x80 { break } } 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 *StatusCause) 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: StatusCause: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: StatusCause: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Type", 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.Type = CauseType(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Message", 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.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Field", 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.Field = string(dAtA[iNdEx:postIndex]) 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 *StatusDetails) 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: StatusDetails: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: StatusDetails: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Group", 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.Group = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Causes", 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.Causes = append(m.Causes, StatusCause{}) if err := m.Causes[len(m.Causes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field RetryAfterSeconds", wireType) } m.RetryAfterSeconds = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.RetryAfterSeconds |= int32(b&0x7F) << shift if b < 0x80 { break } } case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UID", 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.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) 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 *TableOptions) 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: TableOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: TableOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field IncludeObject", 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.IncludeObject = IncludeObjectPolicy(dAtA[iNdEx:postIndex]) 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 *Timestamp) 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: Timestamp: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Timestamp: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Seconds", wireType) } m.Seconds = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Seconds |= int64(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Nanos", wireType) } m.Nanos = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Nanos |= int32(b&0x7F) << shift if b < 0x80 { break } } 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 *TypeMeta) 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: TypeMeta: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: TypeMeta: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.APIVersion = string(dAtA[iNdEx:postIndex]) 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 *UpdateOptions) 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: UpdateOptions: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: UpdateOptions: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", 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.FieldManager = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field FieldValidation", 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.FieldValidation = string(dAtA[iNdEx:postIndex]) 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 *Verbs) 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: Verbs: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Verbs: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Items", 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 = append(*m, string(dAtA[iNdEx:postIndex])) 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 *WatchEvent) 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: WatchEvent: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: WatchEvent: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Type", 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.Type = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Object", 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 } if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthGenerated } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupGenerated } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthGenerated } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") ) golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/generated.proto000066400000000000000000001524351453143165200242550ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.apis.meta.v1; import "k8s.io/apimachinery/pkg/runtime/generated.proto"; import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/apis/meta/v1"; // APIGroup contains the name, the supported versions, and the preferred version // of a group. message APIGroup { // name is the name of the group. optional string name = 1; // versions are the versions supported in this group. repeated GroupVersionForDiscovery versions = 2; // preferredVersion is the version preferred by the API server, which // probably is the storage version. // +optional optional GroupVersionForDiscovery preferredVersion = 3; // 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. // Clients can use the appropriate server address as per the CIDR that they match. // In case of multiple matches, clients should use the longest matching CIDR. // 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. // +optional repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 4; } // APIGroupList is a list of APIGroup, to allow clients to discover the API at // /apis. message APIGroupList { // groups is a list of APIGroup. repeated APIGroup groups = 1; } // APIResource specifies the name of a resource and whether it is namespaced. message APIResource { // name is the plural name of the resource. optional string name = 1; // singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. // The singularName is more correct for reporting status on a single item and both singular and plural are allowed // from the kubectl CLI interface. optional string singularName = 6; // namespaced indicates if a resource is namespaced or not. optional bool namespaced = 2; // group is the preferred group of the resource. Empty implies the group of the containing resource list. // For subresources, this may have a different value, for example: Scale". optional string group = 8; // version is the preferred version of the resource. Empty implies the version of the containing resource list // For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". optional string version = 9; // kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') optional string kind = 3; // verbs is a list of supported kube verbs (this includes get, list, watch, create, // update, patch, delete, deletecollection, and proxy) optional Verbs verbs = 4; // shortNames is a list of suggested short names of the resource. repeated string shortNames = 5; // categories is a list of the grouped resources this resource belongs to (e.g. 'all') repeated string categories = 7; // The hash value of the storage version, the version this resource is // converted to when written to the data store. Value must be treated // as opaque by clients. Only equality comparison on the value is valid. // This is an alpha feature and may change or be removed in the future. // The field is populated by the apiserver only if the // StorageVersionHash feature gate is enabled. // This field will remain optional even if it graduates. // +optional optional string storageVersionHash = 10; } // APIResourceList is a list of APIResource, it is used to expose the name of the // resources supported in a specific group and version, and if the resource // is namespaced. message APIResourceList { // groupVersion is the group and version this APIResourceList is for. optional string groupVersion = 1; // resources contains the name of the resources and if they are namespaced. repeated APIResource resources = 2; } // APIVersions lists the versions that are available, to allow clients to // discover the API at /api, which is the root path of the legacy v1 API. // // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object message APIVersions { // versions are the api versions that are available. repeated string versions = 1; // 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. // Clients can use the appropriate server address as per the CIDR that they match. // In case of multiple matches, clients should use the longest matching CIDR. // 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. repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 2; } // ApplyOptions may be provided when applying an API object. // FieldManager is required for apply requests. // ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation // that speaks specifically to how the options fields relate to apply. message ApplyOptions { // When present, indicates that modifications should not be // persisted. An invalid or unrecognized dryRun directive will // result in an error response and no further processing of the // request. Valid values are: // - All: all dry run stages will be processed // +optional repeated string dryRun = 1; // Force is going to "force" Apply requests. It means user will // re-acquire conflicting fields owned by other people. optional bool force = 2; // fieldManager is a name associated with the actor or entity // that is making these changes. The value must be less than or // 128 characters long, and only contain printable characters, // as defined by https://golang.org/pkg/unicode/#IsPrint. This // field is required. optional string fieldManager = 3; } // Condition contains details for one aspect of the current state of this API Resource. // --- // This struct is intended for direct use as an array at the field path .status.conditions. For example, // // type FooStatus struct{ // // Represents the observations of a foo's current state. // // Known .status.conditions.type are: "Available", "Progressing", and "Degraded" // // +patchMergeKey=type // // +patchStrategy=merge // // +listType=map // // +listMapKey=type // Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` // // // other fields // } message Condition { // type of condition in CamelCase or in foo.example.com/CamelCase. // --- // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be // useful (see .node.status.conditions), the ability to deconflict is important. // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` // +kubebuilder:validation:MaxLength=316 optional string type = 1; // status of the condition, one of True, False, Unknown. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=True;False;Unknown optional string status = 2; // observedGeneration represents the .metadata.generation that the condition was set based upon. // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date // with respect to the current state of the instance. // +optional // +kubebuilder:validation:Minimum=0 optional int64 observedGeneration = 3; // lastTransitionTime is the last time the condition transitioned from one status to another. // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Type=string // +kubebuilder:validation:Format=date-time optional Time lastTransitionTime = 4; // reason contains a programmatic identifier indicating the reason for the condition's last transition. // Producers of specific condition types may define expected values and meanings for this field, // and whether the values are considered a guaranteed API. // The value should be a CamelCase string. // This field may not be empty. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength=1024 // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` optional string reason = 5; // message is a human readable message indicating details about the transition. // This may be an empty string. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength=32768 optional string message = 6; } // CreateOptions may be provided when creating an API object. message CreateOptions { // When present, indicates that modifications should not be // persisted. An invalid or unrecognized dryRun directive will // result in an error response and no further processing of the // request. Valid values are: // - All: all dry run stages will be processed // +optional repeated string dryRun = 1; // fieldManager is a name associated with the actor or entity // that is making these changes. The value must be less than or // 128 characters long, and only contain printable characters, // as defined by https://golang.org/pkg/unicode/#IsPrint. // +optional optional string fieldManager = 3; // fieldValidation instructs the server on how to handle // objects in the request (POST/PUT/PATCH) containing unknown // or duplicate fields. Valid values are: // - Ignore: This will ignore any unknown fields that are silently // dropped from the object, and will ignore all but the last duplicate // field that the decoder encounters. This is the default behavior // prior to v1.23. // - Warn: This will send a warning via the standard warning response // header for each unknown field that is dropped from the object, and // for each duplicate field that is encountered. The request will // still succeed if there are no other errors, and will only persist // the last of any duplicate fields. This is the default in v1.23+ // - Strict: This will fail the request with a BadRequest error if // any unknown fields would be dropped from the object, or if any // duplicate fields are present. The error returned from the server // will contain all unknown and duplicate fields encountered. // +optional optional string fieldValidation = 4; } // DeleteOptions may be provided when deleting an API object. message DeleteOptions { // The duration in seconds before the object should be deleted. Value must be non-negative integer. // The value zero indicates delete immediately. If this value is nil, the default grace period for the // specified type will be used. // Defaults to a per object value if not specified. zero means delete immediately. // +optional optional int64 gracePeriodSeconds = 1; // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be // returned. // +k8s:conversion-gen=false // +optional optional Preconditions preconditions = 2; // 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. // +optional optional bool orphanDependents = 3; // 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. // +optional optional string propagationPolicy = 4; // When present, indicates that modifications should not be // persisted. An invalid or unrecognized dryRun directive will // result in an error response and no further processing of the // request. Valid values are: // - All: all dry run stages will be processed // +optional repeated string dryRun = 5; } // Duration is a wrapper around time.Duration which supports correct // marshaling to YAML and JSON. In particular, it marshals into strings, which // can be used as map keys in json. message Duration { optional int64 duration = 1; } // 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, // or a string representing a sub-field or item. The string will follow one of these four formats: // 'f:', where is the name of a field in a struct, or key in a map // 'v:', where is the exact json formatted value of a list item // 'i:', where is position of a item in a list // 'k:', where is a map of a list item's key fields to their unique values // If a key maps to an empty Fields value, the field that key represents is part of the set. // // The exact format is defined in sigs.k8s.io/structured-merge-diff // +protobuf.options.(gogoproto.goproto_stringer)=false message FieldsV1 { // Raw is the underlying serialization of this object. optional bytes Raw = 1; } // GetOptions is the standard query options to the standard REST get call. message GetOptions { // 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 // details. // // Defaults to unset // +optional optional string resourceVersion = 1; } // GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types // // +protobuf.options.(gogoproto.goproto_stringer)=false message GroupKind { optional string group = 1; optional string kind = 2; } // GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types // // +protobuf.options.(gogoproto.goproto_stringer)=false message GroupResource { optional string group = 1; optional string resource = 2; } // GroupVersion contains the "group" and the "version", which uniquely identifies the API. // // +protobuf.options.(gogoproto.goproto_stringer)=false message GroupVersion { optional string group = 1; optional string version = 2; } // GroupVersion contains the "group/version" and "version" string of a version. // It is made a struct to keep extensibility. message GroupVersionForDiscovery { // groupVersion specifies the API group and version in the form "group/version" optional string groupVersion = 1; // version specifies the version in the form of "version". This is to save // the clients the trouble of splitting the GroupVersion. optional string version = 2; } // GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion // to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling // // +protobuf.options.(gogoproto.goproto_stringer)=false message GroupVersionKind { optional string group = 1; optional string version = 2; optional string kind = 3; } // GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion // to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling // // +protobuf.options.(gogoproto.goproto_stringer)=false message GroupVersionResource { optional string group = 1; optional string version = 2; optional string resource = 3; } // A label selector is a label query over a set of resources. The result of matchLabels and // matchExpressions are ANDed. An empty label selector matches all objects. A null // label selector matches no objects. // +structType=atomic message LabelSelector { // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels // map is equivalent to an element of matchExpressions, whose key field is "key", the // operator is "In", and the values array contains only "value". The requirements are ANDed. // +optional map matchLabels = 1; // matchExpressions is a list of label selector requirements. The requirements are ANDed. // +optional repeated LabelSelectorRequirement matchExpressions = 2; } // A label selector requirement is a selector that contains values, a key, and an operator that // relates the key and values. message LabelSelectorRequirement { // key is the label key that the selector applies to. optional string key = 1; // operator represents a key's relationship to a set of values. // Valid operators are In, NotIn, Exists and DoesNotExist. 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. This array is replaced during a strategic // merge patch. // +optional repeated string values = 3; } // List holds a list of objects, which may not be known by the server. message List { // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional ListMeta metadata = 1; // List of objects repeated k8s.io.apimachinery.pkg.runtime.RawExtension items = 2; } // ListMeta describes metadata that synthetic resources must have, including lists and // various status objects. A resource may have only one of {ObjectMeta, ListMeta}. message ListMeta { // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. // +optional optional string selfLink = 1; // String that identifies the server's internal version of this object that // can be used by clients to determine when objects have changed. // Value must be treated as opaque by clients and passed unmodified back to the server. // Populated by the system. // Read-only. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency // +optional optional string resourceVersion = 2; // continue may be set if the user set a limit on the number of items returned, and indicates that // the server has more data available. The value is opaque and may be used to issue another request // to the endpoint that served this list to retrieve the next set of available objects. Continuing a // consistent list may not be possible if the server configuration has changed or more than a few // minutes have passed. The resourceVersion field returned when using this continue value will be // identical to the value in the first response, unless you have received this token from an error // message. optional string continue = 3; // remainingItemCount is the number of subsequent items in the list which are not included in this // list response. If the list request contained label or field selectors, then the number of // remaining items is unknown and the field will be left unset and omitted during serialization. // If the list is complete (either because it is not chunking or because this is the last chunk), // then there are no more remaining items and this field will be left unset and omitted during // serialization. // Servers older than v1.15 do not set this field. // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients // should not rely on the remainingItemCount to be set or to be exact. // +optional optional int64 remainingItemCount = 4; } // ListOptions is the query options to a standard REST list call. message ListOptions { // A selector to restrict the list of returned objects by their labels. // Defaults to everything. // +optional optional string labelSelector = 1; // A selector to restrict the list of returned objects by their fields. // Defaults to everything. // +optional optional string fieldSelector = 2; // Watch for changes to the described resources and return them as a stream of // add, update, and remove notifications. Specify resourceVersion. // +optional optional bool watch = 3; // allowWatchBookmarks requests watch events with type "BOOKMARK". // Servers that do not implement bookmarks may ignore this flag and // bookmarks are sent at the server's discretion. Clients should not // 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. // +optional optional bool allowWatchBookmarks = 9; // 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 // details. // // Defaults to unset // +optional optional string resourceVersion = 4; // resourceVersionMatch determines how resourceVersion is applied to list calls. // It is highly recommended that resourceVersionMatch be set for list calls where // resourceVersion is set // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for // details. // // Defaults to unset // +optional optional string resourceVersionMatch = 10; // Timeout for the list/watch call. // This limits the duration of the call, regardless of any activity or inactivity. // +optional optional int64 timeoutSeconds = 5; // limit is a maximum number of responses to return for a list call. If more items exist, the // server will set the `continue` field on the list metadata to a value that can be used with the // same initial query to retrieve the next set of results. Setting a limit may return fewer than // the requested amount of items (up to zero items) in the event all requested objects are // filtered out and clients should only use the presence of the continue field to determine whether // more results are available. Servers may choose not to support the limit argument and will return // all of the available results. If limit is specified and the continue field is empty, clients may // assume that no more results are available. This field is not supported if watch is true. // // The server guarantees that the objects returned when using continue will be identical to issuing // a single list call without a limit - that is, no objects created, modified, or deleted after the // first request is issued will be included in any subsequent continued requests. This is sometimes // referred to as a consistent snapshot, and ensures that a client that is using limit to receive // smaller chunks of a very large result can ensure they see all possible objects. If objects are // updated during a chunked list the version of the object that was present at the time the first list // result was calculated is returned. optional int64 limit = 7; // The continue option should be set when retrieving more results from the server. Since this value is // server defined, clients may only use the continue value from a previous query result with identical // query parameters (except for the value of continue) and the server may reject a continue value it // does not recognize. If the specified continue value is no longer valid whether due to expiration // (generally five to fifteen minutes) or a configuration change on the server, the server will // respond with a 410 ResourceExpired error together with a continue token. If the client needs a // consistent list, it must restart their list without the continue field. Otherwise, the client may // send another list request with the token received with the 410 error, the server will respond with // a list starting from the next key, but from the latest snapshot, which is inconsistent from the // previous list results - objects that are created, modified, or deleted after the first list request // will be included in the response, as long as their keys are after the "next key". // // This field is not supported when watch is true. Clients may start a watch from the last // resourceVersion value returned by the server and not miss any modifications. optional string continue = 8; // `sendInitialEvents=true` may be set together with `watch=true`. // In that case, the watch stream will begin with synthetic events to // produce the current state of objects in the collection. Once all such // events have been sent, a synthetic "Bookmark" event will be sent. // The bookmark will report the ResourceVersion (RV) corresponding to the // set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. // Afterwards, the watch stream will proceed as usual, sending watch events // corresponding to changes (subsequent to the RV) to objects watched. // // When `sendInitialEvents` option is set, we require `resourceVersionMatch` // option to also be set. The semantic of the watch request is as following: // - `resourceVersionMatch` = NotOlderThan // is interpreted as "data at least as new as the provided `resourceVersion`" // and the bookmark event is send when the state is synced // to a `resourceVersion` at least as fresh as the one provided by the ListOptions. // If `resourceVersion` is unset, this is interpreted as "consistent read" and the // bookmark event is send when the state is synced at least to the moment // when request started being processed. // - `resourceVersionMatch` set to any other value or unset // Invalid error is returned. // // Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward // compatibility reasons) and to false otherwise. // +optional optional bool sendInitialEvents = 11; } // ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource // that the fieldset applies to. message ManagedFieldsEntry { // Manager is an identifier of the workflow managing these fields. optional string manager = 1; // Operation is the type of operation which lead to this ManagedFieldsEntry being created. // The only valid values for this field are 'Apply' and 'Update'. optional string operation = 2; // APIVersion defines the version of this resource that this field set // applies to. The format is "group/version" just like the top-level // APIVersion field. It is necessary to track the version of a field // set because it cannot be automatically converted. optional string apiVersion = 3; // Time is the timestamp of when the ManagedFields entry was added. The // timestamp will also be updated if a field is added, the manager // changes any of the owned fields value or removes a field. The // timestamp does not update when a field is removed from the entry // because another manager took it over. // +optional optional Time time = 4; // FieldsType is the discriminator for the different fields format and version. // There is currently only one possible value: "FieldsV1" optional string fieldsType = 6; // FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. // +optional optional FieldsV1 fieldsV1 = 7; // Subresource is the name of the subresource used to update that object, or // empty string if the object was updated through the main resource. The // value of this field is used to distinguish between managers, even if they // share the same name. For example, a status update will be distinct from a // regular update using the same manager name. // Note that the APIVersion field is not related to the Subresource field and // it always corresponds to the version of the main resource. optional string subresource = 8; } // MicroTime is version of Time with microsecond level precision. // // +protobuf.options.marshal=false // +protobuf.as=Timestamp // +protobuf.options.(gogoproto.goproto_stringer)=false message MicroTime { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. optional int64 seconds = 1; // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. This field may be limited in precision depending on context. optional int32 nanos = 2; } // ObjectMeta is metadata that all persisted resources must have, which includes all objects // users must create. message ObjectMeta { // Name must be unique within a namespace. Is required when creating resources, although // some resources may allow a client to request the generation of an appropriate name // automatically. Name is primarily intended for creation idempotence and configuration // definition. // Cannot be updated. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names // +optional optional string name = 1; // GenerateName is an optional prefix, used by the server, to generate a unique // name ONLY IF the Name field has not been provided. // If this field is used, the name returned to the client will be different // than the name passed. This value will also be combined with a unique suffix. // The provided value has the same validation rules as the Name field, // and may be truncated by the length of the suffix required to make the value // unique on the server. // // If this field is specified and the generated name exists, the server will return a 409. // // Applied only if Name is not specified. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency // +optional optional string generateName = 2; // Namespace defines the space within which each name must be unique. An empty namespace is // equivalent to the "default" namespace, but "default" is the canonical representation. // Not all objects are required to be scoped to a namespace - the value of this field for // those objects will be empty. // // Must be a DNS_LABEL. // Cannot be updated. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces // +optional optional string namespace = 3; // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. // +optional optional string selfLink = 4; // UID is the unique in time and space value for this object. It is typically generated by // the server on successful creation of a resource and is not allowed to change on PUT // operations. // // Populated by the system. // Read-only. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids // +optional optional string uid = 5; // An opaque value that represents the internal version of this object that can // be used by clients to determine when objects have changed. May be used for optimistic // concurrency, change detection, and the watch operation on a resource or set of resources. // Clients must treat these values as opaque and passed unmodified back to the server. // They may only be valid for a particular resource or set of resources. // // Populated by the system. // Read-only. // Value must be treated as opaque by clients and . // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency // +optional optional string resourceVersion = 6; // A sequence number representing a specific generation of the desired state. // Populated by the system. Read-only. // +optional optional int64 generation = 7; // CreationTimestamp is a timestamp representing the server time when this object was // created. It is not guaranteed to be set in happens-before order across separate operations. // Clients may not set this value. It is represented in RFC3339 form and is in UTC. // // Populated by the system. // Read-only. // Null for lists. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional Time creationTimestamp = 8; // 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 // directly settable by a client. The resource is expected to be deleted (no longer visible // from resource lists, and not reachable by name) after the time in this field, once the // finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. // Once the deletionTimestamp is set, this value may not be unset or be set further into the // future, although it may be shortened or the resource may be deleted prior to this time. // For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react // by sending a graceful termination signal to the containers in the pod. After that 30 seconds, // the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, // remove the pod from the API. In the presence of network partitions, this object may still // exist after this timestamp, until an administrator or automated process can determine the // resource is fully terminated. // If not set, graceful deletion of the object has not been requested. // // Populated by the system when a graceful deletion is requested. // Read-only. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional Time deletionTimestamp = 9; // Number of seconds allowed for this object to gracefully terminate before // it will be removed from the system. Only set when deletionTimestamp is also set. // May only be shortened. // Read-only. // +optional optional int64 deletionGracePeriodSeconds = 10; // Map of string keys and values that can be used to organize and categorize // (scope and select) objects. May match selectors of replication controllers // and services. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels // +optional map labels = 11; // Annotations is an unstructured key value map stored with a resource that may be // set by external tools to store and retrieve arbitrary metadata. They are not // queryable and should be preserved when modifying objects. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations // +optional map annotations = 12; // List of objects depended by this object. If ALL objects in the list have // been deleted, this object will be garbage collected. If this object is managed by a controller, // then an entry in this list will point to this controller, with the controller field set to true. // There cannot be more than one managing controller. // +optional // +patchMergeKey=uid // +patchStrategy=merge repeated OwnerReference ownerReferences = 13; // Must be empty before the object is deleted from the registry. Each entry // is an identifier for the responsible component that will remove the entry // from the list. If the deletionTimestamp of the object is non-nil, entries // in this list can only be removed. // Finalizers may be processed and removed in any order. Order is NOT enforced // because it introduces significant risk of stuck finalizers. // finalizers is a shared field, any actor with permission can reorder it. // If the finalizer list is processed in order, then this can lead to a situation // in which the component responsible for the first finalizer in the list is // waiting for a signal (field value, external system, or other) produced by a // component responsible for a finalizer later in the list, resulting in a deadlock. // Without enforced ordering finalizers are free to order amongst themselves and // are not vulnerable to ordering changes in the list. // +optional // +patchStrategy=merge repeated string finalizers = 14; // ManagedFields maps workflow-id and version to the set of fields // that are managed by that workflow. This is mostly for internal // housekeeping, and users typically shouldn't need to set or // understand this field. A workflow can be the user's name, a // controller's name, or the name of a specific apply path like // "ci-cd". The set of fields is always in the version that the // workflow used when modifying the object. // // +optional repeated ManagedFieldsEntry managedFields = 17; } // OwnerReference contains enough information to let you identify an owning // object. An owning object must be in the same namespace as the dependent, or // be cluster-scoped, so there is no namespace field. // +structType=atomic message OwnerReference { // API version of the referent. optional string apiVersion = 5; // Kind of the referent. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds optional string kind = 1; // Name of the referent. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names optional string name = 3; // UID of the referent. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids optional string uid = 4; // If true, this reference points to the managing controller. // +optional optional bool controller = 6; // If true, AND if the owner has the "foregroundDeletion" finalizer, then // the owner cannot be deleted from the key-value store until this // reference is removed. // See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion // for how the garbage collector interacts with this field and enforces the foreground deletion. // Defaults to false. // To set this field, a user needs "delete" permission of the owner, // otherwise 422 (Unprocessable Entity) will be returned. // +optional optional bool blockOwnerDeletion = 7; } // PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients // to get access to a particular ObjectMeta schema without knowing the details of the version. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object message PartialObjectMetadata { // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional optional ObjectMeta metadata = 1; } // PartialObjectMetadataList contains a list of objects containing only their metadata // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object message PartialObjectMetadataList { // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional ListMeta metadata = 1; // items contains each of the included items. repeated PartialObjectMetadata items = 2; } // Patch is provided to give a concrete name and type to the Kubernetes PATCH request body. message Patch { } // PatchOptions may be provided when patching an API object. // PatchOptions is meant to be a superset of UpdateOptions. message PatchOptions { // When present, indicates that modifications should not be // persisted. An invalid or unrecognized dryRun directive will // result in an error response and no further processing of the // request. Valid values are: // - All: all dry run stages will be processed // +optional repeated string dryRun = 1; // Force is going to "force" Apply requests. It means user will // re-acquire conflicting fields owned by other people. Force // flag must be unset for non-apply patch requests. // +optional optional bool force = 2; // fieldManager is a name associated with the actor or entity // that is making these changes. The value must be less than or // 128 characters long, and only contain printable characters, // as defined by https://golang.org/pkg/unicode/#IsPrint. This // field is required for apply requests // (application/apply-patch) but optional for non-apply patch // types (JsonPatch, MergePatch, StrategicMergePatch). // +optional optional string fieldManager = 3; // fieldValidation instructs the server on how to handle // objects in the request (POST/PUT/PATCH) containing unknown // or duplicate fields. Valid values are: // - Ignore: This will ignore any unknown fields that are silently // dropped from the object, and will ignore all but the last duplicate // field that the decoder encounters. This is the default behavior // prior to v1.23. // - Warn: This will send a warning via the standard warning response // header for each unknown field that is dropped from the object, and // for each duplicate field that is encountered. The request will // still succeed if there are no other errors, and will only persist // the last of any duplicate fields. This is the default in v1.23+ // - Strict: This will fail the request with a BadRequest error if // any unknown fields would be dropped from the object, or if any // duplicate fields are present. The error returned from the server // will contain all unknown and duplicate fields encountered. // +optional optional string fieldValidation = 4; } // Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. message Preconditions { // Specifies the target UID. // +optional optional string uid = 1; // Specifies the target ResourceVersion // +optional optional string resourceVersion = 2; } // RootPaths lists the paths available at root. // For example: "/healthz", "/apis". message RootPaths { // paths are the paths available at root. repeated string paths = 1; } // ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. message ServerAddressByClientCIDR { // The CIDR with which clients can match their IP to figure out the server address that they should use. optional string clientCIDR = 1; // Address of this server, suitable for a client that matches the above CIDR. // This can be a hostname, hostname:port, IP or IP:port. optional string serverAddress = 2; } // Status is a return value for calls that don't return other objects. message Status { // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional ListMeta metadata = 1; // Status of the operation. // One of: "Success" or "Failure". // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status // +optional optional string status = 2; // A human-readable description of the status of this operation. // +optional optional string message = 3; // A machine-readable description of why this operation is in the // "Failure" status. If this value is empty there // is no information available. A Reason clarifies an HTTP status // code but does not override it. // +optional optional string reason = 4; // Extended data associated with the reason. Each reason may define its // own extended details. This field is optional and the data returned // is not guaranteed to conform to any schema except that defined by // the reason type. // +optional optional StatusDetails details = 5; // Suggested HTTP return code for this status, 0 if not set. // +optional optional int32 code = 6; } // StatusCause provides more information about an api.Status failure, including // cases when multiple errors are encountered. message StatusCause { // A machine-readable description of the cause of the error. If this value is // empty there is no information available. // +optional optional string reason = 1; // A human-readable description of the cause of the error. This field may be // presented as-is to a reader. // +optional optional string message = 2; // The field of the resource that has caused this error, as named by its JSON // serialization. May include dot and postfix notation for nested attributes. // Arrays are zero-indexed. Fields may appear more than once in an array of // causes due to fields having multiple errors. // Optional. // // Examples: // "name" - the field "name" on the current resource // "items[0].name" - the field "name" on the first array entry in "items" // +optional optional string field = 3; } // StatusDetails is a set of additional properties that MAY be set by the // server to provide additional information about a response. The Reason // field of a Status object defines what attributes will be set. Clients // must ignore fields that do not match the defined type of each attribute, // and should assume that any attribute may be empty, invalid, or under // defined. message StatusDetails { // The name attribute of the resource associated with the status StatusReason // (when there is a single name which can be described). // +optional optional string name = 1; // The group attribute of the resource associated with the status StatusReason. // +optional optional string group = 2; // The kind attribute of the resource associated with the status StatusReason. // On some operations may differ from the requested resource Kind. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional string kind = 3; // UID of the resource. // (when there is a single resource which can be described). // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids // +optional optional string uid = 6; // The Causes array includes more details associated with the StatusReason // failure. Not all StatusReasons may provide detailed causes. // +optional repeated StatusCause causes = 4; // 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 // before taking the alternate action. // +optional optional int32 retryAfterSeconds = 5; } // TableOptions are used when a Table is requested by the caller. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object message TableOptions { // includeObject decides whether to include each object along with its columnar information. // Specifying "None" will return no object, specifying "Object" will return the full object contents, and // specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind // in version v1beta1 of the meta.k8s.io API group. optional string includeObject = 1; } // Time is a wrapper around time.Time which supports correct // marshaling to YAML and JSON. Wrappers are provided for many // of the factory methods that the time package offers. // // +protobuf.options.marshal=false // +protobuf.as=Timestamp // +protobuf.options.(gogoproto.goproto_stringer)=false message Time { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. optional int64 seconds = 1; // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. This field may be limited in precision depending on context. optional int32 nanos = 2; } // Timestamp is a struct that is equivalent to Time, but intended for // protobuf marshalling/unmarshalling. It is generated into a serialization // that matches Time. Do not use in Go structs. message Timestamp { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. optional int64 seconds = 1; // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. This field may be limited in precision depending on context. optional int32 nanos = 2; } // TypeMeta describes an individual object in an API response or request // with strings representing the type of the object and its API schema version. // Structures that are versioned or persisted should inline TypeMeta. // // +k8s:deepcopy-gen=false message TypeMeta { // Kind is a string value representing the REST resource this object represents. // Servers may infer this from the endpoint the client submits requests to. // Cannot be updated. // In CamelCase. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional optional string kind = 1; // APIVersion defines the versioned schema of this representation of an object. // Servers should convert recognized schemas to the latest internal value, and // may reject unrecognized values. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources // +optional optional string apiVersion = 2; } // UpdateOptions may be provided when updating an API object. // All fields in UpdateOptions should also be present in PatchOptions. message UpdateOptions { // When present, indicates that modifications should not be // persisted. An invalid or unrecognized dryRun directive will // result in an error response and no further processing of the // request. Valid values are: // - All: all dry run stages will be processed // +optional repeated string dryRun = 1; // fieldManager is a name associated with the actor or entity // that is making these changes. The value must be less than or // 128 characters long, and only contain printable characters, // as defined by https://golang.org/pkg/unicode/#IsPrint. // +optional optional string fieldManager = 2; // fieldValidation instructs the server on how to handle // objects in the request (POST/PUT/PATCH) containing unknown // or duplicate fields. Valid values are: // - Ignore: This will ignore any unknown fields that are silently // dropped from the object, and will ignore all but the last duplicate // field that the decoder encounters. This is the default behavior // prior to v1.23. // - Warn: This will send a warning via the standard warning response // header for each unknown field that is dropped from the object, and // for each duplicate field that is encountered. The request will // still succeed if there are no other errors, and will only persist // the last of any duplicate fields. This is the default in v1.23+ // - Strict: This will fail the request with a BadRequest error if // any unknown fields would be dropped from the object, or if any // duplicate fields are present. The error returned from the server // will contain all unknown and duplicate fields encountered. // +optional optional string fieldValidation = 3; } // Verbs masks the value so protobuf can generate // // +protobuf.nullable=true // +protobuf.options.(gogoproto.goproto_stringer)=false message Verbs { // items, if empty, will result in an empty slice repeated string items = 1; } // Event represents a single event to a watched resource. // // +protobuf=true // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object message WatchEvent { optional string type = 1; // Object is: // * If Type is Added or Modified: the new state of the object. // * 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; } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/group_version.go000066400000000000000000000114041453143165200244500ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "encoding/json" "fmt" "strings" "k8s.io/apimachinery/pkg/runtime/schema" ) // GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types // // +protobuf.options.(gogoproto.goproto_stringer)=false type GroupResource struct { Group string `json:"group" protobuf:"bytes,1,opt,name=group"` Resource string `json:"resource" protobuf:"bytes,2,opt,name=resource"` } func (gr *GroupResource) String() string { if gr == nil { return "" } if len(gr.Group) == 0 { return gr.Resource } return gr.Resource + "." + gr.Group } // GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion // to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling // // +protobuf.options.(gogoproto.goproto_stringer)=false type GroupVersionResource struct { Group string `json:"group" protobuf:"bytes,1,opt,name=group"` Version string `json:"version" protobuf:"bytes,2,opt,name=version"` Resource string `json:"resource" protobuf:"bytes,3,opt,name=resource"` } func (gvr *GroupVersionResource) String() string { if gvr == nil { return "" } return strings.Join([]string{gvr.Group, "/", gvr.Version, ", Resource=", gvr.Resource}, "") } // GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types // // +protobuf.options.(gogoproto.goproto_stringer)=false type GroupKind struct { Group string `json:"group" protobuf:"bytes,1,opt,name=group"` Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` } func (gk *GroupKind) String() string { if gk == nil { return "" } if len(gk.Group) == 0 { return gk.Kind } return gk.Kind + "." + gk.Group } // GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion // to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling // // +protobuf.options.(gogoproto.goproto_stringer)=false type GroupVersionKind struct { Group string `json:"group" protobuf:"bytes,1,opt,name=group"` Version string `json:"version" protobuf:"bytes,2,opt,name=version"` Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"` } func (gvk GroupVersionKind) String() string { return gvk.Group + "/" + gvk.Version + ", Kind=" + gvk.Kind } // GroupVersion contains the "group" and the "version", which uniquely identifies the API. // // +protobuf.options.(gogoproto.goproto_stringer)=false type GroupVersion struct { Group string `json:"group" protobuf:"bytes,1,opt,name=group"` Version string `json:"version" protobuf:"bytes,2,opt,name=version"` } // Empty returns true if group and version are empty func (gv GroupVersion) Empty() bool { return len(gv.Group) == 0 && len(gv.Version) == 0 } // String puts "group" and "version" into a single "group/version" string. For the legacy v1 // it returns "v1". func (gv GroupVersion) String() string { // special case the internal apiVersion for the legacy kube types if gv.Empty() { return "" } // special case of "v1" for backward compatibility if len(gv.Group) == 0 && gv.Version == "v1" { return gv.Version } if len(gv.Group) > 0 { return gv.Group + "/" + gv.Version } return gv.Version } // MarshalJSON implements the json.Marshaller interface. func (gv GroupVersion) MarshalJSON() ([]byte, error) { s := gv.String() if strings.Count(s, "/") > 1 { return []byte{}, fmt.Errorf("illegal GroupVersion %v: contains more than one /", s) } return json.Marshal(s) } func (gv *GroupVersion) unmarshal(value []byte) error { var s string if err := json.Unmarshal(value, &s); err != nil { return err } parsed, err := schema.ParseGroupVersion(s) if err != nil { return err } gv.Group, gv.Version = parsed.Group, parsed.Version return nil } // UnmarshalJSON implements the json.Unmarshaller interface. func (gv *GroupVersion) UnmarshalJSON(value []byte) error { return gv.unmarshal(value) } // UnmarshalTEXT implements the Ugorji's encoding.TextUnmarshaler interface. func (gv *GroupVersion) UnmarshalText(value []byte) error { return gv.unmarshal(value) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/group_version_test.go000066400000000000000000000043741453143165200255170ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( gojson "encoding/json" "reflect" "testing" utiljson "k8s.io/apimachinery/pkg/util/json" ) type GroupVersionHolder struct { GV GroupVersion `json:"val"` } func TestGroupVersionUnmarshalJSON(t *testing.T) { cases := []struct { input []byte expect GroupVersion }{ {[]byte(`{"val": "v1"}`), GroupVersion{"", "v1"}}, {[]byte(`{"val": "apps/v1"}`), GroupVersion{"apps", "v1"}}, } for _, c := range cases { var result GroupVersionHolder // test golang lib's JSON codec if err := gojson.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("JSON codec failed to unmarshal input '%v': %v", c.input, err) } if !reflect.DeepEqual(result.GV, c.expect) { t.Errorf("JSON codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV) } // test the utiljson codec if err := utiljson.Unmarshal(c.input, &result); err != nil { t.Errorf("util/json codec failed to unmarshal input '%v': %v", c.input, err) } if !reflect.DeepEqual(result.GV, c.expect) { t.Errorf("util/json codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV) } } } func TestGroupVersionMarshalJSON(t *testing.T) { cases := []struct { input GroupVersion expect []byte }{ {GroupVersion{"", "v1"}, []byte(`{"val":"v1"}`)}, {GroupVersion{"apps", "v1"}, []byte(`{"val":"apps/v1"}`)}, } for _, c := range cases { input := GroupVersionHolder{c.input} result, err := gojson.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input '%v': %v", input, err) } if !reflect.DeepEqual(result, c.expect) { t.Errorf("Failed to marshal input '%+v': expected: %s, got: %s", input, c.expect, result) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/helpers.go000066400000000000000000000230721453143165200232150ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "bytes" "encoding/json" "errors" "fmt" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" ) // LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements // labels.Selector // Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) { if ps == nil { return labels.Nothing(), nil } if len(ps.MatchLabels)+len(ps.MatchExpressions) == 0 { return labels.Everything(), nil } requirements := make([]labels.Requirement, 0, len(ps.MatchLabels)+len(ps.MatchExpressions)) for k, v := range ps.MatchLabels { r, err := labels.NewRequirement(k, selection.Equals, []string{v}) if err != nil { return nil, err } requirements = append(requirements, *r) } for _, expr := range ps.MatchExpressions { var op selection.Operator switch expr.Operator { case LabelSelectorOpIn: op = selection.In case LabelSelectorOpNotIn: op = selection.NotIn case LabelSelectorOpExists: op = selection.Exists case LabelSelectorOpDoesNotExist: op = selection.DoesNotExist default: return nil, fmt.Errorf("%q is not a valid label selector operator", expr.Operator) } r, err := labels.NewRequirement(expr.Key, op, append([]string(nil), expr.Values...)) if err != nil { return nil, err } requirements = append(requirements, *r) } selector := labels.NewSelector() selector = selector.Add(requirements...) return selector, nil } // LabelSelectorAsMap converts the LabelSelector api type into a map of strings, ie. the // original structure of a label selector. Operators that cannot be converted into plain // labels (Exists, DoesNotExist, NotIn, and In with more than one value) will result in // an error. func LabelSelectorAsMap(ps *LabelSelector) (map[string]string, error) { if ps == nil { return nil, nil } selector := map[string]string{} for k, v := range ps.MatchLabels { selector[k] = v } for _, expr := range ps.MatchExpressions { switch expr.Operator { case LabelSelectorOpIn: if len(expr.Values) != 1 { return selector, fmt.Errorf("operator %q without a single value cannot be converted into the old label selector format", expr.Operator) } // Should we do anything in case this will override a previous key-value pair? selector[expr.Key] = expr.Values[0] case LabelSelectorOpNotIn, LabelSelectorOpExists, LabelSelectorOpDoesNotExist: return selector, fmt.Errorf("operator %q cannot be converted into the old label selector format", expr.Operator) default: return selector, fmt.Errorf("%q is not a valid selector operator", expr.Operator) } } return selector, nil } // ParseToLabelSelector parses a string representing a selector into a LabelSelector object. // Note: This function should be kept in sync with the parser in pkg/labels/selector.go func ParseToLabelSelector(selector string) (*LabelSelector, error) { reqs, err := labels.ParseToRequirements(selector) if err != nil { return nil, fmt.Errorf("couldn't parse the selector string \"%s\": %v", selector, err) } labelSelector := &LabelSelector{ MatchLabels: map[string]string{}, MatchExpressions: []LabelSelectorRequirement{}, } for _, req := range reqs { var op LabelSelectorOperator switch req.Operator() { case selection.Equals, selection.DoubleEquals: vals := req.Values() if vals.Len() != 1 { return nil, fmt.Errorf("equals operator must have exactly one value") } val, ok := vals.PopAny() if !ok { return nil, fmt.Errorf("equals operator has exactly one value but it cannot be retrieved") } labelSelector.MatchLabels[req.Key()] = val continue case selection.In: op = LabelSelectorOpIn case selection.NotIn: op = LabelSelectorOpNotIn case selection.Exists: op = LabelSelectorOpExists case selection.DoesNotExist: op = LabelSelectorOpDoesNotExist case selection.GreaterThan, selection.LessThan: // Adding a separate case for these operators to indicate that this is deliberate return nil, fmt.Errorf("%q isn't supported in label selectors", req.Operator()) default: return nil, fmt.Errorf("%q is not a valid label selector operator", req.Operator()) } labelSelector.MatchExpressions = append(labelSelector.MatchExpressions, LabelSelectorRequirement{ Key: req.Key(), Operator: op, Values: req.Values().List(), }) } return labelSelector, nil } // SetAsLabelSelector converts the labels.Set object into a LabelSelector api object. func SetAsLabelSelector(ls labels.Set) *LabelSelector { if ls == nil { return nil } selector := &LabelSelector{ MatchLabels: make(map[string]string, len(ls)), } for label, value := range ls { selector.MatchLabels[label] = value } return selector } // FormatLabelSelector convert labelSelector into plain string func FormatLabelSelector(labelSelector *LabelSelector) string { selector, err := LabelSelectorAsSelector(labelSelector) if err != nil { return "" } l := selector.String() if len(l) == 0 { l = "" } return l } func ExtractGroupVersions(l *APIGroupList) []string { var groupVersions []string for _, g := range l.Groups { for _, gv := range g.Versions { groupVersions = append(groupVersions, gv.GroupVersion) } } return groupVersions } // HasAnnotation returns a bool if passed in annotation exists func HasAnnotation(obj ObjectMeta, ann string) bool { _, found := obj.Annotations[ann] return found } // SetMetaDataAnnotation sets the annotation and value func SetMetaDataAnnotation(obj *ObjectMeta, ann string, value string) { if obj.Annotations == nil { obj.Annotations = make(map[string]string) } obj.Annotations[ann] = value } // HasLabel returns a bool if passed in label exists func HasLabel(obj ObjectMeta, label string) bool { _, found := obj.Labels[label] return found } // SetMetaDataLabel sets the label and value func SetMetaDataLabel(obj *ObjectMeta, label string, value string) { if obj.Labels == nil { obj.Labels = make(map[string]string) } obj.Labels[label] = value } // SingleObject returns a ListOptions for watching a single object. func SingleObject(meta ObjectMeta) ListOptions { return ListOptions{ FieldSelector: fields.OneTermEqualSelector("metadata.name", meta.Name).String(), ResourceVersion: meta.ResourceVersion, } } // NewDeleteOptions returns a DeleteOptions indicating the resource should // be deleted within the specified grace period. Use zero to indicate // immediate deletion. If you would prefer to use the default grace period, // use &metav1.DeleteOptions{} directly. func NewDeleteOptions(grace int64) *DeleteOptions { return &DeleteOptions{GracePeriodSeconds: &grace} } // NewPreconditionDeleteOptions returns a DeleteOptions with a UID precondition set. func NewPreconditionDeleteOptions(uid string) *DeleteOptions { u := types.UID(uid) p := Preconditions{UID: &u} return &DeleteOptions{Preconditions: &p} } // NewUIDPreconditions returns a Preconditions with UID set. func NewUIDPreconditions(uid string) *Preconditions { u := types.UID(uid) return &Preconditions{UID: &u} } // NewRVDeletionPrecondition returns a DeleteOptions with a ResourceVersion precondition set. func NewRVDeletionPrecondition(rv string) *DeleteOptions { p := Preconditions{ResourceVersion: &rv} return &DeleteOptions{Preconditions: &p} } // HasObjectMetaSystemFieldValues returns true if fields that are managed by the system on ObjectMeta have values. func HasObjectMetaSystemFieldValues(meta Object) bool { return !meta.GetCreationTimestamp().Time.IsZero() || len(meta.GetUID()) != 0 } // ResetObjectMetaForStatus forces the meta fields for a status update to match the meta fields // for a pre-existing object. This is opt-in for new objects with Status subresource. func ResetObjectMetaForStatus(meta, existingMeta Object) { meta.SetDeletionTimestamp(existingMeta.GetDeletionTimestamp()) meta.SetGeneration(existingMeta.GetGeneration()) meta.SetSelfLink(existingMeta.GetSelfLink()) meta.SetLabels(existingMeta.GetLabels()) meta.SetAnnotations(existingMeta.GetAnnotations()) meta.SetFinalizers(existingMeta.GetFinalizers()) meta.SetOwnerReferences(existingMeta.GetOwnerReferences()) // managedFields must be preserved since it's been modified to // track changed fields in the status update. //meta.SetManagedFields(existingMeta.GetManagedFields()) } // MarshalJSON implements json.Marshaler // MarshalJSON may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (f FieldsV1) MarshalJSON() ([]byte, error) { if f.Raw == nil { return []byte("null"), nil } 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") } if !bytes.Equal(b, []byte("null")) { f.Raw = append(f.Raw[0:0], b...) } return nil } var _ json.Marshaler = FieldsV1{} var _ json.Unmarshaler = &FieldsV1{} golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/helpers_test.go000066400000000000000000000161571453143165200242620ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "fmt" "reflect" "strings" "testing" "github.com/google/go-cmp/cmp" fuzz "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" ) func TestLabelSelectorAsSelector(t *testing.T) { matchLabels := map[string]string{"foo": "bar"} matchExpressions := []LabelSelectorRequirement{{ Key: "baz", Operator: LabelSelectorOpIn, Values: []string{"qux", "norf"}, }} mustParse := func(s string) labels.Selector { out, e := labels.Parse(s) if e != nil { panic(e) } return out } tc := []struct { in *LabelSelector out labels.Selector expectErr bool }{ {in: nil, out: labels.Nothing()}, {in: &LabelSelector{}, out: labels.Everything()}, { in: &LabelSelector{MatchLabels: matchLabels}, out: mustParse("foo=bar"), }, { in: &LabelSelector{MatchExpressions: matchExpressions}, out: mustParse("baz in (norf,qux)"), }, { in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions}, out: mustParse("baz in (norf,qux),foo=bar"), }, { in: &LabelSelector{ MatchExpressions: []LabelSelectorRequirement{{ Key: "baz", Operator: LabelSelectorOpExists, Values: []string{"qux", "norf"}, }}, }, expectErr: true, }, } for i, tc := range tc { inCopy := tc.in.DeepCopy() out, err := LabelSelectorAsSelector(tc.in) // after calling LabelSelectorAsSelector, tc.in shouldn't be modified if !reflect.DeepEqual(inCopy, tc.in) { t.Errorf("[%v]expected:\n\t%#v\nbut got:\n\t%#v", i, inCopy, tc.in) } if err == nil && tc.expectErr { t.Errorf("[%v]expected error but got none.", i) } if err != nil && !tc.expectErr { t.Errorf("[%v]did not expect error but got: %v", i, err) } // fmt.Sprint() over String() as nil.String() will panic if fmt.Sprint(out) != fmt.Sprint(tc.out) { t.Errorf("[%v]expected:\n\t%s\nbut got:\n\t%s", i, fmt.Sprint(tc.out), fmt.Sprint(out)) } } } func BenchmarkLabelSelectorAsSelector(b *testing.B) { selector := &LabelSelector{ MatchLabels: map[string]string{ "foo": "foo", "bar": "bar", }, MatchExpressions: []LabelSelectorRequirement{{ Key: "baz", Operator: LabelSelectorOpExists, }}, } b.StartTimer() for i := 0; i < b.N; i++ { _, err := LabelSelectorAsSelector(selector) if err != nil { b.Fatal(err) } } } func TestLabelSelectorAsMap(t *testing.T) { matchLabels := map[string]string{"foo": "bar"} matchExpressions := func(operator LabelSelectorOperator, values []string) []LabelSelectorRequirement { return []LabelSelectorRequirement{{ Key: "baz", Operator: operator, Values: values, }} } tests := []struct { in *LabelSelector out map[string]string errString string }{ {in: nil, out: nil}, { in: &LabelSelector{MatchLabels: matchLabels}, out: map[string]string{"foo": "bar"}, }, { in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions(LabelSelectorOpIn, []string{"norf"})}, out: map[string]string{"foo": "bar", "baz": "norf"}, }, { in: &LabelSelector{MatchExpressions: matchExpressions(LabelSelectorOpIn, []string{"norf"})}, out: map[string]string{"baz": "norf"}, }, { in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions(LabelSelectorOpIn, []string{"norf", "qux"})}, out: map[string]string{"foo": "bar"}, errString: "without a single value cannot be converted", }, { in: &LabelSelector{MatchExpressions: matchExpressions(LabelSelectorOpNotIn, []string{"norf", "qux"})}, out: map[string]string{}, errString: "cannot be converted", }, { in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions(LabelSelectorOpExists, []string{})}, out: map[string]string{"foo": "bar"}, errString: "cannot be converted", }, { in: &LabelSelector{MatchExpressions: matchExpressions(LabelSelectorOpDoesNotExist, []string{})}, out: map[string]string{}, errString: "cannot be converted", }, } for i, tc := range tests { out, err := LabelSelectorAsMap(tc.in) if err == nil && len(tc.errString) > 0 { t.Errorf("[%v]expected error but got none.", i) continue } if err != nil && len(tc.errString) == 0 { t.Errorf("[%v]did not expect error but got: %v", i, err) continue } if err != nil && len(tc.errString) > 0 && !strings.Contains(err.Error(), tc.errString) { t.Errorf("[%v]expected error with %q but got: %v", i, tc.errString, err) continue } if !reflect.DeepEqual(out, tc.out) { t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) } } } func TestResetObjectMetaForStatus(t *testing.T) { meta := &ObjectMeta{} existingMeta := &ObjectMeta{} // fuzz the existingMeta to set every field, no nils f := fuzz.New().NilChance(0).NumElements(1, 1).MaxDepth(10) f.Fuzz(existingMeta) ResetObjectMetaForStatus(meta, existingMeta) // not all fields are stomped during the reset. These fields should not have been set. False // set them all to their zero values. Before you add anything to this list, consider whether or not // you're enforcing immutability (those are fine) and whether /status should be able to update // these values (these are usually not fine). // generateName doesn't do anything after create existingMeta.SetGenerateName("") // resourceVersion is enforced in validation and used during the storage update existingMeta.SetResourceVersion("") // fields made immutable in validation existingMeta.SetUID(types.UID("")) existingMeta.SetName("") existingMeta.SetNamespace("") existingMeta.SetCreationTimestamp(Time{}) existingMeta.SetDeletionTimestamp(nil) existingMeta.SetDeletionGracePeriodSeconds(nil) existingMeta.SetManagedFields(nil) if !reflect.DeepEqual(meta, existingMeta) { t.Error(cmp.Diff(meta, existingMeta)) } } func TestSetMetaDataLabel(t *testing.T) { tests := []struct { obj *ObjectMeta label string value string want map[string]string }{ { obj: &ObjectMeta{}, label: "foo", value: "bar", want: map[string]string{"foo": "bar"}, }, { obj: &ObjectMeta{Labels: map[string]string{"foo": "bar"}}, label: "foo", value: "baz", want: map[string]string{"foo": "baz"}, }, { obj: &ObjectMeta{Labels: map[string]string{"foo": "bar"}}, label: "version", value: "1.0.0", want: map[string]string{"foo": "bar", "version": "1.0.0"}, }, } for _, tc := range tests { SetMetaDataLabel(tc.obj, tc.label, tc.value) if !reflect.DeepEqual(tc.obj.Labels, tc.want) { t.Errorf("got %v, want %v", tc.obj.Labels, tc.want) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/labels.go000066400000000000000000000033501453143165200230120ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 // Clones the given selector and returns a new selector with the given key and value added. // Returns the given selector, if labelKey is empty. func CloneSelectorAndAddLabel(selector *LabelSelector, labelKey, labelValue string) *LabelSelector { if labelKey == "" { // Don't need to add a label. return selector } // Clone. newSelector := selector.DeepCopy() if newSelector.MatchLabels == nil { newSelector.MatchLabels = make(map[string]string) } newSelector.MatchLabels[labelKey] = labelValue return newSelector } // AddLabelToSelector returns a selector with the given key and value added to the given selector's MatchLabels. func AddLabelToSelector(selector *LabelSelector, labelKey, labelValue string) *LabelSelector { if labelKey == "" { // Don't need to add a label. return selector } if selector.MatchLabels == nil { selector.MatchLabels = make(map[string]string) } selector.MatchLabels[labelKey] = labelValue return selector } // SelectorHasLabel checks if the given selector contains the given label key in its MatchLabels func SelectorHasLabel(selector *LabelSelector, labelKey string) bool { return len(selector.MatchLabels[labelKey]) > 0 } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/labels_test.go000066400000000000000000000051161453143165200240530ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "reflect" "testing" ) func TestCloneSelectorAndAddLabel(t *testing.T) { labels := map[string]string{ "foo1": "bar1", "foo2": "bar2", "foo3": "bar3", } matchExpressions := []LabelSelectorRequirement{ {Key: "foo", Operator: LabelSelectorOpIn, Values: []string{"foo"}}, } cases := []struct { labels map[string]string labelKey string labelValue string want map[string]string }{ { labels: labels, want: labels, }, { labels: labels, labelKey: "foo4", labelValue: "89", want: map[string]string{ "foo1": "bar1", "foo2": "bar2", "foo3": "bar3", "foo4": "89", }, }, { labels: nil, labelKey: "foo4", labelValue: "12", want: map[string]string{ "foo4": "12", }, }, } for _, tc := range cases { ls_in := LabelSelector{MatchLabels: tc.labels, MatchExpressions: matchExpressions} ls_out := LabelSelector{MatchLabels: tc.want, MatchExpressions: matchExpressions} got := CloneSelectorAndAddLabel(&ls_in, tc.labelKey, tc.labelValue) if !reflect.DeepEqual(got, &ls_out) { t.Errorf("got %v, want %v", got, tc.want) } } } func TestAddLabelToSelector(t *testing.T) { labels := map[string]string{ "foo1": "bar1", "foo2": "bar2", "foo3": "bar3", } cases := []struct { labels map[string]string labelKey string labelValue string want map[string]string }{ { labels: labels, want: labels, }, { labels: labels, labelKey: "foo4", labelValue: "89", want: map[string]string{ "foo1": "bar1", "foo2": "bar2", "foo3": "bar3", "foo4": "89", }, }, { labels: nil, labelKey: "foo4", labelValue: "12", want: map[string]string{ "foo4": "12", }, }, } for _, tc := range cases { ls_in := LabelSelector{MatchLabels: tc.labels} ls_out := LabelSelector{MatchLabels: tc.want} got := AddLabelToSelector(&ls_in, tc.labelKey, tc.labelValue) if !reflect.DeepEqual(got, &ls_out) { t.Errorf("got %v, want %v", got, tc.want) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/meta.go000066400000000000000000000173571453143165200225120ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ) // TODO: move this, Object, List, and Type to a different package type ObjectMetaAccessor interface { GetObjectMeta() Object } // Object lets you work with object metadata from any of the versioned or // internal API objects. Attempting to set or retrieve a field on an object that does // not support that field (Name, UID, Namespace on lists) will be a no-op and return // a default value. type Object interface { GetNamespace() string SetNamespace(namespace string) GetName() string SetName(name string) GetGenerateName() string SetGenerateName(name string) GetUID() types.UID SetUID(uid types.UID) GetResourceVersion() string SetResourceVersion(version string) GetGeneration() int64 SetGeneration(generation int64) GetSelfLink() string SetSelfLink(selfLink string) GetCreationTimestamp() Time SetCreationTimestamp(timestamp Time) GetDeletionTimestamp() *Time SetDeletionTimestamp(timestamp *Time) GetDeletionGracePeriodSeconds() *int64 SetDeletionGracePeriodSeconds(*int64) GetLabels() map[string]string SetLabels(labels map[string]string) GetAnnotations() map[string]string SetAnnotations(annotations map[string]string) GetFinalizers() []string SetFinalizers(finalizers []string) GetOwnerReferences() []OwnerReference SetOwnerReferences([]OwnerReference) GetManagedFields() []ManagedFieldsEntry SetManagedFields(managedFields []ManagedFieldsEntry) } // ListMetaAccessor retrieves the list interface from an object type ListMetaAccessor interface { GetListMeta() ListInterface } // Common lets you work with core metadata from any of the versioned or // internal API objects. Attempting to set or retrieve a field on an object that does // not support that field will be a no-op and return a default value. // TODO: move this, and TypeMeta and ListMeta, to a different package type Common interface { GetResourceVersion() string SetResourceVersion(version string) GetSelfLink() string SetSelfLink(selfLink string) } // ListInterface lets you work with list metadata from any of the versioned or // internal API objects. Attempting to set or retrieve a field on an object that does // not support that field will be a no-op and return a default value. // TODO: move this, and TypeMeta and ListMeta, to a different package type ListInterface interface { GetResourceVersion() string SetResourceVersion(version string) GetSelfLink() string SetSelfLink(selfLink string) GetContinue() string SetContinue(c string) GetRemainingItemCount() *int64 SetRemainingItemCount(c *int64) } // Type exposes the type and APIVersion of versioned or internal API objects. // TODO: move this, and TypeMeta and ListMeta, to a different package type Type interface { GetAPIVersion() string SetAPIVersion(version string) GetKind() string SetKind(kind string) } var _ ListInterface = &ListMeta{} func (meta *ListMeta) GetResourceVersion() string { return meta.ResourceVersion } func (meta *ListMeta) SetResourceVersion(version string) { meta.ResourceVersion = version } func (meta *ListMeta) GetSelfLink() string { return meta.SelfLink } func (meta *ListMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink } func (meta *ListMeta) GetContinue() string { return meta.Continue } func (meta *ListMeta) SetContinue(c string) { meta.Continue = c } func (meta *ListMeta) GetRemainingItemCount() *int64 { return meta.RemainingItemCount } func (meta *ListMeta) SetRemainingItemCount(c *int64) { meta.RemainingItemCount = c } func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj } // SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta func (obj *TypeMeta) SetGroupVersionKind(gvk schema.GroupVersionKind) { obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() } // GroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind { return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) } func (obj *ListMeta) GetListMeta() ListInterface { return obj } func (obj *ObjectMeta) GetObjectMeta() Object { return obj } // Namespace implements metav1.Object for any object with an ObjectMeta typed field. Allows // fast, direct access to metadata fields for API objects. func (meta *ObjectMeta) GetNamespace() string { return meta.Namespace } func (meta *ObjectMeta) SetNamespace(namespace string) { meta.Namespace = namespace } func (meta *ObjectMeta) GetName() string { return meta.Name } func (meta *ObjectMeta) SetName(name string) { meta.Name = name } func (meta *ObjectMeta) GetGenerateName() string { return meta.GenerateName } func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName } func (meta *ObjectMeta) GetUID() types.UID { return meta.UID } func (meta *ObjectMeta) SetUID(uid types.UID) { meta.UID = uid } func (meta *ObjectMeta) GetResourceVersion() string { return meta.ResourceVersion } func (meta *ObjectMeta) SetResourceVersion(version string) { meta.ResourceVersion = version } func (meta *ObjectMeta) GetGeneration() int64 { return meta.Generation } func (meta *ObjectMeta) SetGeneration(generation int64) { meta.Generation = generation } func (meta *ObjectMeta) GetSelfLink() string { return meta.SelfLink } func (meta *ObjectMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink } func (meta *ObjectMeta) GetCreationTimestamp() Time { return meta.CreationTimestamp } func (meta *ObjectMeta) SetCreationTimestamp(creationTimestamp Time) { meta.CreationTimestamp = creationTimestamp } func (meta *ObjectMeta) GetDeletionTimestamp() *Time { return meta.DeletionTimestamp } func (meta *ObjectMeta) SetDeletionTimestamp(deletionTimestamp *Time) { meta.DeletionTimestamp = deletionTimestamp } func (meta *ObjectMeta) GetDeletionGracePeriodSeconds() *int64 { return meta.DeletionGracePeriodSeconds } func (meta *ObjectMeta) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) { meta.DeletionGracePeriodSeconds = deletionGracePeriodSeconds } func (meta *ObjectMeta) GetLabels() map[string]string { return meta.Labels } func (meta *ObjectMeta) SetLabels(labels map[string]string) { meta.Labels = labels } func (meta *ObjectMeta) GetAnnotations() map[string]string { return meta.Annotations } func (meta *ObjectMeta) SetAnnotations(annotations map[string]string) { meta.Annotations = annotations } func (meta *ObjectMeta) GetFinalizers() []string { return meta.Finalizers } func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers } func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { return meta.OwnerReferences } func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { meta.OwnerReferences = references } func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields } func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) { meta.ManagedFields = managedFields } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/micro_time.go000066400000000000000000000112741453143165200237030ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "encoding/json" "time" ) const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00" // MicroTime is version of Time with microsecond level precision. // // +protobuf.options.marshal=false // +protobuf.as=Timestamp // +protobuf.options.(gogoproto.goproto_stringer)=false type MicroTime struct { time.Time `protobuf:"-"` } // DeepCopy returns a deep-copy of the MicroTime value. The underlying time.Time // type is effectively immutable in the time API, so it is safe to // copy-by-assign, despite the presence of (unexported) Pointer fields. func (t *MicroTime) DeepCopyInto(out *MicroTime) { *out = *t } // NewMicroTime returns a wrapped instance of the provided time func NewMicroTime(time time.Time) MicroTime { return MicroTime{time} } // DateMicro returns the MicroTime corresponding to the supplied parameters // by wrapping time.Date. func DateMicro(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) MicroTime { return MicroTime{time.Date(year, month, day, hour, min, sec, nsec, loc)} } // NowMicro returns the current local time. func NowMicro() MicroTime { return MicroTime{time.Now()} } // IsZero returns true if the value is nil or time is zero. func (t *MicroTime) IsZero() bool { if t == nil { return true } return t.Time.IsZero() } // Before reports whether the time instant t is before u. func (t *MicroTime) Before(u *MicroTime) bool { if t != nil && u != nil { return t.Time.Before(u.Time) } return false } // Equal reports whether the time instant t is equal to u. func (t *MicroTime) Equal(u *MicroTime) bool { if t == nil && u == nil { return true } if t != nil && u != nil { return t.Time.Equal(u.Time) } return false } // BeforeTime reports whether the time instant t is before second-lever precision u. func (t *MicroTime) BeforeTime(u *Time) bool { if t != nil && u != nil { return t.Time.Before(u.Time) } return false } // EqualTime reports whether the time instant t is equal to second-lever precision u. func (t *MicroTime) EqualTime(u *Time) bool { if t == nil && u == nil { return true } if t != nil && u != nil { return t.Time.Equal(u.Time) } return false } // UnixMicro returns the local time corresponding to the given Unix time // by wrapping time.Unix. func UnixMicro(sec int64, nsec int64) MicroTime { return MicroTime{time.Unix(sec, nsec)} } // UnmarshalJSON implements the json.Unmarshaller interface. func (t *MicroTime) UnmarshalJSON(b []byte) error { if len(b) == 4 && string(b) == "null" { t.Time = time.Time{} return nil } var str string err := json.Unmarshal(b, &str) if err != nil { return err } pt, err := time.Parse(RFC3339Micro, str) if err != nil { return err } t.Time = pt.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 { t.Time = time.Time{} return nil } // Tolerate requests from older clients that used JSON serialization to build query params if len(str) == 4 && str == "null" { t.Time = time.Time{} return nil } pt, err := time.Parse(RFC3339Micro, str) if err != nil { return err } t.Time = pt.Local() return nil } // MarshalJSON implements the json.Marshaler interface. func (t MicroTime) MarshalJSON() ([]byte, error) { if t.IsZero() { // Encode unset/nil objects as JSON's "null". return []byte("null"), nil } return json.Marshal(t.UTC().Format(RFC3339Micro)) } // OpenAPISchemaType is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. // // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators func (_ MicroTime) OpenAPISchemaType() []string { return []string{"string"} } // OpenAPISchemaFormat is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. func (_ MicroTime) OpenAPISchemaFormat() string { return "date-time" } // MarshalQueryParameter converts to a URL query parameter value func (t MicroTime) MarshalQueryParameter() (string, error) { if t.IsZero() { // Encode unset/nil objects as an empty string return "", nil } return t.UTC().Format(RFC3339Micro), nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/micro_time_fuzz.go000066400000000000000000000021541453143165200247560ustar00rootroot00000000000000//go:build !notest // +build !notest /* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "time" fuzz "github.com/google/gofuzz" ) // Fuzz satisfies fuzz.Interface. func (t *MicroTime) Fuzz(c fuzz.Continue) { 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)) } // ensure MicroTime implements fuzz.Interface var _ fuzz.Interface = &MicroTime{} golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/micro_time_proto.go000066400000000000000000000045161453143165200251270ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "time" ) // Timestamp is declared in time_proto.go // Timestamp returns the Time as a new Timestamp value. func (m *MicroTime) ProtoMicroTime() *Timestamp { if m == nil { return &Timestamp{} } // truncate precision to microseconds to match JSON marshaling/unmarshaling truncatedNanoseconds := time.Duration(m.Time.Nanosecond()).Truncate(time.Microsecond) return &Timestamp{ Seconds: m.Time.Unix(), Nanos: int32(truncatedNanoseconds), } } // Size implements the protobuf marshalling interface. func (m *MicroTime) Size() (n int) { if m == nil || m.Time.IsZero() { return 0 } return m.ProtoMicroTime().Size() } // Reset implements the protobuf marshalling interface. func (m *MicroTime) Unmarshal(data []byte) error { if len(data) == 0 { m.Time = time.Time{} return nil } p := Timestamp{} if err := p.Unmarshal(data); err != nil { return err } // truncate precision to microseconds to match JSON marshaling/unmarshaling truncatedNanoseconds := time.Duration(p.Nanos).Truncate(time.Microsecond) m.Time = time.Unix(p.Seconds, int64(truncatedNanoseconds)).Local() return nil } // Marshal implements the protobuf marshalling interface. func (m *MicroTime) Marshal() (data []byte, err error) { if m == nil || m.Time.IsZero() { return nil, nil } return m.ProtoMicroTime().Marshal() } // MarshalTo implements the protobuf marshalling interface. func (m *MicroTime) MarshalTo(data []byte) (int, error) { if m == nil || m.Time.IsZero() { return 0, nil } return m.ProtoMicroTime().MarshalTo(data) } // MarshalToSizedBuffer implements the protobuf marshalling interface. func (m *MicroTime) MarshalToSizedBuffer(data []byte) (int, error) { if m == nil || m.Time.IsZero() { return 0, nil } return m.ProtoMicroTime().MarshalToSizedBuffer(data) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/micro_time_test.go000066400000000000000000000202511453143165200247350ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "encoding/json" "reflect" "testing" "time" "sigs.k8s.io/yaml" ) type MicroTimeHolder struct { T MicroTime `json:"t"` } func TestMicroTimeMarshalYAML(t *testing.T) { cases := []struct { input MicroTime result string }{ {MicroTime{}, "t: null\n"}, {DateMicro(1998, time.May, 5, 1, 5, 5, 50, time.FixedZone("test", -4*60*60)), "t: \"1998-05-05T05:05:05.000000Z\"\n"}, {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "t: \"1998-05-05T05:05:05.000000Z\"\n"}, } for _, c := range cases { input := MicroTimeHolder{c.input} result, err := yaml.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input: '%v': %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) } } } func TestMicroTimeUnmarshalYAML(t *testing.T) { cases := []struct { input string result MicroTime }{ {"t: null\n", MicroTime{}}, {"t: 1998-05-05T05:05:05.000000Z\n", MicroTime{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, } for _, c := range cases { var result MicroTimeHolder if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) } if result.T != c.result { t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) } } } func TestMicroTimeMarshalJSON(t *testing.T) { cases := []struct { input MicroTime result string }{ {MicroTime{}, "{\"t\":null}"}, {DateMicro(1998, time.May, 5, 5, 5, 5, 50, time.UTC), "{\"t\":\"1998-05-05T05:05:05.000000Z\"}"}, {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "{\"t\":\"1998-05-05T05:05:05.000000Z\"}"}, } for _, c := range cases { input := MicroTimeHolder{c.input} result, err := json.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input: '%v': %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) } } } func TestMicroTimeUnmarshalJSON(t *testing.T) { cases := []struct { input string result MicroTime }{ {"{\"t\":null}", MicroTime{}}, {"{\"t\":\"1998-05-05T05:05:05.000000Z\"}", MicroTime{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, } for _, c := range cases { var result MicroTimeHolder if err := json.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) } if result.T != c.result { t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) } } } func TestMicroTimeProto(t *testing.T) { cases := []struct { input MicroTime }{ {MicroTime{}}, {DateMicro(1998, time.May, 5, 1, 5, 5, 1000, time.Local)}, {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.Local)}, } for _, c := range cases { input := c.input data, err := input.Marshal() if err != nil { t.Fatalf("Failed to marshal input: '%v': %v", input, err) } time := MicroTime{} if err := time.Unmarshal(data); err != nil { t.Fatalf("Failed to unmarshal output: '%v': %v", input, err) } if !reflect.DeepEqual(input, time) { t.Errorf("Marshal->Unmarshal is not idempotent: '%v' vs '%v'", input, time) } } } func TestMicroTimeEqual(t *testing.T) { t1 := NewMicroTime(time.Now()) cases := []struct { name string x *MicroTime y *MicroTime result bool }{ {"nil =? nil", nil, nil, true}, {"!nil =? !nil", &t1, &t1, true}, {"nil =? !nil", nil, &t1, false}, {"!nil =? nil", &t1, nil, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { result := c.x.Equal(c.y) if result != c.result { t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) } }) } } func TestMicroTimeEqualTime(t *testing.T) { t1 := NewMicroTime(time.Now()) t2 := NewTime(t1.Time) cases := []struct { name string x *MicroTime y *Time result bool }{ {"nil =? nil", nil, nil, true}, {"!nil =? !nil", &t1, &t2, true}, {"nil =? !nil", nil, &t2, false}, {"!nil =? nil", &t1, nil, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { result := c.x.EqualTime(c.y) if result != c.result { t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) } }) } } func TestMicroTimeBefore(t *testing.T) { t1 := NewMicroTime(time.Now()) cases := []struct { name string x *MicroTime y *MicroTime }{ {"nil PatchOptions -> UpdateOptions round-trip failed: got: %v want: %v`, got, update) } }) } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/register.go000066400000000000000000000065471453143165200234070ustar00rootroot00000000000000/* 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 v1 import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // GroupName is the group name for this API. const GroupName = "meta.k8s.io" var ( // localSchemeBuilder is used to make compiler happy for autogenerated // conversions. However, it's not used. schemeBuilder runtime.SchemeBuilder localSchemeBuilder = &schemeBuilder ) // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} // Unversioned is group version for unversioned API objects // TODO: this should be v1 probably var Unversioned = schema.GroupVersion{Group: "", Version: "v1"} // WatchEventKind is name reserved for serializing watch events. const WatchEventKind = "WatchEvent" // Kind takes an unqualified kind and returns a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // scheme is the registry for the common types that adhere to the meta v1 API spec. var scheme = runtime.NewScheme() // ParameterCodec knows about query parameters used with the meta v1 API spec. var ParameterCodec = runtime.NewParameterCodec(scheme) var optionsTypes = []runtime.Object{ &ListOptions{}, &GetOptions{}, &DeleteOptions{}, &CreateOptions{}, &UpdateOptions{}, &PatchOptions{}, } // AddToGroupVersion registers common meta types into schemas. func AddToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) { scheme.AddKnownTypeWithName(groupVersion.WithKind(WatchEventKind), &WatchEvent{}) scheme.AddKnownTypeWithName( schema.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal}.WithKind(WatchEventKind), &InternalEvent{}, ) // Supports legacy code paths, most callers should use metav1.ParameterCodec for now scheme.AddKnownTypes(groupVersion, optionsTypes...) // Register Unversioned types under their own special group scheme.AddUnversionedTypes(Unversioned, &Status{}, &APIVersions{}, &APIGroupList{}, &APIGroup{}, &APIResourceList{}, ) // register manually. This usually goes through the SchemeBuilder, which we cannot use here. utilruntime.Must(RegisterConversions(scheme)) utilruntime.Must(RegisterDefaults(scheme)) } // AddMetaToScheme registers base meta types into schemas. func AddMetaToScheme(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Table{}, &TableOptions{}, &PartialObjectMetadata{}, &PartialObjectMetadataList{}, ) return nil } func init() { scheme.AddUnversionedTypes(SchemeGroupVersion, optionsTypes...) utilruntime.Must(AddMetaToScheme(scheme)) // register manually. This usually goes through the SchemeBuilder, which we cannot use here. utilruntime.Must(RegisterDefaults(scheme)) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/time.go000066400000000000000000000113601453143165200225060ustar00rootroot00000000000000/* 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 v1 import ( "encoding/json" "time" ) // Time is a wrapper around time.Time which supports correct // marshaling to YAML and JSON. Wrappers are provided for many // of the factory methods that the time package offers. // // +protobuf.options.marshal=false // +protobuf.as=Timestamp // +protobuf.options.(gogoproto.goproto_stringer)=false type Time struct { time.Time `protobuf:"-"` } // DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time // type is effectively immutable in the time API, so it is safe to // copy-by-assign, despite the presence of (unexported) Pointer fields. func (t *Time) DeepCopyInto(out *Time) { *out = *t } // NewTime returns a wrapped instance of the provided time func NewTime(time time.Time) Time { return Time{time} } // Date returns the Time corresponding to the supplied parameters // by wrapping time.Date. func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} } // Now returns the current local time. func Now() Time { return Time{time.Now()} } // IsZero returns true if the value is nil or time is zero. func (t *Time) IsZero() bool { if t == nil { return true } return t.Time.IsZero() } // Before reports whether the time instant t is before u. func (t *Time) Before(u *Time) bool { if t != nil && u != nil { return t.Time.Before(u.Time) } return false } // Equal reports whether the time instant t is equal to u. func (t *Time) Equal(u *Time) bool { if t == nil && u == nil { return true } if t != nil && u != nil { return t.Time.Equal(u.Time) } return false } // Unix returns the local time corresponding to the given Unix time // by wrapping time.Unix. func Unix(sec int64, nsec int64) Time { return Time{time.Unix(sec, nsec)} } // Rfc3339Copy returns a copy of the Time at second-level precision. func (t Time) Rfc3339Copy() Time { copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) return Time{copied} } // UnmarshalJSON implements the json.Unmarshaller interface. func (t *Time) UnmarshalJSON(b []byte) error { if len(b) == 4 && string(b) == "null" { t.Time = time.Time{} return nil } var str string err := json.Unmarshal(b, &str) if err != nil { return err } pt, err := time.Parse(time.RFC3339, str) if err != nil { return err } t.Time = pt.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 { t.Time = time.Time{} return nil } // Tolerate requests from older clients that used JSON serialization to build query params if len(str) == 4 && str == "null" { t.Time = time.Time{} return nil } pt, err := time.Parse(time.RFC3339, str) if err != nil { return err } t.Time = pt.Local() return nil } // MarshalJSON implements the json.Marshaler interface. func (t Time) MarshalJSON() ([]byte, error) { if t.IsZero() { // Encode unset/nil objects as JSON's "null". return []byte("null"), nil } buf := make([]byte, 0, len(time.RFC3339)+2) buf = append(buf, '"') // time cannot contain non escapable JSON characters buf = t.UTC().AppendFormat(buf, time.RFC3339) buf = append(buf, '"') return buf, nil } // ToUnstructured implements the value.UnstructuredConverter interface. func (t Time) ToUnstructured() interface{} { if t.IsZero() { return nil } buf := make([]byte, 0, len(time.RFC3339)) buf = t.UTC().AppendFormat(buf, time.RFC3339) return string(buf) } // OpenAPISchemaType is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. // // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators func (_ Time) OpenAPISchemaType() []string { return []string{"string"} } // OpenAPISchemaFormat is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. func (_ Time) OpenAPISchemaFormat() string { return "date-time" } // MarshalQueryParameter converts to a URL query parameter value func (t Time) MarshalQueryParameter() (string, error) { if t.IsZero() { // Encode unset/nil objects as an empty string return "", nil } return t.UTC().Format(time.RFC3339), nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/time_fuzz.go000066400000000000000000000020371453143165200235650ustar00rootroot00000000000000//go:build !notest // +build !notest /* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "time" fuzz "github.com/google/gofuzz" ) // Fuzz satisfies fuzz.Interface. func (t *Time) Fuzz(c fuzz.Continue) { 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) } // ensure Time implements fuzz.Interface var _ fuzz.Interface = &Time{} golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/time_proto.go000066400000000000000000000064141453143165200237350ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "time" ) // Timestamp is a struct that is equivalent to Time, but intended for // protobuf marshalling/unmarshalling. It is generated into a serialization // that matches Time. Do not use in Go structs. type Timestamp struct { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. Seconds int64 `json:"seconds" protobuf:"varint,1,opt,name=seconds"` // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. This field may be limited in precision depending on context. Nanos int32 `json:"nanos" protobuf:"varint,2,opt,name=nanos"` } // Timestamp returns the Time as a new Timestamp value. func (m *Time) ProtoTime() *Timestamp { if m == nil { return &Timestamp{} } return &Timestamp{ Seconds: m.Time.Unix(), // leaving this here for the record. our JSON only handled seconds, so this results in writes by // protobuf clients storing values that aren't read by json clients, which results in unexpected // field mutation, which fails various validation and equality code. // Nanos: int32(m.Time.Nanosecond()), } } // Size implements the protobuf marshalling interface. func (m *Time) Size() (n int) { if m == nil || m.Time.IsZero() { return 0 } return m.ProtoTime().Size() } // Reset implements the protobuf marshalling interface. func (m *Time) Unmarshal(data []byte) error { if len(data) == 0 { m.Time = time.Time{} return nil } p := Timestamp{} if err := p.Unmarshal(data); err != nil { return err } // leaving this here for the record. our JSON only handled seconds, so this results in writes by // protobuf clients storing values that aren't read by json clients, which results in unexpected // field mutation, which fails various validation and equality code. // m.Time = time.Unix(p.Seconds, int64(p.Nanos)).Local() m.Time = time.Unix(p.Seconds, int64(0)).Local() return nil } // Marshal implements the protobuf marshaling interface. func (m *Time) Marshal() (data []byte, err error) { if m == nil || m.Time.IsZero() { return nil, nil } return m.ProtoTime().Marshal() } // MarshalTo implements the protobuf marshaling interface. func (m *Time) MarshalTo(data []byte) (int, error) { if m == nil || m.Time.IsZero() { return 0, nil } return m.ProtoTime().MarshalTo(data) } // MarshalToSizedBuffer implements the protobuf reverse marshaling interface. func (m *Time) MarshalToSizedBuffer(data []byte) (int, error) { if m == nil || m.Time.IsZero() { return 0, nil } return m.ProtoTime().MarshalToSizedBuffer(data) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/time_test.go000066400000000000000000000136331453143165200235520ustar00rootroot00000000000000/* 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 v1 import ( "encoding/json" "reflect" "testing" "time" "sigs.k8s.io/yaml" ) type TimeHolder struct { T Time `json:"t"` } func TestTimeMarshalYAML(t *testing.T) { cases := []struct { input Time result string }{ {Time{}, "t: null\n"}, {Date(1998, time.May, 5, 1, 5, 5, 50, time.FixedZone("test", -4*60*60)), "t: \"1998-05-05T05:05:05Z\"\n"}, {Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "t: \"1998-05-05T05:05:05Z\"\n"}, } for _, c := range cases { input := TimeHolder{c.input} result, err := yaml.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input: '%v': %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) } } } func TestTimeUnmarshalYAML(t *testing.T) { cases := []struct { input string result Time }{ {"t: null\n", Time{}}, {"t: 1998-05-05T05:05:05Z\n", Time{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, } for _, c := range cases { var result TimeHolder if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) } if result.T != c.result { t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) } } } func TestTimeMarshalJSON(t *testing.T) { cases := []struct { input Time result string }{ {Time{}, "{\"t\":null}"}, {Date(1998, time.May, 5, 5, 5, 5, 50, time.UTC), "{\"t\":\"1998-05-05T05:05:05Z\"}"}, {Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "{\"t\":\"1998-05-05T05:05:05Z\"}"}, } for _, c := range cases { input := TimeHolder{c.input} result, err := json.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input: '%v': %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) } } } func TestTimeUnmarshalJSON(t *testing.T) { cases := []struct { input string result Time }{ {"{\"t\":null}", Time{}}, {"{\"t\":\"1998-05-05T05:05:05Z\"}", Time{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, } for _, c := range cases { var result TimeHolder if err := json.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) } if result.T != c.result { t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) } } } func TestTimeMarshalJSONUnmarshalYAML(t *testing.T) { cases := []struct { input Time }{ {Time{}}, {Date(1998, time.May, 5, 5, 5, 5, 50, time.Local).Rfc3339Copy()}, {Date(1998, time.May, 5, 5, 5, 5, 0, time.Local).Rfc3339Copy()}, } for i, c := range cases { input := TimeHolder{c.input} jsonMarshalled, err := json.Marshal(&input) if err != nil { t.Errorf("%d-1: Failed to marshal input: '%v': %v", i, input, err) } var result TimeHolder err = yaml.Unmarshal(jsonMarshalled, &result) if err != nil { t.Errorf("%d-2: Failed to unmarshal '%+v': %v", i, string(jsonMarshalled), err) } iN, iO := input.T.Zone() oN, oO := result.T.Zone() if iN != oN || iO != oO { t.Errorf("%d-3: Time zones differ before and after serialization %s:%d %s:%d", i, iN, iO, oN, oO) } if input.T.UnixNano() != result.T.UnixNano() { t.Errorf("%d-4: Failed to marshal input '%#v': got %#v", i, input, result) } } } func TestTimeProto(t *testing.T) { cases := []struct { input Time }{ {Time{}}, {Date(1998, time.May, 5, 1, 5, 5, 0, time.Local)}, {Date(1998, time.May, 5, 5, 5, 5, 0, time.Local)}, } for _, c := range cases { input := c.input data, err := input.Marshal() if err != nil { t.Fatalf("Failed to marshal input: '%v': %v", input, err) } time := Time{} if err := time.Unmarshal(data); err != nil { t.Fatalf("Failed to unmarshal output: '%v': %v", input, err) } if !reflect.DeepEqual(input, time) { t.Errorf("Marshal->Unmarshal is not idempotent: '%v' vs '%v'", input, time) } } } func TestTimeEqual(t *testing.T) { t1 := NewTime(time.Now()) cases := []struct { name string x *Time y *Time result bool }{ {"nil =? nil", nil, nil, true}, {"!nil =? !nil", &t1, &t1, true}, {"nil =? !nil", nil, &t1, false}, {"!nil =? nil", &t1, nil, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { result := c.x.Equal(c.y) if result != c.result { t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) } }) } } func TestTimeBefore(t *testing.T) { t1 := NewTime(time.Now()) cases := []struct { name string x *Time y *Time }{ {"nil ', where is the name of a field in a struct, or key in a map // 'v:', where is the exact json formatted value of a list item // 'i:', where is position of a item in a list // 'k:', where is a map of a list item's key fields to their unique values // If a key maps to an empty Fields value, the field that key represents is part of the set. // // The exact format is defined in sigs.k8s.io/structured-merge-diff // +protobuf.options.(gogoproto.goproto_stringer)=false type FieldsV1 struct { // Raw is the underlying serialization of this object. Raw []byte `json:"-" protobuf:"bytes,1,opt,name=Raw"` } func (f FieldsV1) String() string { return string(f.Raw) } // TODO: Table does not generate to protobuf because of the interface{} - fix protobuf // generation to support a meta type that can accept any valid JSON. This can be introduced // in a v1 because clients a) receive an error if they try to access proto today, and b) // once introduced they would be able to gracefully switch over to using it. // Table is a tabular representation of a set of API resources. The server transforms the // object into a set of preferred columns for quickly reviewing the objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +protobuf=false type Table struct { TypeMeta `json:",inline"` // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional ListMeta `json:"metadata,omitempty"` // columnDefinitions describes each column in the returned items array. The number of cells per row // will always match the number of column definitions. ColumnDefinitions []TableColumnDefinition `json:"columnDefinitions"` // rows is the list of items in the table. Rows []TableRow `json:"rows"` } // TableColumnDefinition contains information about a column returned in the Table. // +protobuf=false type TableColumnDefinition struct { // name is a human readable name for the column. Name string `json:"name"` // type is an OpenAPI type definition for this column, such as number, integer, string, or // array. // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. Type string `json:"type"` // format is an optional OpenAPI type modifier for this column. A format modifies the type and // imposes additional rules, like date or time formatting for a string. The 'name' format is applied // to the primary identifier column which has type 'string' to assist in clients identifying column // is the resource name. // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. Format string `json:"format"` // description is a human readable description of this column. Description string `json:"description"` // priority is an integer defining the relative importance of this column compared to others. Lower // numbers are considered higher priority. Columns that may be omitted in limited space scenarios // should be given a higher priority. Priority int32 `json:"priority"` } // TableRow is an individual row in a table. // +protobuf=false 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. 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 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 // default serialization of the object for the current API version, and if "Metadata" (the default) will // contain the object metadata. Check the returned kind and apiVersion of the object before parsing. // The media type of the object will always match the enclosing list - if this as a JSON table, these // will be JSON encoded objects. // +optional Object runtime.RawExtension `json:"object,omitempty"` } // TableRowCondition allows a row to be marked with additional information. // +protobuf=false type TableRowCondition struct { // Type of row condition. The only defined value is 'Completed' indicating that the // object this row represents has reached a completed state and may be given less visual // priority than other rows. Clients are not required to honor any conditions but should // be consistent where possible about handling the conditions. Type RowConditionType `json:"type"` // Status of the condition, one of True, False, Unknown. Status ConditionStatus `json:"status"` // (brief) machine readable reason for the condition's last transition. // +optional Reason string `json:"reason,omitempty"` // Human readable message indicating details about last transition. // +optional Message string `json:"message,omitempty"` } type RowConditionType string // These are valid conditions of a row. This list is not exhaustive and new conditions may be // included by other resources. const ( // RowCompleted means the underlying resource has reached completion and may be given less // visual priority than other resources. RowCompleted RowConditionType = "Completed" ) type ConditionStatus string // These are valid condition statuses. "ConditionTrue" means a resource is in the condition. // "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes // can't decide if a resource is in the condition or not. In the future, we could add other // intermediate conditions, e.g. ConditionDegraded. const ( ConditionTrue ConditionStatus = "True" ConditionFalse ConditionStatus = "False" ConditionUnknown ConditionStatus = "Unknown" ) // IncludeObjectPolicy controls which portion of the object is returned with a Table. type IncludeObjectPolicy string const ( // IncludeNone returns no object. IncludeNone IncludeObjectPolicy = "None" // IncludeMetadata serializes the object containing only its metadata field. IncludeMetadata IncludeObjectPolicy = "Metadata" // IncludeObject contains the full object. IncludeObject IncludeObjectPolicy = "Object" ) // TableOptions are used when a Table is requested by the caller. // +k8s:conversion-gen:explicit-from=net/url.Values // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type TableOptions struct { TypeMeta `json:",inline"` // NoHeaders is only exposed for internal callers. It is not included in our OpenAPI definitions // and may be removed as a field in a future release. NoHeaders bool `json:"-"` // includeObject decides whether to include each object along with its columnar information. // Specifying "None" will return no object, specifying "Object" will return the full object contents, and // specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind // in version v1beta1 of the meta.k8s.io API group. IncludeObject IncludeObjectPolicy `json:"includeObject,omitempty" protobuf:"bytes,1,opt,name=includeObject,casttype=IncludeObjectPolicy"` } // PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients // to get access to a particular ObjectMeta schema without knowing the details of the version. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type PartialObjectMetadata struct { TypeMeta `json:",inline"` // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` } // PartialObjectMetadataList contains a list of objects containing only their metadata // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type PartialObjectMetadataList struct { TypeMeta `json:",inline"` // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // items contains each of the included items. Items []PartialObjectMetadata `json:"items" protobuf:"bytes,2,rep,name=items"` } // Condition contains details for one aspect of the current state of this API Resource. // --- // This struct is intended for direct use as an array at the field path .status.conditions. For example, // // type FooStatus struct{ // // Represents the observations of a foo's current state. // // Known .status.conditions.type are: "Available", "Progressing", and "Degraded" // // +patchMergeKey=type // // +patchStrategy=merge // // +listType=map // // +listMapKey=type // Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` // // // other fields // } type Condition struct { // type of condition in CamelCase or in foo.example.com/CamelCase. // --- // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be // useful (see .node.status.conditions), the ability to deconflict is important. // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` // +kubebuilder:validation:MaxLength=316 Type string `json:"type" protobuf:"bytes,1,opt,name=type"` // status of the condition, one of True, False, Unknown. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=True;False;Unknown Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"` // observedGeneration represents the .metadata.generation that the condition was set based upon. // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date // with respect to the current state of the instance. // +optional // +kubebuilder:validation:Minimum=0 ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` // lastTransitionTime is the last time the condition transitioned from one status to another. // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Type=string // +kubebuilder:validation:Format=date-time LastTransitionTime Time `json:"lastTransitionTime" protobuf:"bytes,4,opt,name=lastTransitionTime"` // reason contains a programmatic identifier indicating the reason for the condition's last transition. // Producers of specific condition types may define expected values and meanings for this field, // and whether the values are considered a guaranteed API. // The value should be a CamelCase string. // This field may not be empty. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength=1024 // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` Reason string `json:"reason" protobuf:"bytes,5,opt,name=reason"` // message is a human readable message indicating details about the transition. // This may be an empty string. // +required // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength=32768 Message string `json:"message" protobuf:"bytes,6,opt,name=message"` } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/types_swagger_doc_generated.go000066400000000000000000001423741453143165200273100ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 // This file contains a collection of methods that can be used from go-restful to // generate Swagger API documentation for its models. Please read this PR for more // information on the implementation: https://github.com/emicklei/go-restful/pull/215 // // TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if // they are on one line! For multiple line or blocks that you want to ignore use ---. // Any context after a --- is ignored. // // Those methods can be generated by using hack/update-codegen.sh // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_APIGroup = map[string]string{ "": "APIGroup contains the name, the supported versions, and the preferred version of a group.", "name": "name is the name of the group.", "versions": "versions are the versions supported in this group.", "preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.", "serverAddressByClientCIDRs": "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. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. 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.", } func (APIGroup) SwaggerDoc() map[string]string { return map_APIGroup } var map_APIGroupList = map[string]string{ "": "APIGroupList is a list of APIGroup, to allow clients to discover the API at /apis.", "groups": "groups is a list of APIGroup.", } func (APIGroupList) SwaggerDoc() map[string]string { return map_APIGroupList } var map_APIResource = map[string]string{ "": "APIResource specifies the name of a resource and whether it is namespaced.", "name": "name is the plural name of the resource.", "singularName": "singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface.", "namespaced": "namespaced indicates if a resource is namespaced or not.", "group": "group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale\".", "version": "version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".", "kind": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", "verbs": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", "shortNames": "shortNames is a list of suggested short names of the resource.", "categories": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", "storageVersionHash": "The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates.", } func (APIResource) SwaggerDoc() map[string]string { return map_APIResource } var map_APIResourceList = map[string]string{ "": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", "groupVersion": "groupVersion is the group and version this APIResourceList is for.", "resources": "resources contains the name of the resources and if they are namespaced.", } func (APIResourceList) SwaggerDoc() map[string]string { return map_APIResourceList } var map_APIVersions = map[string]string{ "": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", "versions": "versions are the api versions that are available.", "serverAddressByClientCIDRs": "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. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. 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.", } func (APIVersions) SwaggerDoc() map[string]string { return map_APIVersions } var map_ApplyOptions = map[string]string{ "": "ApplyOptions may be provided when applying an API object. FieldManager is required for apply requests. ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation that speaks specifically to how the options fields relate to apply.", "dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "force": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people.", "fieldManager": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required.", } func (ApplyOptions) SwaggerDoc() map[string]string { return map_ApplyOptions } var map_Condition = map[string]string{ "": "Condition contains details for one aspect of the current state of this API Resource.", "type": "type of condition in CamelCase or in foo.example.com/CamelCase.", "status": "status of the condition, one of True, False, Unknown.", "observedGeneration": "observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.", "lastTransitionTime": "lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", "reason": "reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.", "message": "message is a human readable message indicating details about the transition. This may be an empty string.", } func (Condition) SwaggerDoc() map[string]string { return map_Condition } var map_CreateOptions = map[string]string{ "": "CreateOptions may be provided when creating an API object.", "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", "fieldManager": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", "fieldValidation": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", } func (CreateOptions) SwaggerDoc() map[string]string { return map_CreateOptions } var map_DeleteOptions = map[string]string{ "": "DeleteOptions may be provided when deleting an API object.", "gracePeriodSeconds": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", "preconditions": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", "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", } func (DeleteOptions) SwaggerDoc() map[string]string { return map_DeleteOptions } 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:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", } func (FieldsV1) SwaggerDoc() map[string]string { return map_FieldsV1 } var map_GetOptions = map[string]string{ "": "GetOptions is the standard query options to the standard REST get call.", "resourceVersion": "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 details.\n\nDefaults to unset", } func (GetOptions) SwaggerDoc() map[string]string { return map_GetOptions } var map_GroupVersionForDiscovery = map[string]string{ "": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", "groupVersion": "groupVersion specifies the API group and version in the form \"group/version\"", "version": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion.", } func (GroupVersionForDiscovery) SwaggerDoc() map[string]string { return map_GroupVersionForDiscovery } var map_LabelSelector = map[string]string{ "": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", "matchLabels": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", "matchExpressions": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", } func (LabelSelector) SwaggerDoc() map[string]string { return map_LabelSelector } var map_LabelSelectorRequirement = map[string]string{ "": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", "key": "key is the label key that the selector applies to.", "operator": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", "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. This array is replaced during a strategic merge patch.", } func (LabelSelectorRequirement) SwaggerDoc() map[string]string { return map_LabelSelectorRequirement } var map_List = map[string]string{ "": "List holds a list of objects, which may not be known by the server.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "List of objects", } func (List) SwaggerDoc() map[string]string { return map_List } var map_ListMeta = map[string]string{ "": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", "selfLink": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "resourceVersion": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", "continue": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", "remainingItemCount": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", } func (ListMeta) SwaggerDoc() map[string]string { return map_ListMeta } var map_ListOptions = map[string]string{ "": "ListOptions is the query options to a standard REST list call.", "labelSelector": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", "fieldSelector": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", "watch": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", "allowWatchBookmarks": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not 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.", "resourceVersion": "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 details.\n\nDefaults to unset", "resourceVersionMatch": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", "timeoutSeconds": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", "limit": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", "continue": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", "sendInitialEvents": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", } func (ListOptions) SwaggerDoc() map[string]string { return map_ListOptions } var map_ManagedFieldsEntry = map[string]string{ "": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", "manager": "Manager is an identifier of the workflow managing these fields.", "operation": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", "apiVersion": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", "time": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", "fieldsType": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", "fieldsV1": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", "subresource": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", } func (ManagedFieldsEntry) SwaggerDoc() map[string]string { return map_ManagedFieldsEntry } var map_ObjectMeta = map[string]string{ "": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", "name": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", "generateName": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", "namespace": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", "selfLink": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", "uid": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", "resourceVersion": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", "generation": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", "creationTimestamp": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "deletionTimestamp": "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 directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "deletionGracePeriodSeconds": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", "labels": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", "annotations": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", "ownerReferences": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", "finalizers": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", "managedFields": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", } func (ObjectMeta) SwaggerDoc() map[string]string { return map_ObjectMeta } var map_OwnerReference = map[string]string{ "": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", "apiVersion": "API version of the referent.", "kind": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "name": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", "uid": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", "controller": "If true, this reference points to the managing controller.", "blockOwnerDeletion": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", } func (OwnerReference) SwaggerDoc() map[string]string { return map_OwnerReference } var map_PartialObjectMetadata = map[string]string{ "": "PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients to get access to a particular ObjectMeta schema without knowing the details of the version.", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", } func (PartialObjectMetadata) SwaggerDoc() map[string]string { return map_PartialObjectMetadata } var map_PartialObjectMetadataList = map[string]string{ "": "PartialObjectMetadataList contains a list of objects containing only their metadata", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "items contains each of the included items.", } func (PartialObjectMetadataList) SwaggerDoc() map[string]string { return map_PartialObjectMetadataList } var map_Patch = map[string]string{ "": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", } func (Patch) SwaggerDoc() map[string]string { return map_Patch } var map_PatchOptions = map[string]string{ "": "PatchOptions may be provided when patching an API object. PatchOptions is meant to be a superset of UpdateOptions.", "dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "force": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", "fieldManager": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", "fieldValidation": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", } func (PatchOptions) SwaggerDoc() map[string]string { return map_PatchOptions } var map_Preconditions = map[string]string{ "": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", "uid": "Specifies the target UID.", "resourceVersion": "Specifies the target ResourceVersion", } func (Preconditions) SwaggerDoc() map[string]string { return map_Preconditions } var map_RootPaths = map[string]string{ "": "RootPaths lists the paths available at root. For example: \"/healthz\", \"/apis\".", "paths": "paths are the paths available at root.", } func (RootPaths) SwaggerDoc() map[string]string { return map_RootPaths } var map_ServerAddressByClientCIDR = map[string]string{ "": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", "clientCIDR": "The CIDR with which clients can match their IP to figure out the server address that they should use.", "serverAddress": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", } func (ServerAddressByClientCIDR) SwaggerDoc() map[string]string { return map_ServerAddressByClientCIDR } var map_Status = map[string]string{ "": "Status is a return value for calls that don't return other objects.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "status": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", "message": "A human-readable description of the status of this operation.", "reason": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", "details": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type.", "code": "Suggested HTTP return code for this status, 0 if not set.", } func (Status) SwaggerDoc() map[string]string { return map_Status } var map_StatusCause = map[string]string{ "": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", "reason": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", "message": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", "field": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", } func (StatusCause) SwaggerDoc() map[string]string { return map_StatusCause } var map_StatusDetails = map[string]string{ "": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", "name": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", "group": "The group attribute of the resource associated with the status StatusReason.", "kind": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "uid": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", "causes": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", "retryAfterSeconds": "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 before taking the alternate action.", } func (StatusDetails) SwaggerDoc() map[string]string { return map_StatusDetails } var map_Table = map[string]string{ "": "Table is a tabular representation of a set of API resources. The server transforms the object into a set of preferred columns for quickly reviewing the objects.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "columnDefinitions": "columnDefinitions describes each column in the returned items array. The number of cells per row will always match the number of column definitions.", "rows": "rows is the list of items in the table.", } func (Table) SwaggerDoc() map[string]string { return map_Table } var map_TableColumnDefinition = map[string]string{ "": "TableColumnDefinition contains information about a column returned in the Table.", "name": "name is a human readable name for the column.", "type": "type is an OpenAPI type definition for this column, such as number, integer, string, or array. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.", "format": "format is an optional OpenAPI type modifier for this column. A format modifies the type and imposes additional rules, like date or time formatting for a string. The 'name' format is applied to the primary identifier column which has type 'string' to assist in clients identifying column is the resource name. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.", "description": "description is a human readable description of this column.", "priority": "priority is an integer defining the relative importance of this column compared to others. Lower numbers are considered higher priority. Columns that may be omitted in limited space scenarios should be given a higher priority.", } func (TableColumnDefinition) SwaggerDoc() map[string]string { return map_TableColumnDefinition } var map_TableOptions = map[string]string{ "": "TableOptions are used when a Table is requested by the caller.", "includeObject": "includeObject decides whether to include each object along with its columnar information. Specifying \"None\" will return no object, specifying \"Object\" will return the full object contents, and specifying \"Metadata\" (the default) will return the object's metadata in the PartialObjectMetadata kind in version v1beta1 of the meta.k8s.io API group.", } func (TableOptions) SwaggerDoc() map[string]string { return map_TableOptions } var map_TableRow = map[string]string{ "": "TableRow is an individual row in a table.", "cells": "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.", "conditions": "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.", "object": "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 default serialization of the object for the current API version, and if \"Metadata\" (the default) will contain the object metadata. Check the returned kind and apiVersion of the object before parsing. The media type of the object will always match the enclosing list - if this as a JSON table, these will be JSON encoded objects.", } func (TableRow) SwaggerDoc() map[string]string { return map_TableRow } var map_TableRowCondition = map[string]string{ "": "TableRowCondition allows a row to be marked with additional information.", "type": "Type of row condition. The only defined value is 'Completed' indicating that the object this row represents has reached a completed state and may be given less visual priority than other rows. Clients are not required to honor any conditions but should be consistent where possible about handling the conditions.", "status": "Status of the condition, one of True, False, Unknown.", "reason": "(brief) machine readable reason for the condition's last transition.", "message": "Human readable message indicating details about last transition.", } func (TableRowCondition) SwaggerDoc() map[string]string { return map_TableRowCondition } var map_TypeMeta = map[string]string{ "": "TypeMeta describes an individual object in an API response or request with strings representing the type of the object and its API schema version. Structures that are versioned or persisted should inline TypeMeta.", "kind": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "apiVersion": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", } func (TypeMeta) SwaggerDoc() map[string]string { return map_TypeMeta } var map_UpdateOptions = map[string]string{ "": "UpdateOptions may be provided when updating an API object. All fields in UpdateOptions should also be present in PatchOptions.", "dryRun": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", "fieldManager": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", "fieldValidation": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", } func (UpdateOptions) SwaggerDoc() map[string]string { return map_UpdateOptions } // AUTO-GENERATED FUNCTIONS END HERE golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/types_test.go000066400000000000000000000100221453143165200237450ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( gojson "encoding/json" "reflect" "testing" utiljson "k8s.io/apimachinery/pkg/util/json" ) func TestVerbsMarshalJSON(t *testing.T) { cases := []struct { input APIResource result string }{ {APIResource{}, `{"name":"","singularName":"","namespaced":false,"kind":"","verbs":null}`}, {APIResource{Verbs: Verbs([]string{})}, `{"name":"","singularName":"","namespaced":false,"kind":"","verbs":[]}`}, {APIResource{Verbs: Verbs([]string{"delete"})}, `{"name":"","singularName":"","namespaced":false,"kind":"","verbs":["delete"]}`}, } for i, c := range cases { result, err := gojson.Marshal(&c.input) if err != nil { t.Errorf("[%d] Failed to marshal input: '%v': %v", i, c.input, err) } if string(result) != c.result { t.Errorf("[%d] Failed to marshal input: '%v': expected '%v', got '%v'", i, c.input, c.result, string(result)) } } } func TestVerbsJsonIterUnmarshalJSON(t *testing.T) { cases := []struct { input string result APIResource }{ {`{}`, APIResource{}}, {`{"verbs":null}`, APIResource{}}, {`{"verbs":[]}`, APIResource{Verbs: Verbs([]string{})}}, {`{"verbs":["delete"]}`, APIResource{Verbs: Verbs([]string{"delete"})}}, } for i, c := range cases { var result APIResource if err := utiljson.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err) } if !reflect.DeepEqual(result, c.result) { t.Errorf("[%d] Failed to unmarshal input '%v': expected %+v, got %+v", i, c.input, c.result, result) } } } // TestMarshalJSONWithOmit tests that we don't have regressions regarding nil and empty slices with "omit" func TestMarshalJSONWithOmit(t *testing.T) { cases := []struct { input LabelSelector result string }{ {LabelSelector{}, `{}`}, {LabelSelector{MatchExpressions: []LabelSelectorRequirement{}}, `{}`}, {LabelSelector{MatchExpressions: []LabelSelectorRequirement{{}}}, `{"matchExpressions":[{"key":"","operator":""}]}`}, } for i, c := range cases { result, err := gojson.Marshal(&c.input) if err != nil { t.Errorf("[%d] Failed to marshal input: '%v': %v", i, c.input, err) } if string(result) != c.result { t.Errorf("[%d] Failed to marshal input: '%v': expected '%v', got '%v'", i, c.input, c.result, string(result)) } } } func TestVerbsUnmarshalJSON(t *testing.T) { cases := []struct { input string result APIResource }{ {`{}`, APIResource{}}, {`{"verbs":null}`, APIResource{}}, {`{"verbs":[]}`, APIResource{Verbs: Verbs([]string{})}}, {`{"verbs":["delete"]}`, APIResource{Verbs: Verbs([]string{"delete"})}}, } for i, c := range cases { var result APIResource if err := gojson.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err) } if !reflect.DeepEqual(result, c.result) { t.Errorf("[%d] Failed to unmarshal input '%v': expected %+v, got %+v", i, c.input, c.result, result) } } } func TestVerbsProto(t *testing.T) { cases := []APIResource{ {}, {Verbs: Verbs([]string{})}, {Verbs: Verbs([]string{"delete"})}, } for _, input := range cases { data, err := input.Marshal() if err != nil { t.Fatalf("Failed to marshal input: '%v': %v", input, err) } resource := APIResource{} if err := resource.Unmarshal(data); err != nil { t.Fatalf("Failed to unmarshal output: '%v': %v", input, err) } if !reflect.DeepEqual(input, resource) { t.Errorf("Marshal->Unmarshal is not idempotent: '%v' vs '%v'", input, resource) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/000077500000000000000000000000001453143165200237675ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/helpers.go000066400000000000000000000404261453143165200257660ustar00rootroot00000000000000/* Copyright 2015 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 unstructured import ( gojson "encoding/json" "fmt" "io" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" "k8s.io/klog/v2" ) // NestedFieldCopy returns a deep copy of the value of a nested field. // Returns false if the value is missing. // No error is returned for a nil field. // // Note: fields passed to this function are treated as keys within the passed // object; no array/slice syntax is supported. func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return nil, found, err } return runtime.DeepCopyJSONValue(val), true, nil } // NestedFieldNoCopy returns a reference to a nested field. // Returns false if value is not found and an error if unable // to traverse obj. // // Note: fields passed to this function are treated as keys within the passed // object; no array/slice syntax is supported. func NestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) { var val interface{} = obj for i, field := range fields { if val == nil { return nil, false, nil } if m, ok := val.(map[string]interface{}); ok { val, ok = m[field] if !ok { return nil, false, nil } } else { return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields[:i+1]), val, val) } } return val, true, nil } // NestedString returns the string value of a nested field. // Returns false if value is not found and an error if not a string. func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return "", found, err } s, ok := val.(string) if !ok { return "", false, fmt.Errorf("%v accessor error: %v is of the type %T, expected string", jsonPath(fields), val, val) } return s, true, nil } // NestedBool returns the bool value of a nested field. // Returns false if value is not found and an error if not a bool. func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool, error) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return false, found, err } b, ok := val.(bool) if !ok { return false, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected bool", jsonPath(fields), val, val) } return b, true, nil } // NestedFloat64 returns the float64 value of a nested field. // Returns false if value is not found and an error if not a float64. func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return 0, found, err } f, ok := val.(float64) if !ok { return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64", jsonPath(fields), val, val) } return f, true, nil } // NestedInt64 returns the int64 value of a nested field. // Returns false if value is not found and an error if not an int64. func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, error) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return 0, found, err } i, ok := val.(int64) if !ok { return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected int64", jsonPath(fields), val, val) } return i, true, nil } // 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) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return nil, found, err } m, ok := val.([]interface{}) if !ok { return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val) } strSlice := make([]string, 0, len(m)) for _, v := range m { if str, ok := v.(string); ok { strSlice = append(strSlice, str) } else { return nil, false, fmt.Errorf("%v accessor error: contains non-string key in the slice: %v is of the type %T, expected string", jsonPath(fields), v, v) } } return strSlice, true, nil } // NestedSlice returns a deep copy of []interface{} value of a nested field. // Returns false if value is not found and an error if not a []interface{}. func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return nil, found, err } _, ok := val.([]interface{}) if !ok { return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val) } return runtime.DeepCopyJSONValue(val).([]interface{}), true, nil } // 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...) if !found || err != 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 { 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...) if !found || err != nil { return nil, found, err } return runtime.DeepCopyJSON(m), true, nil } // 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) { val, found, err := NestedFieldNoCopy(obj, fields...) if !found || err != nil { return nil, found, err } 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) } return m, true, nil } // SetNestedField sets the value of a nested field to a deep copy of the value provided. // Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) error { return setNestedFieldNoCopy(obj, runtime.DeepCopyJSONValue(value), fields...) } func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) error { m := obj for i, field := range fields[:len(fields)-1] { if val, ok := m[field]; ok { if valMap, ok := val.(map[string]interface{}); ok { m = valMap } else { return fmt.Errorf("value cannot be set because %v is not a map[string]interface{}", jsonPath(fields[:i+1])) } } else { newVal := make(map[string]interface{}) m[field] = newVal m = newVal } } m[fields[len(fields)-1]] = value return nil } // SetNestedStringSlice sets the string slice value of a nested field. // Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. func SetNestedStringSlice(obj map[string]interface{}, value []string, fields ...string) error { m := make([]interface{}, 0, len(value)) // convert []string into []interface{} for _, v := range value { m = append(m, v) } return setNestedFieldNoCopy(obj, m, fields...) } // SetNestedSlice sets the slice value of a nested field. // Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. func SetNestedSlice(obj map[string]interface{}, value []interface{}, fields ...string) error { return SetNestedField(obj, value, fields...) } // SetNestedStringMap sets the map[string]string value of a nested field. // Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. func SetNestedStringMap(obj map[string]interface{}, value map[string]string, fields ...string) error { m := make(map[string]interface{}, len(value)) // convert map[string]string into map[string]interface{} for k, v := range value { m[k] = v } return setNestedFieldNoCopy(obj, m, fields...) } // SetNestedMap sets the map[string]interface{} value of a nested field. // Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. func SetNestedMap(obj map[string]interface{}, value map[string]interface{}, fields ...string) error { return SetNestedField(obj, value, fields...) } // RemoveNestedField removes the nested field from the obj. func RemoveNestedField(obj map[string]interface{}, fields ...string) { m := obj for _, field := range fields[:len(fields)-1] { if x, ok := m[field].(map[string]interface{}); ok { m = x } else { return } } delete(m, fields[len(fields)-1]) } func getNestedString(obj map[string]interface{}, fields ...string) string { val, found, err := NestedString(obj, fields...) if !found || err != nil { return "" } return val } func getNestedInt64Pointer(obj map[string]interface{}, fields ...string) *int64 { val, found, err := NestedInt64(obj, fields...) if !found || err != nil { return nil } return &val } func jsonPath(fields []string) string { return "." + strings.Join(fields, ".") } func extractOwnerReference(v map[string]interface{}) metav1.OwnerReference { // though this field is a *bool, but when decoded from JSON, it's // unmarshalled as bool. var controllerPtr *bool if controller, found, err := NestedBool(v, "controller"); err == nil && found { controllerPtr = &controller } var blockOwnerDeletionPtr *bool if blockOwnerDeletion, found, err := NestedBool(v, "blockOwnerDeletion"); err == nil && found { blockOwnerDeletionPtr = &blockOwnerDeletion } return metav1.OwnerReference{ Kind: getNestedString(v, "kind"), Name: getNestedString(v, "name"), APIVersion: getNestedString(v, "apiVersion"), UID: types.UID(getNestedString(v, "uid")), Controller: controllerPtr, BlockOwnerDeletion: blockOwnerDeletionPtr, } } // UnstructuredJSONScheme is capable of converting JSON data into the Unstructured // type, which can be used for generic access to objects without a predefined scheme. // TODO: move into serializer/json. var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{} type unstructuredJSONScheme struct{} const unstructuredJSONSchemeIdentifier runtime.Identifier = "unstructuredJSON" func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { var err error if obj != nil { err = s.decodeInto(data, obj) } else { obj, err = s.decode(data) } if err != nil { return nil, nil, err } gvk := obj.GetObjectKind().GroupVersionKind() if len(gvk.Kind) == 0 { return nil, &gvk, runtime.NewMissingKindErr(string(data)) } // TODO(109023): require apiVersion here as well return obj, &gvk, nil } func (s unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error { if co, ok := obj.(runtime.CacheableObject); ok { return co.CacheEncode(s.Identifier(), s.doEncode, w) } return s.doEncode(obj, w) } func (unstructuredJSONScheme) doEncode(obj runtime.Object, w io.Writer) error { switch t := obj.(type) { case *Unstructured: return json.NewEncoder(w).Encode(t.Object) case *UnstructuredList: items := make([]interface{}, 0, len(t.Items)) for _, i := range t.Items { items = append(items, i.Object) } listObj := make(map[string]interface{}, len(t.Object)+1) for k, v := range t.Object { // Make a shallow copy listObj[k] = v } listObj["items"] = items return json.NewEncoder(w).Encode(listObj) case *runtime.Unknown: // TODO: Unstructured needs to deal with ContentType. _, err := w.Write(t.Raw) return err default: return json.NewEncoder(w).Encode(t) } } // Identifier implements runtime.Encoder interface. func (unstructuredJSONScheme) Identifier() runtime.Identifier { return unstructuredJSONSchemeIdentifier } func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { type detector struct { Items gojson.RawMessage `json:"items"` } var det detector if err := json.Unmarshal(data, &det); err != nil { return nil, err } if det.Items != nil { list := &UnstructuredList{} err := s.decodeToList(data, list) return list, err } // No Items field, so it wasn't a list. unstruct := &Unstructured{} err := s.decodeToUnstructured(data, unstruct) return unstruct, err } func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error { switch x := obj.(type) { case *Unstructured: return s.decodeToUnstructured(data, x) case *UnstructuredList: return s.decodeToList(data, x) default: return json.Unmarshal(data, x) } } func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { m := make(map[string]interface{}) if err := json.Unmarshal(data, &m); err != nil { return err } unstruct.Object = m return nil } func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { type decodeList struct { Items []gojson.RawMessage `json:"items"` } var dList decodeList if err := json.Unmarshal(data, &dList); err != nil { return err } if err := json.Unmarshal(data, &list.Object); err != nil { return err } // For typed lists, e.g., a PodList, API server doesn't set each item's // APIVersion and Kind. We need to set it. listAPIVersion := list.GetAPIVersion() listKind := list.GetKind() itemKind := strings.TrimSuffix(listKind, "List") delete(list.Object, "items") list.Items = make([]Unstructured, 0, len(dList.Items)) for _, i := range dList.Items { unstruct := &Unstructured{} if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { return err } // This is hacky. Set the item's Kind and APIVersion to those inferred // from the List. if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 { unstruct.SetKind(itemKind) unstruct.SetAPIVersion(listAPIVersion) } list.Items = append(list.Items, *unstruct) } return nil } type jsonFallbackEncoder struct { encoder runtime.Encoder identifier runtime.Identifier } func NewJSONFallbackEncoder(encoder runtime.Encoder) runtime.Encoder { result := map[string]string{ "name": "fallback", "base": string(encoder.Identifier()), } identifier, err := gojson.Marshal(result) if err != nil { klog.Fatalf("Failed marshaling identifier for jsonFallbackEncoder: %v", err) } return &jsonFallbackEncoder{ encoder: encoder, identifier: runtime.Identifier(identifier), } } func (c *jsonFallbackEncoder) Encode(obj runtime.Object, w io.Writer) error { // There is no need to handle runtime.CacheableObject, as we only // fallback to other encoders here. err := c.encoder.Encode(obj, w) if runtime.IsNotRegisteredError(err) { switch obj.(type) { case *Unstructured, *UnstructuredList: return UnstructuredJSONScheme.Encode(obj, w) } } return err } // Identifier implements runtime.Encoder interface. func (c *jsonFallbackEncoder) Identifier() runtime.Identifier { return c.identifier } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/helpers_test.go000066400000000000000000000137121453143165200270230ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package unstructured import ( "io/ioutil" "sync" "testing" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" "github.com/stretchr/testify/assert" ) // TestCodecOfUnstructuredList tests that there are no data races in Encode(). // i.e. that it does not mutate the object being encoded. func TestCodecOfUnstructuredList(t *testing.T) { var wg sync.WaitGroup concurrency := 10 list := UnstructuredList{ Object: map[string]interface{}{}, } wg.Add(concurrency) for i := 0; i < concurrency; i++ { go func() { defer wg.Done() assert.NoError(t, UnstructuredJSONScheme.Encode(&list, ioutil.Discard)) }() } wg.Wait() } func TestRemoveNestedField(t *testing.T) { obj := map[string]interface{}{ "x": map[string]interface{}{ "y": 1, "a": "foo", }, } RemoveNestedField(obj, "x", "a") assert.Len(t, obj["x"], 1) RemoveNestedField(obj, "x", "y") assert.Empty(t, obj["x"]) RemoveNestedField(obj, "x") assert.Empty(t, obj) RemoveNestedField(obj, "x") // Remove of a non-existent field assert.Empty(t, obj) } func TestNestedFieldNoCopy(t *testing.T) { target := map[string]interface{}{"foo": "bar"} obj := map[string]interface{}{ "a": map[string]interface{}{ "b": target, "c": nil, "d": []interface{}{"foo"}, "e": []interface{}{ map[string]interface{}{ "f": "bar", }, }, }, } // case 1: field exists and is non-nil res, exists, err := NestedFieldNoCopy(obj, "a", "b") assert.True(t, exists) assert.NoError(t, err) assert.Equal(t, target, res) target["foo"] = "baz" assert.Equal(t, target["foo"], res.(map[string]interface{})["foo"], "result should be a reference to the expected item") // case 2: field exists and is nil res, exists, err = NestedFieldNoCopy(obj, "a", "c") assert.True(t, exists) assert.NoError(t, err) assert.Nil(t, res) // case 3: error traversing obj res, exists, err = NestedFieldNoCopy(obj, "a", "d", "foo") assert.False(t, exists) assert.Error(t, err) assert.Nil(t, res) // case 4: field does not exist res, exists, err = NestedFieldNoCopy(obj, "a", "g") assert.False(t, exists) assert.NoError(t, err) assert.Nil(t, res) // case 5: intermediate field does not exist res, exists, err = NestedFieldNoCopy(obj, "a", "g", "f") assert.False(t, exists) assert.NoError(t, err) assert.Nil(t, res) // case 6: intermediate field is null // (background: happens easily in YAML) res, exists, err = NestedFieldNoCopy(obj, "a", "c", "f") assert.False(t, exists) assert.NoError(t, err) assert.Nil(t, res) // case 7: array/slice syntax is not supported // (background: users may expect this to be supported) res, exists, err = NestedFieldNoCopy(obj, "a", "e[0]") assert.False(t, exists) assert.NoError(t, err) assert.Nil(t, res) } func TestNestedFieldCopy(t *testing.T) { target := map[string]interface{}{"foo": "bar"} obj := map[string]interface{}{ "a": map[string]interface{}{ "b": target, "c": nil, "d": []interface{}{"foo"}, }, } // case 1: field exists and is non-nil res, exists, err := NestedFieldCopy(obj, "a", "b") assert.True(t, exists) assert.NoError(t, err) assert.Equal(t, target, res) target["foo"] = "baz" assert.NotEqual(t, target["foo"], res.(map[string]interface{})["foo"], "result should be a copy of the expected item") // case 2: field exists and is nil res, exists, err = NestedFieldCopy(obj, "a", "c") assert.True(t, exists) assert.NoError(t, err) assert.Nil(t, res) // case 3: error traversing obj res, exists, err = NestedFieldCopy(obj, "a", "d", "foo") assert.False(t, exists) assert.Error(t, err) assert.Nil(t, res) // case 4: field does not exist res, exists, err = NestedFieldCopy(obj, "a", "e") assert.False(t, exists) assert.NoError(t, err) assert.Nil(t, res) } func TestCacheableObject(t *testing.T) { runtimetesting.CacheableObjectTest(t, UnstructuredJSONScheme) } func TestSetNestedStringSlice(t *testing.T) { obj := map[string]interface{}{ "x": map[string]interface{}{ "y": 1, "a": "foo", }, } err := SetNestedStringSlice(obj, []string{"bar"}, "x", "z") 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") } func TestSetNestedSlice(t *testing.T) { obj := map[string]interface{}{ "x": map[string]interface{}{ "y": 1, "a": "foo", }, } err := SetNestedSlice(obj, []interface{}{"bar"}, "x", "z") 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") } func TestSetNestedStringMap(t *testing.T) { obj := map[string]interface{}{ "x": map[string]interface{}{ "y": 1, "a": "foo", }, } err := SetNestedStringMap(obj, map[string]string{"b": "bar"}, "x", "z") 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") } func TestSetNestedMap(t *testing.T) { obj := map[string]interface{}{ "x": map[string]interface{}{ "y": 1, "a": "foo", }, } err := SetNestedMap(obj, map[string]interface{}{"b": "bar"}, "x", "z") 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") } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructured.go000066400000000000000000000332771453143165200271010ustar00rootroot00000000000000/* Copyright 2015 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 unstructured import ( "bytes" "errors" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // Unstructured allows objects that do not have Golang structs registered to be manipulated // generically. This can be used to deal with the API objects from a plug-in. Unstructured // objects still have functioning TypeMeta features-- kind, version, etc. // // WARNING: This object has accessors for the v1 standard metadata. You *MUST NOT* use this // type if you are dealing with objects that are not in the server meta v1 schema. // // TODO: make the serialization part of this type distinct from the field accessors. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen=true type Unstructured struct { // Object is a JSON compatible map with string, float, int, bool, []interface{}, or // map[string]interface{} // children. Object map[string]interface{} } var _ metav1.Object = &Unstructured{} var _ runtime.Unstructured = &Unstructured{} var _ metav1.ListInterface = &Unstructured{} func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } func (obj *Unstructured) IsList() bool { field, ok := obj.Object["items"] if !ok { return false } _, ok = field.([]interface{}) return ok } func (obj *Unstructured) ToList() (*UnstructuredList, error) { if !obj.IsList() { // return an empty list back return &UnstructuredList{Object: obj.Object}, nil } ret := &UnstructuredList{} ret.Object = obj.Object err := obj.EachListItem(func(item runtime.Object) error { castItem := item.(*Unstructured) ret.Items = append(ret.Items, *castItem) return nil }) if err != nil { return nil, err } return ret, nil } func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { field, ok := obj.Object["items"] if !ok { return errors.New("content is not a list") } items, ok := field.([]interface{}) if !ok { return fmt.Errorf("content is not a list: %T", field) } for _, item := range items { child, ok := item.(map[string]interface{}) if !ok { return fmt.Errorf("items member is not an object: %T", child) } if err := fn(&Unstructured{Object: child}); err != nil { return err } } return nil } func (obj *Unstructured) EachListItemWithAlloc(fn func(runtime.Object) error) error { // EachListItem has allocated a new Object for the user, we can use it directly. return obj.EachListItem(fn) } func (obj *Unstructured) UnstructuredContent() map[string]interface{} { if obj.Object == nil { return make(map[string]interface{}) } return obj.Object } func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) { obj.Object = content } // MarshalJSON ensures that the unstructured object produces proper // JSON when passed to Go's standard JSON library. func (u *Unstructured) MarshalJSON() ([]byte, error) { var buf bytes.Buffer err := UnstructuredJSONScheme.Encode(u, &buf) return buf.Bytes(), err } // UnmarshalJSON ensures that the unstructured object properly decodes // JSON when passed to Go's standard JSON library. func (u *Unstructured) UnmarshalJSON(b []byte) error { _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) return err } // NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data. // This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info. func (in *Unstructured) NewEmptyInstance() runtime.Unstructured { out := new(Unstructured) if in != nil { out.GetObjectKind().SetGroupVersionKind(in.GetObjectKind().GroupVersionKind()) } return out } func (in *Unstructured) DeepCopy() *Unstructured { if in == nil { return nil } out := new(Unstructured) *out = *in out.Object = runtime.DeepCopyJSON(in.Object) return out } func (u *Unstructured) setNestedField(value interface{}, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } SetNestedField(u.Object, value, fields...) } func (u *Unstructured) setNestedStringSlice(value []string, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } SetNestedStringSlice(u.Object, value, fields...) } func (u *Unstructured) setNestedSlice(value []interface{}, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } SetNestedSlice(u.Object, value, fields...) } func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } SetNestedStringMap(u.Object, value, fields...) } func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference { field, found, err := NestedFieldNoCopy(u.Object, "metadata", "ownerReferences") if !found || err != nil { return nil } original, ok := field.([]interface{}) if !ok { return nil } ret := make([]metav1.OwnerReference, 0, len(original)) for _, obj := range original { o, ok := obj.(map[string]interface{}) if !ok { // expected map[string]interface{}, got something else return nil } ret = append(ret, extractOwnerReference(o)) } return ret } func (u *Unstructured) SetOwnerReferences(references []metav1.OwnerReference) { if references == nil { RemoveNestedField(u.Object, "metadata", "ownerReferences") return } newReferences := make([]interface{}, 0, len(references)) for _, reference := range references { out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&reference) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to convert Owner Reference: %v", err)) continue } newReferences = append(newReferences, out) } u.setNestedField(newReferences, "metadata", "ownerReferences") } func (u *Unstructured) GetAPIVersion() string { return getNestedString(u.Object, "apiVersion") } func (u *Unstructured) SetAPIVersion(version string) { u.setNestedField(version, "apiVersion") } func (u *Unstructured) GetKind() string { return getNestedString(u.Object, "kind") } func (u *Unstructured) SetKind(kind string) { u.setNestedField(kind, "kind") } func (u *Unstructured) GetNamespace() string { return getNestedString(u.Object, "metadata", "namespace") } func (u *Unstructured) SetNamespace(namespace string) { if len(namespace) == 0 { RemoveNestedField(u.Object, "metadata", "namespace") return } u.setNestedField(namespace, "metadata", "namespace") } func (u *Unstructured) GetName() string { return getNestedString(u.Object, "metadata", "name") } func (u *Unstructured) SetName(name string) { if len(name) == 0 { RemoveNestedField(u.Object, "metadata", "name") return } u.setNestedField(name, "metadata", "name") } func (u *Unstructured) GetGenerateName() string { return getNestedString(u.Object, "metadata", "generateName") } func (u *Unstructured) SetGenerateName(generateName string) { if len(generateName) == 0 { RemoveNestedField(u.Object, "metadata", "generateName") return } u.setNestedField(generateName, "metadata", "generateName") } func (u *Unstructured) GetUID() types.UID { return types.UID(getNestedString(u.Object, "metadata", "uid")) } func (u *Unstructured) SetUID(uid types.UID) { if len(string(uid)) == 0 { RemoveNestedField(u.Object, "metadata", "uid") return } u.setNestedField(string(uid), "metadata", "uid") } func (u *Unstructured) GetResourceVersion() string { return getNestedString(u.Object, "metadata", "resourceVersion") } func (u *Unstructured) SetResourceVersion(resourceVersion string) { if len(resourceVersion) == 0 { RemoveNestedField(u.Object, "metadata", "resourceVersion") return } u.setNestedField(resourceVersion, "metadata", "resourceVersion") } func (u *Unstructured) GetGeneration() int64 { val, found, err := NestedInt64(u.Object, "metadata", "generation") if !found || err != nil { return 0 } return val } func (u *Unstructured) SetGeneration(generation int64) { if generation == 0 { RemoveNestedField(u.Object, "metadata", "generation") return } u.setNestedField(generation, "metadata", "generation") } func (u *Unstructured) GetSelfLink() string { return getNestedString(u.Object, "metadata", "selfLink") } func (u *Unstructured) SetSelfLink(selfLink string) { if len(selfLink) == 0 { RemoveNestedField(u.Object, "metadata", "selfLink") return } u.setNestedField(selfLink, "metadata", "selfLink") } func (u *Unstructured) GetContinue() string { return getNestedString(u.Object, "metadata", "continue") } func (u *Unstructured) SetContinue(c string) { if len(c) == 0 { RemoveNestedField(u.Object, "metadata", "continue") return } u.setNestedField(c, "metadata", "continue") } func (u *Unstructured) GetRemainingItemCount() *int64 { return getNestedInt64Pointer(u.Object, "metadata", "remainingItemCount") } func (u *Unstructured) SetRemainingItemCount(c *int64) { if c == nil { RemoveNestedField(u.Object, "metadata", "remainingItemCount") } else { u.setNestedField(*c, "metadata", "remainingItemCount") } } func (u *Unstructured) GetCreationTimestamp() metav1.Time { var timestamp metav1.Time timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "creationTimestamp")) return timestamp } func (u *Unstructured) SetCreationTimestamp(timestamp metav1.Time) { ts, _ := timestamp.MarshalQueryParameter() if len(ts) == 0 || timestamp.Time.IsZero() { RemoveNestedField(u.Object, "metadata", "creationTimestamp") return } u.setNestedField(ts, "metadata", "creationTimestamp") } func (u *Unstructured) GetDeletionTimestamp() *metav1.Time { var timestamp metav1.Time timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "deletionTimestamp")) if timestamp.IsZero() { return nil } return ×tamp } func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) { if timestamp == nil { RemoveNestedField(u.Object, "metadata", "deletionTimestamp") return } ts, _ := timestamp.MarshalQueryParameter() u.setNestedField(ts, "metadata", "deletionTimestamp") } func (u *Unstructured) GetDeletionGracePeriodSeconds() *int64 { val, found, err := NestedInt64(u.Object, "metadata", "deletionGracePeriodSeconds") if !found || err != nil { return nil } return &val } func (u *Unstructured) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) { if deletionGracePeriodSeconds == nil { RemoveNestedField(u.Object, "metadata", "deletionGracePeriodSeconds") return } u.setNestedField(*deletionGracePeriodSeconds, "metadata", "deletionGracePeriodSeconds") } func (u *Unstructured) GetLabels() map[string]string { m, _, _ := NestedStringMap(u.Object, "metadata", "labels") return m } func (u *Unstructured) SetLabels(labels map[string]string) { if labels == nil { RemoveNestedField(u.Object, "metadata", "labels") return } u.setNestedMap(labels, "metadata", "labels") } func (u *Unstructured) GetAnnotations() map[string]string { m, _, _ := NestedStringMap(u.Object, "metadata", "annotations") return m } func (u *Unstructured) SetAnnotations(annotations map[string]string) { if annotations == nil { RemoveNestedField(u.Object, "metadata", "annotations") return } u.setNestedMap(annotations, "metadata", "annotations") } func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) { u.SetAPIVersion(gvk.GroupVersion().String()) u.SetKind(gvk.Kind) } func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) if err != nil { return schema.GroupVersionKind{} } gvk := gv.WithKind(u.GetKind()) return gvk } func (u *Unstructured) GetFinalizers() []string { val, _, _ := NestedStringSlice(u.Object, "metadata", "finalizers") return val } func (u *Unstructured) SetFinalizers(finalizers []string) { if finalizers == nil { RemoveNestedField(u.Object, "metadata", "finalizers") return } u.setNestedStringSlice(finalizers, "metadata", "finalizers") } func (u *Unstructured) GetManagedFields() []metav1.ManagedFieldsEntry { items, found, err := NestedSlice(u.Object, "metadata", "managedFields") if !found || err != nil { return nil } managedFields := []metav1.ManagedFieldsEntry{} for _, item := range items { m, ok := item.(map[string]interface{}) if !ok { utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object, item %v is not a map", item)) return nil } out := metav1.ManagedFieldsEntry{} if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, &out); err != nil { utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) return nil } managedFields = append(managedFields, out) } return managedFields } func (u *Unstructured) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) { if managedFields == nil { RemoveNestedField(u.Object, "metadata", "managedFields") return } items := []interface{}{} for _, managedFieldsEntry := range managedFields { out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&managedFieldsEntry) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to set managedFields for object: %v", err)) return } items = append(items, out) } u.setNestedSlice(items, "metadata", "managedFields") } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructured_conversion_test.go000066400000000000000000000405611453143165200323770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package unstructured_test import ( "fmt" "reflect" "strings" "testing" "time" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/testapigroup" testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/test" ) func TestObjectToUnstructuredConversion(t *testing.T) { scheme, _ := test.TestScheme() testCases := []struct { name string objectToConvert runtime.Object expectedErr error expectedConvertedUnstructured *unstructured.Unstructured }{ { name: "convert nil object to unstructured should fail", objectToConvert: nil, expectedErr: fmt.Errorf("unable to convert object type to Unstructured, must be a runtime.Object"), expectedConvertedUnstructured: &unstructured.Unstructured{}, }, { name: "convert versioned empty object to unstructured should work", objectToConvert: &testapigroupv1.Carp{}, expectedConvertedUnstructured: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, }, "spec": map[string]interface{}{}, "status": map[string]interface{}{}, }, }, }, { name: "convert valid versioned object to unstructured should work", objectToConvert: &testapigroupv1.Carp{ ObjectMeta: metav1.ObjectMeta{ Name: "noxu", }, Spec: testapigroupv1.CarpSpec{ Hostname: "example.com", }, }, expectedConvertedUnstructured: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, }, { name: "convert hub-versioned object to unstructured should fail", objectToConvert: &testapigroup.Carp{}, expectedErr: fmt.Errorf("unable to convert the internal object type *testapigroup.Carp to Unstructured without providing a preferred version to convert to"), }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { outUnstructured := &unstructured.Unstructured{} err := scheme.Convert(testCase.objectToConvert, outUnstructured, nil) if err != nil { assert.Equal(t, testCase.expectedErr, err) return } assert.Equal(t, testCase.expectedConvertedUnstructured, outUnstructured) }) } } func TestUnstructuredToObjectConversion(t *testing.T) { scheme, _ := test.TestScheme() testCases := []struct { name string unstructuredToConvert *unstructured.Unstructured convertingObject runtime.Object expectPanic bool expectedErrFunc func(err error) bool expectedConvertedObject runtime.Object }{ { name: "convert empty unstructured w/o gvk to versioned object should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{}, }, convertingObject: &testapigroupv1.Carp{}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) }, }, { name: "convert empty versioned unstructured to versioned object should work", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", }, }, convertingObject: &testapigroupv1.Carp{}, expectedConvertedObject: &testapigroupv1.Carp{}, }, { name: "convert empty unstructured w/o gvk to versioned object should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{}, }, convertingObject: &testapigroupv1.Carp{}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) }, }, { name: "convert valid versioned unstructured to versioned object should work", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, convertingObject: &testapigroupv1.Carp{}, expectedConvertedObject: &testapigroupv1.Carp{ ObjectMeta: metav1.ObjectMeta{ Name: "noxu", }, Spec: testapigroupv1.CarpSpec{ Hostname: "example.com", }, }, }, { name: "convert valid versioned unstructured to hub-versioned object should work", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, convertingObject: &testapigroup.Carp{}, expectedConvertedObject: &testapigroup.Carp{ ObjectMeta: metav1.ObjectMeta{ Name: "noxu", }, Spec: testapigroup.CarpSpec{ Hostname: "example.com", }, }, }, { name: "convert unexisting-versioned unstructured to hub-versioned object should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v9", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, convertingObject: &testapigroup.Carp{}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget( scheme.Name(), schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"}, nil, )) }, }, { name: "convert valid versioned unstructured to object w/ a mismatching kind should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, convertingObject: &metav1.CreateOptions{}, expectedErrFunc: func(err error) bool { return strings.HasPrefix(err.Error(), "converting (v1.Carp) to (v1.CreateOptions):") }, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { defer func() { v := recover() assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic") }() outObject := testCase.convertingObject.DeepCopyObject() // Convert by specifying destination object err := scheme.Convert(testCase.unstructuredToConvert, outObject, nil) if err != nil { if testCase.expectedErrFunc != nil { if !testCase.expectedErrFunc(err) { t.Errorf("error mismatched: %v", err) } } return } assert.Equal(t, testCase.expectedConvertedObject, outObject) }) } } func TestUnstructuredToGVConversion(t *testing.T) { scheme, _ := test.TestScheme() // HACK: registering fake internal/v1beta1 api scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "v1beta1"}, &testapigroup.Carp{}) scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "__internal"}, &testapigroup.Carp{}) testCases := []struct { name string unstructuredToConvert *unstructured.Unstructured targetGV schema.GroupVersion expectPanic bool expectedErrFunc func(err error) bool expectedConvertedObject runtime.Object }{ { name: "convert versioned unstructured to valid external version should work", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", }, }, targetGV: schema.GroupVersion{Group: "", Version: "v1"}, expectedConvertedObject: &testapigroupv1.Carp{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Carp", }, }, }, { name: "convert hub-versioned unstructured to hub version should work", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "__internal", "kind": "Carp", }, }, targetGV: schema.GroupVersion{Group: "", Version: "__internal"}, expectedConvertedObject: &testapigroup.Carp{}, }, { name: "convert empty unstructured w/o gvk to versioned should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{}, }, targetGV: schema.GroupVersion{Group: "", Version: "v1"}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) }, expectedConvertedObject: nil, }, { name: "convert versioned unstructured to mismatching external version should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", }, }, targetGV: schema.GroupVersion{Group: "foo", Version: "v1beta1"}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget( scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "v1beta1"})) }, expectedConvertedObject: nil, }, { name: "convert versioned unstructured to mismatching internal version should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", }, }, targetGV: schema.GroupVersion{Group: "foo", Version: "__internal"}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget( scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "__internal"})) }, expectedConvertedObject: nil, }, { name: "convert valid versioned unstructured to its own version should work", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, targetGV: schema.GroupVersion{Group: "", Version: "v1"}, expectedConvertedObject: &testapigroupv1.Carp{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Carp", }, ObjectMeta: metav1.ObjectMeta{ Name: "noxu", }, Spec: testapigroupv1.CarpSpec{ Hostname: "example.com", }, }, }, { name: "convert valid versioned unstructured to hub-version should work ignoring type meta", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, targetGV: schema.GroupVersion{Group: "", Version: "__internal"}, expectedConvertedObject: &testapigroup.Carp{ ObjectMeta: metav1.ObjectMeta{ Name: "noxu", }, Spec: testapigroup.CarpSpec{ Hostname: "example.com", }, }, }, { name: "convert valid versioned unstructured to unexisting version should fail", unstructuredToConvert: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", "metadata": map[string]interface{}{ "creationTimestamp": nil, "name": "noxu", }, "spec": map[string]interface{}{ "hostname": "example.com", }, "status": map[string]interface{}{}, }, }, targetGV: schema.GroupVersion{Group: "", Version: "v9"}, expectedErrFunc: func(err error) bool { return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget( scheme.Name(), schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"}, nil, )) }, expectedConvertedObject: nil, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { defer func() { v := recover() assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic") }() // Convert by specifying destination object outObject, err := scheme.ConvertToVersion(testCase.unstructuredToConvert, testCase.targetGV) if testCase.expectedErrFunc != nil { if !testCase.expectedErrFunc(err) { t.Errorf("error mismatched: %v", err) } } assert.Equal(t, testCase.expectedConvertedObject, outObject) }) } } func TestUnstructuredToUnstructuredConversion(t *testing.T) { // eventually, we don't want any inter-unstructured conversion happen, but for now, the conversion // just copy/pastes scheme, _ := test.TestScheme() inUnstructured := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Carp", }, } outUnstructured := &unstructured.Unstructured{} err := scheme.Convert(inUnstructured, outUnstructured, nil) assert.NoError(t, err) assert.Equal(t, inUnstructured, outUnstructured) } func benchmarkCarp() *testapigroupv1.Carp { t := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) return &testapigroupv1.Carp{ ObjectMeta: metav1.ObjectMeta{ Name: "name", Namespace: "namespace", }, Spec: testapigroupv1.CarpSpec{ RestartPolicy: "restart", NodeSelector: map[string]string{ "label1": "value1", "label2": "value2", }, ServiceAccountName: "service-account", HostNetwork: false, HostPID: true, Subdomain: "hostname.subdomain.namespace.svc.domain", }, Status: testapigroupv1.CarpStatus{ Phase: "phase", Conditions: []testapigroupv1.CarpCondition{ { Type: "condition1", Status: "true", LastProbeTime: t, LastTransitionTime: t, Reason: "reason", Message: "message", }, }, Message: "message", Reason: "reason", HostIP: "1.2.3.4", }, } } func BenchmarkToUnstructured(b *testing.B) { carp := benchmarkCarp() converter := runtime.DefaultUnstructuredConverter b.ResetTimer() for i := 0; i < b.N; i++ { result, err := converter.ToUnstructured(carp) if err != nil { b.Fatalf("Unexpected conversion error: %v", err) } if len(result) != 3 { b.Errorf("Unexpected conversion result: %#v", result) } } } func BenchmarkFromUnstructured(b *testing.B) { carp := benchmarkCarp() converter := runtime.DefaultUnstructuredConverter unstr, err := converter.ToUnstructured(carp) if err != nil { b.Fatalf("Unexpected conversion error: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { result := testapigroupv1.Carp{} if err := converter.FromUnstructured(unstr, &result); err != nil { b.Fatalf("Unexpected conversion error: %v", err) } if result.Status.Phase != "phase" { b.Errorf("Unexpected conversion result: %#v", result) } } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructured_list.go000066400000000000000000000144071453143165200301260ustar00rootroot00000000000000/* Copyright 2015 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 unstructured import ( "bytes" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) var _ runtime.Unstructured = &UnstructuredList{} var _ metav1.ListInterface = &UnstructuredList{} // UnstructuredList allows lists that do not have Golang structs // registered to be manipulated generically. This can be used to deal // with the API lists from a plug-in. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen=true type UnstructuredList struct { Object map[string]interface{} // Items is a list of unstructured objects. Items []Unstructured `json:"items"` } func (u *UnstructuredList) GetObjectKind() schema.ObjectKind { return u } func (u *UnstructuredList) IsList() bool { return true } func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error { for i := range u.Items { if err := fn(&u.Items[i]); err != nil { return err } } return nil } func (u *UnstructuredList) EachListItemWithAlloc(fn func(runtime.Object) error) error { for i := range u.Items { if err := fn(&Unstructured{Object: u.Items[i].Object}); err != nil { return err } } return nil } // NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data. // This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info. func (u *UnstructuredList) NewEmptyInstance() runtime.Unstructured { out := new(UnstructuredList) if u != nil { out.SetGroupVersionKind(u.GroupVersionKind()) } return out } // UnstructuredContent returns a map contain an overlay of the Items field onto // the Object field. Items always overwrites overlay. func (u *UnstructuredList) UnstructuredContent() map[string]interface{} { out := make(map[string]interface{}, len(u.Object)+1) // shallow copy every property for k, v := range u.Object { out[k] = v } items := make([]interface{}, len(u.Items)) for i, item := range u.Items { items[i] = item.UnstructuredContent() } out["items"] = items return out } // SetUnstructuredContent obeys the conventions of List and keeps Items and the items // array in sync. If items is not an array of objects in the incoming map, then any // mismatched item will be removed. func (obj *UnstructuredList) SetUnstructuredContent(content map[string]interface{}) { obj.Object = content if content == nil { obj.Items = nil return } items, ok := obj.Object["items"].([]interface{}) if !ok || items == nil { items = []interface{}{} } unstructuredItems := make([]Unstructured, 0, len(items)) newItems := make([]interface{}, 0, len(items)) for _, item := range items { o, ok := item.(map[string]interface{}) if !ok { continue } unstructuredItems = append(unstructuredItems, Unstructured{Object: o}) newItems = append(newItems, o) } obj.Items = unstructuredItems obj.Object["items"] = newItems } func (u *UnstructuredList) DeepCopy() *UnstructuredList { if u == nil { return nil } out := new(UnstructuredList) *out = *u out.Object = runtime.DeepCopyJSON(u.Object) out.Items = make([]Unstructured, len(u.Items)) for i := range u.Items { u.Items[i].DeepCopyInto(&out.Items[i]) } return out } // MarshalJSON ensures that the unstructured list object produces proper // JSON when passed to Go's standard JSON library. func (u *UnstructuredList) MarshalJSON() ([]byte, error) { var buf bytes.Buffer err := UnstructuredJSONScheme.Encode(u, &buf) return buf.Bytes(), err } // UnmarshalJSON ensures that the unstructured list object properly // decodes JSON when passed to Go's standard JSON library. func (u *UnstructuredList) UnmarshalJSON(b []byte) error { _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) return err } func (u *UnstructuredList) GetAPIVersion() string { return getNestedString(u.Object, "apiVersion") } func (u *UnstructuredList) SetAPIVersion(version string) { u.setNestedField(version, "apiVersion") } func (u *UnstructuredList) GetKind() string { return getNestedString(u.Object, "kind") } func (u *UnstructuredList) SetKind(kind string) { u.setNestedField(kind, "kind") } func (u *UnstructuredList) GetResourceVersion() string { return getNestedString(u.Object, "metadata", "resourceVersion") } func (u *UnstructuredList) SetResourceVersion(version string) { u.setNestedField(version, "metadata", "resourceVersion") } func (u *UnstructuredList) GetSelfLink() string { return getNestedString(u.Object, "metadata", "selfLink") } func (u *UnstructuredList) SetSelfLink(selfLink string) { u.setNestedField(selfLink, "metadata", "selfLink") } func (u *UnstructuredList) GetContinue() string { return getNestedString(u.Object, "metadata", "continue") } func (u *UnstructuredList) SetContinue(c string) { u.setNestedField(c, "metadata", "continue") } func (u *UnstructuredList) GetRemainingItemCount() *int64 { return getNestedInt64Pointer(u.Object, "metadata", "remainingItemCount") } func (u *UnstructuredList) SetRemainingItemCount(c *int64) { if c == nil { RemoveNestedField(u.Object, "metadata", "remainingItemCount") } else { u.setNestedField(*c, "metadata", "remainingItemCount") } } func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) { u.SetAPIVersion(gvk.GroupVersion().String()) u.SetKind(gvk.Kind) } func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind { gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) if err != nil { return schema.GroupVersionKind{} } gvk := gv.WithKind(u.GetKind()) return gvk } func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) { if u.Object == nil { u.Object = make(map[string]interface{}) } SetNestedField(u.Object, value, fields...) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructured_list_test.go000066400000000000000000000051421453143165200311610ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package unstructured import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestUnstructuredList(t *testing.T) { list := &UnstructuredList{ Object: map[string]interface{}{"kind": "List", "apiVersion": "v1"}, Items: []Unstructured{ {Object: map[string]interface{}{"kind": "Pod", "apiVersion": "v1", "metadata": map[string]interface{}{"name": "test"}}}, }, } content := list.UnstructuredContent() items := content["items"].([]interface{}) require.Len(t, items, 1) val, found, err := NestedFieldCopy(items[0].(map[string]interface{}), "metadata", "name") require.True(t, found) require.NoError(t, err) assert.Equal(t, "test", val) } func TestNilDeletionTimestamp(t *testing.T) { var u Unstructured del := u.GetDeletionTimestamp() if del != nil { t.Errorf("unexpected non-nil deletion timestamp: %v", del) } u.SetDeletionTimestamp(u.GetDeletionTimestamp()) del = u.GetDeletionTimestamp() if del != nil { t.Errorf("unexpected non-nil deletion timestamp: %v", del) } _, ok := u.Object["metadata"] assert.False(t, ok) now := metav1.Now() u.SetDeletionTimestamp(&now) assert.Equal(t, now.Unix(), u.GetDeletionTimestamp().Unix()) u.SetDeletionTimestamp(nil) metadata := u.Object["metadata"].(map[string]interface{}) _, ok = metadata["deletionTimestamp"] assert.False(t, ok) } func TestEmptyCreationTimestampIsOmitted(t *testing.T) { var u Unstructured now := metav1.Now() // set an initial creationTimestamp and ensure the field exists u.SetCreationTimestamp(now) metadata := u.Object["metadata"].(map[string]interface{}) _, exists := metadata["creationTimestamp"] if !exists { t.Fatalf("unexpected missing creationTimestamp") } // set an empty timestamp and ensure the field no longer exists u.SetCreationTimestamp(metav1.Time{}) metadata = u.Object["metadata"].(map[string]interface{}) creationTimestamp, exists := metadata["creationTimestamp"] if exists { t.Errorf("unexpected creation timestamp field: %q", creationTimestamp) } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructured_test.go000066400000000000000000000120611453143165200301240ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package unstructured_test import ( "math/rand" "reflect" "testing" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/equality" metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" ) func TestNilUnstructuredContent(t *testing.T) { var u unstructured.Unstructured uCopy := u.DeepCopy() content := u.UnstructuredContent() expContent := make(map[string]interface{}) assert.EqualValues(t, expContent, content) assert.Equal(t, uCopy, &u) } // TestUnstructuredMetadataRoundTrip checks that metadata accessors // correctly set the metadata for unstructured objects. // First, it fuzzes an empty ObjectMeta and sets this value as the metadata for an unstructured object. // Next, it uses metadata accessor methods to set these fuzzed values to another unstructured object. // Finally, it checks that both the unstructured objects are equal. func TestUnstructuredMetadataRoundTrip(t *testing.T) { scheme := runtime.NewScheme() codecs := serializer.NewCodecFactory(scheme) seed := rand.Int63() fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs) N := 1000 for i := 0; i < N; i++ { u := &unstructured.Unstructured{Object: map[string]interface{}{}} uCopy := u.DeepCopy() metadata := &metav1.ObjectMeta{} fuzzer.Fuzz(metadata) if err := setObjectMeta(u, metadata); err != nil { t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err) } setObjectMetaUsingAccessors(u, uCopy) if !equality.Semantic.DeepEqual(u, uCopy) { t.Errorf("diff: %v", cmp.Diff(u, uCopy)) } } } // TestUnstructuredMetadataOmitempty checks that ObjectMeta omitempty // semantics are enforced for unstructured objects. // The fuzzing test above should catch these cases but this is here just to be safe. // Example: the metadata.clusterName field has the omitempty json tag // so if it is set to it's zero value (""), it should be removed from the metadata map. func TestUnstructuredMetadataOmitempty(t *testing.T) { scheme := runtime.NewScheme() codecs := serializer.NewCodecFactory(scheme) seed := rand.Int63() fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs) // 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) if err := setObjectMeta(u, metadata); err != nil { t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err) } // set zero values for all fields in metadata explicitly // to check that omitempty fields having zero values are never set u.SetName("") u.SetGenerateName("") u.SetNamespace("") u.SetSelfLink("") u.SetUID("") u.SetResourceVersion("") u.SetGeneration(0) u.SetCreationTimestamp(metav1.Time{}) u.SetDeletionTimestamp(nil) u.SetDeletionGracePeriodSeconds(nil) u.SetLabels(nil) u.SetAnnotations(nil) u.SetOwnerReferences(nil) u.SetFinalizers(nil) u.SetManagedFields(nil) gotMetadata, _, err := unstructured.NestedFieldNoCopy(u.UnstructuredContent(), "metadata") if err != nil { t.Error(err) } emptyMetadata := make(map[string]interface{}) if !reflect.DeepEqual(gotMetadata, emptyMetadata) { t.Errorf("expected %v, got %v", emptyMetadata, gotMetadata) } } func setObjectMeta(u *unstructured.Unstructured, objectMeta *metav1.ObjectMeta) error { if objectMeta == nil { unstructured.RemoveNestedField(u.UnstructuredContent(), "metadata") return nil } metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta) if err != nil { return err } u.UnstructuredContent()["metadata"] = metadata return nil } func setObjectMetaUsingAccessors(u, uCopy *unstructured.Unstructured) { uCopy.SetName(u.GetName()) uCopy.SetGenerateName(u.GetGenerateName()) uCopy.SetNamespace(u.GetNamespace()) uCopy.SetSelfLink(u.GetSelfLink()) uCopy.SetUID(u.GetUID()) uCopy.SetResourceVersion(u.GetResourceVersion()) uCopy.SetGeneration(u.GetGeneration()) uCopy.SetCreationTimestamp(u.GetCreationTimestamp()) uCopy.SetDeletionTimestamp(u.GetDeletionTimestamp()) uCopy.SetDeletionGracePeriodSeconds(u.GetDeletionGracePeriodSeconds()) uCopy.SetLabels(u.GetLabels()) uCopy.SetAnnotations(u.GetAnnotations()) uCopy.SetOwnerReferences(u.GetOwnerReferences()) uCopy.SetFinalizers(u.GetFinalizers()) uCopy.SetManagedFields(u.GetManagedFields()) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructuredscheme/000077500000000000000000000000001453143165200277235ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/unstructuredscheme/scheme.go000066400000000000000000000102711453143165200315170ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package unstructuredscheme import ( "fmt" "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/json" "k8s.io/apimachinery/pkg/runtime/serializer/versioning" ) var scheme = runtime.NewScheme() // NewUnstructuredNegotiatedSerializer returns a simple, negotiated serializer func NewUnstructuredNegotiatedSerializer() runtime.NegotiatedSerializer { return unstructuredNegotiatedSerializer{ scheme: scheme, typer: NewUnstructuredObjectTyper(), creator: NewUnstructuredCreator(), } } type unstructuredNegotiatedSerializer struct { scheme *runtime.Scheme typer runtime.ObjectTyper creator runtime.ObjectCreater } func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { return []runtime.SerializerInfo{ { MediaType: "application/json", 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), StreamSerializer: &runtime.StreamSerializerInfo{ EncodesAsText: true, Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false), Framer: json.Framer, }, }, { MediaType: "application/yaml", MediaTypeType: "application", MediaTypeSubType: "yaml", EncodesAsText: true, Serializer: json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer), }, } } func (s unstructuredNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { return versioning.NewDefaultingCodecForScheme(s.scheme, encoder, nil, gv, nil) } func (s unstructuredNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { return versioning.NewDefaultingCodecForScheme(s.scheme, nil, decoder, nil, gv) } type unstructuredObjectTyper struct { } // NewUnstructuredObjectTyper returns an object typer that can deal with unstructured things func NewUnstructuredObjectTyper() runtime.ObjectTyper { return unstructuredObjectTyper{} } func (t unstructuredObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { // Delegate for things other than Unstructured. if _, ok := obj.(runtime.Unstructured); !ok { return nil, false, fmt.Errorf("cannot type %T", obj) } gvk := obj.GetObjectKind().GroupVersionKind() if len(gvk.Kind) == 0 { return nil, false, runtime.NewMissingKindErr("object has no kind field ") } if len(gvk.Version) == 0 { return nil, false, runtime.NewMissingVersionErr("object has no apiVersion field") } return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil } func (t unstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { return true } type unstructuredCreator struct{} // NewUnstructuredCreator returns a simple object creator that always returns an unstructured func NewUnstructuredCreator() runtime.ObjectCreater { return unstructuredCreator{} } func (c unstructuredCreator) New(kind schema.GroupVersionKind) (runtime.Object, error) { ret := &unstructured.Unstructured{} ret.SetGroupVersionKind(kind) return ret, nil } type unstructuredDefaulter struct { } // NewUnstructuredDefaulter returns defaulter suitable for unstructured types that doesn't default anything func NewUnstructuredDefaulter() runtime.ObjectDefaulter { return unstructuredDefaulter{} } func (d unstructuredDefaulter) Default(in runtime.Object) { } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go000066400000000000000000000032171453143165200306110ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package unstructured import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Unstructured) DeepCopyInto(out *Unstructured) { clone := in.DeepCopy() *out = *clone return } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *Unstructured) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnstructuredList) DeepCopyInto(out *UnstructuredList) { clone := in.DeepCopy() *out = *clone return } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *UnstructuredList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/validation/000077500000000000000000000000001453143165200233525ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/validation/validation.go000066400000000000000000000327361453143165200260460ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import ( "fmt" "regexp" "unicode" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) // 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 } // LabelSelectorHasInvalidLabelValue returns true if the given selector contains an invalid label value in a match expression. // This is useful for determining whether AllowInvalidLabelValueInSelector should be set to true when validating an update // based on existing persisted invalid values. func LabelSelectorHasInvalidLabelValue(ps *metav1.LabelSelector) bool { if ps == nil { return false } for _, e := range ps.MatchExpressions { for _, v := range e.Values { if len(validation.IsValidLabelValue(v)) > 0 { return true } } } return false } // ValidateLabelSelector validate the LabelSelector according to the opts and returns any validation errors. // opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data. func ValidateLabelSelector(ps *metav1.LabelSelector, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if ps == nil { return allErrs } allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...) for i, expr := range ps.MatchExpressions { allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, opts, fldPath.Child("matchExpressions").Index(i))...) } return allErrs } // ValidateLabelSelectorRequirement validate the requirement according to the opts and returns any validation errors. // opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data. func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} switch sr.Operator { case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: if len(sr.Values) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) } case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist: if len(sr.Values) > 0 { 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")) } allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...) if !opts.AllowInvalidLabelValueInSelector { for valueIndex, value := range sr.Values { for _, msg := range validation.IsValidLabelValue(value) { allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(valueIndex), value, msg)) } } } return allErrs } // ValidateLabelName validates that the label name is correctly defined. 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)) } return allErrs } // ValidateLabels validates that a set of labels are correctly defined. func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for k, v := range labels { allErrs = append(allErrs, ValidateLabelName(k, fldPath)...) for _, msg := range validation.IsValidLabelValue(v) { allErrs = append(allErrs, field.Invalid(fldPath, v, msg)) } } 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 if options.OrphanDependents != nil && options.PropagationPolicy != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set")) } if options.PropagationPolicy != nil && *options.PropagationPolicy != metav1.DeletePropagationForeground && *options.PropagationPolicy != metav1.DeletePropagationBackground && *options.PropagationPolicy != metav1.DeletePropagationOrphan { 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)...) return allErrs } func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) return allErrs } func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) return allErrs } 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 { 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")) } } allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) return allErrs } var FieldManagerMaxLength = 128 // ValidateFieldManager valides that the fieldManager is the proper length and // only has printable characters. func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // the field can not be set as a `*string`, so a empty string ("") is // 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)) } // Verify that all characters are printable. for i, r := range fieldManager { if !unicode.IsPrint(r) { allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i))) } } return allErrs } var allowedDryRunValues = sets.NewString(metav1.DryRunAll) // ValidateDryRun validates that a dryRun query param only contains allowed values. func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList { allErrs := field.ErrorList{} if !allowedDryRunValues.HasAll(dryRun...) { allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List())) } return allErrs } var allowedFieldValidationValues = sets.NewString("", metav1.FieldValidationIgnore, metav1.FieldValidationWarn, metav1.FieldValidationStrict) // ValidateFieldValidation validates that a fieldValidation query param only contains allowed values. func ValidateFieldValidation(fldPath *field.Path, fieldValidation string) field.ErrorList { allErrs := field.ErrorList{} if !allowedFieldValidationValues.Has(fieldValidation) { allErrs = append(allErrs, field.NotSupported(fldPath, fieldValidation, allowedFieldValidationValues.List())) } return allErrs } const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized` // ValidateTableOptions returns any invalid flags on TableOptions. func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList { var allErrs field.ErrorList switch opts.IncludeObject { case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "": default: allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty")) } return allErrs } const MaxSubresourceNameLength = 256 func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList for i, fields := range fieldsList { fldPath := fldPath.Index(i) switch fields.Operation { case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate: default: allErrs = append(allErrs, field.Invalid(fldPath.Child("operation"), fields.Operation, "must be `Apply` or `Update`")) } if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" { allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`")) } 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)) } } return allErrs } func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList conditionTypeToFirstIndex := map[string]int{} for i, condition := range conditions { if _, ok := conditionTypeToFirstIndex[condition.Type]; ok { allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type)) } else { conditionTypeToFirstIndex[condition.Type] = i } allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...) } return allErrs } // validConditionStatuses is used internally to check validity and provide a good message var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown)) const ( maxReasonLen = 1 * 1024 maxMessageLen = 32 * 1024 ) func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList // type is set and is a valid format allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...) // status is set and is an accepted value if !validConditionStatuses.Has(string(condition.Status)) { allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List())) } if condition.ObservedGeneration < 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero")) } if condition.LastTransitionTime.IsZero() { allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set")) } if len(condition.Reason) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set")) } 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)) } } if len(condition.Message) > maxMessageLen { allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen)) } return allErrs } const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?" const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'" var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$") // isValidConditionReason tests for a string that conforms to rules for condition reasons. This checks the format, but not the length. func isValidConditionReason(value string) []string { if !conditionReasonRegexp.MatchString(value) { return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")} } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/validation/validation_test.go000066400000000000000000000363441453143165200271040ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import ( "fmt" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" ) func TestValidateLabels(t *testing.T) { successCases := []map[string]string{ {"simple": "bar"}, {"now-with-dashes": "bar"}, {"1-starts-with-num": "bar"}, {"1234": "bar"}, {"simple/simple": "bar"}, {"now-with-dashes/simple": "bar"}, {"now-with-dashes/now-with-dashes": "bar"}, {"now.with.dots/simple": "bar"}, {"now-with.dashes-and.dots/simple": "bar"}, {"1-num.2-num/3-num": "bar"}, {"1234/5678": "bar"}, {"1.2.3.4/5678": "bar"}, {"UpperCaseAreOK123": "bar"}, {"goodvalue": "123_-.BaR"}, } for i := range successCases { errs := ValidateLabels(successCases[i], field.NewPath("field")) if len(errs) != 0 { t.Errorf("case[%d] expected success, got %#v", i, errs) } } namePartErrMsg := "name part must consist of" nameErrMsg := "a qualified name must consist of" labelErrMsg := "a valid label must be an empty string or consist of" maxLengthErrMsg := "must be no more than" labelNameErrorCases := []struct { labels map[string]string expect string }{ {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, } for i := range labelNameErrorCases { errs := ValidateLabels(labelNameErrorCases[i].labels, field.NewPath("field")) if len(errs) != 1 { t.Errorf("case[%d]: expected failure", i) } else { if !strings.Contains(errs[0].Detail, labelNameErrorCases[i].expect) { t.Errorf("case[%d]: error details do not include %q: %q", i, labelNameErrorCases[i].expect, errs[0].Detail) } } } labelValueErrorCases := []struct { labels map[string]string expect string }{ {map[string]string{"toolongvalue": strings.Repeat("a", 64)}, maxLengthErrMsg}, {map[string]string{"backslashesinvalue": "some\\bad\\value"}, labelErrMsg}, {map[string]string{"nocommasallowed": "bad,value"}, labelErrMsg}, {map[string]string{"strangecharsinvalue": "?#$notsogood"}, labelErrMsg}, } for i := range labelValueErrorCases { errs := ValidateLabels(labelValueErrorCases[i].labels, field.NewPath("field")) if len(errs) != 1 { t.Errorf("case[%d]: expected failure", i) } else { if !strings.Contains(errs[0].Detail, labelValueErrorCases[i].expect) { t.Errorf("case[%d]: error details do not include %q: %q", i, labelValueErrorCases[i].expect, errs[0].Detail) } } } } func TestValidDryRun(t *testing.T) { tests := [][]string{ {}, {"All"}, {"All", "All"}, } for _, test := range tests { t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { if errs := ValidateDryRun(field.NewPath("dryRun"), test); len(errs) != 0 { t.Errorf("%v should be a valid dry-run value: %v", test, errs) } }) } } func TestInvalidDryRun(t *testing.T) { tests := [][]string{ {"False"}, {"All", "False"}, } for _, test := range tests { t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { if len(ValidateDryRun(field.NewPath("dryRun"), test)) == 0 { t.Errorf("%v shouldn't be a valid dry-run value", test) } }) } } func boolPtr(b bool) *bool { return &b } func TestValidPatchOptions(t *testing.T) { tests := []struct { opts metav1.PatchOptions patchType types.PatchType }{{ opts: metav1.PatchOptions{ Force: boolPtr(true), FieldManager: "kubectl", }, patchType: types.ApplyPatchType, }, { opts: metav1.PatchOptions{ FieldManager: "kubectl", }, patchType: types.ApplyPatchType, }, { opts: metav1.PatchOptions{}, patchType: types.MergePatchType, }, { opts: metav1.PatchOptions{ FieldManager: "patcher", }, patchType: types.MergePatchType, }} for _, test := range tests { t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) { errs := ValidatePatchOptions(&test.opts, test.patchType) if len(errs) != 0 { t.Fatalf("Expected no failures, got: %v", errs) } }) } } func TestInvalidPatchOptions(t *testing.T) { tests := []struct { opts metav1.PatchOptions patchType types.PatchType }{ // missing manager { opts: metav1.PatchOptions{}, patchType: types.ApplyPatchType, }, // force on non-apply { opts: metav1.PatchOptions{ Force: boolPtr(true), }, patchType: types.MergePatchType, }, // manager and force on non-apply { opts: metav1.PatchOptions{ FieldManager: "kubectl", Force: boolPtr(false), }, patchType: types.MergePatchType, }, } for _, test := range tests { t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) { errs := ValidatePatchOptions(&test.opts, test.patchType) if len(errs) == 0 { t.Fatal("Expected failures, got none.") } }) } } func TestValidateFieldManagerValid(t *testing.T) { tests := []string{ "filedManager", "你好", // Hello "🍔", } for _, test := range tests { t.Run(test, func(t *testing.T) { errs := ValidateFieldManager(test, field.NewPath("fieldManager")) if len(errs) != 0 { t.Errorf("Validation failed: %v", errs) } }) } } func TestValidateFieldManagerInvalid(t *testing.T) { tests := []string{ "field\nmanager", // Contains invalid character \n "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // Has 129 chars } for _, test := range tests { t.Run(test, func(t *testing.T) { errs := ValidateFieldManager(test, field.NewPath("fieldManager")) if len(errs) == 0 { t.Errorf("Validation should have failed") } }) } } func TestValidateManagedFieldsInvalid(t *testing.T) { tests := []metav1.ManagedFieldsEntry{{ Operation: metav1.ManagedFieldsOperationUpdate, FieldsType: "RandomVersion", APIVersion: "v1", }, { Operation: "RandomOperation", FieldsType: "FieldsV1", APIVersion: "v1", }, { // Operation is missing FieldsType: "FieldsV1", APIVersion: "v1", }, { Operation: metav1.ManagedFieldsOperationUpdate, FieldsType: "FieldsV1", // Invalid fieldManager Manager: "field\nmanager", APIVersion: "v1", }, { Operation: metav1.ManagedFieldsOperationApply, FieldsType: "FieldsV1", APIVersion: "v1", Subresource: "TooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLong", }} for _, test := range tests { t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) { errs := ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields")) if len(errs) == 0 { t.Errorf("Validation should have failed") } }) } } func TestValidateMangedFieldsValid(t *testing.T) { tests := []metav1.ManagedFieldsEntry{{ Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "v1", // FieldsType is missing }, { Operation: metav1.ManagedFieldsOperationUpdate, FieldsType: "FieldsV1", APIVersion: "v1", }, { Operation: metav1.ManagedFieldsOperationApply, FieldsType: "FieldsV1", APIVersion: "v1", Subresource: "scale", }, { Operation: metav1.ManagedFieldsOperationApply, FieldsType: "FieldsV1", APIVersion: "v1", Manager: "🍔", }} for _, test := range tests { t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) { err := ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields")) if err != nil { t.Errorf("Validation failed: %v", err) } }) } } func TestValidateConditions(t *testing.T) { tests := []struct { name string conditions []metav1.Condition validateErrs func(t *testing.T, errs field.ErrorList) }{{ name: "bunch-of-invalid-fields", conditions: []metav1.Condition{{ Type: ":invalid", Status: "unknown", ObservedGeneration: -1, LastTransitionTime: metav1.Time{}, Reason: "invalid;val", Message: "", }}, validateErrs: func(t *testing.T, errs field.ErrorList) { needle := `status.conditions[0].type: Invalid value: ":invalid": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } needle = `status.conditions[0].status: Unsupported value: "unknown": supported values: "False", "True", "Unknown"` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } needle = `status.conditions[0].observedGeneration: Invalid value: -1: must be greater than or equal to zero` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } needle = `status.conditions[0].lastTransitionTime: Required value: must be set` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } needle = `status.conditions[0].reason: Invalid value: "invalid;val": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } }, }, { name: "duplicates", conditions: []metav1.Condition{{ Type: "First", }, { Type: "Second", }, { Type: "First", }}, validateErrs: func(t *testing.T, errs field.ErrorList) { needle := `status.conditions[2].type: Duplicate value: "First"` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } }, }, { name: "colon-allowed-in-reason", conditions: []metav1.Condition{{ Type: "First", Reason: "valid:val", }}, validateErrs: func(t *testing.T, errs field.ErrorList) { needle := `status.conditions[0].reason` if hasPrefixError(errs, needle) { t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) } }, }, { name: "comma-allowed-in-reason", conditions: []metav1.Condition{{ Type: "First", Reason: "valid,val", }}, validateErrs: func(t *testing.T, errs field.ErrorList) { needle := `status.conditions[0].reason` if hasPrefixError(errs, needle) { t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) } }, }, { name: "reason-does-not-end-in-delimiter", conditions: []metav1.Condition{{ Type: "First", Reason: "valid,val:", }}, validateErrs: func(t *testing.T, errs field.ErrorList) { needle := `status.conditions[0].reason: Invalid value: "valid,val:": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` if !hasError(errs, needle) { t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) } }, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { errs := ValidateConditions(test.conditions, field.NewPath("status").Child("conditions")) test.validateErrs(t, errs) }) } } func TestLabelSelectorMatchExpression(t *testing.T) { testCases := []struct { name string labelSelector *metav1.LabelSelector wantErrorNumber int validateErrs func(t *testing.T, errs field.ErrorList) }{{ name: "Valid LabelSelector", labelSelector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key", Operator: metav1.LabelSelectorOpIn, Values: []string{"value"}, }}, }, wantErrorNumber: 0, validateErrs: nil, }, { name: "MatchExpression's key name isn't valid", labelSelector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "-key", Operator: metav1.LabelSelectorOpIn, Values: []string{"value"}, }}, }, wantErrorNumber: 1, validateErrs: func(t *testing.T, errs field.ErrorList) { errMessage := "name part must consist of alphanumeric characters" if !partStringInErrorMessage(errs, errMessage) { t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) } }, }, { name: "MatchExpression's operator isn't valid", labelSelector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key", Operator: "abc", Values: []string{"value"}, }}, }, wantErrorNumber: 1, validateErrs: func(t *testing.T, errs field.ErrorList) { errMessage := "not a valid selector operator" if !partStringInErrorMessage(errs, errMessage) { t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) } }, }, { name: "MatchExpression's value name isn't valid", labelSelector: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key", Operator: metav1.LabelSelectorOpIn, Values: []string{"-value"}, }}, }, wantErrorNumber: 1, validateErrs: func(t *testing.T, errs field.ErrorList) { errMessage := "a valid label must be an empty string or consist of" if !partStringInErrorMessage(errs, errMessage) { t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) } }, }} for index, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{false}, field.NewPath("labelSelector")) if len(allErrs) != testCase.wantErrorNumber { t.Errorf("case[%d]: expected failure", index) } if len(allErrs) >= 1 && testCase.validateErrs != nil { testCase.validateErrs(t, allErrs) } }) } } func hasError(errs field.ErrorList, needle string) bool { for _, curr := range errs { if curr.Error() == needle { return true } } return false } func hasPrefixError(errs field.ErrorList, prefix string) bool { for _, curr := range errs { if strings.HasPrefix(curr.Error(), prefix) { return true } } return false } func partStringInErrorMessage(errs field.ErrorList, prefix string) bool { for _, curr := range errs { if strings.Contains(curr.Error(), prefix) { return true } } return false } func errorsAsString(errs field.ErrorList) string { messages := []string{} for _, curr := range errs { messages = append(messages, curr.Error()) } return strings.Join(messages, "\n") } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/watch.go000066400000000000000000000055271453143165200226660ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" ) // Event represents a single event to a watched resource. // // +protobuf=true // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type WatchEvent struct { Type string `json:"type" protobuf:"bytes,1,opt,name=type"` // Object is: // * If Type is Added or Modified: the new state of the object. // * 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. Object runtime.RawExtension `json:"object" protobuf:"bytes,2,opt,name=object"` } func Convert_watch_Event_To_v1_WatchEvent(in *watch.Event, out *WatchEvent, s conversion.Scope) error { out.Type = string(in.Type) switch t := in.Object.(type) { case *runtime.Unknown: // TODO: handle other fields on Unknown and detect type out.Object.Raw = t.Raw case nil: default: out.Object.Object = in.Object } return nil } func Convert_v1_InternalEvent_To_v1_WatchEvent(in *InternalEvent, out *WatchEvent, s conversion.Scope) error { return Convert_watch_Event_To_v1_WatchEvent((*watch.Event)(in), out, s) } func Convert_v1_WatchEvent_To_watch_Event(in *WatchEvent, out *watch.Event, s conversion.Scope) error { out.Type = watch.EventType(in.Type) if in.Object.Object != nil { out.Object = in.Object.Object } else if in.Object.Raw != nil { // TODO: handle other fields on Unknown and detect type out.Object = &runtime.Unknown{ Raw: in.Object.Raw, ContentType: runtime.ContentTypeJSON, } } return nil } func Convert_v1_WatchEvent_To_v1_InternalEvent(in *WatchEvent, out *InternalEvent, s conversion.Scope) error { return Convert_v1_WatchEvent_To_watch_Event(in, (*watch.Event)(out), s) } // InternalEvent makes watch.Event versioned // +protobuf=false type InternalEvent watch.Event func (e *InternalEvent) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (e *WatchEvent) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (e *InternalEvent) DeepCopyObject() runtime.Object { if c := e.DeepCopy(); c != nil { return c } else { return nil } } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/zz_generated.conversion.go000066400000000000000000000531371453143165200264250ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by conversion-gen. DO NOT EDIT. package v1 import ( url "net/url" unsafe "unsafe" resource "k8s.io/apimachinery/pkg/api/resource" conversion "k8s.io/apimachinery/pkg/conversion" fields "k8s.io/apimachinery/pkg/fields" labels "k8s.io/apimachinery/pkg/labels" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" watch "k8s.io/apimachinery/pkg/watch" ) func init() { localSchemeBuilder.Register(RegisterConversions) } // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*CreateOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_CreateOptions(a.(*url.Values), b.(*CreateOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*DeleteOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_DeleteOptions(a.(*url.Values), b.(*DeleteOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*GetOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_GetOptions(a.(*url.Values), b.(*GetOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_ListOptions(a.(*url.Values), b.(*ListOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*PatchOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_PatchOptions(a.(*url.Values), b.(*PatchOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*TableOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_TableOptions(a.(*url.Values), b.(*TableOptions), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*UpdateOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_UpdateOptions(a.(*url.Values), b.(*UpdateOptions), scope) }); err != nil { return err } if err := s.AddConversionFunc((*map[string]string)(nil), (*LabelSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Map_string_To_string_To_v1_LabelSelector(a.(*map[string]string), b.(*LabelSelector), scope) }); err != nil { return err } if err := s.AddConversionFunc((**bool)(nil), (*bool)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_bool_To_bool(a.(**bool), b.(*bool), scope) }); err != nil { return err } if err := s.AddConversionFunc((**float64)(nil), (*float64)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_float64_To_float64(a.(**float64), b.(*float64), scope) }); err != nil { return err } if err := s.AddConversionFunc((**int32)(nil), (*int32)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_int32_To_int32(a.(**int32), b.(*int32), scope) }); err != nil { return err } if err := s.AddConversionFunc((**int64)(nil), (*int)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_int64_To_int(a.(**int64), b.(*int), scope) }); err != nil { return err } if err := s.AddConversionFunc((**int64)(nil), (*int64)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_int64_To_int64(a.(**int64), b.(*int64), scope) }); err != nil { return err } if err := s.AddConversionFunc((**intstr.IntOrString)(nil), (*intstr.IntOrString)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_intstr_IntOrString_To_intstr_IntOrString(a.(**intstr.IntOrString), b.(*intstr.IntOrString), scope) }); err != nil { return err } if err := s.AddConversionFunc((**string)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_string_To_string(a.(**string), b.(*string), scope) }); err != nil { return err } if err := s.AddConversionFunc((**Duration)(nil), (*Duration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Pointer_v1_Duration_To_v1_Duration(a.(**Duration), b.(*Duration), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (**DeletionPropagation)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_Pointer_v1_DeletionPropagation(a.(*[]string), b.(**DeletionPropagation), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (**Time)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_Pointer_v1_Time(a.(*[]string), b.(**Time), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*[]int32)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_Slice_int32(a.(*[]string), b.(*[]int32), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*IncludeObjectPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_v1_IncludeObjectPolicy(a.(*[]string), b.(*IncludeObjectPolicy), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*ResourceVersionMatch)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_v1_ResourceVersionMatch(a.(*[]string), b.(*ResourceVersionMatch), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*Time)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_v1_Time(a.(*[]string), b.(*Time), scope) }); err != nil { return err } if err := s.AddConversionFunc((*bool)(nil), (**bool)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_bool_To_Pointer_bool(a.(*bool), b.(**bool), scope) }); err != nil { return err } if err := s.AddConversionFunc((*fields.Selector)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_fields_Selector_To_string(a.(*fields.Selector), b.(*string), scope) }); err != nil { return err } if err := s.AddConversionFunc((*float64)(nil), (**float64)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_float64_To_Pointer_float64(a.(*float64), b.(**float64), scope) }); err != nil { return err } if err := s.AddConversionFunc((*int32)(nil), (**int32)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_int32_To_Pointer_int32(a.(*int32), b.(**int32), scope) }); err != nil { return err } if err := s.AddConversionFunc((*int64)(nil), (**int64)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_int64_To_Pointer_int64(a.(*int64), b.(**int64), scope) }); err != nil { return err } if err := s.AddConversionFunc((*int)(nil), (**int64)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_int_To_Pointer_int64(a.(*int), b.(**int64), scope) }); err != nil { return err } if err := s.AddConversionFunc((*intstr.IntOrString)(nil), (**intstr.IntOrString)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_intstr_IntOrString_To_Pointer_intstr_IntOrString(a.(*intstr.IntOrString), b.(**intstr.IntOrString), scope) }); err != nil { return err } if err := s.AddConversionFunc((*intstr.IntOrString)(nil), (*intstr.IntOrString)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_intstr_IntOrString_To_intstr_IntOrString(a.(*intstr.IntOrString), b.(*intstr.IntOrString), scope) }); err != nil { return err } if err := s.AddConversionFunc((*labels.Selector)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_labels_Selector_To_string(a.(*labels.Selector), b.(*string), scope) }); err != nil { return err } if err := s.AddConversionFunc((*resource.Quantity)(nil), (*resource.Quantity)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_resource_Quantity_To_resource_Quantity(a.(*resource.Quantity), b.(*resource.Quantity), scope) }); err != nil { return err } if err := s.AddConversionFunc((*string)(nil), (**string)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_string_To_Pointer_string(a.(*string), b.(**string), scope) }); err != nil { return err } if err := s.AddConversionFunc((*string)(nil), (*fields.Selector)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_string_To_fields_Selector(a.(*string), b.(*fields.Selector), scope) }); err != nil { return err } if err := s.AddConversionFunc((*string)(nil), (*labels.Selector)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_string_To_labels_Selector(a.(*string), b.(*labels.Selector), scope) }); err != nil { return err } if err := s.AddConversionFunc((*url.Values)(nil), (*DeleteOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1_DeleteOptions(a.(*url.Values), b.(*DeleteOptions), scope) }); err != nil { return err } if err := s.AddConversionFunc((*DeleteOptions)(nil), (*DeleteOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_DeleteOptions_To_v1_DeleteOptions(a.(*DeleteOptions), b.(*DeleteOptions), scope) }); err != nil { return err } if err := s.AddConversionFunc((*Duration)(nil), (**Duration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_Duration_To_Pointer_v1_Duration(a.(*Duration), b.(**Duration), scope) }); err != nil { return err } if err := s.AddConversionFunc((*InternalEvent)(nil), (*WatchEvent)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_InternalEvent_To_v1_WatchEvent(a.(*InternalEvent), b.(*WatchEvent), scope) }); err != nil { return err } if err := s.AddConversionFunc((*LabelSelector)(nil), (*map[string]string)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_LabelSelector_To_Map_string_To_string(a.(*LabelSelector), b.(*map[string]string), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ListMeta)(nil), (*ListMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_ListMeta_To_v1_ListMeta(a.(*ListMeta), b.(*ListMeta), scope) }); err != nil { return err } if err := s.AddConversionFunc((*MicroTime)(nil), (*MicroTime)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_MicroTime_To_v1_MicroTime(a.(*MicroTime), b.(*MicroTime), scope) }); err != nil { return err } if err := s.AddConversionFunc((*Time)(nil), (*Time)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_Time_To_v1_Time(a.(*Time), b.(*Time), scope) }); err != nil { return err } if err := s.AddConversionFunc((*TypeMeta)(nil), (*TypeMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_TypeMeta_To_v1_TypeMeta(a.(*TypeMeta), b.(*TypeMeta), scope) }); err != nil { return err } if err := s.AddConversionFunc((*WatchEvent)(nil), (*InternalEvent)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_WatchEvent_To_v1_InternalEvent(a.(*WatchEvent), b.(*InternalEvent), scope) }); err != nil { return err } if err := s.AddConversionFunc((*WatchEvent)(nil), (*watch.Event)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_WatchEvent_To_watch_Event(a.(*WatchEvent), b.(*watch.Event), scope) }); err != nil { return err } if err := s.AddConversionFunc((*watch.Event)(nil), (*WatchEvent)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_watch_Event_To_v1_WatchEvent(a.(*watch.Event), b.(*WatchEvent), scope) }); err != nil { return err } return nil } func autoConvert_url_Values_To_v1_CreateOptions(in *url.Values, out *CreateOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { out.DryRun = *(*[]string)(unsafe.Pointer(&values)) } else { out.DryRun = nil } if values, ok := map[string][]string(*in)["fieldManager"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldManager, s); err != nil { return err } } else { out.FieldManager = "" } if values, ok := map[string][]string(*in)["fieldValidation"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldValidation, s); err != nil { return err } } else { out.FieldValidation = "" } return nil } // Convert_url_Values_To_v1_CreateOptions is an autogenerated conversion function. func Convert_url_Values_To_v1_CreateOptions(in *url.Values, out *CreateOptions, s conversion.Scope) error { return autoConvert_url_Values_To_v1_CreateOptions(in, out, s) } func autoConvert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["gracePeriodSeconds"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_Pointer_int64(&values, &out.GracePeriodSeconds, s); err != nil { return err } } else { out.GracePeriodSeconds = nil } // INFO: in.Preconditions opted out of conversion generation if values, ok := map[string][]string(*in)["orphanDependents"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.OrphanDependents, s); err != nil { return err } } else { out.OrphanDependents = nil } if values, ok := map[string][]string(*in)["propagationPolicy"]; ok && len(values) > 0 { if err := Convert_Slice_string_To_Pointer_v1_DeletionPropagation(&values, &out.PropagationPolicy, s); err != nil { return err } } else { out.PropagationPolicy = nil } if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { out.DryRun = *(*[]string)(unsafe.Pointer(&values)) } else { out.DryRun = nil } return nil } func autoConvert_url_Values_To_v1_GetOptions(in *url.Values, out *GetOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["resourceVersion"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.ResourceVersion, s); err != nil { return err } } else { out.ResourceVersion = "" } return nil } // Convert_url_Values_To_v1_GetOptions is an autogenerated conversion function. func Convert_url_Values_To_v1_GetOptions(in *url.Values, out *GetOptions, s conversion.Scope) error { return autoConvert_url_Values_To_v1_GetOptions(in, out, s) } func autoConvert_url_Values_To_v1_ListOptions(in *url.Values, out *ListOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["labelSelector"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.LabelSelector, s); err != nil { return err } } else { out.LabelSelector = "" } if values, ok := map[string][]string(*in)["fieldSelector"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldSelector, s); err != nil { return err } } else { out.FieldSelector = "" } if values, ok := map[string][]string(*in)["watch"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_bool(&values, &out.Watch, s); err != nil { return err } } else { out.Watch = false } if values, ok := map[string][]string(*in)["allowWatchBookmarks"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_bool(&values, &out.AllowWatchBookmarks, s); err != nil { return err } } else { out.AllowWatchBookmarks = false } if values, ok := map[string][]string(*in)["resourceVersion"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.ResourceVersion, s); err != nil { return err } } else { out.ResourceVersion = "" } if values, ok := map[string][]string(*in)["resourceVersionMatch"]; ok && len(values) > 0 { if err := Convert_Slice_string_To_v1_ResourceVersionMatch(&values, &out.ResourceVersionMatch, s); err != nil { return err } } else { out.ResourceVersionMatch = "" } if values, ok := map[string][]string(*in)["timeoutSeconds"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_Pointer_int64(&values, &out.TimeoutSeconds, s); err != nil { return err } } else { out.TimeoutSeconds = nil } if values, ok := map[string][]string(*in)["limit"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_int64(&values, &out.Limit, s); err != nil { return err } } else { out.Limit = 0 } if values, ok := map[string][]string(*in)["continue"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.Continue, s); err != nil { return err } } else { out.Continue = "" } if values, ok := map[string][]string(*in)["sendInitialEvents"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.SendInitialEvents, s); err != nil { return err } } else { out.SendInitialEvents = nil } return nil } // Convert_url_Values_To_v1_ListOptions is an autogenerated conversion function. func Convert_url_Values_To_v1_ListOptions(in *url.Values, out *ListOptions, s conversion.Scope) error { return autoConvert_url_Values_To_v1_ListOptions(in, out, s) } func autoConvert_url_Values_To_v1_PatchOptions(in *url.Values, out *PatchOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { out.DryRun = *(*[]string)(unsafe.Pointer(&values)) } else { out.DryRun = nil } if values, ok := map[string][]string(*in)["force"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.Force, s); err != nil { return err } } else { out.Force = nil } if values, ok := map[string][]string(*in)["fieldManager"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldManager, s); err != nil { return err } } else { out.FieldManager = "" } if values, ok := map[string][]string(*in)["fieldValidation"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldValidation, s); err != nil { return err } } else { out.FieldValidation = "" } return nil } // Convert_url_Values_To_v1_PatchOptions is an autogenerated conversion function. func Convert_url_Values_To_v1_PatchOptions(in *url.Values, out *PatchOptions, s conversion.Scope) error { return autoConvert_url_Values_To_v1_PatchOptions(in, out, s) } func autoConvert_url_Values_To_v1_TableOptions(in *url.Values, out *TableOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["-"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_bool(&values, &out.NoHeaders, s); err != nil { return err } } else { out.NoHeaders = false } if values, ok := map[string][]string(*in)["includeObject"]; ok && len(values) > 0 { if err := Convert_Slice_string_To_v1_IncludeObjectPolicy(&values, &out.IncludeObject, s); err != nil { return err } } else { out.IncludeObject = "" } return nil } // Convert_url_Values_To_v1_TableOptions is an autogenerated conversion function. func Convert_url_Values_To_v1_TableOptions(in *url.Values, out *TableOptions, s conversion.Scope) error { return autoConvert_url_Values_To_v1_TableOptions(in, out, s) } func autoConvert_url_Values_To_v1_UpdateOptions(in *url.Values, out *UpdateOptions, s conversion.Scope) error { // WARNING: Field TypeMeta does not have json tag, skipping. if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { out.DryRun = *(*[]string)(unsafe.Pointer(&values)) } else { out.DryRun = nil } if values, ok := map[string][]string(*in)["fieldManager"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldManager, s); err != nil { return err } } else { out.FieldManager = "" } if values, ok := map[string][]string(*in)["fieldValidation"]; ok && len(values) > 0 { if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldValidation, s); err != nil { return err } } else { out.FieldValidation = "" } return nil } // Convert_url_Values_To_v1_UpdateOptions is an autogenerated conversion function. func Convert_url_Values_To_v1_UpdateOptions(in *url.Values, out *UpdateOptions, s conversion.Scope) error { return autoConvert_url_Values_To_v1_UpdateOptions(in, out, s) } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/zz_generated.deepcopy.go000066400000000000000000001002611453143165200260370ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package v1 import ( runtime "k8s.io/apimachinery/pkg/runtime" types "k8s.io/apimachinery/pkg/types" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIGroup) DeepCopyInto(out *APIGroup) { *out = *in out.TypeMeta = in.TypeMeta if in.Versions != nil { in, out := &in.Versions, &out.Versions *out = make([]GroupVersionForDiscovery, len(*in)) copy(*out, *in) } out.PreferredVersion = in.PreferredVersion if in.ServerAddressByClientCIDRs != nil { in, out := &in.ServerAddressByClientCIDRs, &out.ServerAddressByClientCIDRs *out = make([]ServerAddressByClientCIDR, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGroup. func (in *APIGroup) DeepCopy() *APIGroup { if in == nil { return nil } out := new(APIGroup) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *APIGroup) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIGroupList) DeepCopyInto(out *APIGroupList) { *out = *in out.TypeMeta = in.TypeMeta if in.Groups != nil { in, out := &in.Groups, &out.Groups *out = make([]APIGroup, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGroupList. func (in *APIGroupList) DeepCopy() *APIGroupList { if in == nil { return nil } out := new(APIGroupList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *APIGroupList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIResource) DeepCopyInto(out *APIResource) { *out = *in if in.Verbs != nil { in, out := &in.Verbs, &out.Verbs *out = make(Verbs, len(*in)) copy(*out, *in) } if in.ShortNames != nil { in, out := &in.ShortNames, &out.ShortNames *out = make([]string, len(*in)) copy(*out, *in) } if in.Categories != nil { in, out := &in.Categories, &out.Categories *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResource. func (in *APIResource) DeepCopy() *APIResource { if in == nil { return nil } out := new(APIResource) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIResourceList) DeepCopyInto(out *APIResourceList) { *out = *in out.TypeMeta = in.TypeMeta if in.APIResources != nil { in, out := &in.APIResources, &out.APIResources *out = make([]APIResource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceList. func (in *APIResourceList) DeepCopy() *APIResourceList { if in == nil { return nil } out := new(APIResourceList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *APIResourceList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIVersions) DeepCopyInto(out *APIVersions) { *out = *in out.TypeMeta = in.TypeMeta if in.Versions != nil { in, out := &in.Versions, &out.Versions *out = make([]string, len(*in)) copy(*out, *in) } if in.ServerAddressByClientCIDRs != nil { in, out := &in.ServerAddressByClientCIDRs, &out.ServerAddressByClientCIDRs *out = make([]ServerAddressByClientCIDR, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersions. func (in *APIVersions) DeepCopy() *APIVersions { if in == nil { return nil } out := new(APIVersions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *APIVersions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplyOptions) DeepCopyInto(out *ApplyOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.DryRun != nil { in, out := &in.DryRun, &out.DryRun *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplyOptions. func (in *ApplyOptions) DeepCopy() *ApplyOptions { if in == nil { return nil } out := new(ApplyOptions) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. func (in *Condition) DeepCopy() *Condition { if in == nil { return nil } out := new(Condition) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CreateOptions) DeepCopyInto(out *CreateOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.DryRun != nil { in, out := &in.DryRun, &out.DryRun *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CreateOptions. func (in *CreateOptions) DeepCopy() *CreateOptions { if in == nil { return nil } out := new(CreateOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *CreateOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeleteOptions) DeepCopyInto(out *DeleteOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.GracePeriodSeconds != nil { in, out := &in.GracePeriodSeconds, &out.GracePeriodSeconds *out = new(int64) **out = **in } if in.Preconditions != nil { in, out := &in.Preconditions, &out.Preconditions *out = new(Preconditions) (*in).DeepCopyInto(*out) } if in.OrphanDependents != nil { in, out := &in.OrphanDependents, &out.OrphanDependents *out = new(bool) **out = **in } if in.PropagationPolicy != nil { in, out := &in.PropagationPolicy, &out.PropagationPolicy *out = new(DeletionPropagation) **out = **in } if in.DryRun != nil { in, out := &in.DryRun, &out.DryRun *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeleteOptions. func (in *DeleteOptions) DeepCopy() *DeleteOptions { if in == nil { return nil } out := new(DeleteOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *DeleteOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Duration) DeepCopyInto(out *Duration) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Duration. func (in *Duration) DeepCopy() *Duration { if in == nil { return nil } out := new(Duration) 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 if in.Raw != nil { in, out := &in.Raw, &out.Raw *out = make([]byte, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldsV1. func (in *FieldsV1) DeepCopy() *FieldsV1 { if in == nil { return nil } out := new(FieldsV1) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GetOptions) DeepCopyInto(out *GetOptions) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GetOptions. func (in *GetOptions) DeepCopy() *GetOptions { if in == nil { return nil } out := new(GetOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *GetOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupKind) DeepCopyInto(out *GroupKind) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupKind. func (in *GroupKind) DeepCopy() *GroupKind { if in == nil { return nil } out := new(GroupKind) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupResource) DeepCopyInto(out *GroupResource) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupResource. func (in *GroupResource) DeepCopy() *GroupResource { if in == nil { return nil } out := new(GroupResource) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupVersion) DeepCopyInto(out *GroupVersion) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersion. func (in *GroupVersion) DeepCopy() *GroupVersion { if in == nil { return nil } out := new(GroupVersion) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupVersionForDiscovery) DeepCopyInto(out *GroupVersionForDiscovery) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersionForDiscovery. func (in *GroupVersionForDiscovery) DeepCopy() *GroupVersionForDiscovery { if in == nil { return nil } out := new(GroupVersionForDiscovery) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupVersionKind) DeepCopyInto(out *GroupVersionKind) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersionKind. func (in *GroupVersionKind) DeepCopy() *GroupVersionKind { if in == nil { return nil } out := new(GroupVersionKind) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupVersionResource) DeepCopyInto(out *GroupVersionResource) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersionResource. func (in *GroupVersionResource) DeepCopy() *GroupVersionResource { if in == nil { return nil } out := new(GroupVersionResource) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InternalEvent) DeepCopyInto(out *InternalEvent) { *out = *in if in.Object != nil { out.Object = in.Object.DeepCopyObject() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalEvent. func (in *InternalEvent) DeepCopy() *InternalEvent { if in == nil { return nil } out := new(InternalEvent) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LabelSelector) DeepCopyInto(out *LabelSelector) { *out = *in if in.MatchLabels != nil { in, out := &in.MatchLabels, &out.MatchLabels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.MatchExpressions != nil { in, out := &in.MatchExpressions, &out.MatchExpressions *out = make([]LabelSelectorRequirement, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelSelector. func (in *LabelSelector) DeepCopy() *LabelSelector { if in == nil { return nil } out := new(LabelSelector) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LabelSelectorRequirement) DeepCopyInto(out *LabelSelectorRequirement) { *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 LabelSelectorRequirement. func (in *LabelSelectorRequirement) DeepCopy() *LabelSelectorRequirement { if in == nil { return nil } out := new(LabelSelectorRequirement) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *List) DeepCopyInto(out *List) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.RawExtension, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. func (in *List) DeepCopy() *List { if in == nil { return nil } out := new(List) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *List) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListMeta) DeepCopyInto(out *ListMeta) { *out = *in if in.RemainingItemCount != nil { in, out := &in.RemainingItemCount, &out.RemainingItemCount *out = new(int64) **out = **in } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListMeta. func (in *ListMeta) DeepCopy() *ListMeta { if in == nil { return nil } out := new(ListMeta) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListOptions) DeepCopyInto(out *ListOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.TimeoutSeconds != nil { in, out := &in.TimeoutSeconds, &out.TimeoutSeconds *out = new(int64) **out = **in } if in.SendInitialEvents != nil { in, out := &in.SendInitialEvents, &out.SendInitialEvents *out = new(bool) **out = **in } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions. func (in *ListOptions) DeepCopy() *ListOptions { if in == nil { return nil } out := new(ListOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ListOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedFieldsEntry) DeepCopyInto(out *ManagedFieldsEntry) { *out = *in if in.Time != nil { in, out := &in.Time, &out.Time *out = (*in).DeepCopy() } if in.FieldsV1 != nil { in, out := &in.FieldsV1, &out.FieldsV1 *out = new(FieldsV1) (*in).DeepCopyInto(*out) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedFieldsEntry. func (in *ManagedFieldsEntry) DeepCopy() *ManagedFieldsEntry { if in == nil { return nil } out := new(ManagedFieldsEntry) in.DeepCopyInto(out) return out } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroTime. func (in *MicroTime) DeepCopy() *MicroTime { if in == nil { return nil } out := new(MicroTime) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { *out = *in in.CreationTimestamp.DeepCopyInto(&out.CreationTimestamp) if in.DeletionTimestamp != nil { in, out := &in.DeletionTimestamp, &out.DeletionTimestamp *out = (*in).DeepCopy() } if in.DeletionGracePeriodSeconds != nil { in, out := &in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds *out = new(int64) **out = **in } if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.Annotations != nil { in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.OwnerReferences != nil { in, out := &in.OwnerReferences, &out.OwnerReferences *out = make([]OwnerReference, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.Finalizers != nil { in, out := &in.Finalizers, &out.Finalizers *out = make([]string, len(*in)) copy(*out, *in) } if in.ManagedFields != nil { in, out := &in.ManagedFields, &out.ManagedFields *out = make([]ManagedFieldsEntry, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. func (in *ObjectMeta) DeepCopy() *ObjectMeta { if in == nil { return nil } out := new(ObjectMeta) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OwnerReference) DeepCopyInto(out *OwnerReference) { *out = *in if in.Controller != nil { in, out := &in.Controller, &out.Controller *out = new(bool) **out = **in } if in.BlockOwnerDeletion != nil { in, out := &in.BlockOwnerDeletion, &out.BlockOwnerDeletion *out = new(bool) **out = **in } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerReference. func (in *OwnerReference) DeepCopy() *OwnerReference { if in == nil { return nil } out := new(OwnerReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PartialObjectMetadata) DeepCopyInto(out *PartialObjectMetadata) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialObjectMetadata. func (in *PartialObjectMetadata) DeepCopy() *PartialObjectMetadata { if in == nil { return nil } out := new(PartialObjectMetadata) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *PartialObjectMetadata) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PartialObjectMetadataList) DeepCopyInto(out *PartialObjectMetadataList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]PartialObjectMetadata, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialObjectMetadataList. func (in *PartialObjectMetadataList) DeepCopy() *PartialObjectMetadataList { if in == nil { return nil } out := new(PartialObjectMetadataList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *PartialObjectMetadataList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Patch) DeepCopyInto(out *Patch) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Patch. func (in *Patch) DeepCopy() *Patch { if in == nil { return nil } out := new(Patch) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PatchOptions) DeepCopyInto(out *PatchOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.DryRun != nil { in, out := &in.DryRun, &out.DryRun *out = make([]string, len(*in)) copy(*out, *in) } if in.Force != nil { in, out := &in.Force, &out.Force *out = new(bool) **out = **in } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchOptions. func (in *PatchOptions) DeepCopy() *PatchOptions { if in == nil { return nil } out := new(PatchOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *PatchOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Preconditions) DeepCopyInto(out *Preconditions) { *out = *in if in.UID != nil { in, out := &in.UID, &out.UID *out = new(types.UID) **out = **in } if in.ResourceVersion != nil { in, out := &in.ResourceVersion, &out.ResourceVersion *out = new(string) **out = **in } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preconditions. func (in *Preconditions) DeepCopy() *Preconditions { if in == nil { return nil } out := new(Preconditions) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RootPaths) DeepCopyInto(out *RootPaths) { *out = *in if in.Paths != nil { in, out := &in.Paths, &out.Paths *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RootPaths. func (in *RootPaths) DeepCopy() *RootPaths { if in == nil { return nil } out := new(RootPaths) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerAddressByClientCIDR) DeepCopyInto(out *ServerAddressByClientCIDR) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAddressByClientCIDR. func (in *ServerAddressByClientCIDR) DeepCopy() *ServerAddressByClientCIDR { if in == nil { return nil } out := new(ServerAddressByClientCIDR) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Status) DeepCopyInto(out *Status) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Details != nil { in, out := &in.Details, &out.Details *out = new(StatusDetails) (*in).DeepCopyInto(*out) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. func (in *Status) DeepCopy() *Status { if in == nil { return nil } out := new(Status) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *Status) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StatusCause) DeepCopyInto(out *StatusCause) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusCause. func (in *StatusCause) DeepCopy() *StatusCause { if in == nil { return nil } out := new(StatusCause) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StatusDetails) DeepCopyInto(out *StatusDetails) { *out = *in if in.Causes != nil { in, out := &in.Causes, &out.Causes *out = make([]StatusCause, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusDetails. func (in *StatusDetails) DeepCopy() *StatusDetails { if in == nil { return nil } out := new(StatusDetails) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Table) DeepCopyInto(out *Table) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.ColumnDefinitions != nil { in, out := &in.ColumnDefinitions, &out.ColumnDefinitions *out = make([]TableColumnDefinition, len(*in)) copy(*out, *in) } if in.Rows != nil { in, out := &in.Rows, &out.Rows *out = make([]TableRow, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Table. func (in *Table) DeepCopy() *Table { if in == nil { return nil } out := new(Table) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *Table) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TableColumnDefinition) DeepCopyInto(out *TableColumnDefinition) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableColumnDefinition. func (in *TableColumnDefinition) DeepCopy() *TableColumnDefinition { if in == nil { return nil } out := new(TableColumnDefinition) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TableOptions) DeepCopyInto(out *TableOptions) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableOptions. func (in *TableOptions) DeepCopy() *TableOptions { if in == nil { return nil } out := new(TableOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *TableOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TableRow) DeepCopyInto(out *TableRow) { clone := in.DeepCopy() *out = *clone return } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TableRowCondition) DeepCopyInto(out *TableRowCondition) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableRowCondition. func (in *TableRowCondition) DeepCopy() *TableRowCondition { if in == nil { return nil } out := new(TableRowCondition) in.DeepCopyInto(out) return out } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Time. func (in *Time) DeepCopy() *Time { if in == nil { return nil } out := new(Time) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Timestamp) DeepCopyInto(out *Timestamp) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Timestamp. func (in *Timestamp) DeepCopy() *Timestamp { if in == nil { return nil } out := new(Timestamp) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { *out = *in out.TypeMeta = in.TypeMeta if in.DryRun != nil { in, out := &in.DryRun, &out.DryRun *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateOptions. func (in *UpdateOptions) DeepCopy() *UpdateOptions { if in == nil { return nil } out := new(UpdateOptions) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *UpdateOptions) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in Verbs) DeepCopyInto(out *Verbs) { { in := &in *out = make(Verbs, len(*in)) copy(*out, *in) return } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Verbs. func (in Verbs) DeepCopy() Verbs { if in == nil { return nil } out := new(Verbs) in.DeepCopyInto(out) return *out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WatchEvent) DeepCopyInto(out *WatchEvent) { *out = *in in.Object.DeepCopyInto(&out.Object) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchEvent. func (in *WatchEvent) DeepCopy() *WatchEvent { if in == nil { return nil } out := new(WatchEvent) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *WatchEvent) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1/zz_generated.defaults.go000066400000000000000000000017571453143165200260500ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by defaulter-gen. DO NOT EDIT. package v1 import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // RegisterDefaults adds defaulters functions to the given scheme. // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/000077500000000000000000000000001453143165200221355ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/conversion.go000066400000000000000000000034601453143165200246540ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1beta1 import ( "unsafe" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" ) // Convert_Slice_string_To_v1beta1_IncludeObjectPolicy allows converting a URL query parameter value func Convert_Slice_string_To_v1beta1_IncludeObjectPolicy(in *[]string, out *IncludeObjectPolicy, s conversion.Scope) error { if len(*in) > 0 { *out = IncludeObjectPolicy((*in)[0]) } return nil } // Convert_v1beta1_PartialObjectMetadataList_To_v1_PartialObjectMetadataList allows converting PartialObjectMetadataList between versions func Convert_v1beta1_PartialObjectMetadataList_To_v1_PartialObjectMetadataList(in *PartialObjectMetadataList, out *v1.PartialObjectMetadataList, s conversion.Scope) error { out.ListMeta = in.ListMeta out.Items = *(*[]v1.PartialObjectMetadata)(unsafe.Pointer(&in.Items)) return nil } // Convert_v1_PartialObjectMetadataList_To_v1beta1_PartialObjectMetadataList allows converting PartialObjectMetadataList between versions func Convert_v1_PartialObjectMetadataList_To_v1beta1_PartialObjectMetadataList(in *v1.PartialObjectMetadataList, out *PartialObjectMetadataList, s conversion.Scope) error { out.ListMeta = in.ListMeta out.Items = *(*[]v1.PartialObjectMetadata)(unsafe.Pointer(&in.Items)) return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/deepcopy.go000066400000000000000000000011131453143165200242700ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1beta1 golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/doc.go000066400000000000000000000013621453143165200232330ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // +k8s:deepcopy-gen=package // +k8s:openapi-gen=true // +k8s:defaulter-gen=TypeMeta // +groupName=meta.k8s.io package v1beta1 // import "k8s.io/apimachinery/pkg/apis/meta/v1beta1" golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/generated.pb.go000066400000000000000000000262101453143165200250230ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto package v1beta1 import ( fmt "fmt" io "io" proto "github.com/gogo/protobuf/proto" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" math "math" math_bits "math/bits" reflect "reflect" strings "strings" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. 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} } func (m *PartialObjectMetadataList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PartialObjectMetadataList) 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 *PartialObjectMetadataList) XXX_Merge(src proto.Message) { xxx_messageInfo_PartialObjectMetadataList.Merge(m, src) } func (m *PartialObjectMetadataList) XXX_Size() int { return m.Size() } func (m *PartialObjectMetadataList) XXX_DiscardUnknown() { xxx_messageInfo_PartialObjectMetadataList.DiscardUnknown(m) } var xxx_messageInfo_PartialObjectMetadataList proto.InternalMessageInfo func init() { proto.RegisterType((*PartialObjectMetadataList)(nil), "k8s.io.apimachinery.pkg.apis.meta.v1beta1.PartialObjectMetadataList") } func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto", fileDescriptor_90ec10f86b91f9a8) } var fileDescriptor_90ec10f86b91f9a8 = []byte{ // 317 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, } func (m *PartialObjectMetadataList) 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 *PartialObjectMetadataList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PartialObjectMetadataList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 if len(m.Items) > 0 { for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *PartialObjectMetadataList) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.Items) > 0 { for _, e := range m.Items { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } l = m.ListMeta.Size() n += 1 + l + sovGenerated(uint64(l)) return n } func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (this *PartialObjectMetadataList) String() string { if this == nil { return "nil" } repeatedStringForItems := "[]PartialObjectMetadata{" for _, f := range this.Items { repeatedStringForItems += fmt.Sprintf("%v", f) + "," } repeatedStringForItems += "}" s := strings.Join([]string{`&PartialObjectMetadataList{`, `Items:` + repeatedStringForItems + `,`, `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, `}`, }, "") return s } func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { return "nil" } pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } func (m *PartialObjectMetadataList) 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: PartialObjectMetadataList: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PartialObjectMetadataList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Items", 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.Items = append(m.Items, v1.PartialObjectMetadata{}) if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 } if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthGenerated } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupGenerated } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthGenerated } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") ) golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/generated.proto000066400000000000000000000027571453143165200251730ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.apis.meta.v1beta1; import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/apis/meta/v1beta1"; // PartialObjectMetadataList contains a list of objects containing only their metadata. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 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; // items contains each of the included items. repeated k8s.io.apimachinery.pkg.apis.meta.v1.PartialObjectMetadata items = 1; } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/register.go000066400000000000000000000043461453143165200243170ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1beta1 import ( "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // GroupName is the group name for this API. const GroupName = "meta.k8s.io" // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} // Kind takes an unqualified kind and returns a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // AddMetaToScheme registers base meta types into schemas. func AddMetaToScheme(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Table{}, &TableOptions{}, &PartialObjectMetadata{}, &PartialObjectMetadataList{}, ) return nil } // RegisterConversions adds conversion functions to the given scheme. func RegisterConversions(s *runtime.Scheme) error { if err := s.AddGeneratedConversionFunc((*PartialObjectMetadataList)(nil), (*v1.PartialObjectMetadataList)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_PartialObjectMetadataList_To_v1_PartialObjectMetadataList(a.(*PartialObjectMetadataList), b.(*v1.PartialObjectMetadataList), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*v1.PartialObjectMetadataList)(nil), (*PartialObjectMetadataList)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_PartialObjectMetadataList_To_v1beta1_PartialObjectMetadataList(a.(*v1.PartialObjectMetadataList), b.(*PartialObjectMetadataList), scope) }); err != nil { return err } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/types.go000066400000000000000000000061021453143165200236270ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // package v1beta1 is alpha objects from meta that will be introduced. package v1beta1 import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Table is a tabular representation of a set of API resources. The server transforms the // object into a set of preferred columns for quickly reviewing the objects. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +protobuf=false type Table = v1.Table // TableColumnDefinition contains information about a column returned in the Table. // +protobuf=false type TableColumnDefinition = v1.TableColumnDefinition // TableRow is an individual row in a table. // +protobuf=false type TableRow = v1.TableRow // TableRowCondition allows a row to be marked with additional information. // +protobuf=false type TableRowCondition = v1.TableRowCondition type RowConditionType = v1.RowConditionType type ConditionStatus = v1.ConditionStatus type IncludeObjectPolicy = v1.IncludeObjectPolicy // TableOptions are used when a Table is requested by the caller. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type TableOptions = v1.TableOptions // PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients // to get access to a particular ObjectMeta schema without knowing the details of the version. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type PartialObjectMetadata = v1.PartialObjectMetadata // IMPORTANT: PartialObjectMetadataList has different protobuf field ids in v1beta1 than // v1 because ListMeta was accidentally omitted prior to 1.15. Therefore this type must // remain independent of v1.PartialObjectMetadataList to preserve mappings. // PartialObjectMetadataList contains a list of objects containing only their metadata. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type PartialObjectMetadataList struct { v1.TypeMeta `json:",inline"` // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional v1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,2,opt,name=metadata"` // items contains each of the included items. Items []v1.PartialObjectMetadata `json:"items" protobuf:"bytes,1,rep,name=items"` } const ( RowCompleted = v1.RowCompleted ConditionTrue = v1.ConditionTrue ConditionFalse = v1.ConditionFalse ConditionUnknown = v1.ConditionUnknown IncludeNone = v1.IncludeNone IncludeMetadata = v1.IncludeMetadata IncludeObject = v1.IncludeObject ) golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/types_swagger_doc_generated.go000066400000000000000000000032241453143165200302130ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1beta1 // This file contains a collection of methods that can be used from go-restful to // generate Swagger API documentation for its models. Please read this PR for more // information on the implementation: https://github.com/emicklei/go-restful/pull/215 // // TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if // they are on one line! For multiple line or blocks that you want to ignore use ---. // Any context after a --- is ignored. // // Those methods can be generated by using hack/update-codegen.sh // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. var map_PartialObjectMetadataList = map[string]string{ "": "PartialObjectMetadataList contains a list of objects containing only their metadata.", "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "items": "items contains each of the included items.", } func (PartialObjectMetadataList) SwaggerDoc() map[string]string { return map_PartialObjectMetadataList } // AUTO-GENERATED FUNCTIONS END HERE golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/validation/000077500000000000000000000000001453143165200242675ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/validation/validation.go000066400000000000000000000021731453143165200267530ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package validation import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" ) // ValidateTableOptions returns any invalid flags on TableOptions. func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList { var allErrs field.ErrorList switch opts.IncludeObject { case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "": default: allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty")) } return allErrs } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/zz_generated.deepcopy.go000066400000000000000000000034171453143165200267610ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package v1beta1 import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PartialObjectMetadataList) DeepCopyInto(out *PartialObjectMetadataList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]v1.PartialObjectMetadata, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialObjectMetadataList. func (in *PartialObjectMetadataList) DeepCopy() *PartialObjectMetadataList { if in == nil { return nil } out := new(PartialObjectMetadataList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *PartialObjectMetadataList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/meta/v1beta1/zz_generated.defaults.go000066400000000000000000000017641453143165200267630ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by defaulter-gen. DO NOT EDIT. package v1beta1 import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // RegisterDefaults adds defaulters functions to the given scheme. // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/000077500000000000000000000000001453143165200224725ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/doc.go000066400000000000000000000015401453143165200235660ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // +k8s:deepcopy-gen=package // +groupName=testapigroup.apimachinery.k8s.io // // 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" golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/fuzzer/000077500000000000000000000000001453143165200240175ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/fuzzer/fuzzer.go000066400000000000000000000061101453143165200256710ustar00rootroot00000000000000/* Copyright 2015 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 fuzzer import ( "fmt" "github.com/google/gofuzz" 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" "k8s.io/apimachinery/pkg/runtime" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" ) // overrideMetaFuncs override some generic fuzzer funcs from k8s.io/apimachinery in order to have more realistic // values in a Kubernetes context. func overrideMetaFuncs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ func(j *runtime.Object, c fuzz.Continue) { // TODO: uncomment when round trip starts from a versioned object if true { //c.RandBool() { *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"}`), ContentType: runtime.ContentTypeJSON, } } else { types := []runtime.Object{&testapigroup.Carp{}} t := types[c.Rand.Intn(len(types))] c.Fuzz(t) *j = t } }, func(r *runtime.RawExtension, c fuzz.Continue) { // Pick an arbitrary type and fuzz it types := []runtime.Object{&testapigroup.Carp{}} obj := types[c.Rand.Intn(len(types))] c.Fuzz(obj) // Convert the object to raw bytes bytes, err := runtime.Encode(apitesting.TestCodec(codecs, v1.SchemeGroupVersion), obj) if err != nil { panic(fmt.Sprintf("Failed to encode object: %v", err)) } // Set the bytes field on the RawExtension r.Raw = bytes }, } } func testapigroupFuncs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ func(s *testapigroup.CarpSpec, c fuzz.Continue) { c.FuzzNoCustom(s) // has a default value ttl := int64(30) if c.RandBool() { ttl = int64(c.Uint32()) } s.TerminationGracePeriodSeconds = &ttl if s.SchedulerName == "" { s.SchedulerName = "default-scheduler" } }, func(j *testapigroup.CarpPhase, c fuzz.Continue) { statuses := []testapigroup.CarpPhase{"Pending", "Running", "Succeeded", "Failed", "Unknown"} *j = statuses[c.Rand.Intn(len(statuses))] }, func(rp *testapigroup.RestartPolicy, c fuzz.Continue) { policies := []testapigroup.RestartPolicy{"Always", "Never", "OnFailure"} *rp = policies[c.Rand.Intn(len(policies))] }, } } // Funcs returns the fuzzer functions for the testapigroup. var Funcs = fuzzer.MergeFuzzerFuncs( overrideMetaFuncs, testapigroupFuncs, ) golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/install/000077500000000000000000000000001453143165200241405ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/install/install.go000066400000000000000000000022401453143165200261330ustar00rootroot00000000000000/* Copyright 2016 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 install installs the certificates API group, making it available as // an option to all of the API encoding/decoding machinery. package install import ( "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // Install registers the API group and adds types to a scheme func Install(scheme *runtime.Scheme) { utilruntime.Must(testapigroup.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion)) } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/install/roundtrip_test.go000066400000000000000000000016401453143165200275550ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package install import ( "testing" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" testapigroupfuzzer "k8s.io/apimachinery/pkg/apis/testapigroup/fuzzer" ) func TestRoundTrip(t *testing.T) { roundtrip.RoundTripTestForAPIGroup(t, Install, testapigroupfuzzer.Funcs) roundtrip.RoundTripProtobufTestForAPIGroup(t, Install, testapigroupfuzzer.Funcs) } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/register.go000066400000000000000000000031271453143165200246500ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testapigroup import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) var ( SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) AddToScheme = SchemeBuilder.AddToScheme ) // GroupName is the group name use in this package const GroupName = "testapigroup.apimachinery.k8s.io" // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} // Kind takes an unqualified kind and returns a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Carp{}, ) return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/types.go000066400000000000000000000104211453143165200241630ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testapigroup import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ( ConditionStatus string CarpConditionType string CarpPhase string RestartPolicy string ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Carp is a collection of containers, used as either input (create, update) or as output (list, get). type Carp struct { metav1.TypeMeta // +optional metav1.ObjectMeta // Spec defines the behavior of a carp. // +optional Spec CarpSpec // Status represents the current information about a carp. This data may not be up // to date. // +optional Status CarpStatus } // CarpStatus represents information about the status of a carp. Status may trail the actual // state of a system. type CarpStatus struct { // +optional Phase CarpPhase // +optional Conditions []CarpCondition // A human readable message indicating details about why the carp is in this state. // +optional Message string // A brief CamelCase message indicating details about why the carp is in this state. e.g. 'DiskPressure' // +optional Reason string // +optional HostIP string // +optional CarpIP string // 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 StartTime *metav1.Time } type CarpCondition struct { Type CarpConditionType Status ConditionStatus // +optional LastProbeTime metav1.Time // +optional LastTransitionTime metav1.Time // +optional Reason string // +optional Message string } // CarpSpec is a description of a carp type CarpSpec struct { // +optional RestartPolicy RestartPolicy // Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. // Value must be non-negative integer. The value zero indicates delete immediately. // If this value is nil, the default grace period will be used instead. // The grace period is the duration in seconds after the processes running in the carp are sent // a termination signal and the time when the processes are forcibly halted with a kill signal. // Set this value longer than the expected cleanup time for your process. // +optional TerminationGracePeriodSeconds *int64 // Optional duration in seconds relative to the StartTime that the carp may be active on a node // before the system actively tries to terminate the carp; value must be positive integer // +optional ActiveDeadlineSeconds *int64 // NodeSelector is a selector which must be true for the carp to fit on a node // +optional NodeSelector map[string]string // ServiceAccountName is the name of the ServiceAccount to use to run this carp // The carp will be allowed to use secrets referenced by the ServiceAccount ServiceAccountName string // 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 // requirements. // +optional NodeName string // Specifies the hostname of the Carp. // If not specified, the carp's hostname will be set to a system-defined value. // +optional Hostname string // If specified, the fully qualified Carp hostname will be "...svc.". // If not specified, the carp will not have a domainname at all. // +optional Subdomain string // If specified, the carp will be dispatched by specified scheduler. // If not specified, the carp will be dispatched by default scheduler. // +optional SchedulerName string } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // CarpList is a list of Carps. type CarpList struct { metav1.TypeMeta // +optional metav1.ListMeta Items []Carp } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/000077500000000000000000000000001453143165200230205ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/conversion.go000066400000000000000000000014071453143165200255360ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "k8s.io/apimachinery/pkg/runtime" ) func addConversionFuncs(scheme *runtime.Scheme) error { // Add non-generated conversion functions here. Currently there are none. return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/defaults.go000066400000000000000000000013401453143165200251540ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( "k8s.io/apimachinery/pkg/runtime" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { // return RegisterDefaults(scheme) return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/doc.go000066400000000000000000000015071453143165200241170ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // +k8s:deepcopy-gen=package // +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/testapigroup // +k8s:openapi-gen=false // +k8s:defaulter-gen=TypeMeta // +groupName=testapigroup.apimachinery.k8s.io package v1 // import "k8s.io/apimachinery/pkg/apis/testapigroup/v1" golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/generated.pb.go000066400000000000000000001622661453143165200257220ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto package v1 import ( fmt "fmt" io "io" proto "github.com/gogo/protobuf/proto" github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" math "math" math_bits "math/bits" reflect "reflect" strings "strings" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. 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} } func (m *Carp) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Carp) 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 *Carp) XXX_Merge(src proto.Message) { xxx_messageInfo_Carp.Merge(m, src) } func (m *Carp) XXX_Size() int { return m.Size() } func (m *Carp) XXX_DiscardUnknown() { xxx_messageInfo_Carp.DiscardUnknown(m) } 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} } func (m *CarpCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *CarpCondition) 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 *CarpCondition) XXX_Merge(src proto.Message) { xxx_messageInfo_CarpCondition.Merge(m, src) } func (m *CarpCondition) XXX_Size() int { return m.Size() } func (m *CarpCondition) XXX_DiscardUnknown() { xxx_messageInfo_CarpCondition.DiscardUnknown(m) } var xxx_messageInfo_CarpCondition proto.InternalMessageInfo func (m *CarpList) Reset() { *m = CarpList{} } func (*CarpList) ProtoMessage() {} func (*CarpList) Descriptor() ([]byte, []int) { return fileDescriptor_b7eb07c7d80facdf, []int{2} } func (m *CarpList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *CarpList) 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 *CarpList) XXX_Merge(src proto.Message) { xxx_messageInfo_CarpList.Merge(m, src) } func (m *CarpList) XXX_Size() int { return m.Size() } func (m *CarpList) XXX_DiscardUnknown() { xxx_messageInfo_CarpList.DiscardUnknown(m) } 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} } func (m *CarpSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *CarpSpec) 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 *CarpSpec) XXX_Merge(src proto.Message) { xxx_messageInfo_CarpSpec.Merge(m, src) } func (m *CarpSpec) XXX_Size() int { return m.Size() } func (m *CarpSpec) XXX_DiscardUnknown() { xxx_messageInfo_CarpSpec.DiscardUnknown(m) } 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} } func (m *CarpStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *CarpStatus) 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 *CarpStatus) XXX_Merge(src proto.Message) { xxx_messageInfo_CarpStatus.Merge(m, src) } func (m *CarpStatus) XXX_Size() int { return m.Size() } func (m *CarpStatus) XXX_DiscardUnknown() { xxx_messageInfo_CarpStatus.DiscardUnknown(m) } 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((*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") proto.RegisterType((*CarpStatus)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpStatus") } func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto", fileDescriptor_b7eb07c7d80facdf) } 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, } func (m *Carp) 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 *Carp) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Carp) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l { size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a { size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 { size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *CarpCondition) 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 *CarpCondition) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *CarpCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) i-- dAtA[i] = 0x32 i -= len(m.Reason) copy(dAtA[i:], m.Reason) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) i-- dAtA[i] = 0x2a { size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 { size, err := m.LastProbeTime.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a i -= len(m.Status) copy(dAtA[i:], m.Status) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) i-- dAtA[i] = 0x12 i -= len(m.Type) copy(dAtA[i:], m.Type) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *CarpList) 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 *CarpList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *CarpList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.Items) > 0 { for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } { size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *CarpSpec) 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 *CarpSpec) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *CarpSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.SchedulerName) copy(dAtA[i:], m.SchedulerName) i = encodeVarintGenerated(dAtA, i, uint64(len(m.SchedulerName))) i-- dAtA[i] = 0x1 i-- dAtA[i] = 0x9a i -= len(m.Subdomain) copy(dAtA[i:], m.Subdomain) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Subdomain))) i-- dAtA[i] = 0x1 i-- dAtA[i] = 0x8a i -= len(m.Hostname) copy(dAtA[i:], m.Hostname) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Hostname))) i-- dAtA[i] = 0x1 i-- dAtA[i] = 0x82 i-- if m.HostIPC { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x68 i-- if m.HostPID { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x60 i-- if m.HostNetwork { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- dAtA[i] = 0x58 i -= len(m.NodeName) copy(dAtA[i:], m.NodeName) i = encodeVarintGenerated(dAtA, i, uint64(len(m.NodeName))) i-- dAtA[i] = 0x52 i -= len(m.DeprecatedServiceAccount) copy(dAtA[i:], m.DeprecatedServiceAccount) i = encodeVarintGenerated(dAtA, i, uint64(len(m.DeprecatedServiceAccount))) i-- dAtA[i] = 0x4a i -= len(m.ServiceAccountName) copy(dAtA[i:], m.ServiceAccountName) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ServiceAccountName))) i-- dAtA[i] = 0x42 if len(m.NodeSelector) > 0 { keysForNodeSelector := make([]string, 0, len(m.NodeSelector)) for k := range m.NodeSelector { keysForNodeSelector = append(keysForNodeSelector, string(k)) } github_com_gogo_protobuf_sortkeys.Strings(keysForNodeSelector) for iNdEx := len(keysForNodeSelector) - 1; iNdEx >= 0; iNdEx-- { v := m.NodeSelector[string(keysForNodeSelector[iNdEx])] baseI := i i -= len(v) copy(dAtA[i:], v) i = encodeVarintGenerated(dAtA, i, uint64(len(v))) i-- dAtA[i] = 0x12 i -= len(keysForNodeSelector[iNdEx]) copy(dAtA[i:], keysForNodeSelector[iNdEx]) i = encodeVarintGenerated(dAtA, i, uint64(len(keysForNodeSelector[iNdEx]))) i-- dAtA[i] = 0xa i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) i-- dAtA[i] = 0x3a } } if m.ActiveDeadlineSeconds != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.ActiveDeadlineSeconds)) i-- dAtA[i] = 0x28 } if m.TerminationGracePeriodSeconds != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminationGracePeriodSeconds)) i-- dAtA[i] = 0x20 } i -= len(m.RestartPolicy) copy(dAtA[i:], m.RestartPolicy) i = encodeVarintGenerated(dAtA, i, uint64(len(m.RestartPolicy))) i-- dAtA[i] = 0x1a return len(dAtA) - i, nil } func (m *CarpStatus) 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 *CarpStatus) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *CarpStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.StartTime != nil { { size, err := m.StartTime.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x3a } i -= len(m.CarpIP) copy(dAtA[i:], m.CarpIP) i = encodeVarintGenerated(dAtA, i, uint64(len(m.CarpIP))) i-- dAtA[i] = 0x32 i -= len(m.HostIP) copy(dAtA[i:], m.HostIP) i = encodeVarintGenerated(dAtA, i, uint64(len(m.HostIP))) i-- dAtA[i] = 0x2a i -= len(m.Reason) copy(dAtA[i:], m.Reason) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) i-- dAtA[i] = 0x22 i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) i-- dAtA[i] = 0x1a if len(m.Conditions) > 0 { for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } } i -= len(m.Phase) copy(dAtA[i:], m.Phase) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Phase))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *Carp) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.ObjectMeta.Size() n += 1 + l + sovGenerated(uint64(l)) l = m.Spec.Size() n += 1 + l + sovGenerated(uint64(l)) l = m.Status.Size() n += 1 + l + sovGenerated(uint64(l)) return n } func (m *CarpCondition) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Type) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Status) n += 1 + l + sovGenerated(uint64(l)) l = m.LastProbeTime.Size() n += 1 + l + sovGenerated(uint64(l)) l = m.LastTransitionTime.Size() n += 1 + l + sovGenerated(uint64(l)) l = len(m.Reason) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *CarpList) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.ListMeta.Size() n += 1 + l + sovGenerated(uint64(l)) if len(m.Items) > 0 { for _, e := range m.Items { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } return n } func (m *CarpSpec) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.RestartPolicy) n += 1 + l + sovGenerated(uint64(l)) if m.TerminationGracePeriodSeconds != nil { n += 1 + sovGenerated(uint64(*m.TerminationGracePeriodSeconds)) } if m.ActiveDeadlineSeconds != nil { n += 1 + sovGenerated(uint64(*m.ActiveDeadlineSeconds)) } if len(m.NodeSelector) > 0 { for k, v := range m.NodeSelector { _ = k _ = v mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) } } l = len(m.ServiceAccountName) n += 1 + l + sovGenerated(uint64(l)) l = len(m.DeprecatedServiceAccount) n += 1 + l + sovGenerated(uint64(l)) l = len(m.NodeName) n += 1 + l + sovGenerated(uint64(l)) n += 2 n += 2 n += 2 l = len(m.Hostname) n += 2 + l + sovGenerated(uint64(l)) l = len(m.Subdomain) n += 2 + l + sovGenerated(uint64(l)) l = len(m.SchedulerName) n += 2 + l + sovGenerated(uint64(l)) return n } func (m *CarpStatus) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Phase) n += 1 + l + sovGenerated(uint64(l)) if len(m.Conditions) > 0 { for _, e := range m.Conditions { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } l = len(m.Message) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Reason) n += 1 + l + sovGenerated(uint64(l)) l = len(m.HostIP) n += 1 + l + sovGenerated(uint64(l)) l = len(m.CarpIP) n += 1 + l + sovGenerated(uint64(l)) if m.StartTime != nil { l = m.StartTime.Size() n += 1 + l + sovGenerated(uint64(l)) } return n } func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (this *Carp) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Carp{`, `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "CarpSpec", "CarpSpec", 1), `&`, ``, 1) + `,`, `Status:` + strings.Replace(strings.Replace(this.Status.String(), "CarpStatus", "CarpStatus", 1), `&`, ``, 1) + `,`, `}`, }, "") return s } func (this *CarpCondition) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&CarpCondition{`, `Type:` + fmt.Sprintf("%v", this.Type) + `,`, `Status:` + fmt.Sprintf("%v", this.Status) + `,`, `LastProbeTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastProbeTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `}`, }, "") return s } func (this *CarpList) String() string { if this == nil { return "nil" } repeatedStringForItems := "[]Carp{" for _, f := range this.Items { repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "Carp", "Carp", 1), `&`, ``, 1) + "," } repeatedStringForItems += "}" s := strings.Join([]string{`&CarpList{`, `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, `Items:` + repeatedStringForItems + `,`, `}`, }, "") return s } func (this *CarpSpec) String() string { if this == nil { return "nil" } keysForNodeSelector := make([]string, 0, len(this.NodeSelector)) for k := range this.NodeSelector { keysForNodeSelector = append(keysForNodeSelector, k) } github_com_gogo_protobuf_sortkeys.Strings(keysForNodeSelector) mapStringForNodeSelector := "map[string]string{" for _, k := range keysForNodeSelector { mapStringForNodeSelector += fmt.Sprintf("%v: %v,", k, this.NodeSelector[k]) } mapStringForNodeSelector += "}" s := strings.Join([]string{`&CarpSpec{`, `RestartPolicy:` + fmt.Sprintf("%v", this.RestartPolicy) + `,`, `TerminationGracePeriodSeconds:` + valueToStringGenerated(this.TerminationGracePeriodSeconds) + `,`, `ActiveDeadlineSeconds:` + valueToStringGenerated(this.ActiveDeadlineSeconds) + `,`, `NodeSelector:` + mapStringForNodeSelector + `,`, `ServiceAccountName:` + fmt.Sprintf("%v", this.ServiceAccountName) + `,`, `DeprecatedServiceAccount:` + fmt.Sprintf("%v", this.DeprecatedServiceAccount) + `,`, `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`, `HostNetwork:` + fmt.Sprintf("%v", this.HostNetwork) + `,`, `HostPID:` + fmt.Sprintf("%v", this.HostPID) + `,`, `HostIPC:` + fmt.Sprintf("%v", this.HostIPC) + `,`, `Hostname:` + fmt.Sprintf("%v", this.Hostname) + `,`, `Subdomain:` + fmt.Sprintf("%v", this.Subdomain) + `,`, `SchedulerName:` + fmt.Sprintf("%v", this.SchedulerName) + `,`, `}`, }, "") return s } func (this *CarpStatus) String() string { if this == nil { return "nil" } repeatedStringForConditions := "[]CarpCondition{" for _, f := range this.Conditions { repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "CarpCondition", "CarpCondition", 1), `&`, ``, 1) + "," } repeatedStringForConditions += "}" s := strings.Join([]string{`&CarpStatus{`, `Phase:` + fmt.Sprintf("%v", this.Phase) + `,`, `Conditions:` + repeatedStringForConditions + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `HostIP:` + fmt.Sprintf("%v", this.HostIP) + `,`, `CarpIP:` + fmt.Sprintf("%v", this.CarpIP) + `,`, `StartTime:` + strings.Replace(fmt.Sprintf("%v", this.StartTime), "Time", "v1.Time", 1) + `,`, `}`, }, "") return s } func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { return "nil" } pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } func (m *Carp) 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: Carp: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Carp: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", 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 } if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Spec", 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 } if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Status", 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 } if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *CarpCondition) 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: CarpCondition: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: CarpCondition: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Type", 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.Type = CarpConditionType(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Status", 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.Status = ConditionStatus(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastProbeTime", 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 } if err := m.LastProbeTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", 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 } if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Reason", 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.Reason = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Message", 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.Message = string(dAtA[iNdEx:postIndex]) 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 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: CarpList: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: CarpList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 } if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Items", 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.Items = append(m.Items, Carp{}) if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *CarpSpec) 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: CarpSpec: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: CarpSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RestartPolicy", 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.RestartPolicy = RestartPolicy(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field TerminationGracePeriodSeconds", wireType) } var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int64(b&0x7F) << shift if b < 0x80 { break } } m.TerminationGracePeriodSeconds = &v case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ActiveDeadlineSeconds", wireType) } var v int64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int64(b&0x7F) << shift if b < 0x80 { break } } m.ActiveDeadlineSeconds = &v case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NodeSelector", 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 } if m.NodeSelector == nil { m.NodeSelector = make(map[string]string) } var mapkey string var mapvalue string for iNdEx < postIndex { entryPreIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) if fieldNum == 1 { var stringLenmapkey uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapkey |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapkey := int(stringLenmapkey) if intStringLenmapkey < 0 { return ErrInvalidLengthGenerated } postStringIndexmapkey := iNdEx + intStringLenmapkey if postStringIndexmapkey < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapkey > l { return io.ErrUnexpectedEOF } mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) iNdEx = postStringIndexmapkey } else if fieldNum == 2 { var stringLenmapvalue uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLenmapvalue |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLenmapvalue := int(stringLenmapvalue) if intStringLenmapvalue < 0 { return ErrInvalidLengthGenerated } postStringIndexmapvalue := iNdEx + intStringLenmapvalue if postStringIndexmapvalue < 0 { return ErrInvalidLengthGenerated } if postStringIndexmapvalue > l { return io.ErrUnexpectedEOF } mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) iNdEx = postStringIndexmapvalue } else { iNdEx = entryPreIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > postIndex { return io.ErrUnexpectedEOF } iNdEx += skippy } } m.NodeSelector[mapkey] = mapvalue iNdEx = postIndex case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ServiceAccountName", 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.ServiceAccountName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedServiceAccount", 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.DeprecatedServiceAccount = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NodeName", 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.NodeName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field HostNetwork", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.HostNetwork = bool(v != 0) case 12: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field HostPID", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.HostPID = bool(v != 0) case 13: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field HostIPC", wireType) } var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ v |= int(b&0x7F) << shift if b < 0x80 { break } } m.HostIPC = bool(v != 0) case 16: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Hostname", 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.Hostname = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 17: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Subdomain", 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.Subdomain = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 19: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SchedulerName", 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.SchedulerName = string(dAtA[iNdEx:postIndex]) 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 *CarpStatus) 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: CarpStatus: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: CarpStatus: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Phase", 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.Phase = CarpPhase(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Conditions", 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.Conditions = append(m.Conditions, CarpCondition{}) if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Message", 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.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Reason", 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.Reason = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field HostIP", 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.HostIP = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CarpIP", 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.CarpIP = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StartTime", 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 } if m.StartTime == nil { m.StartTime = &v1.Time{} } if err := m.StartTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthGenerated } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthGenerated } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupGenerated } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthGenerated } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") ) golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/generated.proto000066400000000000000000000173051453143165200260510ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.apis.testapigroup.v1; import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; import "k8s.io/apimachinery/pkg/runtime/generated.proto"; import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/apis/testapigroup/v1"; // Carp is a collection of containers, used as either input (create, update) or as output (list, get). 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; // 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 // +optional optional CarpSpec spec = 2; // Most recently observed status of the carp. // This data may not be up to date. // Populated by the system. // Read-only. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status // +optional optional CarpStatus status = 3; } message CarpCondition { // Type is the type of the condition. // Currently only Ready. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions optional string type = 1; // Status is the status of the condition. // Can be True, False, Unknown. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions optional string status = 2; // Last time we probed the condition. // +optional 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; // Unique, one-word, CamelCase reason for the condition's last transition. // +optional optional string reason = 5; // Human-readable message indicating details about last transition. // +optional optional string message = 6; } // 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; // List of carps. // More info: http://kubernetes.io/docs/user-guide/carps repeated Carp items = 2; } // CarpSpec is a description of a carp message CarpSpec { // Restart policy for all containers within the carp. // One of Always, OnFailure, Never. // Default to Always. // More info: http://kubernetes.io/docs/user-guide/carp-states#restartpolicy // +optional optional string restartPolicy = 3; // Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. // Value must be non-negative integer. The value zero indicates delete immediately. // If this value is nil, the default grace period will be used instead. // The grace period is the duration in seconds after the processes running in the carp are sent // a termination signal and the time when the processes are forcibly halted with a kill signal. // Set this value longer than the expected cleanup time for your process. // Defaults to 30 seconds. // +optional optional int64 terminationGracePeriodSeconds = 4; // Optional duration in seconds the carp may be active on the node relative to // StartTime before the system will actively try to mark it failed and kill associated containers. // Value must be a positive integer. // +optional optional int64 activeDeadlineSeconds = 5; // NodeSelector is a selector which must be true for the carp to fit on a node. // Selector which must match a node's labels for the carp to be scheduled on that node. // More info: http://kubernetes.io/docs/user-guide/node-selection/README // +optional map nodeSelector = 7; // ServiceAccountName is the name of the ServiceAccount to use to run this carp. // More info: https://kubernetes.io/docs/concepts/security/service-accounts/ // +optional optional string serviceAccountName = 8; // DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. // Deprecated: Use serviceAccountName instead. // +k8s:conversion-gen=false // +optional optional string serviceAccount = 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 // requirements. // +optional 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 optional bool hostNetwork = 11; // Use the host's pid namespace. // Optional: Default to false. // +k8s:conversion-gen=false // +optional optional bool hostPID = 12; // Use the host's ipc namespace. // Optional: Default to false. // +k8s:conversion-gen=false // +optional optional bool hostIPC = 13; // Specifies the hostname of the Carp // If not specified, the carp's hostname will be set to a system-defined value. // +optional optional string hostname = 16; // If specified, the fully qualified Carp hostname will be "...svc.". // If not specified, the carp will not have a domainname at all. // +optional optional string subdomain = 17; // 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; } // CarpStatus represents information about the status of a carp. Status may trail the actual // state of a system. message CarpStatus { // Current condition of the carp. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-phase // +optional optional string phase = 1; // Current service state of carp. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions // +optional repeated CarpCondition conditions = 2; // A human readable message indicating details about why the carp is in this condition. // +optional optional string message = 3; // A brief CamelCase message indicating details about why the carp is in this state. // e.g. 'DiskPressure' // +optional optional string reason = 4; // IP address of the host to which the carp is assigned. Empty if not yet scheduled. // +optional optional string hostIP = 5; // IP address allocated to the carp. Routable at least within the cluster. // Empty if not yet allocated. // +optional optional string carpIP = 6; // 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; } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/register.go000066400000000000000000000042211453143165200251720ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // GroupName is the group name use in this package const GroupName = "testapigroup.apimachinery.k8s.io" // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} // Kind takes an unqualified kind and returns a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() } // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } var ( // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = &SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. localSchemeBuilder.Register(addKnownTypes, addConversionFuncs, addDefaultingFuncs) } // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Carp{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/types.go000066400000000000000000000222351453143165200245170ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ( ConditionStatus string CarpConditionType string CarpPhase string RestartPolicy string ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Carp is a collection of containers, used as either input (create, update) or as output (list, get). type Carp struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata // +optional metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // 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 // +optional Spec CarpSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // Most recently observed status of the carp. // This data may not be up to date. // Populated by the system. // Read-only. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status // +optional Status CarpStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // CarpStatus represents information about the status of a carp. Status may trail the actual // state of a system. type CarpStatus struct { // Current condition of the carp. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-phase // +optional Phase CarpPhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=CarpPhase"` // Current service state of carp. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions // +optional Conditions []CarpCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,rep,name=conditions"` // A human readable message indicating details about why the carp is in this condition. // +optional Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` // A brief CamelCase message indicating details about why the carp is in this state. // e.g. 'DiskPressure' // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` // IP address of the host to which the carp is assigned. Empty if not yet scheduled. // +optional HostIP string `json:"hostIP,omitempty" protobuf:"bytes,5,opt,name=hostIP"` // IP address allocated to the carp. Routable at least within the cluster. // Empty if not yet allocated. // +optional CarpIP string `json:"carpIP,omitempty" protobuf:"bytes,6,opt,name=carpIP"` // 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 StartTime *metav1.Time `json:"startTime,omitempty" protobuf:"bytes,7,opt,name=startTime"` } type CarpCondition struct { // Type is the type of the condition. // Currently only Ready. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions Type CarpConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CarpConditionType"` // Status is the status of the condition. // Can be True, False, Unknown. // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"` // Last time we probed the condition. // +optional LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"` // Last time the condition transitioned from one status to another. // +optional LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` // Unique, one-word, CamelCase reason for the condition's last transition. // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` // Human-readable message indicating details about last transition. // +optional Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` } // CarpSpec is a description of a carp type CarpSpec struct { // Restart policy for all containers within the carp. // One of Always, OnFailure, Never. // Default to Always. // More info: http://kubernetes.io/docs/user-guide/carp-states#restartpolicy // +optional RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,3,opt,name=restartPolicy,casttype=RestartPolicy"` // Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. // Value must be non-negative integer. The value zero indicates delete immediately. // If this value is nil, the default grace period will be used instead. // The grace period is the duration in seconds after the processes running in the carp are sent // a termination signal and the time when the processes are forcibly halted with a kill signal. // Set this value longer than the expected cleanup time for your process. // Defaults to 30 seconds. // +optional TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,4,opt,name=terminationGracePeriodSeconds"` // Optional duration in seconds the carp may be active on the node relative to // StartTime before the system will actively try to mark it failed and kill associated containers. // Value must be a positive integer. // +optional ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty" protobuf:"varint,5,opt,name=activeDeadlineSeconds"` // NodeSelector is a selector which must be true for the carp to fit on a node. // Selector which must match a node's labels for the carp to be scheduled on that node. // More info: http://kubernetes.io/docs/user-guide/node-selection/README // +optional NodeSelector map[string]string `json:"nodeSelector,omitempty" protobuf:"bytes,7,rep,name=nodeSelector"` // ServiceAccountName is the name of the ServiceAccount to use to run this carp. // More info: https://kubernetes.io/docs/concepts/security/service-accounts/ // +optional ServiceAccountName string `json:"serviceAccountName,omitempty" protobuf:"bytes,8,opt,name=serviceAccountName"` // DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. // Deprecated: Use serviceAccountName instead. // +k8s:conversion-gen=false // +optional DeprecatedServiceAccount string `json:"serviceAccount,omitempty" protobuf:"bytes,9,opt,name=serviceAccount"` // 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 // requirements. // +optional NodeName string `json:"nodeName,omitempty" protobuf:"bytes,10,opt,name=nodeName"` // 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 HostNetwork bool `json:"hostNetwork,omitempty" protobuf:"varint,11,opt,name=hostNetwork"` // Use the host's pid namespace. // Optional: Default to false. // +k8s:conversion-gen=false // +optional HostPID bool `json:"hostPID,omitempty" protobuf:"varint,12,opt,name=hostPID"` // Use the host's ipc namespace. // Optional: Default to false. // +k8s:conversion-gen=false // +optional HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"` // Specifies the hostname of the Carp // If not specified, the carp's hostname will be set to a system-defined value. // +optional Hostname string `json:"hostname,omitempty" protobuf:"bytes,16,opt,name=hostname"` // If specified, the fully qualified Carp hostname will be "...svc.". // If not specified, the carp will not have a domainname at all. // +optional Subdomain string `json:"subdomain,omitempty" protobuf:"bytes,17,opt,name=subdomain"` // If specified, the carp will be dispatched by specified scheduler. // If not specified, the carp will be dispatched by default scheduler. // +optional SchedulerName string `json:"schedulername,omitempty" protobuf:"bytes,19,opt,name=schedulername"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // CarpList is a list of Carps. type CarpList struct { metav1.TypeMeta `json:",inline"` // Standard list metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // List of carps. // More info: http://kubernetes.io/docs/user-guide/carps Items []Carp `json:"items" protobuf:"bytes,2,rep,name=items"` } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/zz_generated.conversion.go000066400000000000000000000265511453143165200302250ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by conversion-gen. DO NOT EDIT. package v1 import ( unsafe "unsafe" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testapigroup "k8s.io/apimachinery/pkg/apis/testapigroup" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) func init() { localSchemeBuilder.Register(RegisterConversions) } // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { if err := s.AddGeneratedConversionFunc((*Carp)(nil), (*testapigroup.Carp)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_Carp_To_testapigroup_Carp(a.(*Carp), b.(*testapigroup.Carp), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*testapigroup.Carp)(nil), (*Carp)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_testapigroup_Carp_To_v1_Carp(a.(*testapigroup.Carp), b.(*Carp), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*CarpCondition)(nil), (*testapigroup.CarpCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_CarpCondition_To_testapigroup_CarpCondition(a.(*CarpCondition), b.(*testapigroup.CarpCondition), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*testapigroup.CarpCondition)(nil), (*CarpCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_testapigroup_CarpCondition_To_v1_CarpCondition(a.(*testapigroup.CarpCondition), b.(*CarpCondition), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*CarpList)(nil), (*testapigroup.CarpList)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_CarpList_To_testapigroup_CarpList(a.(*CarpList), b.(*testapigroup.CarpList), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*testapigroup.CarpList)(nil), (*CarpList)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_testapigroup_CarpList_To_v1_CarpList(a.(*testapigroup.CarpList), b.(*CarpList), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*CarpSpec)(nil), (*testapigroup.CarpSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_CarpSpec_To_testapigroup_CarpSpec(a.(*CarpSpec), b.(*testapigroup.CarpSpec), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*testapigroup.CarpSpec)(nil), (*CarpSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_testapigroup_CarpSpec_To_v1_CarpSpec(a.(*testapigroup.CarpSpec), b.(*CarpSpec), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*CarpStatus)(nil), (*testapigroup.CarpStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_CarpStatus_To_testapigroup_CarpStatus(a.(*CarpStatus), b.(*testapigroup.CarpStatus), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*testapigroup.CarpStatus)(nil), (*CarpStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_testapigroup_CarpStatus_To_v1_CarpStatus(a.(*testapigroup.CarpStatus), b.(*CarpStatus), scope) }); err != nil { return err } return nil } func autoConvert_v1_Carp_To_testapigroup_Carp(in *Carp, out *testapigroup.Carp, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_CarpSpec_To_testapigroup_CarpSpec(&in.Spec, &out.Spec, s); err != nil { return err } if err := Convert_v1_CarpStatus_To_testapigroup_CarpStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } // Convert_v1_Carp_To_testapigroup_Carp is an autogenerated conversion function. func Convert_v1_Carp_To_testapigroup_Carp(in *Carp, out *testapigroup.Carp, s conversion.Scope) error { return autoConvert_v1_Carp_To_testapigroup_Carp(in, out, s) } func autoConvert_testapigroup_Carp_To_v1_Carp(in *testapigroup.Carp, out *Carp, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_testapigroup_CarpSpec_To_v1_CarpSpec(&in.Spec, &out.Spec, s); err != nil { return err } if err := Convert_testapigroup_CarpStatus_To_v1_CarpStatus(&in.Status, &out.Status, s); err != nil { return err } return nil } // Convert_testapigroup_Carp_To_v1_Carp is an autogenerated conversion function. func Convert_testapigroup_Carp_To_v1_Carp(in *testapigroup.Carp, out *Carp, s conversion.Scope) error { return autoConvert_testapigroup_Carp_To_v1_Carp(in, out, s) } func autoConvert_v1_CarpCondition_To_testapigroup_CarpCondition(in *CarpCondition, out *testapigroup.CarpCondition, s conversion.Scope) error { out.Type = testapigroup.CarpConditionType(in.Type) out.Status = testapigroup.ConditionStatus(in.Status) out.LastProbeTime = in.LastProbeTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason out.Message = in.Message return nil } // Convert_v1_CarpCondition_To_testapigroup_CarpCondition is an autogenerated conversion function. func Convert_v1_CarpCondition_To_testapigroup_CarpCondition(in *CarpCondition, out *testapigroup.CarpCondition, s conversion.Scope) error { return autoConvert_v1_CarpCondition_To_testapigroup_CarpCondition(in, out, s) } func autoConvert_testapigroup_CarpCondition_To_v1_CarpCondition(in *testapigroup.CarpCondition, out *CarpCondition, s conversion.Scope) error { out.Type = CarpConditionType(in.Type) out.Status = ConditionStatus(in.Status) out.LastProbeTime = in.LastProbeTime out.LastTransitionTime = in.LastTransitionTime out.Reason = in.Reason out.Message = in.Message return nil } // Convert_testapigroup_CarpCondition_To_v1_CarpCondition is an autogenerated conversion function. func Convert_testapigroup_CarpCondition_To_v1_CarpCondition(in *testapigroup.CarpCondition, out *CarpCondition, s conversion.Scope) error { return autoConvert_testapigroup_CarpCondition_To_v1_CarpCondition(in, out, s) } func autoConvert_v1_CarpList_To_testapigroup_CarpList(in *CarpList, out *testapigroup.CarpList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]testapigroup.Carp, len(*in)) for i := range *in { if err := Convert_v1_Carp_To_testapigroup_Carp(&(*in)[i], &(*out)[i], s); err != nil { return err } } } else { out.Items = nil } return nil } // Convert_v1_CarpList_To_testapigroup_CarpList is an autogenerated conversion function. func Convert_v1_CarpList_To_testapigroup_CarpList(in *CarpList, out *testapigroup.CarpList, s conversion.Scope) error { return autoConvert_v1_CarpList_To_testapigroup_CarpList(in, out, s) } func autoConvert_testapigroup_CarpList_To_v1_CarpList(in *testapigroup.CarpList, out *CarpList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]Carp, len(*in)) for i := range *in { if err := Convert_testapigroup_Carp_To_v1_Carp(&(*in)[i], &(*out)[i], s); err != nil { return err } } } else { out.Items = nil } return nil } // Convert_testapigroup_CarpList_To_v1_CarpList is an autogenerated conversion function. func Convert_testapigroup_CarpList_To_v1_CarpList(in *testapigroup.CarpList, out *CarpList, s conversion.Scope) error { return autoConvert_testapigroup_CarpList_To_v1_CarpList(in, out, s) } func autoConvert_v1_CarpSpec_To_testapigroup_CarpSpec(in *CarpSpec, out *testapigroup.CarpSpec, s conversion.Scope) error { out.RestartPolicy = testapigroup.RestartPolicy(in.RestartPolicy) out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) out.ServiceAccountName = in.ServiceAccountName // INFO: in.DeprecatedServiceAccount opted out of conversion generation out.NodeName = in.NodeName // INFO: in.HostNetwork opted out of conversion generation // INFO: in.HostPID opted out of conversion generation // INFO: in.HostIPC opted out of conversion generation out.Hostname = in.Hostname out.Subdomain = in.Subdomain out.SchedulerName = in.SchedulerName return nil } // Convert_v1_CarpSpec_To_testapigroup_CarpSpec is an autogenerated conversion function. func Convert_v1_CarpSpec_To_testapigroup_CarpSpec(in *CarpSpec, out *testapigroup.CarpSpec, s conversion.Scope) error { return autoConvert_v1_CarpSpec_To_testapigroup_CarpSpec(in, out, s) } func autoConvert_testapigroup_CarpSpec_To_v1_CarpSpec(in *testapigroup.CarpSpec, out *CarpSpec, s conversion.Scope) error { out.RestartPolicy = RestartPolicy(in.RestartPolicy) out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) out.ServiceAccountName = in.ServiceAccountName out.NodeName = in.NodeName out.Hostname = in.Hostname out.Subdomain = in.Subdomain out.SchedulerName = in.SchedulerName return nil } // Convert_testapigroup_CarpSpec_To_v1_CarpSpec is an autogenerated conversion function. func Convert_testapigroup_CarpSpec_To_v1_CarpSpec(in *testapigroup.CarpSpec, out *CarpSpec, s conversion.Scope) error { return autoConvert_testapigroup_CarpSpec_To_v1_CarpSpec(in, out, s) } func autoConvert_v1_CarpStatus_To_testapigroup_CarpStatus(in *CarpStatus, out *testapigroup.CarpStatus, s conversion.Scope) error { out.Phase = testapigroup.CarpPhase(in.Phase) out.Conditions = *(*[]testapigroup.CarpCondition)(unsafe.Pointer(&in.Conditions)) out.Message = in.Message out.Reason = in.Reason out.HostIP = in.HostIP out.CarpIP = in.CarpIP out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime)) return nil } // Convert_v1_CarpStatus_To_testapigroup_CarpStatus is an autogenerated conversion function. func Convert_v1_CarpStatus_To_testapigroup_CarpStatus(in *CarpStatus, out *testapigroup.CarpStatus, s conversion.Scope) error { return autoConvert_v1_CarpStatus_To_testapigroup_CarpStatus(in, out, s) } func autoConvert_testapigroup_CarpStatus_To_v1_CarpStatus(in *testapigroup.CarpStatus, out *CarpStatus, s conversion.Scope) error { out.Phase = CarpPhase(in.Phase) out.Conditions = *(*[]CarpCondition)(unsafe.Pointer(&in.Conditions)) out.Message = in.Message out.Reason = in.Reason out.HostIP = in.HostIP out.CarpIP = in.CarpIP out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime)) return nil } // Convert_testapigroup_CarpStatus_To_v1_CarpStatus is an autogenerated conversion function. func Convert_testapigroup_CarpStatus_To_v1_CarpStatus(in *testapigroup.CarpStatus, out *CarpStatus, s conversion.Scope) error { return autoConvert_testapigroup_CarpStatus_To_v1_CarpStatus(in, out, s) } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go000066400000000000000000000107241453143165200276430ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package v1 import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Carp) DeepCopyInto(out *Carp) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Carp. func (in *Carp) DeepCopy() *Carp { if in == nil { return nil } out := new(Carp) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *Carp) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpCondition) DeepCopyInto(out *CarpCondition) { *out = *in in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpCondition. func (in *CarpCondition) DeepCopy() *CarpCondition { if in == nil { return nil } out := new(CarpCondition) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpList) DeepCopyInto(out *CarpList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]Carp, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpList. func (in *CarpList) DeepCopy() *CarpList { if in == nil { return nil } out := new(CarpList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *CarpList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpSpec) DeepCopyInto(out *CarpSpec) { *out = *in if in.TerminationGracePeriodSeconds != nil { in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds *out = new(int64) **out = **in } if in.ActiveDeadlineSeconds != nil { in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds *out = new(int64) **out = **in } if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpSpec. func (in *CarpSpec) DeepCopy() *CarpSpec { if in == nil { return nil } out := new(CarpSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpStatus) DeepCopyInto(out *CarpStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]CarpCondition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.StartTime != nil { in, out := &in.StartTime, &out.StartTime *out = (*in).DeepCopy() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpStatus. func (in *CarpStatus) DeepCopy() *CarpStatus { if in == nil { return nil } out := new(CarpStatus) in.DeepCopyInto(out) return out } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/v1/zz_generated.defaults.go000066400000000000000000000017571453143165200276500ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by defaulter-gen. DO NOT EDIT. package v1 import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // RegisterDefaults adds defaulters functions to the given scheme. // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { return nil } golang-k8s-apimachinery-0.29.0/pkg/apis/testapigroup/zz_generated.deepcopy.go000066400000000000000000000107361453143165200273200ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package testapigroup import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Carp) DeepCopyInto(out *Carp) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Carp. func (in *Carp) DeepCopy() *Carp { if in == nil { return nil } out := new(Carp) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *Carp) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpCondition) DeepCopyInto(out *CarpCondition) { *out = *in in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpCondition. func (in *CarpCondition) DeepCopy() *CarpCondition { if in == nil { return nil } out := new(CarpCondition) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpList) DeepCopyInto(out *CarpList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]Carp, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpList. func (in *CarpList) DeepCopy() *CarpList { if in == nil { return nil } out := new(CarpList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *CarpList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpSpec) DeepCopyInto(out *CarpSpec) { *out = *in if in.TerminationGracePeriodSeconds != nil { in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds *out = new(int64) **out = **in } if in.ActiveDeadlineSeconds != nil { in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds *out = new(int64) **out = **in } if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpSpec. func (in *CarpSpec) DeepCopy() *CarpSpec { if in == nil { return nil } out := new(CarpSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CarpStatus) DeepCopyInto(out *CarpStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]CarpCondition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.StartTime != nil { in, out := &in.StartTime, &out.StartTime *out = (*in).DeepCopy() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpStatus. func (in *CarpStatus) DeepCopy() *CarpStatus { if in == nil { return nil } out := new(CarpStatus) in.DeepCopyInto(out) return out } golang-k8s-apimachinery-0.29.0/pkg/conversion/000077500000000000000000000000001453143165200211755ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/conversion/converter.go000066400000000000000000000166531453143165200235460ustar00rootroot00000000000000/* 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 conversion import ( "fmt" "reflect" ) type typePair struct { source reflect.Type dest reflect.Type } type NameFunc func(t reflect.Type) string var DefaultNameFunc = func(t reflect.Type) string { return t.Name() } // ConversionFunc converts the object a into the object b, reusing arrays or objects // or pointers if necessary. It should return an error if the object cannot be converted // or if some data is invalid. If you do not wish a and b to share fields or nested // objects, you must copy a before calling this function. type ConversionFunc func(a, b interface{}, scope Scope) error // Converter knows how to convert one type to another. type Converter struct { // Map from the conversion pair to a function which can // do the conversion. conversionFuncs ConversionFuncs generatedConversionFuncs ConversionFuncs // Set of conversions that should be treated as a no-op ignoredUntypedConversions map[typePair]struct{} } // NewConverter creates a new Converter object. // Arg NameFunc is just for backward compatibility. func NewConverter(NameFunc) *Converter { c := &Converter{ conversionFuncs: NewConversionFuncs(), generatedConversionFuncs: NewConversionFuncs(), ignoredUntypedConversions: make(map[typePair]struct{}), } c.RegisterUntypedConversionFunc( (*[]byte)(nil), (*[]byte)(nil), func(a, b interface{}, s Scope) error { return Convert_Slice_byte_To_Slice_byte(a.(*[]byte), b.(*[]byte), s) }, ) return c } // WithConversions returns a Converter that is a copy of c but with the additional // fns merged on top. func (c *Converter) WithConversions(fns ConversionFuncs) *Converter { copied := *c copied.conversionFuncs = c.conversionFuncs.Merge(fns) return &copied } // DefaultMeta returns meta for a given type. func (c *Converter) DefaultMeta(t reflect.Type) *Meta { return &Meta{} } // Convert_Slice_byte_To_Slice_byte prevents recursing into every byte func Convert_Slice_byte_To_Slice_byte(in *[]byte, out *[]byte, s Scope) error { if *in == nil { *out = nil return nil } *out = make([]byte, len(*in)) copy(*out, *in) return nil } // Scope is passed to conversion funcs to allow them to continue an ongoing conversion. // If multiple converters exist in the system, Scope will allow you to use the correct one // from a conversion function--that is, the one your conversion function was called by. type Scope interface { // Call Convert to convert sub-objects. Note that if you call it with your own exact // parameters, you'll run out of stack space before anything useful happens. Convert(src, dest interface{}) error // Meta returns any information originally passed to Convert. Meta() *Meta } func NewConversionFuncs() ConversionFuncs { return ConversionFuncs{ untyped: make(map[typePair]ConversionFunc), } } type ConversionFuncs struct { untyped map[typePair]ConversionFunc } // AddUntyped adds the provided conversion function to the lookup table for the types that are // supplied as a and b. a and b must be pointers or an error is returned. This method overwrites // previously defined functions. func (c ConversionFuncs) AddUntyped(a, b interface{}, fn ConversionFunc) error { tA, tB := reflect.TypeOf(a), reflect.TypeOf(b) if tA.Kind() != reflect.Pointer { return fmt.Errorf("the type %T must be a pointer to register as an untyped conversion", a) } if tB.Kind() != reflect.Pointer { return fmt.Errorf("the type %T must be a pointer to register as an untyped conversion", b) } c.untyped[typePair{tA, tB}] = fn return nil } // Merge returns a new ConversionFuncs that contains all conversions from // both other and c, with other conversions taking precedence. func (c ConversionFuncs) Merge(other ConversionFuncs) ConversionFuncs { merged := NewConversionFuncs() for k, v := range c.untyped { merged.untyped[k] = v } for k, v := range other.untyped { merged.untyped[k] = v } return merged } // Meta is supplied by Scheme, when it calls Convert. type Meta struct { // Context is an optional field that callers may use to pass info to conversion functions. Context interface{} } // scope contains information about an ongoing conversion. type scope struct { converter *Converter meta *Meta } // Convert continues a conversion. func (s *scope) Convert(src, dest interface{}) error { return s.converter.Convert(src, dest, s.meta) } // Meta returns the meta object that was originally passed to Convert. func (s *scope) Meta() *Meta { return s.meta } // RegisterUntypedConversionFunc registers a function that converts between a and b by passing objects of those // types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce // any other guarantee. func (c *Converter) RegisterUntypedConversionFunc(a, b interface{}, fn ConversionFunc) error { return c.conversionFuncs.AddUntyped(a, b, fn) } // RegisterGeneratedUntypedConversionFunc registers a function that converts between a and b by passing objects of those // types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce // any other guarantee. func (c *Converter) RegisterGeneratedUntypedConversionFunc(a, b interface{}, fn ConversionFunc) error { return c.generatedConversionFuncs.AddUntyped(a, b, fn) } // RegisterIgnoredConversion registers a "no-op" for conversion, where any requested // conversion between from and to is ignored. func (c *Converter) RegisterIgnoredConversion(from, to interface{}) error { typeFrom := reflect.TypeOf(from) typeTo := reflect.TypeOf(to) if typeFrom.Kind() != reflect.Pointer { return fmt.Errorf("expected pointer arg for 'from' param 0, got: %v", typeFrom) } if typeTo.Kind() != reflect.Pointer { return fmt.Errorf("expected pointer arg for 'to' param 1, got: %v", typeTo) } c.ignoredUntypedConversions[typePair{typeFrom, typeTo}] = struct{}{} return nil } // Convert will translate src to dest if it knows how. Both must be pointers. // If no conversion func is registered and the default copying mechanism // doesn't work on this type pair, an error will be returned. // 'meta' is given to allow you to pass information to conversion functions, // it is not used by Convert() other than storing it in the scope. // Not safe for objects with cyclic references! func (c *Converter) Convert(src, dest interface{}, meta *Meta) error { pair := typePair{reflect.TypeOf(src), reflect.TypeOf(dest)} scope := &scope{ converter: c, meta: meta, } // ignore conversions of this type if _, ok := c.ignoredUntypedConversions[pair]; ok { return nil } if fn, ok := c.conversionFuncs.untyped[pair]; ok { return fn(src, dest, scope) } if fn, ok := c.generatedConversionFuncs.untyped[pair]; ok { return fn(src, dest, scope) } dv, err := EnforcePtr(dest) if err != nil { return err } sv, err := EnforcePtr(src) if err != nil { return err } return fmt.Errorf("converting (%s) to (%s): unknown conversion", sv.Type(), dv.Type()) } golang-k8s-apimachinery-0.29.0/pkg/conversion/converter_test.go000066400000000000000000000153721453143165200246020ustar00rootroot00000000000000/* 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 conversion import ( "fmt" "reflect" "strconv" "testing" ) func TestConverter_byteSlice(t *testing.T) { c := NewConverter(nil) src := []byte{1, 2, 3} dest := []byte{} err := c.Convert(&src, &dest, nil) if err != nil { t.Fatalf("expected no error") } if e, a := src, dest; !reflect.DeepEqual(e, a) { t.Errorf("expected %#v, got %#v", e, a) } } func TestConverter_MismatchedTypes(t *testing.T) { c := NewConverter(nil) convertFn := func(in *[]string, out *int, s Scope) error { if str, err := strconv.Atoi((*in)[0]); err != nil { return err } else { *out = str return nil } } if err := c.RegisterUntypedConversionFunc( (*[]string)(nil), (*int)(nil), func(a, b interface{}, s Scope) error { return convertFn(a.(*[]string), b.(*int), s) }, ); err != nil { t.Fatalf("Unexpected error: %v", err) } src := []string{"5"} var dest int if err := c.Convert(&src, &dest, nil); err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := 5, dest; e != a { t.Errorf("expected %#v, got %#v", e, a) } } func TestConverter_CallsRegisteredFunctions(t *testing.T) { type A struct { Foo string Baz int } type B struct { Bar string Baz int } type C struct{} c := NewConverter(nil) convertFn1 := func(in *A, out *B, s Scope) error { out.Bar = in.Foo out.Baz = in.Baz return nil } if err := c.RegisterUntypedConversionFunc( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return convertFn1(a.(*A), b.(*B), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } convertFn2 := func(in *B, out *A, s Scope) error { out.Foo = in.Bar out.Baz = in.Baz return nil } if err := c.RegisterUntypedConversionFunc( (*B)(nil), (*A)(nil), func(a, b interface{}, s Scope) error { return convertFn2(a.(*B), b.(*A), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } x := A{"hello, intrepid test reader!", 3} y := B{} if err := c.Convert(&x, &y, nil); err != nil { t.Fatalf("unexpected error %v", err) } if e, a := x.Foo, y.Bar; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := x.Baz, y.Baz; e != a { t.Errorf("expected %v, got %v", e, a) } z := B{"all your test are belong to us", 42} w := A{} if err := c.Convert(&z, &w, nil); err != nil { t.Fatalf("unexpected error %v", err) } if e, a := z.Bar, w.Foo; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := z.Baz, w.Baz; e != a { t.Errorf("expected %v, got %v", e, a) } convertFn3 := func(in *A, out *C, s Scope) error { return fmt.Errorf("C can't store an A, silly") } if err := c.RegisterUntypedConversionFunc( (*A)(nil), (*C)(nil), func(a, b interface{}, s Scope) error { return convertFn3(a.(*A), b.(*C), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } if err := c.Convert(&A{}, &C{}, nil); err == nil { t.Errorf("unexpected non-error") } } func TestConverter_IgnoredConversion(t *testing.T) { type A struct{} type B struct{} count := 0 c := NewConverter(nil) convertFn := func(in *A, out *B, s Scope) error { count++ return nil } if err := c.RegisterUntypedConversionFunc( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return convertFn(a.(*A), b.(*B), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } if err := c.RegisterIgnoredConversion(&A{}, &B{}); err != nil { t.Fatal(err) } a := A{} b := B{} if err := c.Convert(&a, &b, nil); err != nil { t.Errorf("%v", err) } if count != 0 { t.Errorf("unexpected number of conversion invocations") } } func TestConverter_GeneratedConversionOverridden(t *testing.T) { type A struct{} type B struct{} c := NewConverter(nil) convertFn1 := func(in *A, out *B, s Scope) error { return nil } if err := c.RegisterUntypedConversionFunc( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return convertFn1(a.(*A), b.(*B), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } convertFn2 := func(in *A, out *B, s Scope) error { return fmt.Errorf("generated function should be overridden") } if err := c.RegisterGeneratedUntypedConversionFunc( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return convertFn2(a.(*A), b.(*B), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } a := A{} b := B{} if err := c.Convert(&a, &b, nil); err != nil { t.Errorf("%v", err) } } func TestConverter_WithConversionOverridden(t *testing.T) { type A struct{} type B struct{} c := NewConverter(nil) convertFn1 := func(in *A, out *B, s Scope) error { return fmt.Errorf("conversion function should be overridden") } if err := c.RegisterUntypedConversionFunc( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return convertFn1(a.(*A), b.(*B), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } convertFn2 := func(in *A, out *B, s Scope) error { return fmt.Errorf("generated function should be overridden") } if err := c.RegisterGeneratedUntypedConversionFunc( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return convertFn2(a.(*A), b.(*B), s) }, ); err != nil { t.Fatalf("unexpected error %v", err) } ext := NewConversionFuncs() ext.AddUntyped( (*A)(nil), (*B)(nil), func(a, b interface{}, s Scope) error { return nil }, ) newc := c.WithConversions(ext) a := A{} b := B{} if err := c.Convert(&a, &b, nil); err == nil || err.Error() != "conversion function should be overridden" { t.Errorf("unexpected error: %v", err) } if err := newc.Convert(&a, &b, nil); err != nil { t.Errorf("%v", err) } } func TestConverter_meta(t *testing.T) { type Foo struct{ A string } type Bar struct{ A string } c := NewConverter(nil) checks := 0 convertFn1 := func(in *Foo, out *Bar, s Scope) error { if s.Meta() == nil { t.Errorf("Meta did not get passed!") } checks++ out.A = in.A return nil } if err := c.RegisterUntypedConversionFunc( (*Foo)(nil), (*Bar)(nil), func(a, b interface{}, s Scope) error { return convertFn1(a.(*Foo), b.(*Bar), s) }, ); err != nil { t.Fatalf("Unexpected error: %v", err) } if err := c.Convert(&Foo{}, &Bar{}, &Meta{}); err != nil { t.Fatalf("Unexpected error: %v", err) } if checks != 1 { t.Errorf("Registered functions did not get called.") } } golang-k8s-apimachinery-0.29.0/pkg/conversion/deep_equal.go000066400000000000000000000024071453143165200236330ustar00rootroot00000000000000/* Copyright 2015 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 conversion import ( "k8s.io/apimachinery/third_party/forked/golang/reflect" ) // The code for this type must be located in third_party, since it forks from // go std lib. But for convenience, we expose the type here, too. type Equalities struct { reflect.Equalities } // For convenience, panics on errors func EqualitiesOrDie(funcs ...interface{}) Equalities { e := Equalities{reflect.Equalities{}} if err := e.AddFuncs(funcs...); err != nil { panic(err) } return e } // Performs a shallow copy of the equalities map func (e Equalities) Copy() Equalities { result := Equalities{reflect.Equalities{}} for key, value := range e.Equalities { result.Equalities[key] = value } return result } golang-k8s-apimachinery-0.29.0/pkg/conversion/doc.go000066400000000000000000000020641453143165200222730ustar00rootroot00000000000000/* 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 conversion provides go object versioning. // // Specifically, conversion provides a way for you to define multiple versions // of the same object. You may write functions which implement conversion logic, // but for the fields which did not change, copying is automated. This makes it // easy to modify the structures you use in memory without affecting the format // you store on disk or respond to in your external API calls. package conversion // import "k8s.io/apimachinery/pkg/conversion" golang-k8s-apimachinery-0.29.0/pkg/conversion/helper.go000066400000000000000000000023541453143165200230070ustar00rootroot00000000000000/* 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 conversion import ( "fmt" "reflect" ) // EnforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value // of the dereferenced pointer, ensuring that it is settable/addressable. // Returns an error if this is not possible. func EnforcePtr(obj interface{}) (reflect.Value, error) { v := reflect.ValueOf(obj) if v.Kind() != reflect.Pointer { if v.Kind() == reflect.Invalid { return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind") } return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type()) } if v.IsNil() { return reflect.Value{}, fmt.Errorf("expected pointer, but got nil") } return v.Elem(), nil } golang-k8s-apimachinery-0.29.0/pkg/conversion/helper_test.go000066400000000000000000000017221453143165200240440ustar00rootroot00000000000000/* 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 conversion import "testing" func TestInvalidPtrValueKind(t *testing.T) { var simple interface{} switch obj := simple.(type) { default: _, err := EnforcePtr(obj) if err == nil { t.Errorf("Expected error on invalid kind") } } } func TestEnforceNilPtr(t *testing.T) { var nilPtr *struct{} _, err := EnforcePtr(nilPtr) if err == nil { t.Errorf("Expected error on nil pointer") } } golang-k8s-apimachinery-0.29.0/pkg/conversion/queryparams/000077500000000000000000000000001453143165200235465ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/conversion/queryparams/convert.go000066400000000000000000000117011453143165200255550ustar00rootroot00000000000000/* 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 queryparams import ( "fmt" "net/url" "reflect" "strings" ) // Marshaler converts an object to a query parameter string representation type Marshaler interface { MarshalQueryParameter() (string, error) } // Unmarshaler converts a string representation to an object type Unmarshaler interface { UnmarshalQueryParameter(string) error } func jsonTag(field reflect.StructField) (string, bool) { structTag := field.Tag.Get("json") if len(structTag) == 0 { return "", false } parts := strings.Split(structTag, ",") tag := parts[0] if tag == "-" { tag = "" } omitempty := false parts = parts[1:] for _, part := range parts { if part == "omitempty" { omitempty = true break } } return tag, omitempty } func isPointerKind(kind reflect.Kind) bool { return kind == reflect.Pointer } func isStructKind(kind reflect.Kind) bool { return kind == reflect.Struct } func isValueKind(kind reflect.Kind) bool { switch kind { case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: return true default: return false } } func zeroValue(value reflect.Value) bool { return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface()) } func customMarshalValue(value reflect.Value) (reflect.Value, bool) { // Return unless we implement a custom query marshaler if !value.CanInterface() { return reflect.Value{}, false } marshaler, ok := value.Interface().(Marshaler) if !ok { if !isPointerKind(value.Kind()) && value.CanAddr() { marshaler, ok = value.Addr().Interface().(Marshaler) if !ok { return reflect.Value{}, false } } else { return reflect.Value{}, false } } // Don't invoke functions on nil pointers // If the type implements MarshalQueryParameter, AND the tag is not omitempty, AND the value is a nil pointer, "" seems like a reasonable response if isPointerKind(value.Kind()) && zeroValue(value) { return reflect.ValueOf(""), true } // Get the custom marshalled value v, err := marshaler.MarshalQueryParameter() if err != nil { return reflect.Value{}, false } return reflect.ValueOf(v), true } func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) { if omitempty && zeroValue(value) { return } val := "" iValue := fmt.Sprintf("%v", value.Interface()) if iValue != "" { val = iValue } values.Add(tag, val) } func addListOfParams(values url.Values, tag string, omitempty bool, list reflect.Value) { for i := 0; i < list.Len(); i++ { addParam(values, tag, omitempty, list.Index(i)) } } // Convert takes an object and converts it to a url.Values object using JSON tags as // parameter names. Only top-level simple values, arrays, and slices are serialized. // Embedded structs, maps, etc. will not be serialized. func Convert(obj interface{}) (url.Values, error) { result := url.Values{} if obj == nil { return result, nil } var sv reflect.Value switch reflect.TypeOf(obj).Kind() { case reflect.Pointer, reflect.Interface: sv = reflect.ValueOf(obj).Elem() default: return nil, fmt.Errorf("expecting a pointer or interface") } st := sv.Type() if !isStructKind(st.Kind()) { return nil, fmt.Errorf("expecting a pointer to a struct") } // Check all object fields convertStruct(result, st, sv) return result, nil } func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) { for i := 0; i < st.NumField(); i++ { field := sv.Field(i) tag, omitempty := jsonTag(st.Field(i)) if len(tag) == 0 { continue } ft := field.Type() kind := ft.Kind() if isPointerKind(kind) { ft = ft.Elem() kind = ft.Kind() if !field.IsNil() { field = reflect.Indirect(field) // If the field is non-nil, it should be added to params // and the omitempty should be overwite to false omitempty = false } } switch { case isValueKind(kind): addParam(result, tag, omitempty, field) case kind == reflect.Array || kind == reflect.Slice: if isValueKind(ft.Elem().Kind()) { addListOfParams(result, tag, omitempty, field) } case isStructKind(kind) && !(zeroValue(field) && omitempty): if marshalValue, ok := customMarshalValue(field); ok { addParam(result, tag, omitempty, marshalValue) } else { convertStruct(result, ft, field) } } } } golang-k8s-apimachinery-0.29.0/pkg/conversion/queryparams/convert_test.go000066400000000000000000000143651453143165200266250ustar00rootroot00000000000000/* 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 queryparams_test import ( "net/url" "reflect" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion/queryparams" "k8s.io/apimachinery/pkg/runtime/schema" ) type namedString string type namedBool bool type bar struct { Float1 float32 `json:"float1"` Float2 float64 `json:"float2"` Int1 int64 `json:"int1,omitempty"` Int2 int32 `json:"int2,omitempty"` Int3 int16 `json:"int3,omitempty"` Str1 string `json:"str1,omitempty"` Ignored int Ignored2 string } func (obj *bar) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } type foo struct { Str string `json:"str"` Integer int `json:"integer,omitempty"` Slice []string `json:"slice,omitempty"` Boolean bool `json:"boolean,omitempty"` NamedStr namedString `json:"namedStr,omitempty"` NamedBool namedBool `json:"namedBool,omitempty"` Foobar bar `json:"foobar,omitempty"` Testmap map[string]string `json:"testmap,omitempty"` } func (obj *foo) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } type baz struct { Ptr *int `json:"ptr"` Bptr *bool `json:"bptr,omitempty"` } func (obj *baz) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } // childStructs tests some of the types we serialize to query params for log API calls // notably, the nested time struct type childStructs struct { Container string `json:"container,omitempty"` Follow bool `json:"follow,omitempty"` Previous bool `json:"previous,omitempty"` SinceSeconds *int64 `json:"sinceSeconds,omitempty"` TailLines *int64 `json:"tailLines,omitempty"` SinceTime *metav1.Time `json:"sinceTime,omitempty"` EmptyTime *metav1.Time `json:"emptyTime"` NonPointerTime metav1.Time `json:"nonPointerTime"` } func (obj *childStructs) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func validateResult(t *testing.T, input interface{}, actual, expected url.Values) { local := url.Values{} for k, v := range expected { local[k] = v } for k, v := range actual { if ev, ok := local[k]; !ok || !reflect.DeepEqual(ev, v) { if !ok { t.Errorf("%#v: actual value key %s not found in expected map", input, k) } else { t.Errorf("%#v: values don't match: actual: %#v, expected: %#v", input, v, ev) } } delete(local, k) } if len(local) > 0 { t.Errorf("%#v: expected map has keys that were not found in actual map: %#v", input, local) } } func TestConvert(t *testing.T) { sinceSeconds := int64(123) tailLines := int64(0) sinceTime := metav1.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) tests := []struct { input interface{} expected url.Values }{ { input: &foo{ Str: "hello", }, expected: url.Values{"str": {"hello"}}, }, { input: &foo{ Str: "test string", Slice: []string{"one", "two", "three"}, Integer: 234, Boolean: true, }, expected: url.Values{"str": {"test string"}, "slice": {"one", "two", "three"}, "integer": {"234"}, "boolean": {"true"}}, }, { input: &foo{ Str: "named types", NamedStr: "value1", NamedBool: true, }, expected: url.Values{"str": {"named types"}, "namedStr": {"value1"}, "namedBool": {"true"}}, }, { input: &foo{ Str: "don't ignore embedded struct", Foobar: bar{ Float1: 5.0, }, }, expected: url.Values{"str": {"don't ignore embedded struct"}, "float1": {"5"}, "float2": {"0"}}, }, { // Ignore untagged fields input: &bar{ Float1: 23.5, Float2: 100.7, Int1: 1, Int2: 2, Int3: 3, Ignored: 1, Ignored2: "ignored", }, expected: url.Values{"float1": {"23.5"}, "float2": {"100.7"}, "int1": {"1"}, "int2": {"2"}, "int3": {"3"}}, }, { // include fields that are not tagged omitempty input: &foo{ NamedStr: "named str", }, expected: url.Values{"str": {""}, "namedStr": {"named str"}}, }, { input: &baz{ Ptr: intp(5), Bptr: boolp(true), }, expected: url.Values{"ptr": {"5"}, "bptr": {"true"}}, }, { input: &baz{ Bptr: boolp(true), }, expected: url.Values{"ptr": {""}, "bptr": {"true"}}, }, { input: &baz{ Ptr: intp(5), }, expected: url.Values{"ptr": {"5"}}, }, { input: &childStructs{ Container: "mycontainer", Follow: true, Previous: true, SinceSeconds: &sinceSeconds, TailLines: nil, SinceTime: &sinceTime, // test a custom marshaller EmptyTime: nil, // test a nil custom marshaller without omitempty NonPointerTime: sinceTime, }, expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "sinceTime": {"2000-01-01T12:34:56Z"}, "emptyTime": {""}, "nonPointerTime": {"2000-01-01T12:34:56Z"}}, }, { input: &childStructs{ Container: "mycontainer", Follow: true, Previous: true, SinceSeconds: &sinceSeconds, TailLines: &tailLines, SinceTime: nil, // test a nil custom marshaller with omitempty NonPointerTime: sinceTime, }, expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "tailLines": {"0"}, "emptyTime": {""}, "nonPointerTime": {"2000-01-01T12:34:56Z"}}, }, } for _, test := range tests { result, err := queryparams.Convert(test.input) if err != nil { t.Errorf("Unexpected error while converting %#v: %v", test.input, err) } validateResult(t, test.input, result, test.expected) } } func intp(n int) *int { return &n } func boolp(b bool) *bool { return &b } golang-k8s-apimachinery-0.29.0/pkg/conversion/queryparams/doc.go000066400000000000000000000013531453143165200246440ustar00rootroot00000000000000/* 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 queryparams provides conversion from versioned // runtime objects to URL query values package queryparams // import "k8s.io/apimachinery/pkg/conversion/queryparams" golang-k8s-apimachinery-0.29.0/pkg/fields/000077500000000000000000000000001453143165200202565ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/fields/doc.go000066400000000000000000000013401453143165200213500ustar00rootroot00000000000000/* Copyright 2015 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 fields implements a simple field system, parsing and matching // selectors with sets of fields. package fields // import "k8s.io/apimachinery/pkg/fields" golang-k8s-apimachinery-0.29.0/pkg/fields/fields.go000066400000000000000000000032751453143165200220620ustar00rootroot00000000000000/* Copyright 2015 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 fields import ( "sort" "strings" ) // Fields allows you to present fields independently from their storage. type Fields interface { // Has returns whether the provided field exists. Has(field string) (exists bool) // Get returns the value for the provided field. Get(field string) (value string) } // Set is a map of field:value. It implements Fields. type Set map[string]string // String returns all fields listed as a human readable string. // Conveniently, exactly the format that ParseSelector takes. func (ls Set) String() string { selector := make([]string, 0, len(ls)) for key, value := range ls { selector = append(selector, key+"="+value) } // Sort for determinism. sort.StringSlice(selector).Sort() return strings.Join(selector, ",") } // Has returns whether the provided field exists in the map. func (ls Set) Has(field string) bool { _, exists := ls[field] return exists } // Get returns the value in the map for the provided field. func (ls Set) Get(field string) string { return ls[field] } // AsSelector converts fields into a selectors. func (ls Set) AsSelector() Selector { return SelectorFromSet(ls) } golang-k8s-apimachinery-0.29.0/pkg/fields/fields_test.go000066400000000000000000000026071453143165200231170ustar00rootroot00000000000000/* Copyright 2015 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 fields import ( "testing" ) func matches(t *testing.T, ls Set, want string) { if ls.String() != want { t.Errorf("Expected '%s', but got '%s'", want, ls.String()) } } func TestSetString(t *testing.T) { matches(t, Set{"x": "y"}, "x=y") matches(t, Set{"foo": "bar"}, "foo=bar") matches(t, Set{"foo": "bar", "baz": "qup"}, "baz=qup,foo=bar") } func TestFieldHas(t *testing.T) { fieldHasTests := []struct { Ls Fields Key string Has bool }{ {Set{"x": "y"}, "x", true}, {Set{"x": ""}, "x", true}, {Set{"x": "y"}, "foo", false}, } for _, lh := range fieldHasTests { if has := lh.Ls.Has(lh.Key); has != lh.Has { t.Errorf("%#v.Has(%#v) => %v, expected %v", lh.Ls, lh.Key, has, lh.Has) } } } func TestFieldGet(t *testing.T) { ls := Set{"x": "y"} if ls.Get("x") != "y" { t.Errorf("Set.Get is broken") } } golang-k8s-apimachinery-0.29.0/pkg/fields/requirements.go000066400000000000000000000017161453143165200233350ustar00rootroot00000000000000/* Copyright 2016 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 fields import "k8s.io/apimachinery/pkg/selection" // Requirements is AND of all requirements. type Requirements []Requirement // Requirement contains a field, a value, and an operator that relates the field and value. // This is currently for reading internal selection information of field selector. type Requirement struct { Operator selection.Operator Field string Value string } golang-k8s-apimachinery-0.29.0/pkg/fields/selector.go000066400000000000000000000304701453143165200224310ustar00rootroot00000000000000/* Copyright 2015 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 fields import ( "bytes" "fmt" "sort" "strings" "k8s.io/apimachinery/pkg/selection" ) // Selector represents a field selector. type Selector interface { // Matches returns true if this selector matches the given set of fields. Matches(Fields) bool // Empty returns true if this selector does not restrict the selection space. Empty() bool // RequiresExactMatch allows a caller to introspect whether a given selector // requires a single specific field to be set, and if so returns the value it // requires. RequiresExactMatch(field string) (value string, found bool) // Transform returns a new copy of the selector after TransformFunc has been // applied to the entire selector, or an error if fn returns an error. // If for a given requirement both field and value are transformed to empty // string, the requirement is skipped. Transform(fn TransformFunc) (Selector, error) // Requirements converts this interface to Requirements to expose // more detailed selection information. Requirements() Requirements // String returns a human readable string that represents this selector. String() string // Make a deep copy of the selector. DeepCopySelector() Selector } type nothingSelector struct{} func (n nothingSelector) Matches(_ Fields) bool { return false } func (n nothingSelector) Empty() bool { return false } func (n nothingSelector) String() string { return "" } func (n nothingSelector) Requirements() Requirements { return nil } func (n nothingSelector) DeepCopySelector() Selector { return n } func (n nothingSelector) RequiresExactMatch(field string) (value string, found bool) { return "", false } func (n nothingSelector) Transform(fn TransformFunc) (Selector, error) { return n, nil } // Nothing returns a selector that matches no fields func Nothing() Selector { return nothingSelector{} } // Everything returns a selector that matches all fields. func Everything() Selector { return andTerm{} } type hasTerm struct { field, value string } func (t *hasTerm) Matches(ls Fields) bool { return ls.Get(t.field) == t.value } func (t *hasTerm) Empty() bool { return false } func (t *hasTerm) RequiresExactMatch(field string) (value string, found bool) { if t.field == field { return t.value, true } return "", false } func (t *hasTerm) Transform(fn TransformFunc) (Selector, error) { field, value, err := fn(t.field, t.value) if err != nil { return nil, err } if len(field) == 0 && len(value) == 0 { return Everything(), nil } return &hasTerm{field, value}, nil } func (t *hasTerm) Requirements() Requirements { return []Requirement{{ Field: t.field, Operator: selection.Equals, Value: t.value, }} } func (t *hasTerm) String() string { return fmt.Sprintf("%v=%v", t.field, EscapeValue(t.value)) } func (t *hasTerm) DeepCopySelector() Selector { if t == nil { return nil } out := new(hasTerm) *out = *t return out } type notHasTerm struct { field, value string } func (t *notHasTerm) Matches(ls Fields) bool { return ls.Get(t.field) != t.value } func (t *notHasTerm) Empty() bool { return false } func (t *notHasTerm) RequiresExactMatch(field string) (value string, found bool) { return "", false } func (t *notHasTerm) Transform(fn TransformFunc) (Selector, error) { field, value, err := fn(t.field, t.value) if err != nil { return nil, err } if len(field) == 0 && len(value) == 0 { return Everything(), nil } return ¬HasTerm{field, value}, nil } func (t *notHasTerm) Requirements() Requirements { return []Requirement{{ Field: t.field, Operator: selection.NotEquals, Value: t.value, }} } func (t *notHasTerm) String() string { return fmt.Sprintf("%v!=%v", t.field, EscapeValue(t.value)) } func (t *notHasTerm) DeepCopySelector() Selector { if t == nil { return nil } out := new(notHasTerm) *out = *t return out } type andTerm []Selector func (t andTerm) Matches(ls Fields) bool { for _, q := range t { if !q.Matches(ls) { return false } } return true } func (t andTerm) Empty() bool { if t == nil { return true } if len([]Selector(t)) == 0 { return true } for i := range t { if !t[i].Empty() { return false } } return true } func (t andTerm) RequiresExactMatch(field string) (string, bool) { if t == nil || len([]Selector(t)) == 0 { return "", false } for i := range t { if value, found := t[i].RequiresExactMatch(field); found { return value, found } } return "", false } func (t andTerm) Transform(fn TransformFunc) (Selector, error) { next := make([]Selector, 0, len([]Selector(t))) for _, s := range []Selector(t) { n, err := s.Transform(fn) if err != nil { return nil, err } if !n.Empty() { next = append(next, n) } } return andTerm(next), nil } func (t andTerm) Requirements() Requirements { reqs := make([]Requirement, 0, len(t)) for _, s := range []Selector(t) { rs := s.Requirements() reqs = append(reqs, rs...) } return reqs } func (t andTerm) String() string { var terms []string for _, q := range t { terms = append(terms, q.String()) } return strings.Join(terms, ",") } func (t andTerm) DeepCopySelector() Selector { if t == nil { return nil } out := make([]Selector, len(t)) for i := range t { out[i] = t[i].DeepCopySelector() } return andTerm(out) } // SelectorFromSet returns a Selector which will match exactly the given Set. A // nil Set is considered equivalent to Everything(). func SelectorFromSet(ls Set) Selector { if ls == nil { return Everything() } items := make([]Selector, 0, len(ls)) for field, value := range ls { items = append(items, &hasTerm{field: field, value: value}) } if len(items) == 1 { return items[0] } return andTerm(items) } // valueEscaper prefixes \,= characters with a backslash var valueEscaper = strings.NewReplacer( // escape \ characters `\`, `\\`, // then escape , and = characters to allow unambiguous parsing of the value in a fieldSelector `,`, `\,`, `=`, `\=`, ) // EscapeValue escapes an arbitrary literal string for use as a fieldSelector value func EscapeValue(s string) string { return valueEscaper.Replace(s) } // InvalidEscapeSequence indicates an error occurred unescaping a field selector type InvalidEscapeSequence struct { sequence string } func (i InvalidEscapeSequence) Error() string { return fmt.Sprintf("invalid field selector: invalid escape sequence: %s", i.sequence) } // UnescapedRune indicates an error occurred unescaping a field selector type UnescapedRune struct { r rune } func (i UnescapedRune) Error() string { return fmt.Sprintf("invalid field selector: unescaped character in value: %v", i.r) } // UnescapeValue unescapes a fieldSelector value and returns the original literal value. // May return the original string if it contains no escaped or special characters. func UnescapeValue(s string) (string, error) { // if there's no escaping or special characters, just return to avoid allocation if !strings.ContainsAny(s, `\,=`) { return s, nil } v := bytes.NewBuffer(make([]byte, 0, len(s))) inSlash := false for _, c := range s { if inSlash { switch c { case '\\', ',', '=': // omit the \ for recognized escape sequences v.WriteRune(c) default: // error on unrecognized escape sequences return "", InvalidEscapeSequence{sequence: string([]rune{'\\', c})} } inSlash = false continue } switch c { case '\\': inSlash = true case ',', '=': // unescaped , and = characters are not allowed in field selector values return "", UnescapedRune{r: c} default: v.WriteRune(c) } } // Ending with a single backslash is an invalid sequence if inSlash { return "", InvalidEscapeSequence{sequence: "\\"} } return v.String(), nil } // ParseSelectorOrDie takes a string representing a selector and returns an // object suitable for matching, or panic when an error occur. func ParseSelectorOrDie(s string) Selector { selector, err := ParseSelector(s) if err != nil { panic(err) } return selector } // ParseSelector takes a string representing a selector and returns an // object suitable for matching, or an error. func ParseSelector(selector string) (Selector, error) { return parseSelector(selector, func(lhs, rhs string) (newLhs, newRhs string, err error) { return lhs, rhs, nil }) } // ParseAndTransformSelector parses the selector and runs them through the given TransformFunc. func ParseAndTransformSelector(selector string, fn TransformFunc) (Selector, error) { return parseSelector(selector, fn) } // TransformFunc transforms selectors. type TransformFunc func(field, value string) (newField, newValue string, err error) // splitTerms returns the comma-separated terms contained in the given fieldSelector. // Backslash-escaped commas are treated as data instead of delimiters, and are included in the returned terms, with the leading backslash preserved. func splitTerms(fieldSelector string) []string { if len(fieldSelector) == 0 { return nil } terms := make([]string, 0, 1) startIndex := 0 inSlash := false for i, c := range fieldSelector { switch { case inSlash: inSlash = false case c == '\\': inSlash = true case c == ',': terms = append(terms, fieldSelector[startIndex:i]) startIndex = i + 1 } } terms = append(terms, fieldSelector[startIndex:]) return terms } const ( notEqualOperator = "!=" doubleEqualOperator = "==" equalOperator = "=" ) // termOperators holds the recognized operators supported in fieldSelectors. // doubleEqualOperator and equal are equivalent, but doubleEqualOperator is checked first // to avoid leaving a leading = character on the rhs value. var termOperators = []string{notEqualOperator, doubleEqualOperator, equalOperator} // splitTerm returns the lhs, operator, and rhs parsed from the given term, along with an indicator of whether the parse was successful. // no escaping of special characters is supported in the lhs value, so the first occurrence of a recognized operator is used as the split point. // the literal rhs is returned, and the caller is responsible for applying any desired unescaping. func splitTerm(term string) (lhs, op, rhs string, ok bool) { for i := range term { remaining := term[i:] for _, op := range termOperators { if strings.HasPrefix(remaining, op) { return term[0:i], op, term[i+len(op):], true } } } return "", "", "", false } func parseSelector(selector string, fn TransformFunc) (Selector, error) { parts := splitTerms(selector) sort.StringSlice(parts).Sort() var items []Selector for _, part := range parts { if part == "" { continue } lhs, op, rhs, ok := splitTerm(part) if !ok { return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part) } unescapedRHS, err := UnescapeValue(rhs) if err != nil { return nil, err } switch op { case notEqualOperator: items = append(items, ¬HasTerm{field: lhs, value: unescapedRHS}) case doubleEqualOperator: items = append(items, &hasTerm{field: lhs, value: unescapedRHS}) case equalOperator: items = append(items, &hasTerm{field: lhs, value: unescapedRHS}) default: return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part) } } if len(items) == 1 { return items[0].Transform(fn) } return andTerm(items).Transform(fn) } // OneTermEqualSelector returns an object that matches objects where one field/field equals one value. // Cannot return an error. func OneTermEqualSelector(k, v string) Selector { return &hasTerm{field: k, value: v} } // OneTermNotEqualSelector returns an object that matches objects where one field/field does not equal one value. // Cannot return an error. func OneTermNotEqualSelector(k, v string) Selector { return ¬HasTerm{field: k, value: v} } // AndSelectors creates a selector that is the logical AND of all the given selectors func AndSelectors(selectors ...Selector) Selector { return andTerm(selectors) } golang-k8s-apimachinery-0.29.0/pkg/fields/selector_test.go000066400000000000000000000262711453143165200234740ustar00rootroot00000000000000/* Copyright 2015 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 fields import ( "reflect" "testing" ) func TestSplitTerms(t *testing.T) { testcases := map[string][]string{ // Simple selectors `a`: {`a`}, `a=avalue`: {`a=avalue`}, `a=avalue,b=bvalue`: {`a=avalue`, `b=bvalue`}, `a=avalue,b==bvalue,c!=cvalue`: {`a=avalue`, `b==bvalue`, `c!=cvalue`}, // Empty terms ``: nil, `a=a,`: {`a=a`, ``}, `,a=a`: {``, `a=a`}, // Escaped values `k=\,,k2=v2`: {`k=\,`, `k2=v2`}, // escaped comma in value `k=\\,k2=v2`: {`k=\\`, `k2=v2`}, // escaped backslash, unescaped comma `k=\\\,,k2=v2`: {`k=\\\,`, `k2=v2`}, // escaped backslash and comma `k=\a\b\`: {`k=\a\b\`}, // non-escape sequences `k=\`: {`k=\`}, // orphan backslash // Multi-byte `함=수,목=록`: {`함=수`, `목=록`}, } for selector, expectedTerms := range testcases { if terms := splitTerms(selector); !reflect.DeepEqual(terms, expectedTerms) { t.Errorf("splitSelectors(`%s`): Expected\n%#v\ngot\n%#v", selector, expectedTerms, terms) } } } func TestSplitTerm(t *testing.T) { testcases := map[string]struct { lhs string op string rhs string ok bool }{ // Simple terms `a=value`: {lhs: `a`, op: `=`, rhs: `value`, ok: true}, `b==value`: {lhs: `b`, op: `==`, rhs: `value`, ok: true}, `c!=value`: {lhs: `c`, op: `!=`, rhs: `value`, ok: true}, // Empty or invalid terms ``: {lhs: ``, op: ``, rhs: ``, ok: false}, `a`: {lhs: ``, op: ``, rhs: ``, ok: false}, // Escaped values `k=\,`: {lhs: `k`, op: `=`, rhs: `\,`, ok: true}, `k=\=`: {lhs: `k`, op: `=`, rhs: `\=`, ok: true}, `k=\\\a\b\=\,\`: {lhs: `k`, op: `=`, rhs: `\\\a\b\=\,\`, ok: true}, // Multi-byte `함=수`: {lhs: `함`, op: `=`, rhs: `수`, ok: true}, } for term, expected := range testcases { lhs, op, rhs, ok := splitTerm(term) if lhs != expected.lhs || op != expected.op || rhs != expected.rhs || ok != expected.ok { t.Errorf( "splitTerm(`%s`): Expected\n%s,%s,%s,%v\nGot\n%s,%s,%s,%v", term, expected.lhs, expected.op, expected.rhs, expected.ok, lhs, op, rhs, ok, ) } } } func TestEscapeValue(t *testing.T) { // map values to their normalized escaped values testcases := map[string]string{ ``: ``, `a`: `a`, `=`: `\=`, `,`: `\,`, `\`: `\\`, `\=\,\`: `\\\=\\\,\\`, } for unescapedValue, escapedValue := range testcases { actualEscaped := EscapeValue(unescapedValue) if actualEscaped != escapedValue { t.Errorf("EscapeValue(%s): expected %s, got %s", unescapedValue, escapedValue, actualEscaped) } actualUnescaped, err := UnescapeValue(escapedValue) if err != nil { t.Errorf("UnescapeValue(%s): unexpected error %v", escapedValue, err) } if actualUnescaped != unescapedValue { t.Errorf("UnescapeValue(%s): expected %s, got %s", escapedValue, unescapedValue, actualUnescaped) } } // test invalid escape sequences invalidTestcases := []string{ `\`, // orphan slash is invalid `\\\`, // orphan slash is invalid `\a`, // unrecognized escape sequence is invalid } for _, invalidValue := range invalidTestcases { _, err := UnescapeValue(invalidValue) if _, ok := err.(InvalidEscapeSequence); !ok || err == nil { t.Errorf("UnescapeValue(%s): expected invalid escape sequence error, got %#v", invalidValue, err) } } } func TestSelectorParse(t *testing.T) { testGoodStrings := []string{ "x=a,y=b,z=c", "", "x!=a,y=b", `x=a||y\=b`, `x=a\=\=b`, } testBadStrings := []string{ "x=a||y=b", "x==a==b", "x=a,b", "x in (a)", "x in (a,b,c)", "x", } for _, test := range testGoodStrings { lq, err := ParseSelector(test) if err != nil { t.Errorf("%v: error %v (%#v)\n", test, err, err) } if test != lq.String() { t.Errorf("%v restring gave: %v\n", test, lq.String()) } } for _, test := range testBadStrings { _, err := ParseSelector(test) if err == nil { t.Errorf("%v: did not get expected error\n", test) } } } func TestDeterministicParse(t *testing.T) { s1, err := ParseSelector("x=a,a=x") s2, err2 := ParseSelector("a=x,x=a") if err != nil || err2 != nil { t.Errorf("Unexpected parse error") } if s1.String() != s2.String() { t.Errorf("Non-deterministic parse") } } func expectMatch(t *testing.T, selector string, ls Set) { lq, err := ParseSelector(selector) if err != nil { t.Errorf("Unable to parse %v as a selector\n", selector) return } if !lq.Matches(ls) { t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) } } func expectNoMatch(t *testing.T, selector string, ls Set) { lq, err := ParseSelector(selector) if err != nil { t.Errorf("Unable to parse %v as a selector\n", selector) return } if lq.Matches(ls) { t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) } } func TestEverything(t *testing.T) { if !Everything().Matches(Set{"x": "y"}) { t.Errorf("Nil selector didn't match") } if !Everything().Empty() { t.Errorf("Everything was not empty") } } func TestSelectorMatches(t *testing.T) { expectMatch(t, "", Set{"x": "y"}) expectMatch(t, "x=y", Set{"x": "y"}) expectMatch(t, "x=y,z=w", Set{"x": "y", "z": "w"}) expectMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "a"}) expectMatch(t, "notin=in", Set{"notin": "in"}) // in and notin in exactMatch expectNoMatch(t, "x=y", Set{"x": "z"}) expectNoMatch(t, "x=y,z=w", Set{"x": "w", "z": "w"}) expectNoMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "w"}) fieldset := Set{ "foo": "bar", "baz": "blah", "complex": `=value\,\`, } expectMatch(t, "foo=bar", fieldset) expectMatch(t, "baz=blah", fieldset) expectMatch(t, "foo=bar,baz=blah", fieldset) expectMatch(t, `foo=bar,baz=blah,complex=\=value\\\,\\`, fieldset) expectNoMatch(t, "foo=blah", fieldset) expectNoMatch(t, "baz=bar", fieldset) expectNoMatch(t, "foo=bar,foobar=bar,baz=blah", fieldset) } func TestOneTermEqualSelector(t *testing.T) { if !OneTermEqualSelector("x", "y").Matches(Set{"x": "y"}) { t.Errorf("No match when match expected.") } if OneTermEqualSelector("x", "y").Matches(Set{"x": "z"}) { t.Errorf("Match when none expected.") } } func expectMatchDirect(t *testing.T, selector, ls Set) { if !SelectorFromSet(selector).Matches(ls) { t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) } } func expectNoMatchDirect(t *testing.T, selector, ls Set) { if SelectorFromSet(selector).Matches(ls) { t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) } } func TestSetMatches(t *testing.T) { labelset := Set{ "foo": "bar", "baz": "blah", } expectMatchDirect(t, Set{}, labelset) expectMatchDirect(t, Set{"foo": "bar"}, labelset) expectMatchDirect(t, Set{"baz": "blah"}, labelset) expectMatchDirect(t, Set{"foo": "bar", "baz": "blah"}, labelset) expectNoMatchDirect(t, Set{"foo": "=blah"}, labelset) expectNoMatchDirect(t, Set{"baz": "=bar"}, labelset) expectNoMatchDirect(t, Set{"foo": "=bar", "foobar": "bar", "baz": "blah"}, labelset) } func TestNilMapIsValid(t *testing.T) { selector := Set(nil).AsSelector() if selector == nil { t.Errorf("Selector for nil set should be Everything") } if !selector.Empty() { t.Errorf("Selector for nil set should be Empty") } } func TestSetIsEmpty(t *testing.T) { if !(Set{}).AsSelector().Empty() { t.Errorf("Empty set should be empty") } if !(andTerm(nil)).Empty() { t.Errorf("Nil andTerm should be empty") } if (&hasTerm{}).Empty() { t.Errorf("hasTerm should not be empty") } if (¬HasTerm{}).Empty() { t.Errorf("notHasTerm should not be empty") } if !(andTerm{andTerm{}}).Empty() { t.Errorf("Nested andTerm should be empty") } if (andTerm{&hasTerm{"a", "b"}}).Empty() { t.Errorf("Nested andTerm should not be empty") } } func TestRequiresExactMatch(t *testing.T) { testCases := map[string]struct { S Selector Label string Value string Found bool }{ "empty set": {Set{}.AsSelector(), "test", "", false}, "empty hasTerm": {&hasTerm{}, "test", "", false}, "skipped hasTerm": {&hasTerm{"a", "b"}, "test", "", false}, "valid hasTerm": {&hasTerm{"test", "b"}, "test", "b", true}, "valid hasTerm no value": {&hasTerm{"test", ""}, "test", "", true}, "valid notHasTerm": {¬HasTerm{"test", "b"}, "test", "", false}, "valid notHasTerm no value": {¬HasTerm{"test", ""}, "test", "", false}, "nil andTerm": {andTerm(nil), "test", "", false}, "empty andTerm": {andTerm{}, "test", "", false}, "nested andTerm": {andTerm{andTerm{}}, "test", "", false}, "nested andTerm matches": {andTerm{&hasTerm{"test", "b"}}, "test", "b", true}, "andTerm with non-match": {andTerm{&hasTerm{}, &hasTerm{"test", "b"}}, "test", "b", true}, } for k, v := range testCases { value, found := v.S.RequiresExactMatch(v.Label) if value != v.Value { t.Errorf("%s: expected value %s, got %s", k, v.Value, value) } if found != v.Found { t.Errorf("%s: expected found %t, got %t", k, v.Found, found) } } } func TestTransform(t *testing.T) { testCases := []struct { name string selector string transform func(field, value string) (string, string, error) result string isEmpty bool }{ { name: "empty selector", selector: "", transform: func(field, value string) (string, string, error) { return field, value, nil }, result: "", isEmpty: true, }, { name: "no-op transform", selector: "a=b,c=d", transform: func(field, value string) (string, string, error) { return field, value, nil }, result: "a=b,c=d", isEmpty: false, }, { name: "transform one field", selector: "a=b,c=d", transform: func(field, value string) (string, string, error) { if field == "a" { return "e", "f", nil } return field, value, nil }, result: "e=f,c=d", isEmpty: false, }, { name: "remove field to make empty", selector: "a=b", transform: func(field, value string) (string, string, error) { return "", "", nil }, result: "", isEmpty: true, }, { name: "remove only one field", selector: "a=b,c=d,e=f", transform: func(field, value string) (string, string, error) { if field == "c" { return "", "", nil } return field, value, nil }, result: "a=b,e=f", isEmpty: false, }, } for i, tc := range testCases { result, err := ParseAndTransformSelector(tc.selector, tc.transform) if err != nil { t.Errorf("[%d] unexpected error during Transform: %v", i, err) } if result.Empty() != tc.isEmpty { t.Errorf("[%d] expected empty: %t, got: %t", i, tc.isEmpty, result.Empty()) } if result.String() != tc.result { t.Errorf("[%d] unexpected result: %s", i, result.String()) } } } golang-k8s-apimachinery-0.29.0/pkg/labels/000077500000000000000000000000001453143165200202525ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/labels/doc.go000066400000000000000000000013401453143165200213440ustar00rootroot00000000000000/* 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 labels implements a simple label system, parsing and matching // selectors with sets of labels. package labels // import "k8s.io/apimachinery/pkg/labels" golang-k8s-apimachinery-0.29.0/pkg/labels/labels.go000066400000000000000000000110111453143165200220350ustar00rootroot00000000000000/* 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 labels import ( "fmt" "sort" "strings" "k8s.io/apimachinery/pkg/util/validation/field" ) // Labels allows you to present labels independently from their storage. type Labels interface { // Has returns whether the provided label exists. Has(label string) (exists bool) // Get returns the value for the provided label. Get(label string) (value string) } // Set is a map of label:value. It implements Labels. type Set map[string]string // String returns all labels listed as a human readable string. // Conveniently, exactly the format that ParseSelector takes. func (ls Set) String() string { selector := make([]string, 0, len(ls)) for key, value := range ls { selector = append(selector, key+"="+value) } // Sort for determinism. sort.StringSlice(selector).Sort() return strings.Join(selector, ",") } // Has returns whether the provided label exists in the map. func (ls Set) Has(label string) bool { _, exists := ls[label] return exists } // Get returns the value in the map for the provided label. func (ls Set) Get(label string) string { return ls[label] } // AsSelector converts labels into a selectors. It does not // perform any validation, which means the server will reject // the request if the Set contains invalid values. func (ls Set) AsSelector() Selector { return SelectorFromSet(ls) } // AsValidatedSelector converts labels into a selectors. // The Set is validated client-side, which allows to catch errors early. func (ls Set) AsValidatedSelector() (Selector, error) { return ValidatedSelectorFromSet(ls) } // AsSelectorPreValidated converts labels into a selector, but // assumes that labels are already validated and thus doesn't // perform any validation. // According to our measurements this is significantly faster // in codepaths that matter at high scale. // Note: this method copies the Set; if the Set is immutable, consider wrapping it with ValidatedSetSelector // instead, which does not copy. func (ls Set) AsSelectorPreValidated() Selector { return SelectorFromValidatedSet(ls) } // FormatLabels converts label map into plain string func FormatLabels(labelMap map[string]string) string { l := Set(labelMap).String() if l == "" { l = "" } return l } // Conflicts takes 2 maps and returns true if there a key match between // the maps but the value doesn't match, and returns false in other cases func Conflicts(labels1, labels2 Set) bool { small := labels1 big := labels2 if len(labels2) < len(labels1) { small = labels2 big = labels1 } for k, v := range small { if val, match := big[k]; match { if val != v { return true } } } return false } // Merge combines given maps, and does not check for any conflicts // between the maps. In case of conflicts, second map (labels2) wins func Merge(labels1, labels2 Set) Set { mergedMap := Set{} for k, v := range labels1 { mergedMap[k] = v } for k, v := range labels2 { mergedMap[k] = v } return mergedMap } // Equals returns true if the given maps are equal func Equals(labels1, labels2 Set) bool { if len(labels1) != len(labels2) { return false } for k, v := range labels1 { value, ok := labels2[k] if !ok { return false } if value != v { return false } } return true } // ConvertSelectorToLabelsMap converts selector string to labels map // and validates keys and values func ConvertSelectorToLabelsMap(selector string, opts ...field.PathOption) (Set, error) { labelsMap := Set{} if len(selector) == 0 { return labelsMap, nil } labels := strings.Split(selector, ",") for _, label := range labels { l := strings.Split(label, "=") if len(l) != 2 { return labelsMap, fmt.Errorf("invalid selector: %s", l) } key := strings.TrimSpace(l[0]) if err := validateLabelKey(key, field.ToPath(opts...)); err != nil { return labelsMap, err } value := strings.TrimSpace(l[1]) if err := validateLabelValue(key, value, field.ToPath(opts...)); err != nil { return labelsMap, err } labelsMap[key] = value } return labelsMap, nil } golang-k8s-apimachinery-0.29.0/pkg/labels/labels_test.go000066400000000000000000000126411453143165200231060ustar00rootroot00000000000000/* 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 labels import ( "testing" ) func matches(t *testing.T, ls Set, want string) { if ls.String() != want { t.Errorf("Expected '%s', but got '%s'", want, ls.String()) } } func TestSetString(t *testing.T) { matches(t, Set{"x": "y"}, "x=y") matches(t, Set{"foo": "bar"}, "foo=bar") matches(t, Set{"foo": "bar", "baz": "qup"}, "baz=qup,foo=bar") // TODO: Make our label representation robust enough to handle labels // with ",=!" characters in their names. } func TestLabelHas(t *testing.T) { labelHasTests := []struct { Ls Labels Key string Has bool }{ {Set{"x": "y"}, "x", true}, {Set{"x": ""}, "x", true}, {Set{"x": "y"}, "foo", false}, } for _, lh := range labelHasTests { if has := lh.Ls.Has(lh.Key); has != lh.Has { t.Errorf("%#v.Has(%#v) => %v, expected %v", lh.Ls, lh.Key, has, lh.Has) } } } func TestLabelGet(t *testing.T) { ls := Set{"x": "y"} if ls.Get("x") != "y" { t.Errorf("Set.Get is broken") } } func TestLabelConflict(t *testing.T) { tests := []struct { labels1 map[string]string labels2 map[string]string conflict bool }{ { labels1: map[string]string{}, labels2: map[string]string{}, conflict: false, }, { labels1: map[string]string{"env": "test"}, labels2: map[string]string{"infra": "true"}, conflict: false, }, { labels1: map[string]string{"env": "test"}, labels2: map[string]string{"infra": "true", "env": "test"}, conflict: false, }, { labels1: map[string]string{"env": "test"}, labels2: map[string]string{"env": "dev"}, conflict: true, }, { labels1: map[string]string{"env": "test", "infra": "false"}, labels2: map[string]string{"infra": "true", "color": "blue"}, conflict: true, }, } for _, test := range tests { conflict := Conflicts(Set(test.labels1), Set(test.labels2)) if conflict != test.conflict { t.Errorf("expected: %v but got: %v", test.conflict, conflict) } } } func TestLabelMerge(t *testing.T) { tests := []struct { labels1 map[string]string labels2 map[string]string mergedLabels map[string]string }{ { labels1: map[string]string{}, labels2: map[string]string{}, mergedLabels: map[string]string{}, }, { labels1: map[string]string{"infra": "true"}, labels2: map[string]string{}, mergedLabels: map[string]string{"infra": "true"}, }, { labels1: map[string]string{"infra": "true"}, labels2: map[string]string{"env": "test", "color": "blue"}, mergedLabels: map[string]string{"infra": "true", "env": "test", "color": "blue"}, }, } for _, test := range tests { mergedLabels := Merge(Set(test.labels1), Set(test.labels2)) if !Equals(mergedLabels, test.mergedLabels) { t.Errorf("expected: %v but got: %v", test.mergedLabels, mergedLabels) } } } func TestLabelSelectorParse(t *testing.T) { tests := []struct { selector string labels map[string]string valid bool }{ { selector: "", labels: map[string]string{}, valid: true, }, { selector: "x=a", labels: map[string]string{"x": "a"}, valid: true, }, { selector: "x=a,y=b,z=c", labels: map[string]string{"x": "a", "y": "b", "z": "c"}, valid: true, }, { selector: " x = a , y = b , z = c ", labels: map[string]string{"x": "a", "y": "b", "z": "c"}, valid: true, }, { selector: "color=green,env=test,service=front", labels: map[string]string{"color": "green", "env": "test", "service": "front"}, valid: true, }, { selector: "color=green, env=test, service=front", labels: map[string]string{"color": "green", "env": "test", "service": "front"}, valid: true, }, { selector: ",", labels: map[string]string{}, valid: false, }, { selector: "x", labels: map[string]string{}, valid: false, }, { selector: "x,y", labels: map[string]string{}, valid: false, }, { selector: "x=$y", labels: map[string]string{}, valid: false, }, { selector: "x!=y", labels: map[string]string{}, valid: false, }, { selector: "x==y", labels: map[string]string{}, valid: false, }, { selector: "x=a||y=b", labels: map[string]string{}, valid: false, }, { selector: "x in (y)", labels: map[string]string{}, valid: false, }, { selector: "x notin (y)", labels: map[string]string{}, valid: false, }, { selector: "x y", labels: map[string]string{}, valid: false, }, } for _, test := range tests { labels, err := ConvertSelectorToLabelsMap(test.selector) if test.valid && err != nil { t.Errorf("selector: %s, expected no error but got: %s", test.selector, err) } else if !test.valid && err == nil { t.Errorf("selector: %s, expected an error", test.selector) } if !Equals(Set(labels), test.labels) { t.Errorf("expected: %s but got: %s", test.labels, labels) } } } golang-k8s-apimachinery-0.29.0/pkg/labels/selector.go000066400000000000000000000774021453143165200224330ustar00rootroot00000000000000/* 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 labels import ( "fmt" "sort" "strconv" "strings" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/klog/v2" stringslices "k8s.io/utils/strings/slices" ) var ( unaryOperators = []string{ string(selection.Exists), string(selection.DoesNotExist), } binaryOperators = []string{ string(selection.In), string(selection.NotIn), string(selection.Equals), string(selection.DoubleEquals), string(selection.NotEquals), string(selection.GreaterThan), string(selection.LessThan), } validRequirementOperators = append(binaryOperators, unaryOperators...) ) // Requirements is AND of all requirements. type Requirements []Requirement // Selector represents a label selector. type Selector interface { // Matches returns true if this selector matches the given set of labels. Matches(Labels) bool // Empty returns true if this selector does not restrict the selection space. Empty() bool // String returns a human readable string that represents this selector. String() string // Add adds requirements to the Selector Add(r ...Requirement) Selector // Requirements converts this interface into Requirements to expose // more detailed selection information. // If there are querying parameters, it will return converted requirements and selectable=true. // If this selector doesn't want to select anything, it will return selectable=false. Requirements() (requirements Requirements, selectable bool) // Make a deep copy of the selector. DeepCopySelector() Selector // RequiresExactMatch allows a caller to introspect whether a given selector // requires a single specific label to be set, and if so returns the value it // requires. RequiresExactMatch(label string) (value string, found bool) } // Sharing this saves 1 alloc per use; this is safe because it's immutable. var sharedEverythingSelector Selector = internalSelector{} // Everything returns a selector that matches all labels. func Everything() Selector { return sharedEverythingSelector } type nothingSelector struct{} func (n nothingSelector) Matches(_ Labels) bool { return false } func (n nothingSelector) Empty() bool { return false } func (n nothingSelector) String() string { return "" } func (n nothingSelector) Add(_ ...Requirement) Selector { return n } func (n nothingSelector) Requirements() (Requirements, bool) { return nil, false } func (n nothingSelector) DeepCopySelector() Selector { return n } func (n nothingSelector) RequiresExactMatch(label string) (value string, found bool) { return "", false } // Sharing this saves 1 alloc per use; this is safe because it's immutable. var sharedNothingSelector Selector = nothingSelector{} // Nothing returns a selector that matches no labels func Nothing() Selector { return sharedNothingSelector } // NewSelector returns a nil selector func NewSelector() Selector { return internalSelector(nil) } type internalSelector []Requirement func (s internalSelector) DeepCopy() internalSelector { if s == nil { return nil } result := make([]Requirement, len(s)) for i := range s { s[i].DeepCopyInto(&result[i]) } return result } func (s internalSelector) DeepCopySelector() Selector { return s.DeepCopy() } // ByKey sorts requirements by key to obtain deterministic parser type ByKey []Requirement func (a ByKey) Len() int { return len(a) } func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByKey) Less(i, j int) bool { return a[i].key < a[j].key } // Requirement contains values, a key, and an operator that relates the key and values. // The zero value of Requirement is invalid. // Requirement implements both set based match and exact match // Requirement should be initialized via NewRequirement constructor for creating a valid Requirement. // +k8s:deepcopy-gen=true type Requirement struct { key string operator selection.Operator // In huge majority of cases we have at most one value here. // It is generally faster to operate on a single-element slice // than on a single-element map, so we have a slice here. strValues []string } // NewRequirement is the constructor for a Requirement. // If any of these rules is violated, an error is returned: // 1. The operator can only be In, NotIn, Equals, DoubleEquals, Gt, Lt, NotEquals, Exists, or DoesNotExist. // 2. If the operator is In or NotIn, the values set must be non-empty. // 3. If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value. // 4. If the operator is Exists or DoesNotExist, the value set must be empty. // 5. If the operator is Gt or Lt, the values set must contain only one value, which will be interpreted as an integer. // 6. The key is invalid due to its length, or sequence of characters. See validateLabelKey for more details. // // The empty string is a valid value in the input values set. // Returned error, if not nil, is guaranteed to be an aggregated field.ErrorList func NewRequirement(key string, op selection.Operator, vals []string, opts ...field.PathOption) (*Requirement, error) { var allErrs field.ErrorList path := field.ToPath(opts...) if err := validateLabelKey(key, path.Child("key")); err != nil { allErrs = append(allErrs, err) } valuePath := path.Child("values") switch op { case selection.In, selection.NotIn: if len(vals) == 0 { allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'in', 'notin' operators, values set can't be empty")) } case selection.Equals, selection.DoubleEquals, selection.NotEquals: if len(vals) != 1 { allErrs = append(allErrs, field.Invalid(valuePath, vals, "exact-match compatibility requires one single value")) } case selection.Exists, selection.DoesNotExist: if len(vals) != 0 { allErrs = append(allErrs, field.Invalid(valuePath, vals, "values set must be empty for exists and does not exist")) } case selection.GreaterThan, selection.LessThan: if len(vals) != 1 { allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'Gt', 'Lt' operators, exactly one value is required")) } for i := range vals { if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil { allErrs = append(allErrs, field.Invalid(valuePath.Index(i), vals[i], "for 'Gt', 'Lt' operators, the value must be an integer")) } } default: allErrs = append(allErrs, field.NotSupported(path.Child("operator"), op, validRequirementOperators)) } for i := range vals { if err := validateLabelValue(key, vals[i], valuePath.Index(i)); err != nil { allErrs = append(allErrs, err) } } return &Requirement{key: key, operator: op, strValues: vals}, allErrs.ToAggregate() } func (r *Requirement) hasValue(value string) bool { for i := range r.strValues { if r.strValues[i] == value { return true } } return false } // Matches returns true if the Requirement matches the input Labels. // There is a match in the following cases: // 1. The operator is Exists and Labels has the Requirement's key. // 2. The operator is In, Labels has the Requirement's key and Labels' // value for that key is in Requirement's value set. // 3. The operator is NotIn, Labels has the Requirement's key and // Labels' value for that key is not in Requirement's value set. // 4. The operator is DoesNotExist or NotIn and Labels does not have the // Requirement's key. // 5. The operator is GreaterThanOperator or LessThanOperator, and Labels has // the Requirement's key and the corresponding value satisfies mathematical inequality. func (r *Requirement) Matches(ls Labels) bool { switch r.operator { case selection.In, selection.Equals, selection.DoubleEquals: if !ls.Has(r.key) { return false } return r.hasValue(ls.Get(r.key)) case selection.NotIn, selection.NotEquals: if !ls.Has(r.key) { return true } return !r.hasValue(ls.Get(r.key)) case selection.Exists: return ls.Has(r.key) case selection.DoesNotExist: return !ls.Has(r.key) case selection.GreaterThan, selection.LessThan: if !ls.Has(r.key) { return false } lsValue, err := strconv.ParseInt(ls.Get(r.key), 10, 64) if err != nil { klog.V(10).Infof("ParseInt failed for value %+v in label %+v, %+v", ls.Get(r.key), ls, err) return false } // There should be only one strValue in r.strValues, and can be converted to an integer. if len(r.strValues) != 1 { klog.V(10).Infof("Invalid values count %+v of requirement %#v, for 'Gt', 'Lt' operators, exactly one value is required", len(r.strValues), r) return false } var rValue int64 for i := range r.strValues { rValue, err = strconv.ParseInt(r.strValues[i], 10, 64) if err != nil { klog.V(10).Infof("ParseInt failed for value %+v in requirement %#v, for 'Gt', 'Lt' operators, the value must be an integer", r.strValues[i], r) return false } } return (r.operator == selection.GreaterThan && lsValue > rValue) || (r.operator == selection.LessThan && lsValue < rValue) default: return false } } // Key returns requirement key func (r *Requirement) Key() string { return r.key } // Operator returns requirement operator func (r *Requirement) Operator() selection.Operator { return r.operator } // Values returns requirement values func (r *Requirement) Values() sets.String { ret := sets.String{} for i := range r.strValues { ret.Insert(r.strValues[i]) } return ret } // Equal checks the equality of requirement. func (r Requirement) Equal(x Requirement) bool { if r.key != x.key { return false } if r.operator != x.operator { return false } return stringslices.Equal(r.strValues, x.strValues) } // Empty returns true if the internalSelector doesn't restrict selection space func (s internalSelector) Empty() bool { if s == nil { return true } return len(s) == 0 } // String returns a human-readable string that represents this // Requirement. If called on an invalid Requirement, an error is // returned. See NewRequirement for creating a valid Requirement. func (r *Requirement) String() string { var sb strings.Builder sb.Grow( // length of r.key len(r.key) + // length of 'r.operator' + 2 spaces for the worst case ('in' and 'notin') len(r.operator) + 2 + // length of 'r.strValues' slice times. Heuristically 5 chars per word +5*len(r.strValues)) if r.operator == selection.DoesNotExist { sb.WriteString("!") } sb.WriteString(r.key) switch r.operator { case selection.Equals: sb.WriteString("=") case selection.DoubleEquals: sb.WriteString("==") case selection.NotEquals: sb.WriteString("!=") case selection.In: sb.WriteString(" in ") case selection.NotIn: sb.WriteString(" notin ") case selection.GreaterThan: sb.WriteString(">") case selection.LessThan: sb.WriteString("<") case selection.Exists, selection.DoesNotExist: return sb.String() } switch r.operator { case selection.In, selection.NotIn: sb.WriteString("(") } if len(r.strValues) == 1 { sb.WriteString(r.strValues[0]) } else { // only > 1 since == 0 prohibited by NewRequirement // normalizes value order on output, without mutating the in-memory selector representation // also avoids normalization when it is not required, and ensures we do not mutate shared data sb.WriteString(strings.Join(safeSort(r.strValues), ",")) } switch r.operator { case selection.In, selection.NotIn: sb.WriteString(")") } return sb.String() } // safeSort sorts input strings without modification func safeSort(in []string) []string { if sort.StringsAreSorted(in) { return in } out := make([]string, len(in)) copy(out, in) sort.Strings(out) return out } // Add adds requirements to the selector. It copies the current selector returning a new one func (s internalSelector) Add(reqs ...Requirement) Selector { ret := make(internalSelector, 0, len(s)+len(reqs)) ret = append(ret, s...) ret = append(ret, reqs...) sort.Sort(ByKey(ret)) return ret } // Matches for a internalSelector returns true if all // its Requirements match the input Labels. If any // Requirement does not match, false is returned. func (s internalSelector) Matches(l Labels) bool { for ix := range s { if matches := s[ix].Matches(l); !matches { return false } } return true } func (s internalSelector) Requirements() (Requirements, bool) { return Requirements(s), true } // String returns a comma-separated string of all // the internalSelector Requirements' human-readable strings. func (s internalSelector) String() string { var reqs []string for ix := range s { reqs = append(reqs, s[ix].String()) } return strings.Join(reqs, ",") } // RequiresExactMatch introspects whether a given selector requires a single specific field // to be set, and if so returns the value it requires. func (s internalSelector) RequiresExactMatch(label string) (value string, found bool) { for ix := range s { if s[ix].key == label { switch s[ix].operator { case selection.Equals, selection.DoubleEquals, selection.In: if len(s[ix].strValues) == 1 { return s[ix].strValues[0], true } } return "", false } } return "", false } // Token represents constant definition for lexer token type Token int const ( // ErrorToken represents scan error ErrorToken Token = iota // EndOfStringToken represents end of string EndOfStringToken // ClosedParToken represents close parenthesis ClosedParToken // CommaToken represents the comma CommaToken // DoesNotExistToken represents logic not DoesNotExistToken // DoubleEqualsToken represents double equals DoubleEqualsToken // EqualsToken represents equal EqualsToken // GreaterThanToken represents greater than GreaterThanToken // IdentifierToken represents identifier, e.g. keys and values IdentifierToken // InToken represents in InToken // LessThanToken represents less than LessThanToken // NotEqualsToken represents not equal NotEqualsToken // NotInToken represents not in NotInToken // OpenParToken represents open parenthesis OpenParToken ) // string2token contains the mapping between lexer Token and token literal // (except IdentifierToken, EndOfStringToken and ErrorToken since it makes no sense) var string2token = map[string]Token{ ")": ClosedParToken, ",": CommaToken, "!": DoesNotExistToken, "==": DoubleEqualsToken, "=": EqualsToken, ">": GreaterThanToken, "in": InToken, "<": LessThanToken, "!=": NotEqualsToken, "notin": NotInToken, "(": OpenParToken, } // ScannedItem contains the Token and the literal produced by the lexer. type ScannedItem struct { tok Token literal string } // isWhitespace returns true if the rune is a space, tab, or newline. func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' } // isSpecialSymbol detects if the character ch can be an operator func isSpecialSymbol(ch byte) bool { switch ch { case '=', '!', '(', ')', ',', '>', '<': return true } return false } // Lexer represents the Lexer struct for label selector. // It contains necessary informationt to tokenize the input string type Lexer struct { // s stores the string to be tokenized s string // pos is the position currently tokenized pos int } // read returns the character currently lexed // increment the position and check the buffer overflow func (l *Lexer) read() (b byte) { b = 0 if l.pos < len(l.s) { b = l.s[l.pos] l.pos++ } return b } // unread 'undoes' the last read character func (l *Lexer) unread() { l.pos-- } // scanIDOrKeyword scans string to recognize literal token (for example 'in') or an identifier. func (l *Lexer) scanIDOrKeyword() (tok Token, lit string) { var buffer []byte IdentifierLoop: for { switch ch := l.read(); { case ch == 0: break IdentifierLoop case isSpecialSymbol(ch) || isWhitespace(ch): l.unread() break IdentifierLoop default: buffer = append(buffer, ch) } } s := string(buffer) if val, ok := string2token[s]; ok { // is a literal token? return val, s } return IdentifierToken, s // otherwise is an identifier } // scanSpecialSymbol scans string starting with special symbol. // special symbol identify non literal operators. "!=", "==", "=" func (l *Lexer) scanSpecialSymbol() (Token, string) { lastScannedItem := ScannedItem{} var buffer []byte SpecialSymbolLoop: for { switch ch := l.read(); { case ch == 0: break SpecialSymbolLoop case isSpecialSymbol(ch): buffer = append(buffer, ch) if token, ok := string2token[string(buffer)]; ok { lastScannedItem = ScannedItem{tok: token, literal: string(buffer)} } else if lastScannedItem.tok != 0 { l.unread() break SpecialSymbolLoop } default: l.unread() break SpecialSymbolLoop } } if lastScannedItem.tok == 0 { return ErrorToken, fmt.Sprintf("error expected: keyword found '%s'", buffer) } return lastScannedItem.tok, lastScannedItem.literal } // skipWhiteSpaces consumes all blank characters // returning the first non blank character func (l *Lexer) skipWhiteSpaces(ch byte) byte { for { if !isWhitespace(ch) { return ch } ch = l.read() } } // Lex returns a pair of Token and the literal // literal is meaningfull only for IdentifierToken token func (l *Lexer) Lex() (tok Token, lit string) { switch ch := l.skipWhiteSpaces(l.read()); { case ch == 0: return EndOfStringToken, "" case isSpecialSymbol(ch): l.unread() return l.scanSpecialSymbol() default: l.unread() return l.scanIDOrKeyword() } } // Parser data structure contains the label selector parser data structure type Parser struct { l *Lexer scannedItems []ScannedItem position int path *field.Path } // ParserContext represents context during parsing: // some literal for example 'in' and 'notin' can be // recognized as operator for example 'x in (a)' but // it can be recognized as value for example 'value in (in)' type ParserContext int const ( // KeyAndOperator represents key and operator KeyAndOperator ParserContext = iota // Values represents values Values ) // lookahead func returns the current token and string. No increment of current position func (p *Parser) lookahead(context ParserContext) (Token, string) { tok, lit := p.scannedItems[p.position].tok, p.scannedItems[p.position].literal if context == Values { switch tok { case InToken, NotInToken: tok = IdentifierToken } } return tok, lit } // consume returns current token and string. Increments the position func (p *Parser) consume(context ParserContext) (Token, string) { p.position++ tok, lit := p.scannedItems[p.position-1].tok, p.scannedItems[p.position-1].literal if context == Values { switch tok { case InToken, NotInToken: tok = IdentifierToken } } return tok, lit } // scan runs through the input string and stores the ScannedItem in an array // Parser can now lookahead and consume the tokens func (p *Parser) scan() { for { token, literal := p.l.Lex() p.scannedItems = append(p.scannedItems, ScannedItem{token, literal}) if token == EndOfStringToken { break } } } // parse runs the left recursive descending algorithm // on input string. It returns a list of Requirement objects. func (p *Parser) parse() (internalSelector, error) { p.scan() // init scannedItems var requirements internalSelector for { tok, lit := p.lookahead(Values) switch tok { case IdentifierToken, DoesNotExistToken: r, err := p.parseRequirement() if err != nil { return nil, fmt.Errorf("unable to parse requirement: %v", err) } requirements = append(requirements, *r) t, l := p.consume(Values) switch t { case EndOfStringToken: return requirements, nil case CommaToken: t2, l2 := p.lookahead(Values) if t2 != IdentifierToken && t2 != DoesNotExistToken { return nil, fmt.Errorf("found '%s', expected: identifier after ','", l2) } default: return nil, fmt.Errorf("found '%s', expected: ',' or 'end of string'", l) } case EndOfStringToken: return requirements, nil default: return nil, fmt.Errorf("found '%s', expected: !, identifier, or 'end of string'", lit) } } } func (p *Parser) parseRequirement() (*Requirement, error) { key, operator, err := p.parseKeyAndInferOperator() if err != nil { return nil, err } if operator == selection.Exists || operator == selection.DoesNotExist { // operator found lookahead set checked return NewRequirement(key, operator, []string{}, field.WithPath(p.path)) } operator, err = p.parseOperator() if err != nil { return nil, err } var values sets.String switch operator { case selection.In, selection.NotIn: values, err = p.parseValues() case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.GreaterThan, selection.LessThan: values, err = p.parseExactValue() } if err != nil { return nil, err } return NewRequirement(key, operator, values.List(), field.WithPath(p.path)) } // parseKeyAndInferOperator parses literals. // in case of no operator '!, in, notin, ==, =, !=' are found // the 'exists' operator is inferred func (p *Parser) parseKeyAndInferOperator() (string, selection.Operator, error) { var operator selection.Operator tok, literal := p.consume(Values) if tok == DoesNotExistToken { operator = selection.DoesNotExist tok, literal = p.consume(Values) } if tok != IdentifierToken { err := fmt.Errorf("found '%s', expected: identifier", literal) return "", "", err } if err := validateLabelKey(literal, p.path); err != nil { return "", "", err } if t, _ := p.lookahead(Values); t == EndOfStringToken || t == CommaToken { if operator != selection.DoesNotExist { operator = selection.Exists } } return literal, operator, nil } // parseOperator returns operator and eventually matchType // matchType can be exact func (p *Parser) parseOperator() (op selection.Operator, err error) { tok, lit := p.consume(KeyAndOperator) switch tok { // DoesNotExistToken shouldn't be here because it's a unary operator, not a binary operator case InToken: op = selection.In case EqualsToken: op = selection.Equals case DoubleEqualsToken: op = selection.DoubleEquals case GreaterThanToken: op = selection.GreaterThan case LessThanToken: op = selection.LessThan case NotInToken: op = selection.NotIn case NotEqualsToken: op = selection.NotEquals default: return "", fmt.Errorf("found '%s', expected: %v", lit, strings.Join(binaryOperators, ", ")) } return op, nil } // parseValues parses the values for set based matching (x,y,z) func (p *Parser) parseValues() (sets.String, error) { tok, lit := p.consume(Values) if tok != OpenParToken { return nil, fmt.Errorf("found '%s' expected: '('", lit) } tok, lit = p.lookahead(Values) switch tok { case IdentifierToken, CommaToken: s, err := p.parseIdentifiersList() // handles general cases if err != nil { return s, err } if tok, _ = p.consume(Values); tok != ClosedParToken { return nil, fmt.Errorf("found '%s', expected: ')'", lit) } return s, nil case ClosedParToken: // handles "()" p.consume(Values) return sets.NewString(""), nil default: return nil, fmt.Errorf("found '%s', expected: ',', ')' or identifier", lit) } } // parseIdentifiersList parses a (possibly empty) list of // of comma separated (possibly empty) identifiers func (p *Parser) parseIdentifiersList() (sets.String, error) { s := sets.NewString() for { tok, lit := p.consume(Values) switch tok { case IdentifierToken: s.Insert(lit) tok2, lit2 := p.lookahead(Values) switch tok2 { case CommaToken: continue case ClosedParToken: return s, nil default: return nil, fmt.Errorf("found '%s', expected: ',' or ')'", lit2) } case CommaToken: // handled here since we can have "(," if s.Len() == 0 { s.Insert("") // to handle (, } tok2, _ := p.lookahead(Values) if tok2 == ClosedParToken { s.Insert("") // to handle ,) Double "" removed by StringSet return s, nil } if tok2 == CommaToken { p.consume(Values) s.Insert("") // to handle ,, Double "" removed by StringSet } default: // it can be operator return s, fmt.Errorf("found '%s', expected: ',', or identifier", lit) } } } // parseExactValue parses the only value for exact match style func (p *Parser) parseExactValue() (sets.String, error) { s := sets.NewString() tok, _ := p.lookahead(Values) if tok == EndOfStringToken || tok == CommaToken { s.Insert("") return s, nil } tok, lit := p.consume(Values) if tok == IdentifierToken { s.Insert(lit) return s, nil } return nil, fmt.Errorf("found '%s', expected: identifier", lit) } // Parse takes a string representing a selector and returns a selector // object, or an error. This parsing function differs from ParseSelector // as they parse different selectors with different syntaxes. // The input will cause an error if it does not follow this form: // // ::= | "," // ::= [!] KEY [ | ] // ::= "" | // ::= | // ::= "notin" // ::= "in" // ::= "(" ")" // ::= VALUE | VALUE "," // ::= ["="|"=="|"!="] VALUE // // KEY is a sequence of one or more characters following [ DNS_SUBDOMAIN "/" ] DNS_LABEL. Max length is 63 characters. // VALUE is a sequence of zero or more characters "([A-Za-z0-9_-\.])". Max length is 63 characters. // Delimiter is white space: (' ', '\t') // Example of valid syntax: // // "x in (foo,,baz),y,z notin ()" // // Note: // 1. Inclusion - " in " - denotes that the KEY exists and is equal to any of the // VALUEs in its requirement // 2. Exclusion - " notin " - denotes that the KEY is not equal to any // of the VALUEs in its requirement or does not exist // 3. The empty string is a valid VALUE // 4. A requirement with just a KEY - as in "y" above - denotes that // the KEY exists and can be any VALUE. // 5. A requirement with just !KEY requires that the KEY not exist. func Parse(selector string, opts ...field.PathOption) (Selector, error) { parsedSelector, err := parse(selector, field.ToPath(opts...)) if err == nil { return parsedSelector, nil } return nil, err } // parse parses the string representation of the selector and returns the internalSelector struct. // The callers of this method can then decide how to return the internalSelector struct to their // callers. This function has two callers now, one returns a Selector interface and the other // returns a list of requirements. func parse(selector string, path *field.Path) (internalSelector, error) { p := &Parser{l: &Lexer{s: selector, pos: 0}, path: path} items, err := p.parse() if err != nil { return nil, err } sort.Sort(ByKey(items)) // sort to grant determistic parsing return internalSelector(items), err } func validateLabelKey(k string, path *field.Path) *field.Error { if errs := validation.IsQualifiedName(k); len(errs) != 0 { return field.Invalid(path, k, strings.Join(errs, "; ")) } return nil } func validateLabelValue(k, v string, path *field.Path) *field.Error { if errs := validation.IsValidLabelValue(v); len(errs) != 0 { return field.Invalid(path.Key(k), v, strings.Join(errs, "; ")) } return nil } // SelectorFromSet returns a Selector which will match exactly the given Set. A // nil and empty Sets are considered equivalent to Everything(). // It does not perform any validation, which means the server will reject // the request if the Set contains invalid values. func SelectorFromSet(ls Set) Selector { return SelectorFromValidatedSet(ls) } // ValidatedSelectorFromSet returns a Selector which will match exactly the given Set. A // nil and empty Sets are considered equivalent to Everything(). // The Set is validated client-side, which allows to catch errors early. func ValidatedSelectorFromSet(ls Set) (Selector, error) { if ls == nil || len(ls) == 0 { return internalSelector{}, nil } requirements := make([]Requirement, 0, len(ls)) for label, value := range ls { r, err := NewRequirement(label, selection.Equals, []string{value}) if err != nil { return nil, err } requirements = append(requirements, *r) } // sort to have deterministic string representation sort.Sort(ByKey(requirements)) return internalSelector(requirements), nil } // SelectorFromValidatedSet returns a Selector which will match exactly the given Set. // A nil and empty Sets are considered equivalent to Everything(). // It assumes that Set is already validated and doesn't do any validation. // Note: this method copies the Set; if the Set is immutable, consider wrapping it with ValidatedSetSelector // instead, which does not copy. func SelectorFromValidatedSet(ls Set) Selector { if ls == nil || len(ls) == 0 { return internalSelector{} } requirements := make([]Requirement, 0, len(ls)) for label, value := range ls { requirements = append(requirements, Requirement{key: label, operator: selection.Equals, strValues: []string{value}}) } // sort to have deterministic string representation sort.Sort(ByKey(requirements)) return internalSelector(requirements) } // ParseToRequirements takes a string representing a selector and returns a list of // requirements. This function is suitable for those callers that perform additional // processing on selector requirements. // See the documentation for Parse() function for more details. // TODO: Consider exporting the internalSelector type instead. func ParseToRequirements(selector string, opts ...field.PathOption) ([]Requirement, error) { return parse(selector, field.ToPath(opts...)) } // ValidatedSetSelector wraps a Set, allowing it to implement the Selector interface. Unlike // Set.AsSelectorPreValidated (which copies the input Set), this type simply wraps the underlying // Set. As a result, it is substantially more efficient. A nil and empty Sets are considered // equivalent to Everything(). // // Callers MUST ensure the underlying Set is not mutated, and that it is already validated. If these // constraints are not met, Set.AsValidatedSelector should be preferred // // None of the Selector methods mutate the underlying Set, but Add() and Requirements() convert to // the less optimized version. type ValidatedSetSelector Set func (s ValidatedSetSelector) Matches(labels Labels) bool { for k, v := range s { if !labels.Has(k) || v != labels.Get(k) { return false } } return true } func (s ValidatedSetSelector) Empty() bool { return len(s) == 0 } func (s ValidatedSetSelector) String() string { keys := make([]string, 0, len(s)) for k := range s { keys = append(keys, k) } // Ensure deterministic output sort.Strings(keys) b := strings.Builder{} for i, key := range keys { v := s[key] b.Grow(len(key) + 2 + len(v)) if i != 0 { b.WriteString(",") } b.WriteString(key) b.WriteString("=") b.WriteString(v) } return b.String() } func (s ValidatedSetSelector) Add(r ...Requirement) Selector { return s.toFullSelector().Add(r...) } func (s ValidatedSetSelector) Requirements() (requirements Requirements, selectable bool) { return s.toFullSelector().Requirements() } func (s ValidatedSetSelector) DeepCopySelector() Selector { res := make(ValidatedSetSelector, len(s)) for k, v := range s { res[k] = v } return res } func (s ValidatedSetSelector) RequiresExactMatch(label string) (value string, found bool) { v, f := s[label] return v, f } func (s ValidatedSetSelector) toFullSelector() Selector { return SelectorFromValidatedSet(Set(s)) } var _ Selector = ValidatedSetSelector{} golang-k8s-apimachinery-0.29.0/pkg/labels/selector_test.go000066400000000000000000000736361453143165200234770ustar00rootroot00000000000000/* 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 labels import ( "fmt" "reflect" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" ) var ( ignoreDetail = cmpopts.IgnoreFields(field.Error{}, "Detail") ) func TestSelectorParse(t *testing.T) { testGoodStrings := []string{ "x=a,y=b,z=c", "", "x!=a,y=b", "x=", "x= ", "x=,z= ", "x= ,z= ", "!x", "x>1", "x>1,z<5", } testBadStrings := []string{ "x=a||y=b", "x==a==b", "!x=a", "x1", Set{"x": "2"}) expectMatch(t, "x<1", Set{"x": "0"}) expectNoMatch(t, "x=z", Set{}) expectNoMatch(t, "x=y", Set{"x": "z"}) expectNoMatch(t, "x=y,z=w", Set{"x": "w", "z": "w"}) expectNoMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "w"}) expectNoMatch(t, "x", Set{"y": "z"}) expectNoMatch(t, "!x", Set{"x": "z"}) expectNoMatch(t, "x>1", Set{"x": "0"}) expectNoMatch(t, "x<1", Set{"x": "2"}) labelset := Set{ "foo": "bar", "baz": "blah", } expectMatch(t, "foo=bar", labelset) expectMatch(t, "baz=blah", labelset) expectMatch(t, "foo=bar,baz=blah", labelset) expectNoMatch(t, "foo=blah", labelset) expectNoMatch(t, "baz=bar", labelset) expectNoMatch(t, "foo=bar,foobar=bar,baz=blah", labelset) } func expectMatchDirect(t *testing.T, selector, ls Set) { if !SelectorFromSet(selector).Matches(ls) { t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) } } //nolint:staticcheck,unused //iccheck // U1000 currently commented out in TODO of TestSetMatches func expectNoMatchDirect(t *testing.T, selector, ls Set) { if SelectorFromSet(selector).Matches(ls) { t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) } } func TestSetMatches(t *testing.T) { labelset := Set{ "foo": "bar", "baz": "blah", } expectMatchDirect(t, Set{}, labelset) expectMatchDirect(t, Set{"foo": "bar"}, labelset) expectMatchDirect(t, Set{"baz": "blah"}, labelset) expectMatchDirect(t, Set{"foo": "bar", "baz": "blah"}, labelset) //TODO: bad values not handled for the moment in SelectorFromSet //expectNoMatchDirect(t, Set{"foo": "=blah"}, labelset) //expectNoMatchDirect(t, Set{"baz": "=bar"}, labelset) //expectNoMatchDirect(t, Set{"foo": "=bar", "foobar": "bar", "baz": "blah"}, labelset) } func TestNilMapIsValid(t *testing.T) { selector := Set(nil).AsSelector() if selector == nil { t.Errorf("Selector for nil set should be Everything") } if !selector.Empty() { t.Errorf("Selector for nil set should be Empty") } } func TestSetIsEmpty(t *testing.T) { if !(Set{}).AsSelector().Empty() { t.Errorf("Empty set should be empty") } if !(NewSelector()).Empty() { t.Errorf("Nil Selector should be empty") } } func TestLexer(t *testing.T) { testcases := []struct { s string t Token }{ {"", EndOfStringToken}, {",", CommaToken}, {"notin", NotInToken}, {"in", InToken}, {"=", EqualsToken}, {"==", DoubleEqualsToken}, {">", GreaterThanToken}, {"<", LessThanToken}, //Note that Lex returns the longest valid token found {"!", DoesNotExistToken}, {"!=", NotEqualsToken}, {"(", OpenParToken}, {")", ClosedParToken}, //Non-"special" characters are considered part of an identifier {"~", IdentifierToken}, {"||", IdentifierToken}, } for _, v := range testcases { l := &Lexer{s: v.s, pos: 0} token, lit := l.Lex() if token != v.t { t.Errorf("Got %d it should be %d for '%s'", token, v.t, v.s) } if v.t != ErrorToken && lit != v.s { t.Errorf("Got '%s' it should be '%s'", lit, v.s) } } } func min(l, r int) (m int) { m = r if l < r { m = l } return m } func TestLexerSequence(t *testing.T) { testcases := []struct { s string t []Token }{ {"key in ( value )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, ClosedParToken}}, {"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken}}, {"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken}}, {"key", []Token{IdentifierToken}}, {"!key", []Token{DoesNotExistToken, IdentifierToken}}, {"()", []Token{OpenParToken, ClosedParToken}}, {"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken}}, {"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken}}, {"key>2", []Token{IdentifierToken, GreaterThanToken, IdentifierToken}}, {"key<1", []Token{IdentifierToken, LessThanToken, IdentifierToken}}, } for _, v := range testcases { var tokens []Token l := &Lexer{s: v.s, pos: 0} for { token, _ := l.Lex() if token == EndOfStringToken { break } tokens = append(tokens, token) } if len(tokens) != len(v.t) { t.Errorf("Bad number of tokens for '%s %d, %d", v.s, len(tokens), len(v.t)) } for i := 0; i < min(len(tokens), len(v.t)); i++ { if tokens[i] != v.t[i] { t.Errorf("Test '%s': Mismatching in token type found '%v' it should be '%v'", v.s, tokens[i], v.t[i]) } } } } func TestParserLookahead(t *testing.T) { testcases := []struct { s string t []Token }{ {"key in ( value )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, {"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, {"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, {"key", []Token{IdentifierToken, EndOfStringToken}}, {"!key", []Token{DoesNotExistToken, IdentifierToken, EndOfStringToken}}, {"()", []Token{OpenParToken, ClosedParToken, EndOfStringToken}}, {"", []Token{EndOfStringToken}}, {"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken, EndOfStringToken}}, {"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken, EndOfStringToken}}, {"key>2", []Token{IdentifierToken, GreaterThanToken, IdentifierToken, EndOfStringToken}}, {"key<1", []Token{IdentifierToken, LessThanToken, IdentifierToken, EndOfStringToken}}, } for _, v := range testcases { p := &Parser{l: &Lexer{s: v.s, pos: 0}, position: 0} p.scan() if len(p.scannedItems) != len(v.t) { t.Errorf("Expected %d items found %d", len(v.t), len(p.scannedItems)) } for { token, lit := p.lookahead(KeyAndOperator) token2, lit2 := p.consume(KeyAndOperator) if token == EndOfStringToken { break } if token != token2 || lit != lit2 { t.Errorf("Bad values") } } } } func TestParseOperator(t *testing.T) { testcases := []struct { token string expectedError error }{ {"in", nil}, {"=", nil}, {"==", nil}, {">", nil}, {"<", nil}, {"notin", nil}, {"!=", nil}, {"!", fmt.Errorf("found '%s', expected: %v", selection.DoesNotExist, strings.Join(binaryOperators, ", "))}, {"exists", fmt.Errorf("found '%s', expected: %v", selection.Exists, strings.Join(binaryOperators, ", "))}, {"(", fmt.Errorf("found '%s', expected: %v", "(", strings.Join(binaryOperators, ", "))}, } for _, testcase := range testcases { p := &Parser{l: &Lexer{s: testcase.token, pos: 0}, position: 0} p.scan() _, err := p.parseOperator() if ok := reflect.DeepEqual(testcase.expectedError, err); !ok { t.Errorf("\nexpect err [%v], \nactual err [%v]", testcase.expectedError, err) } } } func TestRequirementConstructor(t *testing.T) { requirementConstructorTests := []struct { Key string Op selection.Operator Vals sets.String WantErr field.ErrorList }{ { Key: "x1", Op: selection.In, WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values", BadValue: []string{}, }, }, }, { Key: "x2", Op: selection.NotIn, Vals: sets.NewString(), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values", BadValue: []string{}, }, }, }, { Key: "x3", Op: selection.In, Vals: sets.NewString("foo"), }, { Key: "x4", Op: selection.NotIn, Vals: sets.NewString("foo"), }, { Key: "x5", Op: selection.Equals, Vals: sets.NewString("foo", "bar"), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values", BadValue: []string{"bar", "foo"}, }, }, }, { Key: "x6", Op: selection.Exists, }, { Key: "x7", Op: selection.DoesNotExist, }, { Key: "x8", Op: selection.Exists, Vals: sets.NewString("foo"), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values", BadValue: []string{"foo"}, }, }, }, { Key: "x9", Op: selection.In, Vals: sets.NewString("bar"), }, { Key: "x10", Op: selection.In, Vals: sets.NewString("bar"), }, { Key: "x11", Op: selection.GreaterThan, Vals: sets.NewString("1"), }, { Key: "x12", Op: selection.LessThan, Vals: sets.NewString("6"), }, { Key: "x13", Op: selection.GreaterThan, WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values", BadValue: []string{}, }, }, }, { Key: "x14", Op: selection.GreaterThan, Vals: sets.NewString("bar"), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values[0]", BadValue: "bar", }, }, }, { Key: "x15", Op: selection.LessThan, Vals: sets.NewString("bar"), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values[0]", BadValue: "bar", }, }, }, { Key: strings.Repeat("a", 254), //breaks DNS rule that len(key) <= 253 Op: selection.Exists, WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "key", BadValue: strings.Repeat("a", 254), }, }, }, { Key: "x16", Op: selection.Equals, Vals: sets.NewString(strings.Repeat("a", 254)), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values[0][x16]", BadValue: strings.Repeat("a", 254), }, }, }, { Key: "x17", Op: selection.Equals, Vals: sets.NewString("a b"), WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values[0][x17]", BadValue: "a b", }, }, }, { Key: "x18", Op: "unsupportedOp", WantErr: field.ErrorList{ &field.Error{ Type: field.ErrorTypeNotSupported, Field: "operator", BadValue: selection.Operator("unsupportedOp"), }, }, }, } for _, rc := range requirementConstructorTests { _, err := NewRequirement(rc.Key, rc.Op, rc.Vals.List()) if diff := cmp.Diff(rc.WantErr.ToAggregate(), err, ignoreDetail); diff != "" { t.Errorf("NewRequirement test %v returned unexpected error (-want,+got):\n%s", rc.Key, diff) } } } func TestToString(t *testing.T) { var req Requirement toStringTests := []struct { In *internalSelector Out string Valid bool }{ {&internalSelector{ getRequirement("x", selection.In, sets.NewString("abc", "def"), t), getRequirement("y", selection.NotIn, sets.NewString("jkl"), t), getRequirement("z", selection.Exists, nil, t)}, "x in (abc,def),y notin (jkl),z", true}, {&internalSelector{ getRequirement("x", selection.NotIn, sets.NewString("abc", "def"), t), getRequirement("y", selection.NotEquals, sets.NewString("jkl"), t), getRequirement("z", selection.DoesNotExist, nil, t)}, "x notin (abc,def),y!=jkl,!z", true}, {&internalSelector{ getRequirement("x", selection.In, sets.NewString("abc", "def"), t), req}, // adding empty req for the trailing ',' "x in (abc,def),", false}, {&internalSelector{ getRequirement("x", selection.NotIn, sets.NewString("abc"), t), getRequirement("y", selection.In, sets.NewString("jkl", "mno"), t), getRequirement("z", selection.NotIn, sets.NewString(""), t)}, "x notin (abc),y in (jkl,mno),z notin ()", true}, {&internalSelector{ getRequirement("x", selection.Equals, sets.NewString("abc"), t), getRequirement("y", selection.DoubleEquals, sets.NewString("jkl"), t), getRequirement("z", selection.NotEquals, sets.NewString("a"), t), getRequirement("z", selection.Exists, nil, t)}, "x=abc,y==jkl,z!=a,z", true}, {&internalSelector{ getRequirement("x", selection.GreaterThan, sets.NewString("2"), t), getRequirement("y", selection.LessThan, sets.NewString("8"), t), getRequirement("z", selection.Exists, nil, t)}, "x>2,y<8,z", true}, } for _, ts := range toStringTests { if out := ts.In.String(); out == "" && ts.Valid { t.Errorf("%#v.String() => '%v' expected no error", ts.In, out) } else if out != ts.Out { t.Errorf("%#v.String() => '%v' want '%v'", ts.In, out, ts.Out) } } } func TestRequirementSelectorMatching(t *testing.T) { var req Requirement labelSelectorMatchingTests := []struct { Set Set Sel Selector Match bool }{ {Set{"x": "foo", "y": "baz"}, &internalSelector{ req, }, false}, {Set{"x": "foo", "y": "baz"}, &internalSelector{ getRequirement("x", selection.In, sets.NewString("foo"), t), getRequirement("y", selection.NotIn, sets.NewString("alpha"), t), }, true}, {Set{"x": "foo", "y": "baz"}, &internalSelector{ getRequirement("x", selection.In, sets.NewString("foo"), t), getRequirement("y", selection.In, sets.NewString("alpha"), t), }, false}, {Set{"y": ""}, &internalSelector{ getRequirement("x", selection.NotIn, sets.NewString(""), t), getRequirement("y", selection.Exists, nil, t), }, true}, {Set{"y": ""}, &internalSelector{ getRequirement("x", selection.DoesNotExist, nil, t), getRequirement("y", selection.Exists, nil, t), }, true}, {Set{"y": ""}, &internalSelector{ getRequirement("x", selection.NotIn, sets.NewString(""), t), getRequirement("y", selection.DoesNotExist, nil, t), }, false}, {Set{"y": "baz"}, &internalSelector{ getRequirement("x", selection.In, sets.NewString(""), t), }, false}, {Set{"z": "2"}, &internalSelector{ getRequirement("z", selection.GreaterThan, sets.NewString("1"), t), }, true}, {Set{"z": "v2"}, &internalSelector{ getRequirement("z", selection.GreaterThan, sets.NewString("1"), t), }, false}, } for _, lsm := range labelSelectorMatchingTests { if match := lsm.Sel.Matches(lsm.Set); match != lsm.Match { t.Errorf("%+v.Matches(%#v) => %v, want %v", lsm.Sel, lsm.Set, match, lsm.Match) } } } func TestSetSelectorParser(t *testing.T) { setSelectorParserTests := []struct { In string Out Selector Match bool Valid bool }{ {"", NewSelector(), true, true}, {"\rx", internalSelector{ getRequirement("x", selection.Exists, nil, t), }, true, true}, {"this-is-a-dns.domain.com/key-with-dash", internalSelector{ getRequirement("this-is-a-dns.domain.com/key-with-dash", selection.Exists, nil, t), }, true, true}, {"this-is-another-dns.domain.com/key-with-dash in (so,what)", internalSelector{ getRequirement("this-is-another-dns.domain.com/key-with-dash", selection.In, sets.NewString("so", "what"), t), }, true, true}, {"0.1.2.domain/99 notin (10.10.100.1, tick.tack.clock)", internalSelector{ getRequirement("0.1.2.domain/99", selection.NotIn, sets.NewString("10.10.100.1", "tick.tack.clock"), t), }, true, true}, {"foo in (abc)", internalSelector{ getRequirement("foo", selection.In, sets.NewString("abc"), t), }, true, true}, {"x notin\n (abc)", internalSelector{ getRequirement("x", selection.NotIn, sets.NewString("abc"), t), }, true, true}, {"x notin \t (abc,def)", internalSelector{ getRequirement("x", selection.NotIn, sets.NewString("abc", "def"), t), }, true, true}, {"x in (abc,def)", internalSelector{ getRequirement("x", selection.In, sets.NewString("abc", "def"), t), }, true, true}, {"x in (abc,)", internalSelector{ getRequirement("x", selection.In, sets.NewString("abc", ""), t), }, true, true}, {"x in ()", internalSelector{ getRequirement("x", selection.In, sets.NewString(""), t), }, true, true}, {"x notin (abc,,def),bar,z in (),w", internalSelector{ getRequirement("bar", selection.Exists, nil, t), getRequirement("w", selection.Exists, nil, t), getRequirement("x", selection.NotIn, sets.NewString("abc", "", "def"), t), getRequirement("z", selection.In, sets.NewString(""), t), }, true, true}, {"x,y in (a)", internalSelector{ getRequirement("y", selection.In, sets.NewString("a"), t), getRequirement("x", selection.Exists, nil, t), }, false, true}, {"x=a", internalSelector{ getRequirement("x", selection.Equals, sets.NewString("a"), t), }, true, true}, {"x>1", internalSelector{ getRequirement("x", selection.GreaterThan, sets.NewString("1"), t), }, true, true}, {"x<7", internalSelector{ getRequirement("x", selection.LessThan, sets.NewString("7"), t), }, true, true}, {"x=a,y!=b", internalSelector{ getRequirement("x", selection.Equals, sets.NewString("a"), t), getRequirement("y", selection.NotEquals, sets.NewString("b"), t), }, true, true}, {"x=a,y!=b,z in (h,i,j)", internalSelector{ getRequirement("x", selection.Equals, sets.NewString("a"), t), getRequirement("y", selection.NotEquals, sets.NewString("b"), t), getRequirement("z", selection.In, sets.NewString("h", "i", "j"), t), }, true, true}, {"x=a||y=b", internalSelector{}, false, false}, {"x,,y", nil, true, false}, {",x,y", nil, true, false}, {"x nott in (y)", nil, true, false}, {"x notin ( )", internalSelector{ getRequirement("x", selection.NotIn, sets.NewString(""), t), }, true, true}, {"x notin (, a)", internalSelector{ getRequirement("x", selection.NotIn, sets.NewString("", "a"), t), }, true, true}, {"a in (xyz),", nil, true, false}, {"a in (xyz)b notin ()", nil, true, false}, {"a ", internalSelector{ getRequirement("a", selection.Exists, nil, t), }, true, true}, {"a in (x,y,notin, z,in)", internalSelector{ getRequirement("a", selection.In, sets.NewString("in", "notin", "x", "y", "z"), t), }, true, true}, // operator 'in' inside list of identifiers {"a in (xyz abc)", nil, false, false}, // no comma {"a notin(", nil, true, false}, // bad formed {"a (", nil, false, false}, // cpar {"(", nil, false, false}, // opar } for _, ssp := range setSelectorParserTests { if sel, err := Parse(ssp.In); err != nil && ssp.Valid { t.Errorf("Parse(%s) => %v expected no error", ssp.In, err) } else if err == nil && !ssp.Valid { t.Errorf("Parse(%s) => %+v expected error", ssp.In, sel) } else if ssp.Match && !reflect.DeepEqual(sel, ssp.Out) { t.Errorf("Parse(%s) => parse output '%#v' doesn't match '%#v' expected match", ssp.In, sel, ssp.Out) } } } func getRequirement(key string, op selection.Operator, vals sets.String, t *testing.T) Requirement { req, err := NewRequirement(key, op, vals.List()) if err != nil { t.Errorf("NewRequirement(%v, %v, %v) resulted in error:%v", key, op, vals, err) return Requirement{} } return *req } func TestAdd(t *testing.T) { testCases := []struct { name string sel Selector key string operator selection.Operator values []string refSelector Selector }{ { "keyInOperator", internalSelector{}, "key", selection.In, []string{"value"}, internalSelector{Requirement{"key", selection.In, []string{"value"}}}, }, { "keyEqualsOperator", internalSelector{Requirement{"key", selection.In, []string{"value"}}}, "key2", selection.Equals, []string{"value2"}, internalSelector{ Requirement{"key", selection.In, []string{"value"}}, Requirement{"key2", selection.Equals, []string{"value2"}}, }, }, } for _, ts := range testCases { req, err := NewRequirement(ts.key, ts.operator, ts.values) if err != nil { t.Errorf("%s - Unable to create labels.Requirement", ts.name) } ts.sel = ts.sel.Add(*req) if !reflect.DeepEqual(ts.sel, ts.refSelector) { t.Errorf("%s - Expected %v found %v", ts.name, ts.refSelector, ts.sel) } } } func TestSafeSort(t *testing.T) { tests := []struct { name string in []string inCopy []string want []string }{ { name: "nil strings", in: nil, inCopy: nil, want: nil, }, { name: "ordered strings", in: []string{"bar", "foo"}, inCopy: []string{"bar", "foo"}, want: []string{"bar", "foo"}, }, { name: "unordered strings", in: []string{"foo", "bar"}, inCopy: []string{"foo", "bar"}, want: []string{"bar", "foo"}, }, { name: "duplicated strings", in: []string{"foo", "bar", "foo", "bar"}, inCopy: []string{"foo", "bar", "foo", "bar"}, want: []string{"bar", "bar", "foo", "foo"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := safeSort(tt.in); !reflect.DeepEqual(got, tt.want) { t.Errorf("safeSort() = %v, want %v", got, tt.want) } if !reflect.DeepEqual(tt.in, tt.inCopy) { t.Errorf("after safeSort(), input = %v, want %v", tt.in, tt.inCopy) } }) } } func BenchmarkSelectorFromValidatedSet(b *testing.B) { set := map[string]string{ "foo": "foo", "bar": "bar", } matchee := Set(map[string]string{ "foo": "foo", "bar": "bar", "extra": "label", }) for i := 0; i < b.N; i++ { s := SelectorFromValidatedSet(set) if s.Empty() { b.Errorf("Unexpected selector") } if !s.Matches(matchee) { b.Errorf("Unexpected match") } } } func BenchmarkSetSelector(b *testing.B) { set := map[string]string{ "foo": "foo", "bar": "bar", } matchee := Set(map[string]string{ "foo": "foo", "bar": "bar", "extra": "label", }) for i := 0; i < b.N; i++ { s := ValidatedSetSelector(set) if s.Empty() { b.Errorf("Unexpected selector") } if !s.Matches(matchee) { b.Errorf("Unexpected match") } } } func TestSetSelectorString(t *testing.T) { cases := []struct { set Set out string }{ { Set{}, "", }, { Set{"app": "foo"}, "app=foo", }, { Set{"app": "foo", "a": "b"}, "a=b,app=foo", }, } for _, tt := range cases { t.Run(tt.out, func(t *testing.T) { if got := ValidatedSetSelector(tt.set).String(); tt.out != got { t.Fatalf("expected %v, got %v", tt.out, got) } }) } } func TestRequiresExactMatch(t *testing.T) { testCases := []struct { name string sel Selector label string expectedFound bool expectedValue string }{ { name: "keyInOperatorExactMatch", sel: internalSelector{Requirement{"key", selection.In, []string{"value"}}}, label: "key", expectedFound: true, expectedValue: "value", }, { name: "keyInOperatorNotExactMatch", sel: internalSelector{Requirement{"key", selection.In, []string{"value", "value2"}}}, label: "key", expectedFound: false, expectedValue: "", }, { name: "keyInOperatorNotExactMatch", sel: internalSelector{ Requirement{"key", selection.In, []string{"value", "value1"}}, Requirement{"key2", selection.In, []string{"value2"}}, }, label: "key2", expectedFound: true, expectedValue: "value2", }, { name: "keyEqualOperatorExactMatch", sel: internalSelector{Requirement{"key", selection.Equals, []string{"value"}}}, label: "key", expectedFound: true, expectedValue: "value", }, { name: "keyDoubleEqualOperatorExactMatch", sel: internalSelector{Requirement{"key", selection.DoubleEquals, []string{"value"}}}, label: "key", expectedFound: true, expectedValue: "value", }, { name: "keyNotEqualOperatorExactMatch", sel: internalSelector{Requirement{"key", selection.NotEquals, []string{"value"}}}, label: "key", expectedFound: false, expectedValue: "", }, { name: "keyEqualOperatorExactMatchFirst", sel: internalSelector{ Requirement{"key", selection.In, []string{"value"}}, Requirement{"key2", selection.In, []string{"value2"}}, }, label: "key", expectedFound: true, expectedValue: "value", }, } for _, ts := range testCases { t.Run(ts.name, func(t *testing.T) { value, found := ts.sel.RequiresExactMatch(ts.label) if found != ts.expectedFound { t.Errorf("Expected match %v, found %v", ts.expectedFound, found) } if found && value != ts.expectedValue { t.Errorf("Expected value %v, found %v", ts.expectedValue, value) } }) } } func TestValidatedSelectorFromSet(t *testing.T) { tests := []struct { name string input Set expectedSelector internalSelector expectedError field.ErrorList }{ { name: "Simple Set, no error", input: Set{"key": "val"}, expectedSelector: internalSelector{ Requirement{ key: "key", operator: selection.Equals, strValues: []string{"val"}, }, }, }, { name: "Invalid Set, value too long", input: Set{"Key": "axahm2EJ8Phiephe2eixohbee9eGeiyees1thuozi1xoh0GiuH3diewi8iem7Nui"}, expectedError: field.ErrorList{ &field.Error{ Type: field.ErrorTypeInvalid, Field: "values[0][Key]", BadValue: "axahm2EJ8Phiephe2eixohbee9eGeiyees1thuozi1xoh0GiuH3diewi8iem7Nui", }, }, }, } for _, tc := range tests { selector, err := ValidatedSelectorFromSet(tc.input) if diff := cmp.Diff(tc.expectedError.ToAggregate(), err, ignoreDetail); diff != "" { t.Errorf("ValidatedSelectorFromSet %#v returned unexpected error (-want,+got):\n%s", tc.name, diff) } if err == nil { if diff := cmp.Diff(tc.expectedSelector, selector); diff != "" { t.Errorf("ValidatedSelectorFromSet %#v returned unexpected selector (-want,+got):\n%s", tc.name, diff) } } } } func BenchmarkRequirementString(b *testing.B) { r := Requirement{ key: "environment", operator: selection.NotIn, strValues: []string{ "dev", }, } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { if r.String() != "environment notin (dev)" { b.Errorf("Unexpected Requirement string") } } } func TestRequirementEqual(t *testing.T) { tests := []struct { name string x, y *Requirement want bool }{ { name: "same requirements should be equal", x: &Requirement{ key: "key", operator: selection.Equals, strValues: []string{"foo", "bar"}, }, y: &Requirement{ key: "key", operator: selection.Equals, strValues: []string{"foo", "bar"}, }, want: true, }, { name: "requirements with different keys should not be equal", x: &Requirement{ key: "key1", operator: selection.Equals, strValues: []string{"foo", "bar"}, }, y: &Requirement{ key: "key2", operator: selection.Equals, strValues: []string{"foo", "bar"}, }, want: false, }, { name: "requirements with different operators should not be equal", x: &Requirement{ key: "key", operator: selection.Equals, strValues: []string{"foo", "bar"}, }, y: &Requirement{ key: "key", operator: selection.In, strValues: []string{"foo", "bar"}, }, want: false, }, { name: "requirements with different values should not be equal", x: &Requirement{ key: "key", operator: selection.Equals, strValues: []string{"foo", "bar"}, }, y: &Requirement{ key: "key", operator: selection.Equals, strValues: []string{"foobar"}, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := cmp.Equal(tt.x, tt.y); got != tt.want { t.Errorf("cmp.Equal() = %v, want %v", got, tt.want) } }) } } golang-k8s-apimachinery-0.29.0/pkg/labels/zz_generated.deepcopy.go000066400000000000000000000023521453143165200250730ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package labels // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Requirement) DeepCopyInto(out *Requirement) { *out = *in if in.strValues != nil { in, out := &in.strValues, &out.strValues *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Requirement. func (in *Requirement) DeepCopy() *Requirement { if in == nil { return nil } out := new(Requirement) in.DeepCopyInto(out) return out } golang-k8s-apimachinery-0.29.0/pkg/runtime/000077500000000000000000000000001453143165200204735ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/allocator.go000066400000000000000000000043651453143165200230120ustar00rootroot00000000000000/* Copyright 2022 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 runtime import ( "sync" ) // AllocatorPool simply stores Allocator objects to avoid additional memory allocations // by caching created but unused items for later reuse, relieving pressure on the garbage collector. // // Usage: // // memoryAllocator := runtime.AllocatorPool.Get().(*runtime.Allocator) // defer runtime.AllocatorPool.Put(memoryAllocator) // // A note for future: // // consider introducing multiple pools for storing buffers of different sizes // perhaps this could allow us to be more efficient. var AllocatorPool = sync.Pool{ New: func() interface{} { return &Allocator{} }, } // Allocator knows how to allocate memory // It exists to make the cost of object serialization cheaper. // In some cases, it allows for allocating memory only once and then reusing it. // This approach puts less load on GC and leads to less fragmented memory in general. type Allocator struct { buf []byte } var _ MemoryAllocator = &Allocator{} // Allocate reserves memory for n bytes only if the underlying array doesn't have enough capacity // otherwise it returns previously allocated block of memory. // // Note that the returned array is not zeroed, it is the caller's // responsibility to clean the memory if needed. func (a *Allocator) Allocate(n uint64) []byte { if uint64(cap(a.buf)) >= n { a.buf = a.buf[:n] return a.buf } // grow the buffer size := uint64(2*cap(a.buf)) + n a.buf = make([]byte, size) a.buf = a.buf[:n] return a.buf } // SimpleAllocator a wrapper around make([]byte) // conforms to the MemoryAllocator interface type SimpleAllocator struct{} var _ MemoryAllocator = &SimpleAllocator{} func (sa *SimpleAllocator) Allocate(n uint64) []byte { return make([]byte, n) } golang-k8s-apimachinery-0.29.0/pkg/runtime/allocator_test.go000066400000000000000000000045661453143165200240540ustar00rootroot00000000000000/* Copyright 2022 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 runtime import ( "math/rand" "testing" ) func TestAllocatorRandomInputs(t *testing.T) { maxBytes := 5 * 1000000 // 5 MB iterations := rand.Intn(10000) + 10 target := &Allocator{} for i := 0; i < iterations; i++ { bytesToAllocate := rand.Intn(maxBytes) buff := target.Allocate(uint64(bytesToAllocate)) if cap(buff) < bytesToAllocate { t.Fatalf("expected the buffer to allocate: %v bytes whereas it allocated: %v bytes", bytesToAllocate, cap(buff)) } if len(buff) != bytesToAllocate { t.Fatalf("unexpected length of the buffer, expected: %v, got: %v", bytesToAllocate, len(buff)) } } } func TestAllocatorNeverShrinks(t *testing.T) { target := &Allocator{} initialSize := 1000000 // 1MB initialBuff := target.Allocate(uint64(initialSize)) if cap(initialBuff) < initialSize { t.Fatalf("unexpected size of the buffer, expected at least 1MB, got: %v", cap(initialBuff)) } for i := initialSize; i > 0; i = i / 10 { newBuff := target.Allocate(uint64(i)) if cap(newBuff) < initialSize { t.Fatalf("allocator is now allowed to shrink memory") } if len(newBuff) != i { t.Fatalf("unexpected length of the buffer, expected: %v, got: %v", i, len(newBuff)) } } } func TestAllocatorZero(t *testing.T) { target := &Allocator{} initialSize := 1000000 // 1MB buff := target.Allocate(uint64(initialSize)) if cap(buff) < initialSize { t.Fatalf("unexpected size of the buffer, expected at least 1MB, got: %v", cap(buff)) } if len(buff) != initialSize { t.Fatalf("unexpected length of the buffer, expected: %v, got: %v", initialSize, len(buff)) } buff = target.Allocate(0) if cap(buff) < initialSize { t.Fatalf("unexpected size of the buffer, expected at least 1MB, got: %v", cap(buff)) } if len(buff) != 0 { t.Fatalf("unexpected length of the buffer, expected: 0, got: %v", len(buff)) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/codec.go000066400000000000000000000302051453143165200220770ustar00rootroot00000000000000/* 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 runtime import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "net/url" "reflect" "strconv" "strings" "k8s.io/apimachinery/pkg/conversion/queryparams" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" ) // codec binds an encoder and decoder. type codec struct { Encoder Decoder } // NewCodec creates a Codec from an Encoder and Decoder. func NewCodec(e Encoder, d Decoder) Codec { return codec{e, d} } // Encode is a convenience wrapper for encoding to a []byte from an Encoder func Encode(e Encoder, obj Object) ([]byte, error) { buf := &bytes.Buffer{} if err := e.Encode(obj, buf); err != nil { return nil, err } return buf.Bytes(), nil } // Decode is a convenience wrapper for decoding data into an Object. func Decode(d Decoder, data []byte) (Object, error) { obj, _, err := d.Decode(data, nil, nil) return obj, err } // DecodeInto performs a Decode into the provided object. func DecodeInto(d Decoder, data []byte, into Object) error { out, gvk, err := d.Decode(data, nil, into) if err != nil { return err } if out != into { return fmt.Errorf("unable to decode %s into %v", gvk, reflect.TypeOf(into)) } return nil } // EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. func EncodeOrDie(e Encoder, obj Object) string { bytes, err := Encode(e, obj) if err != nil { panic(err) } return string(bytes) } // UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or // invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object. func UseOrCreateObject(t ObjectTyper, c ObjectCreater, gvk schema.GroupVersionKind, obj Object) (Object, error) { if obj != nil { kinds, _, err := t.ObjectKinds(obj) if err != nil { return nil, err } for _, kind := range kinds { if gvk == kind { return obj, nil } } } return c.New(gvk) } // NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding. type NoopEncoder struct { Decoder } var _ Serializer = NoopEncoder{} const noopEncoderIdentifier Identifier = "noop" func (n NoopEncoder) Encode(obj Object, w io.Writer) error { // There is no need to handle runtime.CacheableObject, as we don't // process the obj at all. return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder)) } // Identifier implements runtime.Encoder interface. func (n NoopEncoder) Identifier() Identifier { return noopEncoderIdentifier } // NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding. type NoopDecoder struct { Encoder } var _ Serializer = NoopDecoder{} func (n NoopDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder)) } // NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back. func NewParameterCodec(scheme *Scheme) ParameterCodec { return ¶meterCodec{ typer: scheme, convertor: scheme, creator: scheme, defaulter: scheme, } } // parameterCodec implements conversion to and from query parameters and objects. type parameterCodec struct { typer ObjectTyper convertor ObjectConvertor creator ObjectCreater defaulter ObjectDefaulter } var _ ParameterCodec = ¶meterCodec{} // DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then // converts that object to into (if necessary). Returns an error if the operation cannot be completed. func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error { if len(parameters) == 0 { return nil } targetGVKs, _, err := c.typer.ObjectKinds(into) if err != nil { return err } for i := range targetGVKs { if targetGVKs[i].GroupVersion() == from { if err := c.convertor.Convert(¶meters, into, nil); err != nil { return err } // in the case where we going into the same object we're receiving, default on the outbound object if c.defaulter != nil { c.defaulter.Default(into) } return nil } } input, err := c.creator.New(from.WithKind(targetGVKs[0].Kind)) if err != nil { return err } if err := c.convertor.Convert(¶meters, input, nil); err != nil { return err } // if we have defaulter, default the input before converting to output if c.defaulter != nil { c.defaulter.Default(input) } return c.convertor.Convert(input, into, nil) } // EncodeParameters converts the provided object into the to version, then converts that object to url.Values. // Returns an error if conversion is not possible. func (c *parameterCodec) EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) { gvks, _, err := c.typer.ObjectKinds(obj) if err != nil { return nil, err } gvk := gvks[0] if to != gvk.GroupVersion() { out, err := c.convertor.ConvertToVersion(obj, to) if err != nil { return nil, err } obj = out } return queryparams.Convert(obj) } type base64Serializer struct { Encoder Decoder identifier Identifier } func NewBase64Serializer(e Encoder, d Decoder) Serializer { return &base64Serializer{ Encoder: e, Decoder: d, identifier: identifier(e), } } func identifier(e Encoder) Identifier { result := map[string]string{ "name": "base64", } if e != nil { result["encoder"] = string(e.Identifier()) } identifier, err := json.Marshal(result) if err != nil { klog.Fatalf("Failed marshaling identifier for base64Serializer: %v", err) } return Identifier(identifier) } func (s base64Serializer) Encode(obj Object, stream io.Writer) error { if co, ok := obj.(CacheableObject); ok { return co.CacheEncode(s.Identifier(), s.doEncode, stream) } return s.doEncode(obj, stream) } func (s base64Serializer) doEncode(obj Object, stream io.Writer) error { e := base64.NewEncoder(base64.StdEncoding, stream) err := s.Encoder.Encode(obj, e) e.Close() return err } // Identifier implements runtime.Encoder interface. func (s base64Serializer) Identifier() Identifier { return s.identifier } func (s base64Serializer) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { out := make([]byte, base64.StdEncoding.DecodedLen(len(data))) n, err := base64.StdEncoding.Decode(out, data) if err != nil { return nil, nil, err } return s.Decoder.Decode(out[:n], defaults, into) } // SerializerInfoForMediaType returns the first info in types that has a matching media type (which cannot // include media-type parameters), or the first info with an empty media type, or false if no type matches. func SerializerInfoForMediaType(types []SerializerInfo, mediaType string) (SerializerInfo, bool) { for _, info := range types { if info.MediaType == mediaType { return info, true } } for _, info := range types { if len(info.MediaType) == 0 { return info, true } } return SerializerInfo{}, false } var ( // InternalGroupVersioner will always prefer the internal version for a given group version kind. InternalGroupVersioner GroupVersioner = internalGroupVersioner{} // DisabledGroupVersioner will reject all kinds passed to it. DisabledGroupVersioner GroupVersioner = disabledGroupVersioner{} ) const ( internalGroupVersionerIdentifier = "internal" disabledGroupVersionerIdentifier = "disabled" ) type internalGroupVersioner struct{} // KindForGroupVersionKinds returns an internal Kind if one is found, or converts the first provided kind to the internal version. func (internalGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { for _, kind := range kinds { if kind.Version == APIVersionInternal { return kind, true } } for _, kind := range kinds { return schema.GroupVersionKind{Group: kind.Group, Version: APIVersionInternal, Kind: kind.Kind}, true } return schema.GroupVersionKind{}, false } // Identifier implements GroupVersioner interface. func (internalGroupVersioner) Identifier() string { return internalGroupVersionerIdentifier } type disabledGroupVersioner struct{} // KindForGroupVersionKinds returns false for any input. func (disabledGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { return schema.GroupVersionKind{}, false } // Identifier implements GroupVersioner interface. func (disabledGroupVersioner) Identifier() string { return disabledGroupVersionerIdentifier } // Assert that schema.GroupVersion and GroupVersions implement GroupVersioner var _ GroupVersioner = schema.GroupVersion{} var _ GroupVersioner = schema.GroupVersions{} var _ GroupVersioner = multiGroupVersioner{} type multiGroupVersioner struct { target schema.GroupVersion acceptedGroupKinds []schema.GroupKind coerce bool } // NewMultiGroupVersioner returns the provided group version for any kind that matches one of the provided group kinds. // Kind may be empty in the provided group kind, in which case any kind will match. func NewMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner { if len(groupKinds) == 0 || (len(groupKinds) == 1 && groupKinds[0].Group == gv.Group) { return gv } return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds} } // NewCoercingMultiGroupVersioner returns the provided group version for any incoming kind. // Incoming kinds that match the provided groupKinds are preferred. // Kind may be empty in the provided group kind, in which case any kind will match. // Examples: // // gv=mygroup/__internal, groupKinds=mygroup/Foo, anothergroup/Bar // KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group/kind) // // gv=mygroup/__internal, groupKinds=mygroup, anothergroup // KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group) // // gv=mygroup/__internal, groupKinds=mygroup, anothergroup // KindForGroupVersionKinds(yetanother/v1/Baz, yetanother/v1/Bar) -> mygroup/__internal/Baz (no preferred group/kind match, uses first kind in list) func NewCoercingMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner { return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds, coerce: true} } // KindForGroupVersionKinds returns the target group version if any kind matches any of the original group kinds. It will // use the originating kind where possible. func (v multiGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { for _, src := range kinds { for _, kind := range v.acceptedGroupKinds { if kind.Group != src.Group { continue } if len(kind.Kind) > 0 && kind.Kind != src.Kind { continue } return v.target.WithKind(src.Kind), true } } if v.coerce && len(kinds) > 0 { return v.target.WithKind(kinds[0].Kind), true } return schema.GroupVersionKind{}, false } // Identifier implements GroupVersioner interface. func (v multiGroupVersioner) Identifier() string { groupKinds := make([]string, 0, len(v.acceptedGroupKinds)) for _, gk := range v.acceptedGroupKinds { groupKinds = append(groupKinds, gk.String()) } result := map[string]string{ "name": "multi", "target": v.target.String(), "accepted": strings.Join(groupKinds, ","), "coerce": strconv.FormatBool(v.coerce), } identifier, err := json.Marshal(result) if err != nil { klog.Fatalf("Failed marshaling Identifier for %#v: %v", v, err) } return string(identifier) } golang-k8s-apimachinery-0.29.0/pkg/runtime/codec_check.go000066400000000000000000000034761453143165200232460ustar00rootroot00000000000000/* Copyright 2016 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 runtime import ( "fmt" "reflect" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/json" ) // CheckCodec makes sure that the codec can encode objects like internalType, // decode all of the external types listed, and also decode them into the given // object. (Will modify internalObject.) (Assumes JSON serialization.) // TODO: verify that the correct external version is chosen on encode... func CheckCodec(c Codec, internalType Object, externalTypes ...schema.GroupVersionKind) error { if _, err := Encode(c, internalType); err != nil { return fmt.Errorf("internal type not encodable: %v", err) } for _, et := range externalTypes { typeMeta := TypeMeta{ Kind: et.Kind, APIVersion: et.GroupVersion().String(), } exBytes, err := json.Marshal(&typeMeta) if err != nil { return err } obj, err := Decode(c, exBytes) if err != nil { return fmt.Errorf("external type %s not interpretable: %v", et, err) } if reflect.TypeOf(obj) != reflect.TypeOf(internalType) { return fmt.Errorf("decode of external type %s produced: %#v", et, obj) } if err = DecodeInto(c, exBytes, internalType); err != nil { return fmt.Errorf("external type %s not convertible to internal type: %v", et, err) } } return nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/codec_test.go000066400000000000000000000073441453143165200231460ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime_test import ( "io" "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" ) func gv(group, version string) schema.GroupVersion { return schema.GroupVersion{Group: group, Version: version} } func gvk(group, version, kind string) schema.GroupVersionKind { return schema.GroupVersionKind{Group: group, Version: version, Kind: kind} } func gk(group, kind string) schema.GroupKind { return schema.GroupKind{Group: group, Kind: kind} } func TestCoercingMultiGroupVersioner(t *testing.T) { testcases := []struct { name string target schema.GroupVersion preferredKinds []schema.GroupKind kinds []schema.GroupVersionKind expectKind schema.GroupVersionKind expectedId string }{ { name: "matched preferred group/kind", target: gv("mygroup", "__internal"), preferredKinds: []schema.GroupKind{gk("mygroup", "Foo"), gk("anothergroup", "Bar")}, kinds: []schema.GroupVersionKind{gvk("yetanother", "v1", "Baz"), gvk("anothergroup", "v1", "Bar")}, expectKind: gvk("mygroup", "__internal", "Bar"), expectedId: "{\"accepted\":\"Foo.mygroup,Bar.anothergroup\",\"coerce\":\"true\",\"name\":\"multi\",\"target\":\"mygroup/__internal\"}", }, { name: "matched preferred group", target: gv("mygroup", "__internal"), preferredKinds: []schema.GroupKind{gk("mygroup", ""), gk("anothergroup", "")}, kinds: []schema.GroupVersionKind{gvk("yetanother", "v1", "Baz"), gvk("anothergroup", "v1", "Bar")}, expectKind: gvk("mygroup", "__internal", "Bar"), expectedId: "{\"accepted\":\".mygroup,.anothergroup\",\"coerce\":\"true\",\"name\":\"multi\",\"target\":\"mygroup/__internal\"}", }, { name: "no preferred group/kind match, uses first kind in list", target: gv("mygroup", "__internal"), preferredKinds: []schema.GroupKind{gk("mygroup", ""), gk("anothergroup", "")}, kinds: []schema.GroupVersionKind{gvk("yetanother", "v1", "Baz"), gvk("yetanother", "v1", "Bar")}, expectKind: gvk("mygroup", "__internal", "Baz"), expectedId: "{\"accepted\":\".mygroup,.anothergroup\",\"coerce\":\"true\",\"name\":\"multi\",\"target\":\"mygroup/__internal\"}", }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { v := runtime.NewCoercingMultiGroupVersioner(tc.target, tc.preferredKinds...) kind, ok := v.KindForGroupVersionKinds(tc.kinds) if !ok { t.Error("got no kind") } if kind != tc.expectKind { t.Errorf("expected %#v, got %#v", tc.expectKind, kind) } if e, a := tc.expectedId, v.Identifier(); e != a { t.Errorf("unexpected identifier: %s, expected: %s", a, e) } }) } } type mockEncoder struct{} func (m *mockEncoder) Encode(obj runtime.Object, w io.Writer) error { _, err := w.Write([]byte("mock-result")) return err } func (m *mockEncoder) Identifier() runtime.Identifier { return runtime.Identifier("mock-identifier") } func TestCacheableObject(t *testing.T) { serializer := runtime.NewBase64Serializer(&mockEncoder{}, nil) runtimetesting.CacheableObjectTest(t, serializer) } golang-k8s-apimachinery-0.29.0/pkg/runtime/conversion.go000066400000000000000000000123131453143165200232070ustar00rootroot00000000000000/* 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 runtime defines conversions between generic types and structs to map query strings // to struct objects. package runtime import ( "fmt" "reflect" "strconv" "strings" "k8s.io/apimachinery/pkg/conversion" ) // DefaultMetaV1FieldSelectorConversion auto-accepts metav1 values for name and namespace. // A cluster scoped resource specifying namespace empty works fine and specifying a particular // namespace will return no results, as expected. func DefaultMetaV1FieldSelectorConversion(label, value string) (string, string, error) { switch label { case "metadata.name": return label, value, nil case "metadata.namespace": return label, value, nil default: return "", "", fmt.Errorf("%q is not a known field selector: only %q, %q", label, "metadata.name", "metadata.namespace") } } // JSONKeyMapper uses the struct tags on a conversion to determine the key value for // the other side. Use when mapping from a map[string]* to a struct or vice versa. func JSONKeyMapper(key string, sourceTag, destTag reflect.StructTag) (string, string) { if s := destTag.Get("json"); len(s) > 0 { return strings.SplitN(s, ",", 2)[0], key } if s := sourceTag.Get("json"); len(s) > 0 { return key, strings.SplitN(s, ",", 2)[0] } return key, key } func Convert_Slice_string_To_string(in *[]string, out *string, s conversion.Scope) error { if len(*in) == 0 { *out = "" return nil } *out = (*in)[0] return nil } func Convert_Slice_string_To_int(in *[]string, out *int, s conversion.Scope) error { if len(*in) == 0 { *out = 0 return nil } str := (*in)[0] i, err := strconv.Atoi(str) if err != nil { return err } *out = i return nil } // Convert_Slice_string_To_bool will convert a string parameter to boolean. // Only the absence of a value (i.e. zero-length slice), a value of "false", or a // value of "0" resolve to false. // Any other value (including empty string) resolves to true. func Convert_Slice_string_To_bool(in *[]string, out *bool, s conversion.Scope) error { if len(*in) == 0 { *out = false return nil } switch { case (*in)[0] == "0", strings.EqualFold((*in)[0], "false"): *out = false default: *out = true } return nil } // Convert_Slice_string_To_bool will convert a string parameter to boolean. // Only the absence of a value (i.e. zero-length slice), a value of "false", or a // value of "0" resolve to false. // Any other value (including empty string) resolves to true. func Convert_Slice_string_To_Pointer_bool(in *[]string, out **bool, s conversion.Scope) error { if len(*in) == 0 { boolVar := false *out = &boolVar return nil } switch { case (*in)[0] == "0", strings.EqualFold((*in)[0], "false"): boolVar := false *out = &boolVar default: boolVar := true *out = &boolVar } return nil } func string_to_int64(in string) (int64, error) { return strconv.ParseInt(in, 10, 64) } func Convert_string_To_int64(in *string, out *int64, s conversion.Scope) error { if in == nil { *out = 0 return nil } i, err := string_to_int64(*in) if err != nil { return err } *out = i return nil } func Convert_Slice_string_To_int64(in *[]string, out *int64, s conversion.Scope) error { if len(*in) == 0 { *out = 0 return nil } i, err := string_to_int64((*in)[0]) if err != nil { return err } *out = i return nil } func Convert_string_To_Pointer_int64(in *string, out **int64, s conversion.Scope) error { if in == nil { *out = nil return nil } i, err := string_to_int64(*in) if err != nil { return err } *out = &i return nil } func Convert_Slice_string_To_Pointer_int64(in *[]string, out **int64, s conversion.Scope) error { if len(*in) == 0 { *out = nil return nil } i, err := string_to_int64((*in)[0]) if err != nil { return err } *out = &i return nil } func RegisterStringConversions(s *Scheme) error { if err := s.AddConversionFunc((*[]string)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_string(a.(*[]string), b.(*string), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*int)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_int(a.(*[]string), b.(*int), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*bool)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_bool(a.(*[]string), b.(*bool), scope) }); err != nil { return err } if err := s.AddConversionFunc((*[]string)(nil), (*int64)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_Slice_string_To_int64(a.(*[]string), b.(*int64), scope) }); err != nil { return err } return nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/converter.go000066400000000000000000000615711453143165200230430ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime import ( encodingjson "encoding/json" "fmt" "math" "os" "reflect" "sort" "strconv" "strings" "sync" "sync/atomic" "time" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/util/json" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "sigs.k8s.io/structured-merge-diff/v4/value" "k8s.io/klog/v2" ) // UnstructuredConverter is an interface for converting between interface{} // and map[string]interface representation. type UnstructuredConverter interface { ToUnstructured(obj interface{}) (map[string]interface{}, error) FromUnstructured(u map[string]interface{}, obj interface{}) error } type structField struct { structType reflect.Type field int } type fieldInfo struct { name string nameValue reflect.Value omitempty bool } type fieldsCacheMap map[structField]*fieldInfo type fieldsCache struct { sync.Mutex value atomic.Value } func newFieldsCache() *fieldsCache { cache := &fieldsCache{} cache.value.Store(make(fieldsCacheMap)) return cache } var ( mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) stringType = reflect.TypeOf(string("")) fieldCache = newFieldsCache() // DefaultUnstructuredConverter performs unstructured to Go typed object conversions. DefaultUnstructuredConverter = &unstructuredConverter{ mismatchDetection: parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")), comparison: conversion.EqualitiesOrDie( func(a, b time.Time) bool { return a.UTC() == b.UTC() }, ), } ) func parseBool(key string) bool { if len(key) == 0 { return false } value, err := strconv.ParseBool(key) if err != nil { utilruntime.HandleError(fmt.Errorf("couldn't parse '%s' as bool for unstructured mismatch detection", key)) } return value } // unstructuredConverter knows how to convert between interface{} and // Unstructured in both ways. type unstructuredConverter struct { // If true, we will be additionally running conversion via json // to ensure that the result is true. // This is supposed to be set only in tests. mismatchDetection bool // comparison is the default test logic used to compare comparison conversion.Equalities } // NewTestUnstructuredConverter creates an UnstructuredConverter that accepts JSON typed maps and translates them // to Go types via reflection. It performs mismatch detection automatically and is intended for use by external // test tools. Use DefaultUnstructuredConverter if you do not explicitly need mismatch detection. func NewTestUnstructuredConverter(comparison conversion.Equalities) UnstructuredConverter { return NewTestUnstructuredConverterWithValidation(comparison) } // NewTestUnstrucutredConverterWithValidation allows for access to // FromUnstructuredWithValidation from within tests. func NewTestUnstructuredConverterWithValidation(comparison conversion.Equalities) *unstructuredConverter { return &unstructuredConverter{ mismatchDetection: true, comparison: comparison, } } // fromUnstructuredContext provides options for informing the converter // the state of its recursive walk through the conversion process. type fromUnstructuredContext struct { // isInlined indicates whether the converter is currently in // an inlined field or not to determine whether it should // validate the matchedKeys yet or only collect them. // This should only be set from `structFromUnstructured` isInlined bool // matchedKeys is a stack of the set of all fields that exist in the // concrete go type of the object being converted into. // This should only be manipulated via `pushMatchedKeyTracker`, // `recordMatchedKey`, or `popAndVerifyMatchedKeys` matchedKeys []map[string]struct{} // parentPath collects the path that the conversion // takes as it traverses the unstructured json map. // It is used to report the full path to any unknown // fields that the converter encounters. parentPath []string // returnUnknownFields indicates whether or not // unknown field errors should be collected and // returned to the caller returnUnknownFields bool // unknownFieldErrors are the collection of // the full path to each unknown field in the // object. unknownFieldErrors []error } // pushMatchedKeyTracker adds a placeholder set for tracking // matched keys for the given level. This should only be // called from `structFromUnstructured`. func (c *fromUnstructuredContext) pushMatchedKeyTracker() { if !c.returnUnknownFields { return } c.matchedKeys = append(c.matchedKeys, nil) } // recordMatchedKey initializes the last element of matchedKeys // (if needed) and sets 'key'. This should only be called from // `structFromUnstructured`. func (c *fromUnstructuredContext) recordMatchedKey(key string) { if !c.returnUnknownFields { return } last := len(c.matchedKeys) - 1 if c.matchedKeys[last] == nil { c.matchedKeys[last] = map[string]struct{}{} } c.matchedKeys[last][key] = struct{}{} } // popAndVerifyMatchedKeys pops the last element of matchedKeys, // checks the matched keys against the data, and adds unknown // field errors for any matched keys. // `mapValue` is the value of sv containing all of the keys that exist at this level // (ie. sv.MapKeys) in the source data. // `matchedKeys` are all the keys found for that level in the destination object. // This should only be called from `structFromUnstructured`. func (c *fromUnstructuredContext) popAndVerifyMatchedKeys(mapValue reflect.Value) { if !c.returnUnknownFields { return } last := len(c.matchedKeys) - 1 curMatchedKeys := c.matchedKeys[last] c.matchedKeys[last] = nil c.matchedKeys = c.matchedKeys[:last] for _, key := range mapValue.MapKeys() { if _, ok := curMatchedKeys[key.String()]; !ok { c.recordUnknownField(key.String()) } } } func (c *fromUnstructuredContext) recordUnknownField(field string) { if !c.returnUnknownFields { return } pathLen := len(c.parentPath) c.pushKey(field) errPath := strings.Join(c.parentPath, "") c.parentPath = c.parentPath[:pathLen] c.unknownFieldErrors = append(c.unknownFieldErrors, fmt.Errorf(`unknown field "%s"`, errPath)) } func (c *fromUnstructuredContext) pushIndex(index int) { if !c.returnUnknownFields { return } c.parentPath = append(c.parentPath, "[", strconv.Itoa(index), "]") } func (c *fromUnstructuredContext) pushKey(key string) { if !c.returnUnknownFields { return } if len(c.parentPath) > 0 { c.parentPath = append(c.parentPath, ".") } c.parentPath = append(c.parentPath, key) } // FromUnstructuredWithValidation converts an object from map[string]interface{} representation into a concrete type. // It uses encoding/json/Unmarshaler if object implements it or reflection if not. // It takes a validationDirective that indicates how to behave when it encounters unknown fields. func (c *unstructuredConverter) FromUnstructuredWithValidation(u map[string]interface{}, obj interface{}, returnUnknownFields bool) error { t := reflect.TypeOf(obj) value := reflect.ValueOf(obj) if t.Kind() != reflect.Pointer || value.IsNil() { return fmt.Errorf("FromUnstructured requires a non-nil pointer to an object, got %v", t) } fromUnstructuredContext := &fromUnstructuredContext{ returnUnknownFields: returnUnknownFields, } err := fromUnstructured(reflect.ValueOf(u), value.Elem(), fromUnstructuredContext) if c.mismatchDetection { newObj := reflect.New(t.Elem()).Interface() newErr := fromUnstructuredViaJSON(u, newObj) if (err != nil) != (newErr != nil) { klog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err) } if err == nil && !c.comparison.DeepEqual(obj, newObj) { klog.Fatalf("FromUnstructured mismatch\nobj1: %#v\nobj2: %#v", obj, newObj) } } if err != nil { return err } if returnUnknownFields && len(fromUnstructuredContext.unknownFieldErrors) > 0 { sort.Slice(fromUnstructuredContext.unknownFieldErrors, func(i, j int) bool { return fromUnstructuredContext.unknownFieldErrors[i].Error() < fromUnstructuredContext.unknownFieldErrors[j].Error() }) return NewStrictDecodingError(fromUnstructuredContext.unknownFieldErrors) } return nil } // FromUnstructured converts an object from map[string]interface{} representation into a concrete type. // It uses encoding/json/Unmarshaler if object implements it or reflection if not. func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error { return c.FromUnstructuredWithValidation(u, obj, false) } func fromUnstructuredViaJSON(u map[string]interface{}, obj interface{}) error { data, err := json.Marshal(u) if err != nil { return err } return json.Unmarshal(data, obj) } func fromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { sv = unwrapInterface(sv) if !sv.IsValid() { dv.Set(reflect.Zero(dv.Type())) return nil } st, dt := sv.Type(), dv.Type() switch dt.Kind() { case reflect.Map, reflect.Slice, reflect.Pointer, reflect.Struct, reflect.Interface: // Those require non-trivial conversion. default: // This should handle all simple types. if st.AssignableTo(dt) { dv.Set(sv) return nil } // We cannot simply use "ConvertibleTo", as JSON doesn't support conversions // between those four groups: bools, integers, floats and string. We need to // do the same. if st.ConvertibleTo(dt) { switch st.Kind() { case reflect.String: switch dt.Kind() { case reflect.String: dv.Set(sv.Convert(dt)) return nil } case reflect.Bool: switch dt.Kind() { case reflect.Bool: dv.Set(sv.Convert(dt)) return nil } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: switch dt.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: dv.Set(sv.Convert(dt)) return nil case reflect.Float32, reflect.Float64: dv.Set(sv.Convert(dt)) return nil } case reflect.Float32, reflect.Float64: switch dt.Kind() { case reflect.Float32, reflect.Float64: dv.Set(sv.Convert(dt)) return nil } if sv.Float() == math.Trunc(sv.Float()) { dv.Set(sv.Convert(dt)) return nil } } return fmt.Errorf("cannot convert %s to %s", st.String(), dt.String()) } } // Check if the object has a custom JSON marshaller/unmarshaller. entry := value.TypeReflectEntryOf(dv.Type()) if entry.CanConvertFromUnstructured() { return entry.FromUnstructured(sv, dv) } switch dt.Kind() { case reflect.Map: return mapFromUnstructured(sv, dv, ctx) case reflect.Slice: return sliceFromUnstructured(sv, dv, ctx) case reflect.Pointer: return pointerFromUnstructured(sv, dv, ctx) case reflect.Struct: return structFromUnstructured(sv, dv, ctx) case reflect.Interface: return interfaceFromUnstructured(sv, dv) default: return fmt.Errorf("unrecognized type: %v", dt.Kind()) } } func fieldInfoFromField(structType reflect.Type, field int) *fieldInfo { fieldCacheMap := fieldCache.value.Load().(fieldsCacheMap) if info, ok := fieldCacheMap[structField{structType, field}]; ok { return info } // Cache miss - we need to compute the field name. info := &fieldInfo{} typeField := structType.Field(field) jsonTag := typeField.Tag.Get("json") if len(jsonTag) == 0 { // Make the first character lowercase. if typeField.Name == "" { info.name = typeField.Name } else { info.name = strings.ToLower(typeField.Name[:1]) + typeField.Name[1:] } } else { items := strings.Split(jsonTag, ",") info.name = items[0] for i := range items { if items[i] == "omitempty" { info.omitempty = true break } } } info.nameValue = reflect.ValueOf(info.name) fieldCache.Lock() defer fieldCache.Unlock() fieldCacheMap = fieldCache.value.Load().(fieldsCacheMap) newFieldCacheMap := make(fieldsCacheMap) for k, v := range fieldCacheMap { newFieldCacheMap[k] = v } newFieldCacheMap[structField{structType, field}] = info fieldCache.value.Store(newFieldCacheMap) return info } func unwrapInterface(v reflect.Value) reflect.Value { for v.Kind() == reflect.Interface { v = v.Elem() } return v } func mapFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { st, dt := sv.Type(), dv.Type() if st.Kind() != reflect.Map { return fmt.Errorf("cannot restore map from %v", st.Kind()) } if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) { return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key()) } if sv.IsNil() { dv.Set(reflect.Zero(dt)) return nil } dv.Set(reflect.MakeMap(dt)) for _, key := range sv.MapKeys() { value := reflect.New(dt.Elem()).Elem() if val := unwrapInterface(sv.MapIndex(key)); val.IsValid() { if err := fromUnstructured(val, value, ctx); err != nil { return err } } else { value.Set(reflect.Zero(dt.Elem())) } if st.Key().AssignableTo(dt.Key()) { dv.SetMapIndex(key, value) } else { dv.SetMapIndex(key.Convert(dt.Key()), value) } } return nil } func sliceFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { st, dt := sv.Type(), dv.Type() if st.Kind() == reflect.String && dt.Elem().Kind() == reflect.Uint8 { // We store original []byte representation as string. // This conversion is allowed, but we need to be careful about // marshaling data appropriately. if len(sv.Interface().(string)) > 0 { marshalled, err := json.Marshal(sv.Interface()) if err != nil { return fmt.Errorf("error encoding %s to json: %v", st, err) } // TODO: Is this Unmarshal needed? var data []byte err = json.Unmarshal(marshalled, &data) if err != nil { return fmt.Errorf("error decoding from json: %v", err) } dv.SetBytes(data) } else { dv.Set(reflect.MakeSlice(dt, 0, 0)) } return nil } if st.Kind() != reflect.Slice { return fmt.Errorf("cannot restore slice from %v", st.Kind()) } if sv.IsNil() { dv.Set(reflect.Zero(dt)) return nil } dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap())) pathLen := len(ctx.parentPath) defer func() { ctx.parentPath = ctx.parentPath[:pathLen] }() for i := 0; i < sv.Len(); i++ { ctx.pushIndex(i) if err := fromUnstructured(sv.Index(i), dv.Index(i), ctx); err != nil { return err } ctx.parentPath = ctx.parentPath[:pathLen] } return nil } func pointerFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { st, dt := sv.Type(), dv.Type() if st.Kind() == reflect.Pointer && sv.IsNil() { dv.Set(reflect.Zero(dt)) return nil } dv.Set(reflect.New(dt.Elem())) switch st.Kind() { case reflect.Pointer, reflect.Interface: return fromUnstructured(sv.Elem(), dv.Elem(), ctx) default: return fromUnstructured(sv, dv.Elem(), ctx) } } func structFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { st, dt := sv.Type(), dv.Type() if st.Kind() != reflect.Map { return fmt.Errorf("cannot restore struct from: %v", st.Kind()) } pathLen := len(ctx.parentPath) svInlined := ctx.isInlined defer func() { ctx.parentPath = ctx.parentPath[:pathLen] ctx.isInlined = svInlined }() if !svInlined { ctx.pushMatchedKeyTracker() } for i := 0; i < dt.NumField(); i++ { fieldInfo := fieldInfoFromField(dt, i) fv := dv.Field(i) if len(fieldInfo.name) == 0 { // This field is inlined, recurse into fromUnstructured again // with the same set of matched keys. ctx.isInlined = true if err := fromUnstructured(sv, fv, ctx); err != nil { return err } ctx.isInlined = svInlined } else { // This field is not inlined so we recurse into // child field of sv corresponding to field i of // dv, with a new set of matchedKeys and updating // the parentPath to indicate that we are one level // deeper. ctx.recordMatchedKey(fieldInfo.name) value := unwrapInterface(sv.MapIndex(fieldInfo.nameValue)) if value.IsValid() { ctx.isInlined = false ctx.pushKey(fieldInfo.name) if err := fromUnstructured(value, fv, ctx); err != nil { return err } ctx.parentPath = ctx.parentPath[:pathLen] ctx.isInlined = svInlined } else { fv.Set(reflect.Zero(fv.Type())) } } } if !svInlined { ctx.popAndVerifyMatchedKeys(sv) } return nil } func interfaceFromUnstructured(sv, dv reflect.Value) error { // TODO: Is this conversion safe? dv.Set(sv) return nil } // ToUnstructured converts an object into map[string]interface{} representation. // It uses encoding/json/Marshaler if object implements it or reflection if not. func (c *unstructuredConverter) ToUnstructured(obj interface{}) (map[string]interface{}, error) { var u map[string]interface{} var err error if unstr, ok := obj.(Unstructured); ok { u = unstr.UnstructuredContent() } else { t := reflect.TypeOf(obj) value := reflect.ValueOf(obj) if t.Kind() != reflect.Pointer || value.IsNil() { return nil, fmt.Errorf("ToUnstructured requires a non-nil pointer to an object, got %v", t) } u = map[string]interface{}{} err = toUnstructured(value.Elem(), reflect.ValueOf(&u).Elem()) } if c.mismatchDetection { newUnstr := map[string]interface{}{} newErr := toUnstructuredViaJSON(obj, &newUnstr) if (err != nil) != (newErr != nil) { klog.Fatalf("ToUnstructured unexpected error for %v: error: %v; newErr: %v", obj, err, newErr) } if err == nil && !c.comparison.DeepEqual(u, newUnstr) { klog.Fatalf("ToUnstructured mismatch\nobj1: %#v\nobj2: %#v", u, newUnstr) } } if err != nil { return nil, err } return u, nil } // DeepCopyJSON deep copies the passed value, assuming it is a valid JSON representation i.e. only contains // types produced by json.Unmarshal() and also int64. // bool, int64, float64, string, []interface{}, map[string]interface{}, json.Number and nil func DeepCopyJSON(x map[string]interface{}) map[string]interface{} { return DeepCopyJSONValue(x).(map[string]interface{}) } // DeepCopyJSONValue deep copies the passed value, assuming it is a valid JSON representation i.e. only contains // types produced by json.Unmarshal() and also int64. // bool, int64, float64, string, []interface{}, map[string]interface{}, json.Number and nil func DeepCopyJSONValue(x interface{}) interface{} { switch x := x.(type) { case map[string]interface{}: if x == nil { // Typed nil - an interface{} that contains a type map[string]interface{} with a value of nil return x } clone := make(map[string]interface{}, len(x)) for k, v := range x { clone[k] = DeepCopyJSONValue(v) } return clone case []interface{}: if x == nil { // Typed nil - an interface{} that contains a type []interface{} with a value of nil return x } clone := make([]interface{}, len(x)) for i, v := range x { clone[i] = DeepCopyJSONValue(v) } return clone case string, int64, bool, float64, nil, encodingjson.Number: return x default: panic(fmt.Errorf("cannot deep copy %T", x)) } } func toUnstructuredViaJSON(obj interface{}, u *map[string]interface{}) error { data, err := json.Marshal(obj) if err != nil { return err } return json.Unmarshal(data, u) } func toUnstructured(sv, dv reflect.Value) error { // Check if the object has a custom string converter. entry := value.TypeReflectEntryOf(sv.Type()) if entry.CanConvertToUnstructured() { v, err := entry.ToUnstructured(sv) if err != nil { return err } if v != nil { dv.Set(reflect.ValueOf(v)) } return nil } st := sv.Type() switch st.Kind() { case reflect.String: dv.Set(reflect.ValueOf(sv.String())) return nil case reflect.Bool: dv.Set(reflect.ValueOf(sv.Bool())) return nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: dv.Set(reflect.ValueOf(sv.Int())) return nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: uVal := sv.Uint() if uVal > math.MaxInt64 { return fmt.Errorf("unsigned value %d does not fit into int64 (overflow)", uVal) } dv.Set(reflect.ValueOf(int64(uVal))) return nil case reflect.Float32, reflect.Float64: dv.Set(reflect.ValueOf(sv.Float())) return nil case reflect.Map: return mapToUnstructured(sv, dv) case reflect.Slice: return sliceToUnstructured(sv, dv) case reflect.Pointer: return pointerToUnstructured(sv, dv) case reflect.Struct: return structToUnstructured(sv, dv) case reflect.Interface: return interfaceToUnstructured(sv, dv) default: return fmt.Errorf("unrecognized type: %v", st.Kind()) } } func mapToUnstructured(sv, dv reflect.Value) error { st, dt := sv.Type(), dv.Type() if sv.IsNil() { dv.Set(reflect.Zero(dt)) return nil } if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 { if st.Key().Kind() == reflect.String { dv.Set(reflect.MakeMap(mapStringInterfaceType)) dv = dv.Elem() dt = dv.Type() } } if dt.Kind() != reflect.Map { return fmt.Errorf("cannot convert map to: %v", dt.Kind()) } if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) { return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key()) } for _, key := range sv.MapKeys() { value := reflect.New(dt.Elem()).Elem() if err := toUnstructured(sv.MapIndex(key), value); err != nil { return err } if st.Key().AssignableTo(dt.Key()) { dv.SetMapIndex(key, value) } else { dv.SetMapIndex(key.Convert(dt.Key()), value) } } return nil } func sliceToUnstructured(sv, dv reflect.Value) error { st, dt := sv.Type(), dv.Type() if sv.IsNil() { dv.Set(reflect.Zero(dt)) return nil } if st.Elem().Kind() == reflect.Uint8 { dv.Set(reflect.New(stringType)) data, err := json.Marshal(sv.Bytes()) if err != nil { return err } var result string if err = json.Unmarshal(data, &result); err != nil { return err } dv.Set(reflect.ValueOf(result)) return nil } if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 { dv.Set(reflect.MakeSlice(reflect.SliceOf(dt), sv.Len(), sv.Cap())) dv = dv.Elem() dt = dv.Type() } if dt.Kind() != reflect.Slice { return fmt.Errorf("cannot convert slice to: %v", dt.Kind()) } for i := 0; i < sv.Len(); i++ { if err := toUnstructured(sv.Index(i), dv.Index(i)); err != nil { return err } } return nil } func pointerToUnstructured(sv, dv reflect.Value) error { if sv.IsNil() { // We're done - we don't need to store anything. return nil } return toUnstructured(sv.Elem(), dv) } func isZero(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Map, reflect.Slice: // TODO: It seems that 0-len maps are ignored in it. return v.IsNil() || v.Len() == 0 case reflect.Pointer, reflect.Interface: return v.IsNil() } return false } func structToUnstructured(sv, dv reflect.Value) error { st, dt := sv.Type(), dv.Type() if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 { dv.Set(reflect.MakeMapWithSize(mapStringInterfaceType, st.NumField())) dv = dv.Elem() dt = dv.Type() } if dt.Kind() != reflect.Map { return fmt.Errorf("cannot convert struct to: %v", dt.Kind()) } realMap := dv.Interface().(map[string]interface{}) for i := 0; i < st.NumField(); i++ { fieldInfo := fieldInfoFromField(st, i) fv := sv.Field(i) if fieldInfo.name == "-" { // This field should be skipped. continue } if fieldInfo.omitempty && isZero(fv) { // omitempty fields should be ignored. continue } if len(fieldInfo.name) == 0 { // This field is inlined. if err := toUnstructured(fv, dv); err != nil { return err } continue } switch fv.Type().Kind() { case reflect.String: realMap[fieldInfo.name] = fv.String() case reflect.Bool: realMap[fieldInfo.name] = fv.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: realMap[fieldInfo.name] = fv.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: realMap[fieldInfo.name] = fv.Uint() case reflect.Float32, reflect.Float64: realMap[fieldInfo.name] = fv.Float() default: subv := reflect.New(dt.Elem()).Elem() if err := toUnstructured(fv, subv); err != nil { return err } dv.SetMapIndex(fieldInfo.nameValue, subv) } } return nil } func interfaceToUnstructured(sv, dv reflect.Value) error { if !sv.IsValid() || sv.IsNil() { dv.Set(reflect.Zero(dv.Type())) return nil } return toUnstructured(sv.Elem(), dv) } golang-k8s-apimachinery-0.29.0/pkg/runtime/converter_test.go000066400000000000000000000555621453143165200241050ustar00rootroot00000000000000/* Copyright 2015 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. */ // These tests are in a separate package to break cyclic dependency in tests. // Unstructured type depends on unstructured converter package but we want to test how the converter handles // the Unstructured type so we need to import both. package runtime_test import ( encodingjson "encoding/json" "fmt" "reflect" "regexp" "strconv" "strings" "testing" "time" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/json" "github.com/google/go-cmp/cmp" fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var simpleEquality = conversion.EqualitiesOrDie( func(a, b time.Time) bool { return a.UTC() == b.UTC() }, ) // Define a number of test types. type A struct { A int `json:"aa,omitempty"` B string `json:"ab,omitempty"` C bool `json:"ac,omitempty"` } type B struct { A A `json:"ba"` B string `json:"bb"` C map[string]string `json:"bc"` D []string `json:"bd"` } type C struct { A []A `json:"ca"` B `json:",inline"` C string `json:"cc"` D *int64 `json:"cd"` E map[string]int `json:"ce"` F []bool `json:"cf"` G []int `json:"cg"` H float32 `json:"ch"` I []interface{} `json:"ci"` } type D struct { A []interface{} `json:"da"` } type E struct { A interface{} `json:"ea"` } type F struct { A string `json:"fa"` B map[string]string `json:"fb"` C []A `json:"fc"` D int `json:"fd"` E float32 `json:"fe"` F []string `json:"ff"` G []int `json:"fg"` H []bool `json:"fh"` I []float32 `json:"fi"` J []byte `json:"fj"` } type G struct { CustomValue1 CustomValue `json:"customValue1"` CustomValue2 *CustomValue `json:"customValue2"` CustomPointer1 CustomPointer `json:"customPointer1"` CustomPointer2 *CustomPointer `json:"customPointer2"` } type H struct { A A `json:"ha"` C `json:",inline"` } type I struct { A A `json:"ia"` H `json:",inline"` UL1 UnknownLevel1 `json:"ul1"` } type UnknownLevel1 struct { A int64 `json:"a"` InlinedAA `json:",inline"` InlinedAAA `json:",inline"` } type InlinedAA struct { AA int64 `json:"aa"` } type InlinedAAA struct { AAA int64 `json:"aaa"` Child UnknownLevel2 `json:"child"` } type UnknownLevel2 struct { B int64 `json:"b"` InlinedBB `json:",inline"` InlinedBBB `json:",inline"` } type InlinedBB struct { BB int64 `json:"bb"` } type InlinedBBB struct { BBB int64 `json:"bbb"` Child UnknownLevel3 `json:"child"` } type UnknownLevel3 struct { C int64 `json:"c"` InlinedCC `json:",inline"` InlinedCCC `json:",inline"` } type InlinedCC struct { CC int64 `json:"cc"` } type InlinedCCC struct { CCC int64 `json:"ccc"` } type CustomValue struct { data []byte } // MarshalJSON has a value receiver on this type. func (c CustomValue) MarshalJSON() ([]byte, error) { return c.data, nil } type CustomPointer struct { data []byte } // MarshalJSON has a pointer receiver on this type. func (c *CustomPointer) MarshalJSON() ([]byte, error) { return c.data, nil } func doRoundTrip(t *testing.T, item interface{}) { data, err := json.Marshal(item) if err != nil { t.Errorf("Error when marshaling object: %v", err) return } unstr := make(map[string]interface{}) err = json.Unmarshal(data, &unstr) if err != nil { t.Errorf("Error when unmarshaling to unstructured: %v", err) return } data, err = json.Marshal(unstr) if err != nil { t.Errorf("Error when marshaling unstructured: %v", err) return } unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() err = json.Unmarshal(data, unmarshalledObj) if err != nil { t.Errorf("Error when unmarshaling to object: %v", err) return } if !reflect.DeepEqual(item, unmarshalledObj) { t.Errorf("Object changed during JSON operations, diff: %v", cmp.Diff(item, unmarshalledObj)) return } // TODO: should be using mismatch detection but fails due to another error newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item) if err != nil { t.Errorf("ToUnstructured failed: %v", err) return } newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(newUnstr, newObj) if err != nil { t.Errorf("FromUnstructured failed: %v", err) return } if !reflect.DeepEqual(item, newObj) { t.Errorf("Object changed, diff: %v", cmp.Diff(item, newObj)) } } func TestRoundTrip(t *testing.T) { intVal := int64(42) testCases := []struct { obj interface{} }{ { obj: &unstructured.UnstructuredList{ Object: map[string]interface{}{ "kind": "List", }, // Not testing a list with nil Items because items is a non-optional field and hence // is always marshaled into an empty array which is not equal to nil when unmarshalled and will fail. // That is expected. Items: []unstructured.Unstructured{}, }, }, { obj: &unstructured.UnstructuredList{ Object: map[string]interface{}{ "kind": "List", }, Items: []unstructured.Unstructured{ { Object: map[string]interface{}{ "kind": "Pod", }, }, }, }, }, { obj: &unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "Pod", }, }, }, { obj: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Foo", "metadata": map[string]interface{}{ "name": "foo1", }, }, }, }, { // This (among others) tests nil map, slice and pointer. obj: &C{ C: "ccc", }, }, { // This (among others) tests empty map and slice. obj: &C{ A: []A{}, C: "ccc", E: map[string]int{}, I: []interface{}{}, }, }, { obj: &C{ A: []A{ { A: 1, B: "11", C: true, }, { A: 2, B: "22", C: false, }, }, B: B{ A: A{ A: 3, B: "33", }, B: "bbb", C: map[string]string{ "k1": "v1", "k2": "v2", }, D: []string{"s1", "s2"}, }, C: "ccc", D: &intVal, E: map[string]int{ "k1": 1, "k2": 2, }, F: []bool{true, false, false}, G: []int{1, 2, 5}, H: 3.3, I: []interface{}{nil, nil, nil}, }, }, { // Test slice of interface{} with empty slices. obj: &D{ A: []interface{}{[]interface{}{}, []interface{}{}}, }, }, { // Test slice of interface{} with different values. obj: &D{ A: []interface{}{float64(3.5), int64(4), "3.0", nil}, }, }, } for i := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { doRoundTrip(t, testCases[i].obj) }) } } // TestUnknownFields checks for the collection of unknown // field errors from the various possible locations of // unknown fields (e.g. fields on struct, inlined struct, slice, etc) func TestUnknownFields(t *testing.T) { // simples checks that basic unknown fields are found // in fields, subfields and slices. var simplesData = `{ "ca": [ { "aa": 1, "ab": "ab", "ac": true, "unknown1": 24 } ], "cc": "ccstring", "unknown2": "foo" }` var simplesErrs = []string{ `unknown field "ca[0].unknown1"`, `unknown field "unknown2"`, } // same-name, different-levels checks that // fields at a higher level in the json // are not persisted to unrecognized fields // at lower levels and vice-versa. // // In this case, the field "cc" exists at the root level // but not in the nested field ul1. If we are // improperly retaining matched keys, this not // see an issue with "cc" existing inside "ul1" // // The opposite for "aaa", which exists at the // nested level but not at the root. var sameNameDiffLevelData = ` { "cc": "foo", "aaa": 1, "ul1": { "aa": 1, "aaa": 1, "cc": 1 } }` var sameNameDiffLevelErrs = []string{ `unknown field "aaa"`, `unknown field "ul1.cc"`, } // inlined-inlined confirms that we see // fields that are doubly nested and don't recognize // those that aren't var inlinedInlinedData = `{ "bb": "foo", "bc": { "foo": "bar" }, "bd": ["d1", "d2"], "aa": 1 }` var inlinedInlinedErrs = []string{ `unknown field "aa"`, } // combined tests everything together var combinedData = ` { "ia": { "aa": 1, "ab": "ab", "unknownI": "foo" }, "ha": { "aa": 2, "ab": "ab2", "unknownH": "foo" }, "ca":[ { "aa":1, "ab":"11", "ac":true }, { "aa":2, "ab":"22", "unknown1": "foo" }, { "aa":3, "ab":"33", "unknown2": "foo" } ], "ba":{ "aa":3, "ab":"33", "ac": true, "unknown3": 26, "unknown4": "foo" }, "unknown5": "foo", "bb":"bbb", "bc":{ "k1":"v1", "k2":"v2" }, "bd":[ "s1", "s2" ], "cc":"ccc", "cd":42, "ce":{ "k1":1, "k2":2 }, "cf":[ true, false, false ], "cg": [ 1, 2, 5 ], "ch":3.3, "ci":[ null, null, null ], "ul1": { "a": 1, "aa": 1, "aaa": 1, "b": 1, "bb": 1, "bbb": 1, "c": 1, "cc": 1, "ccc": 1, "child": { "a": 1, "aa": 1, "aaa": 1, "b": 1, "bb": 1, "bbb": 1, "c": 1, "cc": 1, "ccc": 1, "child": { "a": 1, "aa": 1, "aaa": 1, "b": 1, "bb": 1, "bbb": 1, "c": 1, "cc": 1, "ccc": 1 } } } }` var combinedErrs = []string{ `unknown field "ca[1].unknown1"`, `unknown field "ca[2].unknown2"`, `unknown field "ba.unknown3"`, `unknown field "ba.unknown4"`, `unknown field "unknown5"`, `unknown field "ha.unknownH"`, `unknown field "ia.unknownI"`, `unknown field "ul1.b"`, `unknown field "ul1.bb"`, `unknown field "ul1.bbb"`, `unknown field "ul1.c"`, `unknown field "ul1.cc"`, `unknown field "ul1.ccc"`, `unknown field "ul1.child.a"`, `unknown field "ul1.child.aa"`, `unknown field "ul1.child.aaa"`, `unknown field "ul1.child.c"`, `unknown field "ul1.child.cc"`, `unknown field "ul1.child.ccc"`, `unknown field "ul1.child.child.a"`, `unknown field "ul1.child.child.aa"`, `unknown field "ul1.child.child.aaa"`, `unknown field "ul1.child.child.b"`, `unknown field "ul1.child.child.bb"`, `unknown field "ul1.child.child.bbb"`, } testCases := []struct { jsonData string obj interface{} returnUnknownFields bool expectedErrs []string }{ { jsonData: simplesData, obj: &C{}, returnUnknownFields: true, expectedErrs: simplesErrs, }, { jsonData: simplesData, obj: &C{}, returnUnknownFields: false, }, { jsonData: sameNameDiffLevelData, obj: &I{}, returnUnknownFields: true, expectedErrs: sameNameDiffLevelErrs, }, { jsonData: sameNameDiffLevelData, obj: &I{}, returnUnknownFields: false, }, { jsonData: inlinedInlinedData, obj: &I{}, returnUnknownFields: true, expectedErrs: inlinedInlinedErrs, }, { jsonData: inlinedInlinedData, obj: &I{}, returnUnknownFields: false, }, { jsonData: combinedData, obj: &I{}, returnUnknownFields: true, expectedErrs: combinedErrs, }, { jsonData: combinedData, obj: &I{}, returnUnknownFields: false, }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { unstr := make(map[string]interface{}) err := json.Unmarshal([]byte(tc.jsonData), &unstr) if err != nil { t.Errorf("Error when unmarshaling to unstructured: %v", err) return } err = runtime.NewTestUnstructuredConverterWithValidation(simpleEquality).FromUnstructuredWithValidation(unstr, tc.obj, tc.returnUnknownFields) if len(tc.expectedErrs) == 0 && err != nil { t.Errorf("unexpected err: %v", err) } var errString string if err != nil { errString = err.Error() } missedErrs := []string{} failed := false for _, expected := range tc.expectedErrs { if !strings.Contains(errString, expected) { failed = true missedErrs = append(missedErrs, expected) } else { errString = strings.Replace(errString, expected, "", 1) } } if failed { for _, e := range missedErrs { t.Errorf("missing err: %v\n", e) } } leftoverErrors := strings.TrimSpace(strings.TrimPrefix(strings.ReplaceAll(errString, ",", ""), "strict decoding error:")) if leftoverErrors != "" { t.Errorf("found unexpected errors: %s", leftoverErrors) } }) } } // BenchmarkFromUnstructuredWithValidation benchmarks // the time and memory required to perform FromUnstructured // with the various validation directives (Ignore, Warn, Strict) func BenchmarkFromUnstructuredWithValidation(b *testing.B) { re := regexp.MustCompile("^I$") f := fuzz.NewWithSeed(1).NilChance(0.1).SkipFieldsWithPattern(re) iObj := &I{} f.Fuzz(&iObj) unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(iObj) if err != nil { b.Fatalf("ToUnstructured failed: %v", err) return } for _, shouldReturn := range []bool{false, true} { b.Run(fmt.Sprintf("shouldReturn=%t", shouldReturn), func(b *testing.B) { newObj := reflect.New(reflect.TypeOf(iObj).Elem()).Interface() b.ReportAllocs() for i := 0; i < b.N; i++ { if err = runtime.NewTestUnstructuredConverterWithValidation(simpleEquality).FromUnstructuredWithValidation(unstr, newObj, shouldReturn); err != nil { b.Fatalf("FromUnstructured failed: %v", err) return } } }) } } // Verifies that: // 1) serialized json -> object // 2) serialized json -> map[string]interface{} -> object // produces the same object. func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr error) { unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() err := json.Unmarshal([]byte(jsonData), unmarshalledObj) if (err != nil) != (expectedErr != nil) { t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr) return } unstr := make(map[string]interface{}) err = json.Unmarshal([]byte(jsonData), &unstr) if err != nil { t.Errorf("Error when unmarshaling to unstructured: %v", err) return } newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, newObj) if (err != nil) != (expectedErr != nil) { t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr) } if expectedErr == nil && !reflect.DeepEqual(unmarshalledObj, newObj) { t.Errorf("Object changed, diff: %v", cmp.Diff(unmarshalledObj, newObj)) } } func TestUnrecognized(t *testing.T) { testCases := []struct { data string obj interface{} err error }{ { data: "{\"da\":[3.5,4,\"3.0\",null]}", obj: &D{}, }, { data: "{\"ea\":[3.5,4,\"3.0\",null]}", obj: &E{}, }, { data: "{\"ea\":[null,null,null]}", obj: &E{}, }, { data: "{\"ea\":[[],[null]]}", obj: &E{}, }, { data: "{\"ea\":{\"a\":[],\"b\":null}}", obj: &E{}, }, { data: "{\"fa\":\"fa\",\"fb\":{\"a\":\"a\"}}", obj: &F{}, }, { data: "{\"fa\":\"fa\",\"fb\":{\"a\":null}}", obj: &F{}, }, { data: "{\"fc\":[null]}", obj: &F{}, }, { data: "{\"fc\":[{\"aa\":123,\"ab\":\"bbb\"}]}", obj: &F{}, }, { // Only unknown fields data: "{\"fx\":[{\"aa\":123,\"ab\":\"bbb\"}],\"fz\":123}", obj: &F{}, }, { data: "{\"fc\":[{\"aa\":\"aaa\",\"ab\":\"bbb\"}]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"), }, { data: "{\"fd\":123,\"fe\":3.5}", obj: &F{}, }, { data: "{\"ff\":[\"abc\"],\"fg\":[123],\"fh\":[true,false]}", obj: &F{}, }, { data: "{\"fj\":\"\"}", obj: &F{}, }, { // Invalid string data data: "{\"fa\":123}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), }, { // Invalid string data data: "{\"fa\":13.5}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), }, { // Invalid string data data: "{\"fa\":true}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal bool into Go value of type string"), }, { // Invalid []string data data: "{\"ff\":123}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"), }, { // Invalid []string data data: "{\"ff\":3.5}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"), }, { // Invalid []string data data: "{\"ff\":[123,345]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), }, { // Invalid []int data data: "{\"fg\":123}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type []int"), }, { // Invalid []int data data: "{\"fg\":\"abc\"}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type []int"), }, { // Invalid []int data data: "{\"fg\":[\"abc\"]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"), }, { // Invalid []int data data: "{\"fg\":[3.5]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"), }, { // Invalid []int data data: "{\"fg\":[true,false]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"), }, { // Invalid []bool data data: "{\"fh\":123}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type []bool"), }, { // Invalid []bool data data: "{\"fh\":\"abc\"}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type []bool"), }, { // Invalid []bool data data: "{\"fh\":[\"abc\"]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type bool"), }, { // Invalid []bool data data: "{\"fh\":[3.5]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"), }, { // Invalid []bool data data: "{\"fh\":[123]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"), }, { // Invalid []float data data: "{\"fi\":123}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal number into Go value of type []float32"), }, { // Invalid []float data data: "{\"fi\":\"abc\"}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type []float32"), }, { // Invalid []float data data: "{\"fi\":[\"abc\"]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal string into Go value of type float32"), }, { // Invalid []float data data: "{\"fi\":[true]}", obj: &F{}, err: fmt.Errorf("json: cannot unmarshal bool into Go value of type float32"), }, } for _, tc := range testCases { t.Run(tc.data, func(t *testing.T) { doUnrecognized(t, tc.data, tc.obj, tc.err) }) } } func TestDeepCopyJSON(t *testing.T) { src := map[string]interface{}{ "a": nil, "b": int64(123), "c": map[string]interface{}{ "a": "b", }, "d": []interface{}{ int64(1), int64(2), }, "e": "estr", "f": true, "g": encodingjson.Number("123"), } deepCopy := runtime.DeepCopyJSON(src) assert.Equal(t, src, deepCopy) } func TestFloatIntConversion(t *testing.T) { unstr := map[string]interface{}{"fd": float64(3)} var obj F if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { t.Errorf("Unexpected error in FromUnstructured: %v", err) } data, err := json.Marshal(unstr) if err != nil { t.Fatalf("Error when marshaling unstructured: %v", err) } var unmarshalled F if err := json.Unmarshal(data, &unmarshalled); err != nil { t.Fatalf("Error when unmarshaling to object: %v", err) } if !reflect.DeepEqual(obj, unmarshalled) { t.Errorf("Incorrect conversion, diff: %v", cmp.Diff(obj, unmarshalled)) } } func TestIntFloatConversion(t *testing.T) { unstr := map[string]interface{}{"ch": int64(3)} var obj C if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { t.Errorf("Unexpected error in FromUnstructured: %v", err) } data, err := json.Marshal(unstr) if err != nil { t.Fatalf("Error when marshaling unstructured: %v", err) } var unmarshalled C if err := json.Unmarshal(data, &unmarshalled); err != nil { t.Fatalf("Error when unmarshaling to object: %v", err) } if !reflect.DeepEqual(obj, unmarshalled) { t.Errorf("Incorrect conversion, diff: %v", cmp.Diff(obj, unmarshalled)) } } func TestCustomToUnstructured(t *testing.T) { testcases := []struct { Data string Expected interface{} }{ {Data: `null`, Expected: nil}, {Data: `true`, Expected: true}, {Data: `false`, Expected: false}, {Data: `[]`, Expected: []interface{}{}}, {Data: `[1]`, Expected: []interface{}{int64(1)}}, {Data: `{}`, Expected: map[string]interface{}{}}, {Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}}, {Data: `0`, Expected: int64(0)}, {Data: `0.0`, Expected: float64(0)}, } for _, tc := range testcases { tc := tc t.Run(tc.Data, func(t *testing.T) { t.Parallel() result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{ CustomValue1: CustomValue{data: []byte(tc.Data)}, CustomValue2: &CustomValue{data: []byte(tc.Data)}, CustomPointer1: CustomPointer{data: []byte(tc.Data)}, CustomPointer2: &CustomPointer{data: []byte(tc.Data)}, }) require.NoError(t, err) for field, fieldResult := range result { assert.Equal(t, tc.Expected, fieldResult, field) } }) } } func TestCustomToUnstructuredTopLevel(t *testing.T) { // Only objects are supported at the top level topLevelCases := []interface{}{ &CustomValue{data: []byte(`{"a":1}`)}, &CustomPointer{data: []byte(`{"a":1}`)}, } expected := map[string]interface{}{"a": int64(1)} for i, obj := range topLevelCases { obj := obj t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj) require.NoError(t, err) assert.Equal(t, expected, result) }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/doc.go000066400000000000000000000037741453143165200216020ustar00rootroot00000000000000/* 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 runtime includes helper functions for working with API objects // that follow the kubernetes API object conventions, which are: // // 0. Your API objects have a common metadata struct member, TypeMeta. // // 1. Your code refers to an internal set of API objects. // // 2. In a separate package, you have an external set of API objects. // // 3. The external set is considered to be versioned, and no breaking // changes are ever made to it (fields may be added but not changed // or removed). // // 4. As your api evolves, you'll make an additional versioned package // with every major change. // // 5. Versioned packages have conversion functions which convert to // and from the internal version. // // 6. You'll continue to support older versions according to your // deprecation policy, and you can easily provide a program/library // to update old versions into new versions because of 5. // // 7. All of your serializations and deserializations are handled in a // centralized place. // // Package runtime provides a conversion helper to make 5 easy, and the // Encode/Decode/DecodeInto trio to accomplish 7. You can also register // additional "codecs" which use a version of your choice. It's // recommended that you register your types with runtime in your // package's init function. // // As a bonus, a few common types useful from all api objects and versions // are provided in types.go. package runtime // import "k8s.io/apimachinery/pkg/runtime" golang-k8s-apimachinery-0.29.0/pkg/runtime/embedded.go000066400000000000000000000104761453143165200225630ustar00rootroot00000000000000/* 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 runtime import ( "errors" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime/schema" ) type encodable struct { E Encoder `json:"-"` obj Object versions []schema.GroupVersion } func (e encodable) GetObjectKind() schema.ObjectKind { return e.obj.GetObjectKind() } func (e encodable) DeepCopyObject() Object { out := e out.obj = e.obj.DeepCopyObject() copy(out.versions, e.versions) return out } // NewEncodable creates an object that will be encoded with the provided codec on demand. // Provided as a convenience for test cases dealing with internal objects. func NewEncodable(e Encoder, obj Object, versions ...schema.GroupVersion) Object { if _, ok := obj.(*Unknown); ok { return obj } return encodable{e, obj, versions} } func (e encodable) UnmarshalJSON(in []byte) error { return errors.New("runtime.encodable cannot be unmarshalled from JSON") } // Marshal may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (e encodable) MarshalJSON() ([]byte, error) { return Encode(e.E, e.obj) } // NewEncodableList creates an object that will be encoded with the provided codec on demand. // Provided as a convenience for test cases dealing with internal objects. func NewEncodableList(e Encoder, objects []Object, versions ...schema.GroupVersion) []Object { out := make([]Object, len(objects)) for i := range objects { if _, ok := objects[i].(*Unknown); ok { out[i] = objects[i] continue } out[i] = NewEncodable(e, objects[i], versions...) } return out } func (e *Unknown) UnmarshalJSON(in []byte) error { if e == nil { return errors.New("runtime.Unknown: UnmarshalJSON on nil pointer") } e.TypeMeta = TypeMeta{} e.Raw = append(e.Raw[0:0], in...) e.ContentEncoding = "" e.ContentType = ContentTypeJSON return nil } // Marshal may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (e Unknown) MarshalJSON() ([]byte, error) { // If ContentType is unset, we assume this is JSON. if e.ContentType != "" && e.ContentType != ContentTypeJSON { return nil, errors.New("runtime.Unknown: MarshalJSON on non-json data") } if e.Raw == nil { return []byte("null"), nil } return e.Raw, nil } func Convert_runtime_Object_To_runtime_RawExtension(in *Object, out *RawExtension, s conversion.Scope) error { if in == nil { out.Raw = []byte("null") return nil } obj := *in if unk, ok := obj.(*Unknown); ok { if unk.Raw != nil { out.Raw = unk.Raw return nil } obj = out.Object } if obj == nil { out.Raw = nil return nil } out.Object = obj return nil } func Convert_runtime_RawExtension_To_runtime_Object(in *RawExtension, out *Object, s conversion.Scope) error { if in.Object != nil { *out = in.Object return nil } data := in.Raw if len(data) == 0 || (len(data) == 4 && string(data) == "null") { *out = nil return nil } *out = &Unknown{ Raw: data, // TODO: Set ContentEncoding and ContentType appropriately. // Currently we set ContentTypeJSON to make tests passing. ContentType: ContentTypeJSON, } return nil } func RegisterEmbeddedConversions(s *Scheme) error { if err := s.AddConversionFunc((*Object)(nil), (*RawExtension)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_runtime_Object_To_runtime_RawExtension(a.(*Object), b.(*RawExtension), scope) }); err != nil { return err } if err := s.AddConversionFunc((*RawExtension)(nil), (*Object)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_runtime_RawExtension_To_runtime_Object(a.(*RawExtension), b.(*Object), scope) }); err != nil { return err } return nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/embedded_test.go000066400000000000000000000222751453143165200236220ustar00rootroot00000000000000/* 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 runtime_test import ( "encoding/json" "reflect" "testing" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" "k8s.io/apimachinery/pkg/util/diff" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} externalGVK := externalGV.WithKind("ObjectTest") s := runtime.NewScheme() s.AddKnownTypes(internalGV, &runtimetesting.ObjectTest{}) s.AddKnownTypeWithName(externalGVK, &runtimetesting.ObjectTestExternal{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) obj, gvk, err := codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{}]}`), nil, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } test := obj.(*runtimetesting.ObjectTest) if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != "{}" || unk.ContentType != runtime.ContentTypeJSON { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { t.Fatalf("unexpected kind: %#v", gvk) } obj, gvk, err = codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{"kind":"Other","apiVersion":"v1"}]}`), nil, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } test = obj.(*runtimetesting.ObjectTest) if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != `{"kind":"Other","apiVersion":"v1"}` || unk.ContentType != runtime.ContentTypeJSON { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { t.Fatalf("unexpected kind: %#v", gvk) } } func TestArrayOfRuntimeObject(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} s := runtime.NewScheme() s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{}) s.AddKnownTypeWithName(externalGV.WithKind("EmbeddedTest"), &runtimetesting.EmbeddedTestExternal{}) s.AddKnownTypes(internalGV, &runtimetesting.ObjectTest{}) s.AddKnownTypeWithName(externalGV.WithKind("ObjectTest"), &runtimetesting.ObjectTestExternal{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) innerItems := []runtime.Object{ &runtimetesting.EmbeddedTest{ID: "baz"}, } items := []runtime.Object{ &runtimetesting.EmbeddedTest{ID: "foo"}, &runtimetesting.EmbeddedTest{ID: "bar"}, // TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization &runtime.Unknown{ Raw: []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`), ContentType: runtime.ContentTypeJSON, }, &runtimetesting.ObjectTest{ Items: runtime.NewEncodableList(codec, innerItems), }, } internal := &runtimetesting.ObjectTest{ Items: runtime.NewEncodableList(codec, items), } wire, err := runtime.Encode(codec, internal) if err != nil { t.Fatalf("unexpected error: %v", err) } t.Logf("Wire format is:\n%s\n", string(wire)) obj := &runtimetesting.ObjectTestExternal{} if err := json.Unmarshal(wire, obj); err != nil { t.Fatalf("unexpected error: %v", err) } t.Logf("exact wire is: %s", string(obj.Items[0].Raw)) items[3] = &runtimetesting.ObjectTest{Items: innerItems} internal.Items = items decoded, err := runtime.Decode(codec, wire) if err != nil { t.Fatalf("unexpected error: %v", err) } list, err := meta.ExtractList(decoded) if err != nil { t.Fatalf("unexpected error: %v", err) } if errs := runtime.DecodeList(list, codec); len(errs) > 0 { t.Fatalf("unexpected error: %v", errs) } list2, err := meta.ExtractList(list[3]) if err != nil { t.Fatalf("unexpected error: %v", err) } if errs := runtime.DecodeList(list2, codec); len(errs) > 0 { t.Fatalf("unexpected error: %v", errs) } if err := meta.SetList(list[3], list2); err != nil { t.Fatalf("unexpected error: %v", err) } // we want DecodeList to set type meta if possible, even on runtime.Unknown objects internal.Items[2].(*runtime.Unknown).TypeMeta = runtime.TypeMeta{Kind: "OtherTest", APIVersion: "unknown.group/unknown"} if e, a := internal.Items, list; !reflect.DeepEqual(e, a) { t.Errorf("mismatched decoded: %s", diff.ObjectGoPrintSideBySide(e, a)) } } func TestNestedObject(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest") s := runtime.NewScheme() s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{}) s.AddKnownTypeWithName(embeddedTestExternalGVK, &runtimetesting.EmbeddedTestExternal{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) inner := &runtimetesting.EmbeddedTest{ ID: "inner", } outer := &runtimetesting.EmbeddedTest{ ID: "outer", Object: runtime.NewEncodable(codec, inner), } wire, err := runtime.Encode(codec, outer) if err != nil { t.Fatalf("Unexpected encode error '%v'", err) } t.Logf("Wire format is:\n%v\n", string(wire)) decoded, err := runtime.Decode(codec, wire) if err != nil { t.Fatalf("Unexpected decode error %v", err) } // for later tests outer.Object = inner if e, a := outer, decoded; reflect.DeepEqual(e, a) { t.Errorf("Expected unequal %#v %#v", e, a) } obj, err := runtime.Decode(codec, decoded.(*runtimetesting.EmbeddedTest).Object.(*runtime.Unknown).Raw) if err != nil { t.Fatal(err) } decoded.(*runtimetesting.EmbeddedTest).Object = obj if e, a := outer, decoded; !reflect.DeepEqual(e, a) { t.Errorf("Expected equal %#v %#v", e, a) } // test JSON decoding of the external object, which should preserve // raw bytes var externalViaJSON runtimetesting.EmbeddedTestExternal err = json.Unmarshal(wire, &externalViaJSON) if err != nil { t.Fatalf("Unexpected decode error %v", err) } if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" { t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON) } if len(externalViaJSON.EmptyObject.Raw) > 0 { t.Errorf("Expected deserialization of empty nested objects into empty bytes, got %#v", externalViaJSON) } // test JSON decoding, too, since Decode uses yaml unmarshalling. // Generic Unmarshalling of JSON cannot load the nested objects because there is // no default schema set. Consumers wishing to get direct JSON decoding must use // the external representation var decodedViaJSON runtimetesting.EmbeddedTest err = json.Unmarshal(wire, &decodedViaJSON) if err == nil { t.Fatal("Expeceted decode error") } if _, ok := err.(*json.UnmarshalTypeError); !ok { t.Fatalf("Unexpected decode error: %v", err) } if a := decodedViaJSON; a.Object != nil || a.EmptyObject != nil { t.Errorf("Expected embedded objects to be nil: %#v", a) } } // TestDeepCopyOfRuntimeObject checks to make sure that runtime.Objects's can be passed through DeepCopy with fidelity func TestDeepCopyOfRuntimeObject(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest") s := runtime.NewScheme() s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{}) s.AddKnownTypeWithName(embeddedTestExternalGVK, &runtimetesting.EmbeddedTestExternal{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) original := &runtimetesting.EmbeddedTest{ ID: "outer", Object: &runtimetesting.EmbeddedTest{ ID: "inner", }, } codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) originalData, err := runtime.Encode(codec, original) if err != nil { t.Errorf("unexpected error: %v", err) } t.Logf("originalRole = %v\n", string(originalData)) copyOfOriginal := original.DeepCopy() copiedData, err := runtime.Encode(codec, copyOfOriginal) if err != nil { t.Errorf("unexpected error: %v", err) } t.Logf("copyOfRole = %v\n", string(copiedData)) if !reflect.DeepEqual(original, copyOfOriginal) { t.Errorf("expected \n%v\n, got \n%v", string(originalData), string(copiedData)) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/error.go000066400000000000000000000112621453143165200221550ustar00rootroot00000000000000/* 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 runtime import ( "fmt" "reflect" "strings" "k8s.io/apimachinery/pkg/runtime/schema" ) type notRegisteredErr struct { schemeName string gvk schema.GroupVersionKind target GroupVersioner t reflect.Type } func NewNotRegisteredErrForKind(schemeName string, gvk schema.GroupVersionKind) error { return ¬RegisteredErr{schemeName: schemeName, gvk: gvk} } func NewNotRegisteredErrForType(schemeName string, t reflect.Type) error { return ¬RegisteredErr{schemeName: schemeName, t: t} } func NewNotRegisteredErrForTarget(schemeName string, t reflect.Type, target GroupVersioner) error { return ¬RegisteredErr{schemeName: schemeName, t: t, target: target} } func NewNotRegisteredGVKErrForTarget(schemeName string, gvk schema.GroupVersionKind, target GroupVersioner) error { return ¬RegisteredErr{schemeName: schemeName, gvk: gvk, target: target} } func (k *notRegisteredErr) Error() string { if k.t != nil && k.target != nil { return fmt.Sprintf("%v is not suitable for converting to %q in scheme %q", k.t, k.target, k.schemeName) } nullGVK := schema.GroupVersionKind{} if k.gvk != nullGVK && k.target != nil { return fmt.Sprintf("%q is not suitable for converting to %q in scheme %q", k.gvk.GroupVersion(), k.target, k.schemeName) } if k.t != nil { return fmt.Sprintf("no kind is registered for the type %v in scheme %q", k.t, k.schemeName) } if len(k.gvk.Kind) == 0 { return fmt.Sprintf("no version %q has been registered in scheme %q", k.gvk.GroupVersion(), k.schemeName) } if k.gvk.Version == APIVersionInternal { return fmt.Sprintf("no kind %q is registered for the internal version of group %q in scheme %q", k.gvk.Kind, k.gvk.Group, k.schemeName) } return fmt.Sprintf("no kind %q is registered for version %q in scheme %q", k.gvk.Kind, k.gvk.GroupVersion(), k.schemeName) } // IsNotRegisteredError returns true if the error indicates the provided // object or input data is not registered. func IsNotRegisteredError(err error) bool { if err == nil { return false } _, ok := err.(*notRegisteredErr) return ok } type missingKindErr struct { data string } func NewMissingKindErr(data string) error { return &missingKindErr{data} } func (k *missingKindErr) Error() string { return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data) } // IsMissingKind returns true if the error indicates that the provided object // is missing a 'Kind' field. func IsMissingKind(err error) bool { if err == nil { return false } _, ok := err.(*missingKindErr) return ok } type missingVersionErr struct { data string } func NewMissingVersionErr(data string) error { return &missingVersionErr{data} } func (k *missingVersionErr) Error() string { return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data) } // IsMissingVersion returns true if the error indicates that the provided object // is missing a 'Version' field. func IsMissingVersion(err error) bool { if err == nil { return false } _, ok := err.(*missingVersionErr) return ok } // strictDecodingError is a base error type that is returned by a strict Decoder such // as UniversalStrictDecoder. type strictDecodingError struct { errors []error } // NewStrictDecodingError creates a new strictDecodingError object. func NewStrictDecodingError(errors []error) error { return &strictDecodingError{ errors: errors, } } func (e *strictDecodingError) Error() string { var s strings.Builder s.WriteString("strict decoding error: ") for i, err := range e.errors { if i != 0 { s.WriteString(", ") } s.WriteString(err.Error()) } return s.String() } func (e *strictDecodingError) Errors() []error { return e.errors } // IsStrictDecodingError returns true if the error indicates that the provided object // strictness violations. func IsStrictDecodingError(err error) bool { if err == nil { return false } _, ok := err.(*strictDecodingError) return ok } // AsStrictDecodingError returns a strict decoding error // containing all the strictness violations. func AsStrictDecodingError(err error) (*strictDecodingError, bool) { if err == nil { return nil, false } strictErr, ok := err.(*strictDecodingError) return strictErr, ok } golang-k8s-apimachinery-0.29.0/pkg/runtime/extension.go000066400000000000000000000033171453143165200230420ustar00rootroot00000000000000/* 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 runtime import ( "bytes" "encoding/json" "errors" ) func (re *RawExtension) UnmarshalJSON(in []byte) error { if re == nil { return errors.New("runtime.RawExtension: UnmarshalJSON on nil pointer") } if !bytes.Equal(in, []byte("null")) { re.Raw = append(re.Raw[0:0], in...) } return nil } // MarshalJSON may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (re RawExtension) MarshalJSON() ([]byte, error) { if re.Raw == nil { // TODO: this is to support legacy behavior of JSONPrinter and YAMLPrinter, which // expect to call json.Marshal on arbitrary versioned objects (even those not in // the scheme). pkg/kubectl/resource#AsVersionedObjects and its interaction with // kubectl get on objects not in the scheme needs to be updated to ensure that the // objects that are not part of the scheme are correctly put into the right form. if re.Object != nil { return json.Marshal(re.Object) } return []byte("null"), nil } // TODO: Check whether ContentType is actually JSON before returning it. return re.Raw, nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/extension_test.go000066400000000000000000000055301453143165200241000ustar00rootroot00000000000000/* 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 runtime_test import ( "bytes" "encoding/json" "reflect" "testing" "k8s.io/apimachinery/pkg/runtime" ) func TestEmbeddedRawExtensionMarshal(t *testing.T) { type test struct { Ext runtime.RawExtension } extension := test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}} data, err := json.Marshal(extension) if err != nil { t.Fatalf("unexpected error: %v", err) } if string(data) != `{"Ext":{"foo":"bar"}}` { t.Errorf("unexpected data: %s", string(data)) } } func TestEmbeddedRawExtensionUnmarshal(t *testing.T) { type test struct { Ext runtime.RawExtension } testCases := map[string]struct { orig test }{ "non-empty object": { orig: test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}}, }, "empty object": { orig: test{Ext: runtime.RawExtension{}}, }, } for k, tc := range testCases { new := test{} data, _ := json.Marshal(tc.orig) if err := json.Unmarshal(data, &new); err != nil { t.Errorf("%s: umarshal error: %v", k, err) } if !reflect.DeepEqual(tc.orig, new) { t.Errorf("%s: unmarshaled struct differs from original: %v %v", k, tc.orig, new) } } } func TestEmbeddedRawExtensionRoundTrip(t *testing.T) { type test struct { Ext runtime.RawExtension } testCases := map[string]struct { orig test }{ "non-empty object": { orig: test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}}, }, "empty object": { orig: test{Ext: runtime.RawExtension{}}, }, } for k, tc := range testCases { new1 := test{} new2 := test{} data, err := json.Marshal(tc.orig) if err != nil { t.Errorf("1st marshal error: %v", err) } if err = json.Unmarshal(data, &new1); err != nil { t.Errorf("1st unmarshal error: %v", err) } newData, err := json.Marshal(new1) if err != nil { t.Errorf("2st marshal error: %v", err) } if err = json.Unmarshal(newData, &new2); err != nil { t.Errorf("2nd unmarshal error: %v", err) } if !bytes.Equal(data, newData) { t.Errorf("%s: re-marshaled data differs from original: %v %v", k, data, newData) } if !reflect.DeepEqual(tc.orig, new1) { t.Errorf("%s: unmarshaled struct differs from original: %v %v", k, tc.orig, new1) } if !reflect.DeepEqual(new1, new2) { t.Errorf("%s: re-unmarshaled struct differs from original: %v %v", k, new1, new2) } } } golang-k8s-apimachinery-0.29.0/pkg/runtime/generated.pb.go000066400000000000000000000512421453143165200233640ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime/generated.proto package runtime import ( fmt "fmt" io "io" math "math" math_bits "math/bits" reflect "reflect" strings "strings" proto "github.com/gogo/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package func (m *RawExtension) Reset() { *m = RawExtension{} } func (*RawExtension) ProtoMessage() {} func (*RawExtension) Descriptor() ([]byte, []int) { return fileDescriptor_9d3c45d7f546725c, []int{0} } func (m *RawExtension) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *RawExtension) 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 *RawExtension) XXX_Merge(src proto.Message) { xxx_messageInfo_RawExtension.Merge(m, src) } func (m *RawExtension) XXX_Size() int { return m.Size() } func (m *RawExtension) XXX_DiscardUnknown() { xxx_messageInfo_RawExtension.DiscardUnknown(m) } var xxx_messageInfo_RawExtension proto.InternalMessageInfo func (m *TypeMeta) Reset() { *m = TypeMeta{} } func (*TypeMeta) ProtoMessage() {} func (*TypeMeta) Descriptor() ([]byte, []int) { return fileDescriptor_9d3c45d7f546725c, []int{1} } func (m *TypeMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *TypeMeta) 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 *TypeMeta) XXX_Merge(src proto.Message) { xxx_messageInfo_TypeMeta.Merge(m, src) } func (m *TypeMeta) XXX_Size() int { return m.Size() } func (m *TypeMeta) XXX_DiscardUnknown() { xxx_messageInfo_TypeMeta.DiscardUnknown(m) } var xxx_messageInfo_TypeMeta proto.InternalMessageInfo func (m *Unknown) Reset() { *m = Unknown{} } func (*Unknown) ProtoMessage() {} func (*Unknown) Descriptor() ([]byte, []int) { return fileDescriptor_9d3c45d7f546725c, []int{2} } func (m *Unknown) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Unknown) 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 *Unknown) XXX_Merge(src proto.Message) { xxx_messageInfo_Unknown.Merge(m, src) } func (m *Unknown) XXX_Size() int { return m.Size() } func (m *Unknown) XXX_DiscardUnknown() { xxx_messageInfo_Unknown.DiscardUnknown(m) } var xxx_messageInfo_Unknown proto.InternalMessageInfo func init() { proto.RegisterType((*RawExtension)(nil), "k8s.io.apimachinery.pkg.runtime.RawExtension") proto.RegisterType((*TypeMeta)(nil), "k8s.io.apimachinery.pkg.runtime.TypeMeta") proto.RegisterType((*Unknown)(nil), "k8s.io.apimachinery.pkg.runtime.Unknown") } func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime/generated.proto", fileDescriptor_9d3c45d7f546725c) } var fileDescriptor_9d3c45d7f546725c = []byte{ // 380 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0xaa, 0x13, 0x31, 0x14, 0xc6, 0x27, 0xb7, 0x85, 0x7b, 0x4d, 0x0b, 0x57, 0xe2, 0xc2, 0xd1, 0x45, 0xe6, 0xd2, 0x95, 0x77, 0x61, 0x02, 0x17, 0x04, 0xb7, 0x9d, 0x52, 0x50, 0x44, 0x90, 0xe0, 0x1f, 0x70, 0x65, 0x3a, 0x13, 0xa7, 0x61, 0xe8, 0xc9, 0x90, 0x66, 0x1c, 0xbb, 0xf3, 0x11, 0x7c, 0xac, 0x2e, 0xbb, 0xec, 0xaa, 0xd8, 0xf1, 0x21, 0xdc, 0x4a, 0xd3, 0xb4, 0x56, 0x5d, 0x74, 0x97, 0x73, 0xbe, 0xef, 0xf7, 0x9d, 0x73, 0x20, 0xf8, 0x45, 0xf9, 0x7c, 0xce, 0xb4, 0xe1, 0x65, 0x3d, 0x51, 0x16, 0x94, 0x53, 0x73, 0xfe, 0x45, 0x41, 0x6e, 0x2c, 0x0f, 0x82, 0xac, 0xf4, 0x4c, 0x66, 0x53, 0x0d, 0xca, 0x2e, 0x78, 0x55, 0x16, 0xdc, 0xd6, 0xe0, 0xf4, 0x4c, 0xf1, 0x42, 0x81, 0xb2, 0xd2, 0xa9, 0x9c, 0x55, 0xd6, 0x38, 0x43, 0x92, 0x3d, 0xc0, 0x4e, 0x01, 0x56, 0x95, 0x05, 0x0b, 0xc0, 0xe3, 0xa7, 0x85, 0x76, 0xd3, 0x7a, 0xc2, 0x32, 0x33, 0xe3, 0x85, 0x29, 0x0c, 0xf7, 0xdc, 0xa4, 0xfe, 0xec, 0x2b, 0x5f, 0xf8, 0xd7, 0x3e, 0x6f, 0x70, 0x8b, 0xfb, 0x42, 0x36, 0xe3, 0xaf, 0x4e, 0xc1, 0x5c, 0x1b, 0x20, 0x8f, 0x70, 0xc7, 0xca, 0x26, 0x46, 0x37, 0xe8, 0x49, 0x3f, 0xbd, 0x6c, 0x37, 0x49, 0x47, 0xc8, 0x46, 0xec, 0x7a, 0x83, 0x4f, 0xf8, 0xea, 0xed, 0xa2, 0x52, 0xaf, 0x95, 0x93, 0xe4, 0x0e, 0x63, 0x59, 0xe9, 0xf7, 0xca, 0xee, 0x20, 0xef, 0xbe, 0x97, 0x92, 0xe5, 0x26, 0x89, 0xda, 0x4d, 0x82, 0x87, 0x6f, 0x5e, 0x06, 0x45, 0x9c, 0xb8, 0xc8, 0x0d, 0xee, 0x96, 0x1a, 0xf2, 0xf8, 0xc2, 0xbb, 0xfb, 0xc1, 0xdd, 0x7d, 0xa5, 0x21, 0x17, 0x5e, 0x19, 0xfc, 0x42, 0xf8, 0xf2, 0x1d, 0x94, 0x60, 0x1a, 0x20, 0x1f, 0xf0, 0x95, 0x0b, 0xd3, 0x7c, 0x7e, 0xef, 0xee, 0x96, 0x9d, 0xb9, 0x9d, 0x1d, 0xd6, 0x4b, 0xef, 0x87, 0xf0, 0xe3, 0xc2, 0xe2, 0x18, 0x76, 0xb8, 0xf0, 0xe2, 0xff, 0x0b, 0xc9, 0x10, 0x5f, 0x67, 0x06, 0x9c, 0x02, 0x37, 0x86, 0xcc, 0xe4, 0x1a, 0x8a, 0xb8, 0xe3, 0x97, 0x7d, 0x18, 0xf2, 0xae, 0x47, 0x7f, 0xcb, 0xe2, 0x5f, 0x3f, 0x79, 0x86, 0x7b, 0xa1, 0xb5, 0x1b, 0x1d, 0x77, 0x3d, 0xfe, 0x20, 0xe0, 0xbd, 0xd1, 0x1f, 0x49, 0x9c, 0xfa, 0xd2, 0xf1, 0x72, 0x4b, 0xa3, 0xd5, 0x96, 0x46, 0xeb, 0x2d, 0x8d, 0xbe, 0xb5, 0x14, 0x2d, 0x5b, 0x8a, 0x56, 0x2d, 0x45, 0xeb, 0x96, 0xa2, 0x1f, 0x2d, 0x45, 0xdf, 0x7f, 0xd2, 0xe8, 0x63, 0x72, 0xe6, 0xb7, 0xfc, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x32, 0xd5, 0x68, 0x68, 0x02, 0x00, 0x00, } func (m *RawExtension) 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 *RawExtension) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *RawExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.Raw != nil { i -= len(m.Raw) copy(dAtA[i:], m.Raw) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Raw))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *TypeMeta) 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 *TypeMeta) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *TypeMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.Kind) copy(dAtA[i:], m.Kind) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) i-- dAtA[i] = 0x12 i -= len(m.APIVersion) copy(dAtA[i:], m.APIVersion) i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *Unknown) 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 *Unknown) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.ContentType) copy(dAtA[i:], m.ContentType) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ContentType))) i-- dAtA[i] = 0x22 i -= len(m.ContentEncoding) copy(dAtA[i:], m.ContentEncoding) i = encodeVarintGenerated(dAtA, i, uint64(len(m.ContentEncoding))) i-- dAtA[i] = 0x1a if m.Raw != nil { i -= len(m.Raw) copy(dAtA[i:], m.Raw) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Raw))) i-- dAtA[i] = 0x12 } { size, err := m.TypeMeta.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *RawExtension) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.Raw != nil { l = len(m.Raw) n += 1 + l + sovGenerated(uint64(l)) } return n } func (m *TypeMeta) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.APIVersion) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Kind) n += 1 + l + sovGenerated(uint64(l)) return n } func (m *Unknown) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.TypeMeta.Size() n += 1 + l + sovGenerated(uint64(l)) if m.Raw != nil { l = len(m.Raw) n += 1 + l + sovGenerated(uint64(l)) } l = len(m.ContentEncoding) n += 1 + l + sovGenerated(uint64(l)) l = len(m.ContentType) n += 1 + l + sovGenerated(uint64(l)) return n } func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (this *RawExtension) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&RawExtension{`, `Raw:` + valueToStringGenerated(this.Raw) + `,`, `}`, }, "") return s } func (this *TypeMeta) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&TypeMeta{`, `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, `}`, }, "") return s } func (this *Unknown) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Unknown{`, `TypeMeta:` + strings.Replace(strings.Replace(this.TypeMeta.String(), "TypeMeta", "TypeMeta", 1), `&`, ``, 1) + `,`, `Raw:` + valueToStringGenerated(this.Raw) + `,`, `ContentEncoding:` + fmt.Sprintf("%v", this.ContentEncoding) + `,`, `ContentType:` + fmt.Sprintf("%v", this.ContentType) + `,`, `}`, }, "") return s } func valueToStringGenerated(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { return "nil" } pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } func (m *RawExtension) 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: RawExtension: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: RawExtension: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...) if m.Raw == nil { m.Raw = []byte{} } 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 *TypeMeta) 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: TypeMeta: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: TypeMeta: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.APIVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Kind", 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.Kind = string(dAtA[iNdEx:postIndex]) 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 *Unknown) 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: Unknown: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Unknown: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TypeMeta", 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 } if err := m.TypeMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } if byteLen < 0 { return ErrInvalidLengthGenerated } postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...) if m.Raw == nil { m.Raw = []byte{} } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ContentEncoding", 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.ContentEncoding = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ContentType", 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.ContentType = string(dAtA[iNdEx:postIndex]) 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 skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthGenerated } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupGenerated } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthGenerated } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") ) golang-k8s-apimachinery-0.29.0/pkg/runtime/generated.proto000066400000000000000000000103571453143165200235240ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.runtime; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/runtime"; // RawExtension is used to hold extensions in external versions. // // To use this, make a field which has RawExtension as its type in your external, versioned // struct, and Object in your internal struct. You also need to register your // various plugin types. // // // Internal package: // // type MyAPIObject struct { // runtime.TypeMeta `json:",inline"` // MyPlugin runtime.Object `json:"myPlugin"` // } // // type PluginA struct { // AOption string `json:"aOption"` // } // // // External package: // // type MyAPIObject struct { // runtime.TypeMeta `json:",inline"` // MyPlugin runtime.RawExtension `json:"myPlugin"` // } // // type PluginA struct { // AOption string `json:"aOption"` // } // // // On the wire, the JSON will look something like this: // // { // "kind":"MyAPIObject", // "apiVersion":"v1", // "myPlugin": { // "kind":"PluginA", // "aOption":"foo", // }, // } // // So what happens? Decode first uses json or yaml to unmarshal the serialized data into // your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. // The next step is to copy (using pkg/conversion) into the internal struct. The runtime // package's DefaultScheme has conversion functions installed which will unpack the // JSON stored in RawExtension, turning it into the correct object type, and storing it // in the Object. (TODO: In the case where the object is of an unknown type, a // runtime.Unknown object will be created and stored.) // // +k8s:deepcopy-gen=true // +protobuf=true // +k8s:openapi-gen=true message RawExtension { // Raw is the underlying serialization of this object. // // TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data. optional bytes raw = 1; } // TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, // like this: // // type MyAwesomeAPIObject struct { // runtime.TypeMeta `json:",inline"` // ... // other fields // } // // func (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind // // TypeMeta is provided here for convenience. You may use it directly from this package or define // your own with the same fields. // // +k8s:deepcopy-gen=false // +protobuf=true // +k8s:openapi-gen=true message TypeMeta { // +optional optional string apiVersion = 1; // +optional optional string kind = 2; } // Unknown allows api objects with unknown types to be passed-through. This can be used // to deal with the API objects from a plug-in. Unknown objects still have functioning // TypeMeta features-- kind, version, etc. // TODO: Make this object have easy access to field based accessors and settors for // metadata and field mutatation. // // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +protobuf=true // +k8s:openapi-gen=true message Unknown { optional TypeMeta typeMeta = 1; // Raw will hold the complete serialized object which couldn't be matched // with a registered type. Most likely, nothing should be done with this // except for passing it through the system. optional bytes raw = 2; // ContentEncoding is encoding used to encode 'Raw' data. // Unspecified means no encoding. optional string contentEncoding = 3; // ContentType is serialization method used to serialize 'Raw'. // Unspecified means ContentTypeJSON. optional string contentType = 4; } golang-k8s-apimachinery-0.29.0/pkg/runtime/helper.go000066400000000000000000000204321453143165200223020ustar00rootroot00000000000000/* 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 runtime import ( "fmt" "io" "reflect" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/errors" ) // unsafeObjectConvertor implements ObjectConvertor using the unsafe conversion path. type unsafeObjectConvertor struct { *Scheme } var _ ObjectConvertor = unsafeObjectConvertor{} // ConvertToVersion converts in to the provided outVersion without copying the input first, which // is only safe if the output object is not mutated or reused. func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion GroupVersioner) (Object, error) { return c.Scheme.UnsafeConvertToVersion(in, outVersion) } // UnsafeObjectConvertor performs object conversion without copying the object structure, // for use when the converted object will not be reused or mutated. Primarily for use within // versioned codecs, which use the external object for serialization but do not return it. func UnsafeObjectConvertor(scheme *Scheme) ObjectConvertor { return unsafeObjectConvertor{scheme} } // SetField puts the value of src, into fieldName, which must be a member of v. // The value of src must be assignable to the field. func SetField(src interface{}, v reflect.Value, fieldName string) error { field := v.FieldByName(fieldName) if !field.IsValid() { return fmt.Errorf("couldn't find %v field in %T", fieldName, v.Interface()) } srcValue := reflect.ValueOf(src) if srcValue.Type().AssignableTo(field.Type()) { field.Set(srcValue) return nil } if srcValue.Type().ConvertibleTo(field.Type()) { field.Set(srcValue.Convert(field.Type())) return nil } return fmt.Errorf("couldn't assign/convert %v to %v", srcValue.Type(), field.Type()) } // Field puts the value of fieldName, which must be a member of v, into dest, // which must be a variable to which this field's value can be assigned. func Field(v reflect.Value, fieldName string, dest interface{}) error { field := v.FieldByName(fieldName) if !field.IsValid() { return fmt.Errorf("couldn't find %v field in %T", fieldName, v.Interface()) } destValue, err := conversion.EnforcePtr(dest) if err != nil { return err } if field.Type().AssignableTo(destValue.Type()) { destValue.Set(field) return nil } if field.Type().ConvertibleTo(destValue.Type()) { destValue.Set(field.Convert(destValue.Type())) return nil } return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), destValue.Type()) } // FieldPtr puts the address of fieldName, which must be a member of v, // into dest, which must be an address of a variable to which this field's // address can be assigned. func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error { field := v.FieldByName(fieldName) if !field.IsValid() { return fmt.Errorf("couldn't find %v field in %T", fieldName, v.Interface()) } v, err := conversion.EnforcePtr(dest) if err != nil { return err } field = field.Addr() if field.Type().AssignableTo(v.Type()) { v.Set(field) return nil } if field.Type().ConvertibleTo(v.Type()) { v.Set(field.Convert(v.Type())) return nil } return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), v.Type()) } // EncodeList ensures that each object in an array is converted to a Unknown{} in serialized form. // TODO: accept a content type. func EncodeList(e Encoder, objects []Object) error { var errs []error for i := range objects { data, err := Encode(e, objects[i]) if err != nil { errs = append(errs, err) continue } // TODO: Set ContentEncoding and ContentType. objects[i] = &Unknown{Raw: data} } return errors.NewAggregate(errs) } func decodeListItem(obj *Unknown, decoders []Decoder) (Object, error) { for _, decoder := range decoders { // TODO: Decode based on ContentType. obj, err := Decode(decoder, obj.Raw) if err != nil { if IsNotRegisteredError(err) { continue } return nil, err } return obj, nil } // could not decode, so leave the object as Unknown, but give the decoders the // chance to set Unknown.TypeMeta if it is available. for _, decoder := range decoders { if err := DecodeInto(decoder, obj.Raw, obj); err == nil { return obj, nil } } return obj, nil } // DecodeList alters the list in place, attempting to decode any objects found in // the list that have the Unknown type. Any errors that occur are returned // after the entire list is processed. Decoders are tried in order. func DecodeList(objects []Object, decoders ...Decoder) []error { errs := []error(nil) for i, obj := range objects { switch t := obj.(type) { case *Unknown: decoded, err := decodeListItem(t, decoders) if err != nil { errs = append(errs, err) break } objects[i] = decoded } } return errs } // MultiObjectTyper returns the types of objects across multiple schemes in order. type MultiObjectTyper []ObjectTyper var _ ObjectTyper = MultiObjectTyper{} func (m MultiObjectTyper) ObjectKinds(obj Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) { for _, t := range m { gvks, unversionedType, err = t.ObjectKinds(obj) if err == nil { return } } return } func (m MultiObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { for _, t := range m { if t.Recognizes(gvk) { return true } } return false } // SetZeroValue would set the object of objPtr to zero value of its type. func SetZeroValue(objPtr Object) error { v, err := conversion.EnforcePtr(objPtr) if err != nil { return err } v.Set(reflect.Zero(v.Type())) return nil } // DefaultFramer is valid for any stream that can read objects serially without // any separation in the stream. var DefaultFramer = defaultFramer{} type defaultFramer struct{} func (defaultFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { return r } func (defaultFramer) NewFrameWriter(w io.Writer) io.Writer { return w } // WithVersionEncoder serializes an object and ensures the GVK is set. type WithVersionEncoder struct { Version GroupVersioner Encoder ObjectTyper } // Encode does not do conversion. It sets the gvk during serialization. func (e WithVersionEncoder) Encode(obj Object, stream io.Writer) error { gvks, _, err := e.ObjectTyper.ObjectKinds(obj) if err != nil { if IsNotRegisteredError(err) { return e.Encoder.Encode(obj, stream) } return err } kind := obj.GetObjectKind() oldGVK := kind.GroupVersionKind() gvk := gvks[0] if e.Version != nil { preferredGVK, ok := e.Version.KindForGroupVersionKinds(gvks) if ok { gvk = preferredGVK } } kind.SetGroupVersionKind(gvk) err = e.Encoder.Encode(obj, stream) kind.SetGroupVersionKind(oldGVK) return err } // WithoutVersionDecoder clears the group version kind of a deserialized object. type WithoutVersionDecoder struct { Decoder } // Decode does not do conversion. It removes the gvk during deserialization. func (d WithoutVersionDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { obj, gvk, err := d.Decoder.Decode(data, defaults, into) if obj != nil { kind := obj.GetObjectKind() // clearing the gvk is just a convention of a codec kind.SetGroupVersionKind(schema.GroupVersionKind{}) } return obj, gvk, err } type encoderWithAllocator struct { encoder EncoderWithAllocator memAllocator MemoryAllocator } // NewEncoderWithAllocator returns a new encoder func NewEncoderWithAllocator(e EncoderWithAllocator, a MemoryAllocator) Encoder { return &encoderWithAllocator{ encoder: e, memAllocator: a, } } // Encode writes the provided object to the nested writer func (e *encoderWithAllocator) Encode(obj Object, w io.Writer) error { return e.encoder.EncodeWithAllocator(obj, w, e.memAllocator) } // Identifier returns identifier of this encoder. func (e *encoderWithAllocator) Identifier() Identifier { return e.encoder.Identifier() } golang-k8s-apimachinery-0.29.0/pkg/runtime/interfaces.go000066400000000000000000000457761453143165200231700ustar00rootroot00000000000000/* 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 runtime import ( "io" "net/url" "k8s.io/apimachinery/pkg/runtime/schema" ) const ( // APIVersionInternal may be used if you are registering a type that should not // be considered stable or serialized - it is a convention only and has no // special behavior in this package. APIVersionInternal = "__internal" ) // GroupVersioner refines a set of possible conversion targets into a single option. type GroupVersioner interface { // KindForGroupVersionKinds returns a desired target group version kind for the given input, or returns ok false if no // target is known. In general, if the return target is not in the input list, the caller is expected to invoke // Scheme.New(target) and then perform a conversion between the current Go type and the destination Go type. // Sophisticated implementations may use additional information about the input kinds to pick a destination kind. KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (target schema.GroupVersionKind, ok bool) // Identifier returns string representation of the object. // Identifiers of two different encoders should be equal only if for every input // kinds they return the same result. Identifier() string } // Identifier represents an identifier. // Identitier of two different objects should be equal if and only if for every // input the output they produce is exactly the same. type Identifier string // Encoder writes objects to a serialized form type Encoder interface { // Encode writes an object to a stream. Implementations may return errors if the versions are // incompatible, or if no conversion is defined. Encode(obj Object, w io.Writer) error // Identifier returns an identifier of the encoder. // Identifiers of two different encoders should be equal if and only if for every input // object it will be encoded to the same representation by both of them. // // Identifier is intended for use with CacheableObject#CacheEncode method. In order to // correctly handle CacheableObject, Encode() method should look similar to below, where // doEncode() is the encoding logic of implemented encoder: // func (e *MyEncoder) Encode(obj Object, w io.Writer) error { // if co, ok := obj.(CacheableObject); ok { // return co.CacheEncode(e.Identifier(), e.doEncode, w) // } // return e.doEncode(obj, w) // } Identifier() Identifier } // MemoryAllocator is responsible for allocating memory. // By encapsulating memory allocation into its own interface, we can reuse the memory // across many operations in places we know it can significantly improve the performance. type MemoryAllocator interface { // Allocate reserves memory for n bytes. // Note that implementations of this method are not required to zero the returned array. // It is the caller's responsibility to clean the memory if needed. Allocate(n uint64) []byte } // EncoderWithAllocator serializes objects in a way that allows callers to manage any additional memory allocations. type EncoderWithAllocator interface { Encoder // EncodeWithAllocator writes an object to a stream as Encode does. // In addition, it allows for providing a memory allocator for efficient memory usage during object serialization EncodeWithAllocator(obj Object, w io.Writer, memAlloc MemoryAllocator) error } // Decoder attempts to load an object from data. type Decoder interface { // Decode attempts to deserialize the provided data using either the innate typing of the scheme or the // default kind, group, and version provided. It returns a decoded object as well as the kind, group, and // version from the serialized data, or an error. If into is non-nil, it will be used as the target type // and implementations may choose to use it rather than reallocating an object. However, the object is not // guaranteed to be populated. The returned object is not guaranteed to match into. If defaults are // provided, they are applied to the data by default. If no defaults or partial defaults are provided, the // type of the into may be used to guide conversion decisions. Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) } // Serializer is the core interface for transforming objects into a serialized format and back. // Implementations may choose to perform conversion of the object, but no assumptions should be made. type Serializer interface { Encoder Decoder } // Codec is a Serializer that deals with the details of versioning objects. It offers the same // interface as Serializer, so this is a marker to consumers that care about the version of the objects // they receive. type Codec Serializer // ParameterCodec defines methods for serializing and deserializing API objects to url.Values and // performing any necessary conversion. Unlike the normal Codec, query parameters are not self describing // and the desired version must be specified. type ParameterCodec interface { // DecodeParameters takes the given url.Values in the specified group version and decodes them // into the provided object, or returns an error. DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error // EncodeParameters encodes the provided object as query parameters or returns an error. EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) } // Framer is a factory for creating readers and writers that obey a particular framing pattern. type Framer interface { NewFrameReader(r io.ReadCloser) io.ReadCloser NewFrameWriter(w io.Writer) io.Writer } // SerializerInfo contains information about a specific serialization format type SerializerInfo struct { // MediaType is the value that represents this serializer over the wire. MediaType string // MediaTypeType is the first part of the MediaType ("application" in "application/json"). MediaTypeType string // MediaTypeSubType is the second part of the MediaType ("json" in "application/json"). MediaTypeSubType string // EncodesAsText indicates this serializer can be encoded to UTF-8 safely. EncodesAsText bool // Serializer is the individual object serializer for this media type. Serializer Serializer // PrettySerializer, if set, can serialize this object in a form biased towards // readability. PrettySerializer Serializer // StrictSerializer, if set, deserializes this object strictly, // erring on unknown fields. StrictSerializer Serializer // StreamSerializer, if set, describes the streaming serialization format // for this media type. StreamSerializer *StreamSerializerInfo } // StreamSerializerInfo contains information about a specific stream serialization format type StreamSerializerInfo struct { // EncodesAsText indicates this serializer can be encoded to UTF-8 safely. EncodesAsText bool // Serializer is the top level object serializer for this type when streaming Serializer // Framer is the factory for retrieving streams that separate objects on the wire Framer } // NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers // for multiple supported media types. This would commonly be accepted by a server component // that performs HTTP content negotiation to accept multiple formats. type NegotiatedSerializer interface { // SupportedMediaTypes is the media types supported for reading and writing single objects. SupportedMediaTypes() []SerializerInfo // EncoderForVersion returns an encoder that ensures objects being written to the provided // serializer are in the provided group version. EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder // DecoderToVersion returns a decoder that ensures objects being read by the provided // serializer are in the provided group version by default. DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder } // ClientNegotiator handles turning an HTTP content type into the appropriate encoder. // Use NewClientNegotiator or NewVersionedClientNegotiator to create this interface from // a NegotiatedSerializer. type ClientNegotiator interface { // Encoder returns the appropriate encoder for the provided contentType (e.g. application/json) // and any optional mediaType parameters (e.g. pretty=1), or an error. If no serializer is found // a NegotiateError will be returned. The current client implementations consider params to be // optional modifiers to the contentType and will ignore unrecognized parameters. Encoder(contentType string, params map[string]string) (Encoder, error) // Decoder returns the appropriate decoder for the provided contentType (e.g. application/json) // and any optional mediaType parameters (e.g. pretty=1), or an error. If no serializer is found // a NegotiateError will be returned. The current client implementations consider params to be // optional modifiers to the contentType and will ignore unrecognized parameters. Decoder(contentType string, params map[string]string) (Decoder, error) // StreamDecoder returns the appropriate stream decoder for the provided contentType (e.g. // application/json) and any optional mediaType parameters (e.g. pretty=1), or an error. If no // serializer is found a NegotiateError will be returned. The Serializer and Framer will always // be returned if a Decoder is returned. The current client implementations consider params to be // optional modifiers to the contentType and will ignore unrecognized parameters. StreamDecoder(contentType string, params map[string]string) (Decoder, Serializer, Framer, error) } // StorageSerializer is an interface used for obtaining encoders, decoders, and serializers // that can read and write data at rest. This would commonly be used by client tools that must // read files, or server side storage interfaces that persist restful objects. type StorageSerializer interface { // SupportedMediaTypes are the media types supported for reading and writing objects. SupportedMediaTypes() []SerializerInfo // UniversalDeserializer returns a Serializer that can read objects in multiple supported formats // by introspecting the data at rest. UniversalDeserializer() Decoder // EncoderForVersion returns an encoder that ensures objects being written to the provided // serializer are in the provided group version. EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder // DecoderForVersion returns a decoder that ensures objects being read by the provided // serializer are in the provided group version by default. DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder } // NestedObjectEncoder is an optional interface that objects may implement to be given // an opportunity to encode any nested Objects / RawExtensions during serialization. type NestedObjectEncoder interface { EncodeNestedObjects(e Encoder) error } // NestedObjectDecoder is an optional interface that objects may implement to be given // an opportunity to decode any nested Objects / RawExtensions during serialization. // It is possible for DecodeNestedObjects to return a non-nil error but for the decoding // to have succeeded in the case of strict decoding errors (e.g. unknown/duplicate fields). // As such it is important for callers of DecodeNestedObjects to check to confirm whether // an error is a runtime.StrictDecodingError before short circuiting. // Similarly, implementations of DecodeNestedObjects should ensure that a runtime.StrictDecodingError // is only returned when the rest of decoding has succeeded. type NestedObjectDecoder interface { DecodeNestedObjects(d Decoder) error } /////////////////////////////////////////////////////////////////////////////// // Non-codec interfaces type ObjectDefaulter interface { // Default takes an object (must be a pointer) and applies any default values. // Defaulters may not error. Default(in Object) } type ObjectVersioner interface { ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error) } // ObjectConvertor converts an object to a different version. type ObjectConvertor interface { // Convert attempts to convert one object into another, or returns an error. This // method does not mutate the in object, but the in and out object might share data structures, // i.e. the out object cannot be mutated without mutating the in object as well. // The context argument will be passed to all nested conversions. Convert(in, out, context interface{}) error // ConvertToVersion takes the provided object and converts it the provided version. This // method does not mutate the in object, but the in and out object might share data structures, // i.e. the out object cannot be mutated without mutating the in object as well. // This method is similar to Convert() but handles specific details of choosing the correct // output version. ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) } // ObjectTyper contains methods for extracting the APIVersion and Kind // of objects. type ObjectTyper interface { // ObjectKinds returns the all possible group,version,kind of the provided object, true if // the object is unversioned, or an error if the object is not recognized // (IsNotRegisteredError will return true). ObjectKinds(Object) ([]schema.GroupVersionKind, bool, error) // Recognizes returns true if the scheme is able to handle the provided version and kind, // or more precisely that the provided version is a possible conversion or decoding // target. Recognizes(gvk schema.GroupVersionKind) bool } // ObjectCreater contains methods for instantiating an object by kind and version. type ObjectCreater interface { New(kind schema.GroupVersionKind) (out Object, err error) } // EquivalentResourceMapper provides information about resources that address the same underlying data as a specified resource type EquivalentResourceMapper interface { // EquivalentResourcesFor returns a list of resources that address the same underlying data as resource. // If subresource is specified, only equivalent resources which also have the same subresource are included. // The specified resource can be included in the returned list. EquivalentResourcesFor(resource schema.GroupVersionResource, subresource string) []schema.GroupVersionResource // KindFor returns the kind expected by the specified resource[/subresource]. // A zero value is returned if the kind is unknown. KindFor(resource schema.GroupVersionResource, subresource string) schema.GroupVersionKind } // EquivalentResourceRegistry provides an EquivalentResourceMapper interface, // and allows registering known resource[/subresource] -> kind type EquivalentResourceRegistry interface { EquivalentResourceMapper // RegisterKindFor registers the existence of the specified resource[/subresource] along with its expected kind. RegisterKindFor(resource schema.GroupVersionResource, subresource string, kind schema.GroupVersionKind) } // ResourceVersioner provides methods for setting and retrieving // the resource version from an API object. type ResourceVersioner interface { SetResourceVersion(obj Object, version string) error ResourceVersion(obj Object) (string, error) } // Namer provides methods for retrieving name and namespace of an API object. type Namer interface { // Name returns the name of a given object. Name(obj Object) (string, error) // Namespace returns the name of a given object. Namespace(obj Object) (string, error) } // Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are // expected to be serialized to the wire, the interface an Object must provide to the Scheme allows // serializers to set the kind, version, and group the object is represented as. An Object may choose // to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized. type Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object } // CacheableObject allows an object to cache its different serializations // to avoid performing the same serialization multiple times. type CacheableObject interface { // CacheEncode writes an object to a stream. The function will // be used in case of cache miss. The function takes ownership // of the object. // If CacheableObject is a wrapper, then deep-copy of the wrapped object // should be passed to function. // CacheEncode assumes that for two different calls with the same , // function will also be the same. CacheEncode(id Identifier, encode func(Object, io.Writer) error, w io.Writer) error // GetObject returns a deep-copy of an object to be encoded - the caller of // GetObject() is the owner of returned object. The reason for making a copy // is to avoid bugs, where caller modifies the object and forgets to copy it, // thus modifying the object for everyone. // The object returned by GetObject should be the same as the one that is supposed // to be passed to function in CacheEncode method. // If CacheableObject is a wrapper, the copy of wrapped object should be returned. GetObject() Object } // Unstructured objects store values as map[string]interface{}, with only values that can be serialized // to JSON allowed. type Unstructured interface { Object // NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data. // This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info. NewEmptyInstance() Unstructured // UnstructuredContent returns a non-nil map with this object's contents. Values may be // []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to // and from JSON. SetUnstructuredContent should be used to mutate the contents. UnstructuredContent() map[string]interface{} // SetUnstructuredContent updates the object content to match the provided map. SetUnstructuredContent(map[string]interface{}) // IsList returns true if this type is a list or matches the list convention - has an array called "items". IsList() bool // EachListItem should pass a single item out of the list as an Object to the provided function. Any // error should terminate the iteration. If IsList() returns false, this method should return an error // instead of calling the provided function. EachListItem(func(Object) error) error // EachListItemWithAlloc works like EachListItem, but avoids retaining references to a slice of items. // It does this by making a shallow copy of non-pointer items before passing them to fn. // // If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency. EachListItemWithAlloc(func(Object) error) error } golang-k8s-apimachinery-0.29.0/pkg/runtime/local_scheme_test.go000066400000000000000000000102011453143165200244710ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime import ( "testing" "reflect" "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestPreferredVersionsAllGroups(t *testing.T) { tests := []struct { name string versionPriority map[string][]string observedVersions []schema.GroupVersion expectedPrioritized map[string][]schema.GroupVersion expectedPreferred map[schema.GroupVersion]bool }{ { name: "observedOnly", observedVersions: []schema.GroupVersion{ {Group: "", Version: "v3"}, {Group: "foo", Version: "v1"}, {Group: "foo", Version: "v2"}, {Group: "", Version: "v1"}, }, expectedPrioritized: map[string][]schema.GroupVersion{ "": { {Group: "", Version: "v3"}, {Group: "", Version: "v1"}, }, "foo": { {Group: "foo", Version: "v1"}, {Group: "foo", Version: "v2"}, }, }, expectedPreferred: map[schema.GroupVersion]bool{ {Group: "", Version: "v3"}: true, {Group: "foo", Version: "v1"}: true, }, }, { name: "specifiedOnly", versionPriority: map[string][]string{ "": {"v3", "v1"}, "foo": {"v1", "v2"}, }, expectedPrioritized: map[string][]schema.GroupVersion{ "": { {Group: "", Version: "v3"}, {Group: "", Version: "v1"}, }, "foo": { {Group: "foo", Version: "v1"}, {Group: "foo", Version: "v2"}, }, }, expectedPreferred: map[schema.GroupVersion]bool{ {Group: "", Version: "v3"}: true, {Group: "foo", Version: "v1"}: true, }, }, { name: "both", versionPriority: map[string][]string{ "": {"v3", "v1"}, "foo": {"v1", "v2"}, }, observedVersions: []schema.GroupVersion{ {Group: "", Version: "v1"}, {Group: "", Version: "v3"}, {Group: "", Version: "v4"}, {Group: "", Version: "v5"}, {Group: "bar", Version: "v1"}, {Group: "bar", Version: "v2"}, }, expectedPrioritized: map[string][]schema.GroupVersion{ "": { {Group: "", Version: "v3"}, {Group: "", Version: "v1"}, {Group: "", Version: "v4"}, {Group: "", Version: "v5"}, }, "foo": { {Group: "foo", Version: "v1"}, {Group: "foo", Version: "v2"}, }, "bar": { {Group: "bar", Version: "v1"}, {Group: "bar", Version: "v2"}, }, }, expectedPreferred: map[schema.GroupVersion]bool{ {Group: "", Version: "v3"}: true, {Group: "foo", Version: "v1"}: true, {Group: "bar", Version: "v1"}: true, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { scheme := NewScheme() scheme.versionPriority = test.versionPriority scheme.observedVersions = test.observedVersions for group, expected := range test.expectedPrioritized { actual := scheme.PrioritizedVersionsForGroup(group) if !reflect.DeepEqual(expected, actual) { t.Error(cmp.Diff(expected, actual)) } } prioritizedAll := scheme.PrioritizedVersionsAllGroups() actualPrioritizedAll := map[string][]schema.GroupVersion{} for _, actual := range prioritizedAll { actualPrioritizedAll[actual.Group] = append(actualPrioritizedAll[actual.Group], actual) } if !reflect.DeepEqual(test.expectedPrioritized, actualPrioritizedAll) { t.Error(cmp.Diff(test.expectedPrioritized, actualPrioritizedAll)) } preferredAll := scheme.PreferredVersionAllGroups() actualPreferredAll := map[schema.GroupVersion]bool{} for _, actual := range preferredAll { actualPreferredAll[actual] = true } if !reflect.DeepEqual(test.expectedPreferred, actualPreferredAll) { t.Error(cmp.Diff(test.expectedPreferred, actualPreferredAll)) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/mapper.go000066400000000000000000000071031453143165200223070ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime import ( "sync" "k8s.io/apimachinery/pkg/runtime/schema" ) type equivalentResourceRegistry struct { // keyFunc computes a key for the specified resource (this allows honoring colocated resources across API groups). // if null, or if "" is returned, resource.String() is used as the key keyFunc func(resource schema.GroupResource) string // resources maps key -> subresource -> equivalent resources (subresource is not included in the returned resources). // main resources are stored with subresource="". resources map[string]map[string][]schema.GroupVersionResource // kinds maps resource -> subresource -> kind kinds map[schema.GroupVersionResource]map[string]schema.GroupVersionKind // keys caches the computed key for each GroupResource keys map[schema.GroupResource]string mutex sync.RWMutex } var _ EquivalentResourceMapper = (*equivalentResourceRegistry)(nil) var _ EquivalentResourceRegistry = (*equivalentResourceRegistry)(nil) // NewEquivalentResourceRegistry creates a resource registry that considers all versions of a GroupResource to be equivalent. func NewEquivalentResourceRegistry() EquivalentResourceRegistry { return &equivalentResourceRegistry{} } // NewEquivalentResourceRegistryWithIdentity creates a resource mapper with a custom identity function. // If "" is returned by the function, GroupResource#String is used as the identity. // GroupResources with the same identity string are considered equivalent. func NewEquivalentResourceRegistryWithIdentity(keyFunc func(schema.GroupResource) string) EquivalentResourceRegistry { return &equivalentResourceRegistry{keyFunc: keyFunc} } func (r *equivalentResourceRegistry) EquivalentResourcesFor(resource schema.GroupVersionResource, subresource string) []schema.GroupVersionResource { r.mutex.RLock() defer r.mutex.RUnlock() return r.resources[r.keys[resource.GroupResource()]][subresource] } func (r *equivalentResourceRegistry) KindFor(resource schema.GroupVersionResource, subresource string) schema.GroupVersionKind { r.mutex.RLock() defer r.mutex.RUnlock() return r.kinds[resource][subresource] } func (r *equivalentResourceRegistry) RegisterKindFor(resource schema.GroupVersionResource, subresource string, kind schema.GroupVersionKind) { r.mutex.Lock() defer r.mutex.Unlock() if r.kinds == nil { r.kinds = map[schema.GroupVersionResource]map[string]schema.GroupVersionKind{} } if r.kinds[resource] == nil { r.kinds[resource] = map[string]schema.GroupVersionKind{} } r.kinds[resource][subresource] = kind // get the shared key of the parent resource key := "" gr := resource.GroupResource() if r.keyFunc != nil { key = r.keyFunc(gr) } if key == "" { key = gr.String() } if r.keys == nil { r.keys = map[schema.GroupResource]string{} } r.keys[gr] = key if r.resources == nil { r.resources = map[string]map[string][]schema.GroupVersionResource{} } if r.resources[key] == nil { r.resources[key] = map[string][]schema.GroupVersionResource{} } r.resources[key][subresource] = append(r.resources[key][subresource], resource) } golang-k8s-apimachinery-0.29.0/pkg/runtime/mapper_test.go000066400000000000000000000153071453143165200233530ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime import ( "reflect" "testing" "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestResourceMapper(t *testing.T) { gvr := func(g, v, r string) schema.GroupVersionResource { return schema.GroupVersionResource{Group: g, Version: v, Resource: r} } gvk := func(g, v, k string) schema.GroupVersionKind { return schema.GroupVersionKind{Group: g, Version: v, Kind: k} } kindsToRegister := []struct { gvr schema.GroupVersionResource subresource string gvk schema.GroupVersionKind }{ // pods {gvr("", "v1", "pods"), "", gvk("", "v1", "Pod")}, // pods/status {gvr("", "v1", "pods"), "status", gvk("", "v1", "Pod")}, // deployments {gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")}, {gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")}, {gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")}, {gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")}, // deployments/scale (omitted for apps/v1alpha1) {gvr("apps", "v1", "deployments"), "scale", gvk("", "", "Scale")}, {gvr("apps", "v1beta1", "deployments"), "scale", gvk("", "", "Scale")}, {gvr("extensions", "v1beta1", "deployments"), "scale", gvk("", "", "Scale")}, // deployments/status (omitted for apps/v1alpha1) {gvr("apps", "v1", "deployments"), "status", gvk("apps", "v1", "Deployment")}, {gvr("apps", "v1beta1", "deployments"), "status", gvk("apps", "v1beta1", "Deployment")}, {gvr("extensions", "v1beta1", "deployments"), "status", gvk("extensions", "v1beta1", "Deployment")}, } testcases := []struct { Name string IdentityFunc func(schema.GroupResource) string ResourcesForV1Deployment []schema.GroupVersionResource ResourcesForV1DeploymentScale []schema.GroupVersionResource ResourcesForV1DeploymentStatus []schema.GroupVersionResource }{ { Name: "no identityfunc", ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments")}, ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, }, { Name: "empty identityfunc", IdentityFunc: func(schema.GroupResource) string { return "" }, // same group ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments")}, ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, }, { Name: "common identityfunc", IdentityFunc: func(schema.GroupResource) string { return "x" }, // all resources are seen as equivalent ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("", "v1", "pods"), gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, // all resources with scale are seen as equivalent ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, // all resources with status are seen as equivalent ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("", "v1", "pods"), gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, }, { Name: "colocated deployments", IdentityFunc: func(resource schema.GroupResource) string { if resource.Resource == "deployments" { return "deployments" } return "" }, // all deployments are seen as equivalent ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, // all deployments with scale are seen as equivalent ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, // all deployments with status are seen as equivalent ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { mapper := NewEquivalentResourceRegistryWithIdentity(tc.IdentityFunc) // register for _, data := range kindsToRegister { mapper.RegisterKindFor(data.gvr, data.subresource, data.gvk) } // verify for _, data := range kindsToRegister { if kind := mapper.KindFor(data.gvr, data.subresource); kind != data.gvk { t.Errorf("KindFor(%#v, %v) returned %#v, expected %#v", data.gvr, data.subresource, kind, data.gvk) } } // Verify equivalents to primary resource if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), ""); !reflect.DeepEqual(resources, tc.ResourcesForV1Deployment) { t.Errorf("diff:\n%s", cmp.Diff(tc.ResourcesForV1Deployment, resources)) } // Verify equivalents to subresources if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), "scale"); !reflect.DeepEqual(resources, tc.ResourcesForV1DeploymentScale) { t.Errorf("diff:\n%s", cmp.Diff(tc.ResourcesForV1DeploymentScale, resources)) } if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), "status"); !reflect.DeepEqual(resources, tc.ResourcesForV1DeploymentStatus) { t.Errorf("diff:\n%s", cmp.Diff(tc.ResourcesForV1DeploymentStatus, resources)) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/negotiate.go000066400000000000000000000073331453143165200230070ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" ) // NegotiateError is returned when a ClientNegotiator is unable to locate // a serializer for the requested operation. type NegotiateError struct { ContentType string Stream bool } func (e NegotiateError) Error() string { if e.Stream { return fmt.Sprintf("no stream serializers registered for %s", e.ContentType) } return fmt.Sprintf("no serializers registered for %s", e.ContentType) } type clientNegotiator struct { serializer NegotiatedSerializer encode, decode GroupVersioner } func (n *clientNegotiator) Encoder(contentType string, params map[string]string) (Encoder, error) { // TODO: `pretty=1` is handled in NegotiateOutputMediaType, consider moving it to this method // if client negotiators truly need to use it mediaTypes := n.serializer.SupportedMediaTypes() info, ok := SerializerInfoForMediaType(mediaTypes, contentType) if !ok { if len(contentType) != 0 || len(mediaTypes) == 0 { return nil, NegotiateError{ContentType: contentType} } info = mediaTypes[0] } return n.serializer.EncoderForVersion(info.Serializer, n.encode), nil } func (n *clientNegotiator) Decoder(contentType string, params map[string]string) (Decoder, error) { mediaTypes := n.serializer.SupportedMediaTypes() info, ok := SerializerInfoForMediaType(mediaTypes, contentType) if !ok { if len(contentType) != 0 || len(mediaTypes) == 0 { return nil, NegotiateError{ContentType: contentType} } info = mediaTypes[0] } return n.serializer.DecoderToVersion(info.Serializer, n.decode), nil } func (n *clientNegotiator) StreamDecoder(contentType string, params map[string]string) (Decoder, Serializer, Framer, error) { mediaTypes := n.serializer.SupportedMediaTypes() info, ok := SerializerInfoForMediaType(mediaTypes, contentType) if !ok { if len(contentType) != 0 || len(mediaTypes) == 0 { return nil, nil, nil, NegotiateError{ContentType: contentType, Stream: true} } info = mediaTypes[0] } if info.StreamSerializer == nil { return nil, nil, nil, NegotiateError{ContentType: info.MediaType, Stream: true} } return n.serializer.DecoderToVersion(info.Serializer, n.decode), info.StreamSerializer.Serializer, info.StreamSerializer.Framer, nil } // NewClientNegotiator will attempt to retrieve the appropriate encoder, decoder, or // stream decoder for a given content type. Does not perform any conversion, but will // encode the object to the desired group, version, and kind. Use when creating a client. func NewClientNegotiator(serializer NegotiatedSerializer, gv schema.GroupVersion) ClientNegotiator { return &clientNegotiator{ serializer: serializer, encode: gv, } } type simpleNegotiatedSerializer struct { info SerializerInfo } func NewSimpleNegotiatedSerializer(info SerializerInfo) NegotiatedSerializer { return &simpleNegotiatedSerializer{info: info} } func (n *simpleNegotiatedSerializer) SupportedMediaTypes() []SerializerInfo { return []SerializerInfo{n.info} } func (n *simpleNegotiatedSerializer) EncoderForVersion(e Encoder, _ GroupVersioner) Encoder { return e } func (n *simpleNegotiatedSerializer) DecoderToVersion(d Decoder, _gv GroupVersioner) Decoder { return d } golang-k8s-apimachinery-0.29.0/pkg/runtime/register.go000066400000000000000000000022011453143165200226410ustar00rootroot00000000000000/* Copyright 2015 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 runtime import "k8s.io/apimachinery/pkg/runtime/schema" // SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta func (obj *TypeMeta) SetGroupVersionKind(gvk schema.GroupVersionKind) { obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() } // GroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind { return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) } func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj } golang-k8s-apimachinery-0.29.0/pkg/runtime/schema/000077500000000000000000000000001453143165200217335ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/schema/generated.pb.go000066400000000000000000000051131453143165200246200ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime/schema/generated.proto package schema import ( fmt "fmt" math "math" proto "github.com/gogo/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime/schema/generated.proto", fileDescriptor_0462724132518e0d) } var fileDescriptor_0462724132518e0d = []byte{ // 186 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0xce, 0xad, 0x8e, 0xc3, 0x30, 0x0c, 0xc0, 0xf1, 0x84, 0x1e, 0x3c, 0x78, 0xc0, 0xb0, 0xec, 0x62, 0x7a, 0xf8, 0xf0, 0xa4, 0xf1, 0xb1, 0xb4, 0xf5, 0xd2, 0x28, 0xca, 0x87, 0xd2, 0x64, 0xd2, 0xd8, 0x1e, 0x61, 0x8f, 0x55, 0x58, 0x58, 0xb8, 0x66, 0x2f, 0x32, 0x29, 0x2d, 0x18, 0x1c, 0xf3, 0x5f, 0xd6, 0xcf, 0xf2, 0xd7, 0xd1, 0xfc, 0x8d, 0x42, 0x7b, 0x34, 0xb9, 0xa5, 0xe8, 0x28, 0xd1, 0x88, 0x17, 0x72, 0xbd, 0x8f, 0xb8, 0x2f, 0x64, 0xd0, 0x56, 0x76, 0x83, 0x76, 0x14, 0xaf, 0x18, 0x8c, 0xc2, 0x98, 0x5d, 0xd2, 0x96, 0x70, 0xec, 0x06, 0xb2, 0x12, 0x15, 0x39, 0x8a, 0x32, 0x51, 0x2f, 0x42, 0xf4, 0xc9, 0x7f, 0x37, 0x9b, 0x13, 0xef, 0x4e, 0x04, 0xa3, 0xc4, 0xee, 0xc4, 0xe6, 0x7e, 0x7e, 0x95, 0x4e, 0x43, 0x6e, 0x45, 0xe7, 0x2d, 0x2a, 0xaf, 0x3c, 0x56, 0xde, 0xe6, 0x73, 0xad, 0x1a, 0x75, 0xda, 0xce, 0xfe, 0x1f, 0xa6, 0x15, 0xd8, 0xbc, 0x02, 0x5b, 0x56, 0x60, 0xb7, 0x02, 0x7c, 0x2a, 0xc0, 0xe7, 0x02, 0x7c, 0x29, 0xc0, 0x1f, 0x05, 0xf8, 0xfd, 0x09, 0xec, 0xd4, 0x7c, 0xf6, 0xf4, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x12, 0xb4, 0xae, 0x48, 0xf6, 0x00, 0x00, 0x00, } golang-k8s-apimachinery-0.29.0/pkg/runtime/schema/generated.proto000066400000000000000000000014751453143165200247650ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.runtime.schema; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/runtime/schema"; golang-k8s-apimachinery-0.29.0/pkg/runtime/schema/group_version.go000066400000000000000000000243741453143165200251750ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package schema import ( "fmt" "strings" ) // ParseResourceArg takes the common style of string which may be either `resource.group.com` or `resource.version.group.com` // and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended // but with a knowledge of all GroupVersions, calling code can take a very good guess. If there are only two segments, then // `*GroupVersionResource` is nil. // `resource.group.com` -> `group=com, version=group, resource=resource` and `group=group.com, resource=resource` func ParseResourceArg(arg string) (*GroupVersionResource, GroupResource) { var gvr *GroupVersionResource if strings.Count(arg, ".") >= 2 { s := strings.SplitN(arg, ".", 3) gvr = &GroupVersionResource{Group: s[2], Version: s[1], Resource: s[0]} } return gvr, ParseGroupResource(arg) } // ParseKindArg takes the common style of string which may be either `Kind.group.com` or `Kind.version.group.com` // and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended // but with a knowledge of all GroupKinds, calling code can take a very good guess. If there are only two segments, then // `*GroupVersionKind` is nil. // `Kind.group.com` -> `group=com, version=group, kind=Kind` and `group=group.com, kind=Kind` func ParseKindArg(arg string) (*GroupVersionKind, GroupKind) { var gvk *GroupVersionKind if strings.Count(arg, ".") >= 2 { s := strings.SplitN(arg, ".", 3) gvk = &GroupVersionKind{Group: s[2], Version: s[1], Kind: s[0]} } return gvk, ParseGroupKind(arg) } // GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types type GroupResource struct { Group string Resource string } func (gr GroupResource) WithVersion(version string) GroupVersionResource { return GroupVersionResource{Group: gr.Group, Version: version, Resource: gr.Resource} } func (gr GroupResource) Empty() bool { return len(gr.Group) == 0 && len(gr.Resource) == 0 } func (gr GroupResource) String() string { if len(gr.Group) == 0 { return gr.Resource } return gr.Resource + "." + gr.Group } func ParseGroupKind(gk string) GroupKind { i := strings.Index(gk, ".") if i == -1 { return GroupKind{Kind: gk} } return GroupKind{Group: gk[i+1:], Kind: gk[:i]} } // ParseGroupResource turns "resource.group" string into a GroupResource struct. Empty strings are allowed // for each field. func ParseGroupResource(gr string) GroupResource { if i := strings.Index(gr, "."); i >= 0 { return GroupResource{Group: gr[i+1:], Resource: gr[:i]} } return GroupResource{Resource: gr} } // GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion // to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling type GroupVersionResource struct { Group string Version string Resource string } func (gvr GroupVersionResource) Empty() bool { return len(gvr.Group) == 0 && len(gvr.Version) == 0 && len(gvr.Resource) == 0 } func (gvr GroupVersionResource) GroupResource() GroupResource { return GroupResource{Group: gvr.Group, Resource: gvr.Resource} } func (gvr GroupVersionResource) GroupVersion() GroupVersion { return GroupVersion{Group: gvr.Group, Version: gvr.Version} } func (gvr GroupVersionResource) String() string { return strings.Join([]string{gvr.Group, "/", gvr.Version, ", Resource=", gvr.Resource}, "") } // GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types type GroupKind struct { Group string Kind string } func (gk GroupKind) Empty() bool { return len(gk.Group) == 0 && len(gk.Kind) == 0 } func (gk GroupKind) WithVersion(version string) GroupVersionKind { return GroupVersionKind{Group: gk.Group, Version: version, Kind: gk.Kind} } func (gk GroupKind) String() string { if len(gk.Group) == 0 { return gk.Kind } return gk.Kind + "." + gk.Group } // GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion // to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling type GroupVersionKind struct { Group string Version string Kind string } // Empty returns true if group, version, and kind are empty func (gvk GroupVersionKind) Empty() bool { return len(gvk.Group) == 0 && len(gvk.Version) == 0 && len(gvk.Kind) == 0 } func (gvk GroupVersionKind) GroupKind() GroupKind { return GroupKind{Group: gvk.Group, Kind: gvk.Kind} } func (gvk GroupVersionKind) GroupVersion() GroupVersion { return GroupVersion{Group: gvk.Group, Version: gvk.Version} } func (gvk GroupVersionKind) String() string { return gvk.Group + "/" + gvk.Version + ", Kind=" + gvk.Kind } // GroupVersion contains the "group" and the "version", which uniquely identifies the API. type GroupVersion struct { Group string Version string } // Empty returns true if group and version are empty func (gv GroupVersion) Empty() bool { return len(gv.Group) == 0 && len(gv.Version) == 0 } // String puts "group" and "version" into a single "group/version" string. For the legacy v1 // it returns "v1". func (gv GroupVersion) String() string { if len(gv.Group) > 0 { return gv.Group + "/" + gv.Version } return gv.Version } // Identifier implements runtime.GroupVersioner interface. func (gv GroupVersion) Identifier() string { return gv.String() } // KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false // if none of the options match the group. It prefers a match to group and version over just group. // TODO: Move GroupVersion to a package under pkg/runtime, since it's used by scheme. // TODO: Introduce an adapter type between GroupVersion and runtime.GroupVersioner, and use LegacyCodec(GroupVersion) // in fewer places. func (gv GroupVersion) KindForGroupVersionKinds(kinds []GroupVersionKind) (target GroupVersionKind, ok bool) { for _, gvk := range kinds { if gvk.Group == gv.Group && gvk.Version == gv.Version { return gvk, true } } for _, gvk := range kinds { if gvk.Group == gv.Group { return gv.WithKind(gvk.Kind), true } } return GroupVersionKind{}, false } // ParseGroupVersion turns "group/version" string into a GroupVersion struct. It reports error // if it cannot parse the string. func ParseGroupVersion(gv string) (GroupVersion, error) { // this can be the internal version for the legacy kube types // TODO once we've cleared the last uses as strings, this special case should be removed. if (len(gv) == 0) || (gv == "/") { return GroupVersion{}, nil } switch strings.Count(gv, "/") { case 0: return GroupVersion{"", gv}, nil case 1: i := strings.Index(gv, "/") return GroupVersion{gv[:i], gv[i+1:]}, nil default: return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) } } // WithKind creates a GroupVersionKind based on the method receiver's GroupVersion and the passed Kind. func (gv GroupVersion) WithKind(kind string) GroupVersionKind { return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} } // WithResource creates a GroupVersionResource based on the method receiver's GroupVersion and the passed Resource. func (gv GroupVersion) WithResource(resource string) GroupVersionResource { return GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: resource} } // GroupVersions can be used to represent a set of desired group versions. // TODO: Move GroupVersions to a package under pkg/runtime, since it's used by scheme. // TODO: Introduce an adapter type between GroupVersions and runtime.GroupVersioner, and use LegacyCodec(GroupVersion) // in fewer places. type GroupVersions []GroupVersion // Identifier implements runtime.GroupVersioner interface. func (gvs GroupVersions) Identifier() string { groupVersions := make([]string, 0, len(gvs)) for i := range gvs { groupVersions = append(groupVersions, gvs[i].String()) } return fmt.Sprintf("[%s]", strings.Join(groupVersions, ",")) } // KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false // if none of the options match the group. func (gvs GroupVersions) KindForGroupVersionKinds(kinds []GroupVersionKind) (GroupVersionKind, bool) { var targets []GroupVersionKind for _, gv := range gvs { target, ok := gv.KindForGroupVersionKinds(kinds) if !ok { continue } targets = append(targets, target) } if len(targets) == 1 { return targets[0], true } if len(targets) > 1 { return bestMatch(kinds, targets), true } return GroupVersionKind{}, false } // bestMatch tries to pick best matching GroupVersionKind and falls back to the first // found if no exact match exists. func bestMatch(kinds []GroupVersionKind, targets []GroupVersionKind) GroupVersionKind { for _, gvk := range targets { for _, k := range kinds { if k == gvk { return k } } } return targets[0] } // ToAPIVersionAndKind is a convenience method for satisfying runtime.Object on types that // do not use TypeMeta. func (gvk GroupVersionKind) ToAPIVersionAndKind() (string, string) { if gvk.Empty() { return "", "" } return gvk.GroupVersion().String(), gvk.Kind } // FromAPIVersionAndKind returns a GVK representing the provided fields for types that // do not use TypeMeta. This method exists to support test types and legacy serializations // that have a distinct group and kind. // TODO: further reduce usage of this method. func FromAPIVersionAndKind(apiVersion, kind string) GroupVersionKind { if gv, err := ParseGroupVersion(apiVersion); err == nil { return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} } return GroupVersionKind{Kind: kind} } golang-k8s-apimachinery-0.29.0/pkg/runtime/schema/group_version_test.go000066400000000000000000000163351453143165200262320ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package schema import ( "testing" ) func TestGroupVersionParse(t *testing.T) { tests := []struct { input string out GroupVersion err func(error) bool }{ {input: "v1", out: GroupVersion{Version: "v1"}}, {input: "v2", out: GroupVersion{Version: "v2"}}, {input: "/v1", out: GroupVersion{Version: "v1"}}, {input: "v1/", out: GroupVersion{Group: "v1"}}, {input: "/v1/", err: func(err error) bool { return err.Error() == "unexpected GroupVersion string: /v1/" }}, {input: "v1/a", out: GroupVersion{Group: "v1", Version: "a"}}, } for i, test := range tests { out, err := ParseGroupVersion(test.input) if test.err == nil && err != nil || err == nil && test.err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } if test.err != nil && !test.err(err) { t.Errorf("%d: unexpected error: %v", i, err) continue } if out != test.out { t.Errorf("%d: unexpected output: %#v", i, out) } } } func TestGroupResourceParse(t *testing.T) { tests := []struct { input string out GroupResource }{ {input: "v1", out: GroupResource{Resource: "v1"}}, {input: ".v1", out: GroupResource{Group: "v1"}}, {input: "v1.", out: GroupResource{Resource: "v1"}}, {input: "v1.a", out: GroupResource{Group: "a", Resource: "v1"}}, {input: "b.v1.a", out: GroupResource{Group: "v1.a", Resource: "b"}}, } for i, test := range tests { out := ParseGroupResource(test.input) if out != test.out { t.Errorf("%d: unexpected output: %#v", i, out) } } } func TestParseResourceArg(t *testing.T) { tests := []struct { input string gvr *GroupVersionResource gr GroupResource }{ {input: "v1", gr: GroupResource{Resource: "v1"}}, {input: ".v1", gr: GroupResource{Group: "v1"}}, {input: "v1.", gr: GroupResource{Resource: "v1"}}, {input: "v1.a", gr: GroupResource{Group: "a", Resource: "v1"}}, {input: "b.v1.a", gvr: &GroupVersionResource{Group: "a", Version: "v1", Resource: "b"}, gr: GroupResource{Group: "v1.a", Resource: "b"}}, } for i, test := range tests { gvr, gr := ParseResourceArg(test.input) if (gvr != nil && test.gvr == nil) || (gvr == nil && test.gvr != nil) || (test.gvr != nil && *gvr != *test.gvr) { t.Errorf("%d: unexpected output: %#v", i, gvr) } if gr != test.gr { t.Errorf("%d: unexpected output: %#v", i, gr) } } } func TestKindForGroupVersionKinds(t *testing.T) { gvks := GroupVersions{ GroupVersion{Group: "batch", Version: "v1"}, GroupVersion{Group: "batch", Version: "v2alpha1"}, GroupVersion{Group: "policy", Version: "v1beta1"}, } cases := []struct { input []GroupVersionKind target GroupVersionKind ok bool }{ { input: []GroupVersionKind{{Group: "batch", Version: "v2alpha1", Kind: "ScheduledJob"}}, target: GroupVersionKind{Group: "batch", Version: "v2alpha1", Kind: "ScheduledJob"}, ok: true, }, { input: []GroupVersionKind{{Group: "batch", Version: "v3alpha1", Kind: "CronJob"}}, target: GroupVersionKind{Group: "batch", Version: "v1", Kind: "CronJob"}, ok: true, }, { input: []GroupVersionKind{{Group: "policy", Version: "v1beta1", Kind: "PodDisruptionBudget"}}, target: GroupVersionKind{Group: "policy", Version: "v1beta1", Kind: "PodDisruptionBudget"}, ok: true, }, { input: []GroupVersionKind{{Group: "apps", Version: "v1alpha1", Kind: "StatefulSet"}}, target: GroupVersionKind{}, ok: false, }, } for i, c := range cases { target, ok := gvks.KindForGroupVersionKinds(c.input) if c.target != target { t.Errorf("%d: unexpected target: %v, expected %v", i, target, c.target) } if c.ok != ok { t.Errorf("%d: unexpected ok: %v, expected %v", i, ok, c.ok) } } } func TestParseKindArg(t *testing.T) { tests := []struct { input string gvk *GroupVersionKind gk GroupKind }{ {input: "Pod", gk: GroupKind{Kind: "Pod"}}, {input: ".apps", gk: GroupKind{Group: "apps"}}, {input: "Pod.", gk: GroupKind{Kind: "Pod"}}, {input: "StatefulSet.apps", gk: GroupKind{Group: "apps", Kind: "StatefulSet"}}, {input: "StatefulSet.v1.apps", gvk: &GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}, gk: GroupKind{Group: "v1.apps", Kind: "StatefulSet"}}, } for i, test := range tests { t.Run(test.input, func(t *testing.T) { gvk, gk := ParseKindArg(test.input) if (gvk != nil && test.gvk == nil) || (gvk == nil && test.gvk != nil) || (test.gvk != nil && *gvk != *test.gvk) { t.Errorf("%d: expected output: %#v, got: %#v", i, test.gvk, gvk) } if gk != test.gk { t.Errorf("%d: expected output: %#v, got: %#v", i, test.gk, gk) } }) } } func TestParseGroupKind(t *testing.T) { tests := []struct { input string out GroupKind }{ {input: "Pod", out: GroupKind{Kind: "Pod"}}, {input: ".StatefulSet", out: GroupKind{Group: "StatefulSet"}}, {input: "StatefulSet.apps", out: GroupKind{Group: "apps", Kind: "StatefulSet"}}, } for i, test := range tests { t.Run(test.input, func(t *testing.T) { out := ParseGroupKind(test.input) if out != test.out { t.Errorf("%d: expected output: %#v, got: %#v", i, test.out, out) } }) } } func TestToAPIVersionAndKind(t *testing.T) { tests := []struct { desc string input GroupVersionKind GroupVersion string Kind string }{ { desc: "gvk object is not empty", input: GroupVersionKind{Version: "V1", Kind: "pod"}, GroupVersion: "V1", Kind: "pod", }, { desc: "gvk object is empty", input: GroupVersionKind{}, GroupVersion: "", Kind: "", }, } for i, test := range tests { version, kind := test.input.ToAPIVersionAndKind() if version != test.GroupVersion { t.Errorf("%d: expected version: %#v, got: %#v", i, test.GroupVersion, version) } if kind != test.Kind { t.Errorf("%d: expected kind: %#v, got: %#v", i, test.Kind, kind) } } } func TestBestMatch(t *testing.T) { tests := []struct { desc string kinds []GroupVersionKind targets []GroupVersionKind output GroupVersionKind }{ { desc: "targets and kinds have match items", kinds: []GroupVersionKind{{Version: "V1", Kind: "pod"}, {Version: "V2", Kind: "pod"}}, targets: []GroupVersionKind{{Version: "V1", Kind: "pod"}}, output: GroupVersionKind{Version: "V1", Kind: "pod"}, }, { desc: "targets and kinds do not have match items", kinds: []GroupVersionKind{{Version: "V1", Kind: "pod"}, {Version: "V2", Kind: "pod"}}, targets: []GroupVersionKind{{Version: "V3", Kind: "pod"}, {Version: "V4", Kind: "pod"}}, output: GroupVersionKind{Version: "V3", Kind: "pod"}, }, } for i, test := range tests { out := bestMatch(test.kinds, test.targets) if out != test.output { t.Errorf("%d: expected out: %#v, got: %#v", i, test.output, out) } } } golang-k8s-apimachinery-0.29.0/pkg/runtime/schema/interfaces.go000066400000000000000000000032551453143165200244120ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package schema // All objects that are serialized from a Scheme encode their type information. This interface is used // by serialization to set type information from the Scheme onto the serialized version of an object. // For objects that cannot be serialized or have unique requirements, this interface may be a no-op. type ObjectKind interface { // SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil // should clear the current setting. SetGroupVersionKind(kind GroupVersionKind) // GroupVersionKind returns the stored group, version, and kind of an object, or an empty struct // if the object does not expose or provide these fields. GroupVersionKind() GroupVersionKind } // EmptyObjectKind implements the ObjectKind interface as a noop var EmptyObjectKind = emptyObjectKind{} type emptyObjectKind struct{} // SetGroupVersionKind implements the ObjectKind interface func (emptyObjectKind) SetGroupVersionKind(gvk GroupVersionKind) {} // GroupVersionKind implements the ObjectKind interface func (emptyObjectKind) GroupVersionKind() GroupVersionKind { return GroupVersionKind{} } golang-k8s-apimachinery-0.29.0/pkg/runtime/scheme.go000066400000000000000000000623431453143165200222760ustar00rootroot00000000000000/* 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 runtime import ( "fmt" "reflect" "strings" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/naming" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" ) // Scheme defines methods for serializing and deserializing API objects, a type // registry for converting group, version, and kind information to and from Go // schemas, and mappings between Go schemas of different versions. A scheme is the // foundation for a versioned API and versioned configuration over time. // // In a Scheme, a Type is a particular Go struct, a Version is a point-in-time // identifier for a particular representation of that Type (typically backwards // compatible), a Kind is the unique name for that Type within the Version, and a // Group identifies a set of Versions, Kinds, and Types that evolve over time. An // Unversioned Type is one that is not yet formally bound to a type and is promised // to be backwards compatible (effectively a "v1" of a Type that does not expect // to break in the future). // // Schemes are not expected to change at runtime and are only threadsafe after // registration is complete. type Scheme struct { // gvkToType allows one to figure out the go type of an object with // the given version and name. gvkToType map[schema.GroupVersionKind]reflect.Type // typeToGVK allows one to find metadata for a given go object. // The reflect.Type we index by should *not* be a pointer. typeToGVK map[reflect.Type][]schema.GroupVersionKind // unversionedTypes are transformed without conversion in ConvertToVersion. unversionedTypes map[reflect.Type]schema.GroupVersionKind // unversionedKinds are the names of kinds that can be created in the context of any group // or version // TODO: resolve the status of unversioned types. unversionedKinds map[string]reflect.Type // Map from version and resource to the corresponding func to convert // resource field labels in that version to internal version. fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc // defaulterFuncs is a map to funcs to be called with an object to provide defaulting // the provided object must be a pointer. defaulterFuncs map[reflect.Type]func(interface{}) // converter stores all registered conversion functions. It also has // default converting behavior. converter *conversion.Converter // versionPriority is a map of groups to ordered lists of versions for those groups indicating the // default priorities of these versions as registered in the scheme versionPriority map[string][]string // observedVersions keeps track of the order we've seen versions during type registration observedVersions []schema.GroupVersion // schemeName is the name of this scheme. If you don't specify a name, the stack of the NewScheme caller will be used. // This is useful for error reporting to indicate the origin of the scheme. schemeName string } // FieldLabelConversionFunc converts a field selector to internal representation. type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error) // NewScheme creates a new Scheme. This scheme is pluggable by default. func NewScheme() *Scheme { s := &Scheme{ gvkToType: map[schema.GroupVersionKind]reflect.Type{}, typeToGVK: map[reflect.Type][]schema.GroupVersionKind{}, unversionedTypes: map[reflect.Type]schema.GroupVersionKind{}, unversionedKinds: map[string]reflect.Type{}, fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{}, defaulterFuncs: map[reflect.Type]func(interface{}){}, versionPriority: map[string][]string{}, schemeName: naming.GetNameFromCallsite(internalPackages...), } s.converter = conversion.NewConverter(nil) // Enable couple default conversions by default. utilruntime.Must(RegisterEmbeddedConversions(s)) utilruntime.Must(RegisterStringConversions(s)) return s } // Converter allows access to the converter for the scheme func (s *Scheme) Converter() *conversion.Converter { return s.converter } // AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules. // Whenever an object of this type is serialized, it is serialized with the provided group version and is not // converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an // API group and version that would never be updated. // // TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into // every version with particular schemas. Resolve this method at that point. func (s *Scheme) AddUnversionedTypes(version schema.GroupVersion, types ...Object) { s.addObservedVersion(version) s.AddKnownTypes(version, types...) for _, obj := range types { t := reflect.TypeOf(obj).Elem() gvk := version.WithKind(t.Name()) s.unversionedTypes[t] = gvk if old, ok := s.unversionedKinds[gvk.Kind]; ok && t != old { panic(fmt.Sprintf("%v.%v has already been registered as unversioned kind %q - kind name must be unique in scheme %q", old.PkgPath(), old.Name(), gvk, s.schemeName)) } s.unversionedKinds[gvk.Kind] = t } } // AddKnownTypes registers all types passed in 'types' as being members of version 'version'. // All objects passed to types should be pointers to structs. The name that go reports for // the struct becomes the "kind" field when encoding. Version may not be empty - use the // APIVersionInternal constant if you have a type that does not have a formal version. func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) { s.addObservedVersion(gv) for _, obj := range types { t := reflect.TypeOf(obj) if t.Kind() != reflect.Pointer { panic("All types must be pointers to structs.") } t = t.Elem() s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj) } } // AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should // be encoded as. Useful for testing when you don't want to make multiple packages to define // your structs. Version may not be empty - use the APIVersionInternal constant if you have a // type that does not have a formal version. func (s *Scheme) AddKnownTypeWithName(gvk schema.GroupVersionKind, obj Object) { s.addObservedVersion(gvk.GroupVersion()) t := reflect.TypeOf(obj) if len(gvk.Version) == 0 { panic(fmt.Sprintf("version is required on all types: %s %v", gvk, t)) } if t.Kind() != reflect.Pointer { panic("All types must be pointers to structs.") } t = t.Elem() if t.Kind() != reflect.Struct { panic("All types must be pointers to structs.") } if oldT, found := s.gvkToType[gvk]; found && oldT != t { panic(fmt.Sprintf("Double registration of different types for %v: old=%v.%v, new=%v.%v in scheme %q", gvk, oldT.PkgPath(), oldT.Name(), t.PkgPath(), t.Name(), s.schemeName)) } s.gvkToType[gvk] = t for _, existingGvk := range s.typeToGVK[t] { if existingGvk == gvk { return } } s.typeToGVK[t] = append(s.typeToGVK[t], gvk) // if the type implements DeepCopyInto(), register a self-conversion if m := reflect.ValueOf(obj).MethodByName("DeepCopyInto"); m.IsValid() && m.Type().NumIn() == 1 && m.Type().NumOut() == 0 && m.Type().In(0) == reflect.TypeOf(obj) { if err := s.AddGeneratedConversionFunc(obj, obj, func(a, b interface{}, scope conversion.Scope) error { // copy a to b reflect.ValueOf(a).MethodByName("DeepCopyInto").Call([]reflect.Value{reflect.ValueOf(b)}) // clear TypeMeta to match legacy reflective conversion b.(Object).GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) return nil }); err != nil { panic(err) } } } // KnownTypes returns the types known for the given version. func (s *Scheme) KnownTypes(gv schema.GroupVersion) map[string]reflect.Type { types := make(map[string]reflect.Type) for gvk, t := range s.gvkToType { if gv != gvk.GroupVersion() { continue } types[gvk.Kind] = t } return types } // VersionsForGroupKind returns the versions that a particular GroupKind can be converted to within the given group. // A GroupKind might be converted to a different group. That information is available in EquivalentResourceMapper. func (s *Scheme) VersionsForGroupKind(gk schema.GroupKind) []schema.GroupVersion { availableVersions := []schema.GroupVersion{} for gvk := range s.gvkToType { if gk != gvk.GroupKind() { continue } availableVersions = append(availableVersions, gvk.GroupVersion()) } // order the return for stability ret := []schema.GroupVersion{} for _, version := range s.PrioritizedVersionsForGroup(gk.Group) { for _, availableVersion := range availableVersions { if version != availableVersion { continue } ret = append(ret, availableVersion) } } return ret } // AllKnownTypes returns the all known types. func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type { return s.gvkToType } // ObjectKinds returns all possible group,version,kind of the go object, true if the // object is considered unversioned, or an error if it's not a pointer or is unregistered. func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) { // Unstructured objects are always considered to have their declared GVK if _, ok := obj.(Unstructured); ok { // we require that the GVK be populated in order to recognize the object gvk := obj.GetObjectKind().GroupVersionKind() if len(gvk.Kind) == 0 { return nil, false, NewMissingKindErr("unstructured object has no kind") } if len(gvk.Version) == 0 { return nil, false, NewMissingVersionErr("unstructured object has no version") } return []schema.GroupVersionKind{gvk}, false, nil } v, err := conversion.EnforcePtr(obj) if err != nil { return nil, false, err } t := v.Type() gvks, ok := s.typeToGVK[t] if !ok { return nil, false, NewNotRegisteredErrForType(s.schemeName, t) } _, unversionedType := s.unversionedTypes[t] return gvks, unversionedType, nil } // Recognizes returns true if the scheme is able to handle the provided group,version,kind // of an object. func (s *Scheme) Recognizes(gvk schema.GroupVersionKind) bool { _, exists := s.gvkToType[gvk] return exists } func (s *Scheme) IsUnversioned(obj Object) (bool, bool) { v, err := conversion.EnforcePtr(obj) if err != nil { return false, false } t := v.Type() if _, ok := s.typeToGVK[t]; !ok { return false, false } _, ok := s.unversionedTypes[t] return ok, true } // New returns a new API object of the given version and name, or an error if it hasn't // been registered. The version and kind fields must be specified. func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) { if t, exists := s.gvkToType[kind]; exists { return reflect.New(t).Interface().(Object), nil } if t, exists := s.unversionedKinds[kind.Kind]; exists { return reflect.New(t).Interface().(Object), nil } return nil, NewNotRegisteredErrForKind(s.schemeName, kind) } // AddIgnoredConversionType identifies a pair of types that should be skipped by // conversion (because the data inside them is explicitly dropped during // conversion). func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error { return s.converter.RegisterIgnoredConversion(from, to) } // AddConversionFunc registers a function that converts between a and b by passing objects of those // types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce // any other guarantee. func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error { return s.converter.RegisterUntypedConversionFunc(a, b, fn) } // AddGeneratedConversionFunc registers a function that converts between a and b by passing objects of those // types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce // any other guarantee. func (s *Scheme) AddGeneratedConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error { return s.converter.RegisterGeneratedUntypedConversionFunc(a, b, fn) } // AddFieldLabelConversionFunc adds a conversion function to convert field selectors // of the given kind from the given version to internal version representation. func (s *Scheme) AddFieldLabelConversionFunc(gvk schema.GroupVersionKind, conversionFunc FieldLabelConversionFunc) error { s.fieldLabelConversionFuncs[gvk] = conversionFunc return nil } // AddTypeDefaultingFunc registers a function that is passed a pointer to an // object and can default fields on the object. These functions will be invoked // when Default() is called. The function will never be called unless the // defaulted object matches srcType. If this function is invoked twice with the // same srcType, the fn passed to the later call will be used instead. func (s *Scheme) AddTypeDefaultingFunc(srcType Object, fn func(interface{})) { s.defaulterFuncs[reflect.TypeOf(srcType)] = fn } // Default sets defaults on the provided Object. func (s *Scheme) Default(src Object) { if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok { fn(src) } } // Convert will attempt to convert in into out. Both must be pointers. For easy // testing of conversion functions. Returns an error if the conversion isn't // possible. You can call this with types that haven't been registered (for example, // a to test conversion of types that are nested within registered types). The // context interface is passed to the convertor. Convert also supports Unstructured // types and will convert them intelligently. func (s *Scheme) Convert(in, out interface{}, context interface{}) error { unstructuredIn, okIn := in.(Unstructured) unstructuredOut, okOut := out.(Unstructured) switch { case okIn && okOut: // converting unstructured input to an unstructured output is a straight copy - unstructured // is a "smart holder" and the contents are passed by reference between the two objects unstructuredOut.SetUnstructuredContent(unstructuredIn.UnstructuredContent()) return nil case okOut: // if the output is an unstructured object, use the standard Go type to unstructured // conversion. The object must not be internal. obj, ok := in.(Object) if !ok { return fmt.Errorf("unable to convert object type %T to Unstructured, must be a runtime.Object", in) } gvks, unversioned, err := s.ObjectKinds(obj) if err != nil { return err } gvk := gvks[0] // if no conversion is necessary, convert immediately if unversioned || gvk.Version != APIVersionInternal { content, err := DefaultUnstructuredConverter.ToUnstructured(in) if err != nil { return err } unstructuredOut.SetUnstructuredContent(content) unstructuredOut.GetObjectKind().SetGroupVersionKind(gvk) return nil } // attempt to convert the object to an external version first. target, ok := context.(GroupVersioner) if !ok { return fmt.Errorf("unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to", in) } // Convert is implicitly unsafe, so we don't need to perform a safe conversion versioned, err := s.UnsafeConvertToVersion(obj, target) if err != nil { return err } content, err := DefaultUnstructuredConverter.ToUnstructured(versioned) if err != nil { return err } unstructuredOut.SetUnstructuredContent(content) return nil case okIn: // converting an unstructured object to any type is modeled by first converting // the input to a versioned type, then running standard conversions typed, err := s.unstructuredToTyped(unstructuredIn) if err != nil { return err } in = typed } meta := s.generateConvertMeta(in) meta.Context = context return s.converter.Convert(in, out, meta) } // ConvertFieldLabel alters the given field label and value for an kind field selector from // versioned representation to an unversioned one or returns an error. func (s *Scheme) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { conversionFunc, ok := s.fieldLabelConversionFuncs[gvk] if !ok { return DefaultMetaV1FieldSelectorConversion(label, value) } return conversionFunc(label, value) } // ConvertToVersion attempts to convert an input object to its matching Kind in another // version within this scheme. Will return an error if the provided version does not // contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also // return an error if the conversion does not result in a valid Object being // returned. Passes target down to the conversion methods as the Context on the scope. func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) { return s.convertToVersion(true, in, target) } // UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible, // but does not guarantee the output object does not share fields with the input object. It attempts to be as // efficient as possible when doing conversion. func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) { return s.convertToVersion(false, in, target) } // convertToVersion handles conversion with an optional copy. func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) { var t reflect.Type if u, ok := in.(Unstructured); ok { typed, err := s.unstructuredToTyped(u) if err != nil { return nil, err } in = typed // unstructuredToTyped returns an Object, which must be a pointer to a struct. t = reflect.TypeOf(in).Elem() } else { // determine the incoming kinds with as few allocations as possible. t = reflect.TypeOf(in) if t.Kind() != reflect.Pointer { return nil, fmt.Errorf("only pointer types may be converted: %v", t) } t = t.Elem() if t.Kind() != reflect.Struct { return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t) } } kinds, ok := s.typeToGVK[t] if !ok || len(kinds) == 0 { return nil, NewNotRegisteredErrForType(s.schemeName, t) } gvk, ok := target.KindForGroupVersionKinds(kinds) if !ok { // try to see if this type is listed as unversioned (for legacy support) // TODO: when we move to server API versions, we should completely remove the unversioned concept if unversionedKind, ok := s.unversionedTypes[t]; ok { if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok { return copyAndSetTargetKind(copy, in, gvk) } return copyAndSetTargetKind(copy, in, unversionedKind) } return nil, NewNotRegisteredErrForTarget(s.schemeName, t, target) } // target wants to use the existing type, set kind and return (no conversion necessary) for _, kind := range kinds { if gvk == kind { return copyAndSetTargetKind(copy, in, gvk) } } // type is unversioned, no conversion necessary if unversionedKind, ok := s.unversionedTypes[t]; ok { if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok { return copyAndSetTargetKind(copy, in, gvk) } return copyAndSetTargetKind(copy, in, unversionedKind) } out, err := s.New(gvk) if err != nil { return nil, err } if copy { in = in.DeepCopyObject() } meta := s.generateConvertMeta(in) meta.Context = target if err := s.converter.Convert(in, out, meta); err != nil { return nil, err } setTargetKind(out, gvk) return out, nil } // unstructuredToTyped attempts to transform an unstructured object to a typed // object if possible. It will return an error if conversion is not possible, or the versioned // Go form of the object. Note that this conversion will lose fields. func (s *Scheme) unstructuredToTyped(in Unstructured) (Object, error) { // the type must be something we recognize gvks, _, err := s.ObjectKinds(in) if err != nil { return nil, err } typed, err := s.New(gvks[0]) if err != nil { return nil, err } if err := DefaultUnstructuredConverter.FromUnstructured(in.UnstructuredContent(), typed); err != nil { return nil, fmt.Errorf("unable to convert unstructured object to %v: %v", gvks[0], err) } return typed, nil } // generateConvertMeta constructs the meta value we pass to Convert. func (s *Scheme) generateConvertMeta(in interface{}) *conversion.Meta { return s.converter.DefaultMeta(reflect.TypeOf(in)) } // copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful. func copyAndSetTargetKind(copy bool, obj Object, kind schema.GroupVersionKind) (Object, error) { if copy { obj = obj.DeepCopyObject() } setTargetKind(obj, kind) return obj, nil } // setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version. func setTargetKind(obj Object, kind schema.GroupVersionKind) { if kind.Version == APIVersionInternal { // internal is a special case // TODO: look at removing the need to special case this obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) return } obj.GetObjectKind().SetGroupVersionKind(kind) } // SetVersionPriority allows specifying a precise order of priority. All specified versions must be in the same group, // and the specified order overwrites any previously specified order for this group func (s *Scheme) SetVersionPriority(versions ...schema.GroupVersion) error { groups := sets.String{} order := []string{} for _, version := range versions { if len(version.Version) == 0 || version.Version == APIVersionInternal { return fmt.Errorf("internal versions cannot be prioritized: %v", version) } groups.Insert(version.Group) order = append(order, version.Version) } if len(groups) != 1 { return fmt.Errorf("must register versions for exactly one group: %v", strings.Join(groups.List(), ", ")) } s.versionPriority[groups.List()[0]] = order return nil } // PrioritizedVersionsForGroup returns versions for a single group in priority order func (s *Scheme) PrioritizedVersionsForGroup(group string) []schema.GroupVersion { ret := []schema.GroupVersion{} for _, version := range s.versionPriority[group] { ret = append(ret, schema.GroupVersion{Group: group, Version: version}) } for _, observedVersion := range s.observedVersions { if observedVersion.Group != group { continue } found := false for _, existing := range ret { if existing == observedVersion { found = true break } } if !found { ret = append(ret, observedVersion) } } return ret } // PrioritizedVersionsAllGroups returns all known versions in their priority order. Groups are random, but // versions for a single group are prioritized func (s *Scheme) PrioritizedVersionsAllGroups() []schema.GroupVersion { ret := []schema.GroupVersion{} for group, versions := range s.versionPriority { for _, version := range versions { ret = append(ret, schema.GroupVersion{Group: group, Version: version}) } } for _, observedVersion := range s.observedVersions { found := false for _, existing := range ret { if existing == observedVersion { found = true break } } if !found { ret = append(ret, observedVersion) } } return ret } // PreferredVersionAllGroups returns the most preferred version for every group. // group ordering is random. func (s *Scheme) PreferredVersionAllGroups() []schema.GroupVersion { ret := []schema.GroupVersion{} for group, versions := range s.versionPriority { for _, version := range versions { ret = append(ret, schema.GroupVersion{Group: group, Version: version}) break } } for _, observedVersion := range s.observedVersions { found := false for _, existing := range ret { if existing.Group == observedVersion.Group { found = true break } } if !found { ret = append(ret, observedVersion) } } return ret } // IsGroupRegistered returns true if types for the group have been registered with the scheme func (s *Scheme) IsGroupRegistered(group string) bool { for _, observedVersion := range s.observedVersions { if observedVersion.Group == group { return true } } return false } // IsVersionRegistered returns true if types for the version have been registered with the scheme func (s *Scheme) IsVersionRegistered(version schema.GroupVersion) bool { for _, observedVersion := range s.observedVersions { if observedVersion == version { return true } } return false } func (s *Scheme) addObservedVersion(version schema.GroupVersion) { if len(version.Version) == 0 || version.Version == APIVersionInternal { return } for _, observedVersion := range s.observedVersions { if observedVersion == version { return } } s.observedVersions = append(s.observedVersions, version) } func (s *Scheme) Name() string { return s.schemeName } // internalPackages are packages that ignored when creating a default reflector name. These packages are in the common // call chains to NewReflector, so they'd be low entropy names for reflectors var internalPackages = []string{"k8s.io/apimachinery/pkg/runtime/scheme.go"} golang-k8s-apimachinery-0.29.0/pkg/runtime/scheme_builder.go000066400000000000000000000027631453143165200240040ustar00rootroot00000000000000/* Copyright 2015 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 runtime // SchemeBuilder collects functions that add things to a scheme. It's to allow // code to compile without explicitly referencing generated types. You should // declare one in each package that will have generated deep copy or conversion // functions. type SchemeBuilder []func(*Scheme) error // AddToScheme applies all the stored functions to the scheme. A non-nil error // indicates that one function failed and the attempt was abandoned. func (sb *SchemeBuilder) AddToScheme(s *Scheme) error { for _, f := range *sb { if err := f(s); err != nil { return err } } return nil } // Register adds a scheme setup function to the list. func (sb *SchemeBuilder) Register(funcs ...func(*Scheme) error) { for _, f := range funcs { *sb = append(*sb, f) } } // NewSchemeBuilder calls Register for you. func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder { var sb SchemeBuilder sb.Register(funcs...) return sb } golang-k8s-apimachinery-0.29.0/pkg/runtime/scheme_test.go000066400000000000000000001143541453143165200233350ustar00rootroot00000000000000/* 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 runtime_test import ( "fmt" "reflect" "strings" "testing" "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" "k8s.io/apimachinery/pkg/util/diff" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) type testConversions struct { internalToExternalCalls int externalToInternalCalls int } func (c *testConversions) internalToExternalSimple(in *runtimetesting.InternalSimple, out *runtimetesting.ExternalSimple, scope conversion.Scope) error { out.TypeMeta = in.TypeMeta out.TestString = in.TestString c.internalToExternalCalls++ return nil } func (c *testConversions) externalToInternalSimple(in *runtimetesting.ExternalSimple, out *runtimetesting.InternalSimple, scope conversion.Scope) error { out.TypeMeta = in.TypeMeta out.TestString = in.TestString c.externalToInternalCalls++ return nil } func (c *testConversions) registerConversions(s *runtime.Scheme) error { if err := s.AddConversionFunc((*runtimetesting.InternalSimple)(nil), (*runtimetesting.ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { return c.internalToExternalSimple(a.(*runtimetesting.InternalSimple), b.(*runtimetesting.ExternalSimple), scope) }); err != nil { return err } if err := s.AddConversionFunc((*runtimetesting.ExternalSimple)(nil), (*runtimetesting.InternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { return c.externalToInternalSimple(a.(*runtimetesting.ExternalSimple), b.(*runtimetesting.InternalSimple), scope) }); err != nil { return err } return nil } func TestScheme(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} internalGVK := internalGV.WithKind("Simple") externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} externalGVK := externalGV.WithKind("Simple") scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) utilruntime.Must(runtimetesting.RegisterConversions(scheme)) // If set, would clear TypeMeta during conversion. //scheme.AddIgnoredConversionType(&TypeMeta{}, &TypeMeta{}) // test that scheme is an ObjectTyper var _ runtime.ObjectTyper = scheme conversions := &testConversions{ internalToExternalCalls: 0, externalToInternalCalls: 0, } // Register functions to verify that scope.Meta() gets set correctly. utilruntime.Must(conversions.registerConversions(scheme)) t.Run("Encode, Decode, DecodeInto, and DecodeToVersion", func(t *testing.T) { simple := &runtimetesting.InternalSimple{ TestString: "foo", } codecs := serializer.NewCodecFactory(scheme) codec := codecs.LegacyCodec(externalGV) info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) jsonserializer := info.Serializer obj := runtime.Object(simple) data, err := runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } obj2, err := runtime.Decode(codec, data) if err != nil { t.Fatal(err) } if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { t.Fatalf("Got wrong type") } if e, a := simple, obj2; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } obj3 := &runtimetesting.InternalSimple{} if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Fatal(err) } // clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion // does not automatically clear TypeMeta anymore). simple.TypeMeta = runtime.TypeMeta{Kind: "Simple", APIVersion: externalGV.String()} if e, a := simple, obj3; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } obj4, err := runtime.Decode(jsonserializer, data) if err != nil { t.Fatal(err) } if _, ok := obj4.(*runtimetesting.ExternalSimple); !ok { t.Fatalf("Got wrong type") } }) t.Run("Convert", func(t *testing.T) { simple := &runtimetesting.InternalSimple{ TestString: "foo", } external := &runtimetesting.ExternalSimple{} if err := scheme.Convert(simple, external, nil); err != nil { t.Fatalf("Unexpected error: %v", err) } if e, a := simple.TestString, external.TestString; e != a { t.Errorf("Expected %q, got %q", e, a) } }) t.Run("Convert internal to unstructured", func(t *testing.T) { simple := &runtimetesting.InternalSimple{ TestString: "foo", } unstructuredObj := &runtimetesting.Unstructured{} err := scheme.Convert(simple, unstructuredObj, nil) if err == nil || !strings.Contains(err.Error(), "to Unstructured without providing a preferred version to convert to") { t.Fatalf("Unexpected non-error: %v", err) } if err := scheme.Convert(simple, unstructuredObj, externalGV); err != nil { t.Fatalf("Unexpected error: %v", err) } if e, a := simple.TestString, unstructuredObj.Object["testString"].(string); e != a { t.Errorf("Expected %q, got %q", e, a) } if e := unstructuredObj.GetObjectKind().GroupVersionKind(); e != externalGVK { t.Errorf("Unexpected object kind: %#v", e) } if gvks, unversioned, err := scheme.ObjectKinds(unstructuredObj); err != nil || gvks[0] != externalGVK || unversioned { t.Errorf("Scheme did not recognize unversioned: %v, %#v %t", err, gvks, unversioned) } }) t.Run("Convert external to unstructured", func(t *testing.T) { unstructuredObj := &runtimetesting.Unstructured{} external := &runtimetesting.ExternalSimple{ TestString: "foo", } if err := scheme.Convert(external, unstructuredObj, nil); err != nil { t.Fatalf("Unexpected error: %v", err) } if e, a := external.TestString, unstructuredObj.Object["testString"].(string); e != a { t.Errorf("Expected %q, got %q", e, a) } if e := unstructuredObj.GetObjectKind().GroupVersionKind(); e != externalGVK { t.Errorf("Unexpected object kind: %#v", e) } }) t.Run("Convert unstructured to unstructured", func(t *testing.T) { uIn := &runtimetesting.Unstructured{Object: map[string]interface{}{ "test": []interface{}{"other", "test"}, }} uOut := &runtimetesting.Unstructured{} if err := scheme.Convert(uIn, uOut, nil); err != nil { t.Fatalf("Unexpected error: %v", err) } if !reflect.DeepEqual(uIn.Object, uOut.Object) { t.Errorf("Unexpected object contents: %#v", uOut.Object) } }) t.Run("Convert unstructured to structured", func(t *testing.T) { unstructuredObj := &runtimetesting.Unstructured{ Object: map[string]interface{}{ "testString": "bla", }, } unstructuredObj.SetGroupVersionKind(externalGV.WithKind("Simple")) externalOut := &runtimetesting.ExternalSimple{} if err := scheme.Convert(unstructuredObj, externalOut, nil); err != nil { t.Fatalf("Unexpected error: %v", err) } if externalOut.TestString != "bla" { t.Errorf("Unexpected object contents: %#v", externalOut) } }) t.Run("Encode and Convert should each have caused an increment", func(t *testing.T) { if e, a := 3, conversions.internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } }) t.Run("DecodeInto and Decode should each have caused an increment because of a conversion", func(t *testing.T) { if e, a := 2, conversions.externalToInternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } }) t.Run("Verify that unstructured types must have V and K set", func(t *testing.T) { emptyObj := &runtimetesting.Unstructured{Object: make(map[string]interface{})} if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingKind(err) { t.Errorf("unexpected error: %v", err) } emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test"}) if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingVersion(err) { t.Errorf("unexpected error: %v", err) } emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test", Version: "v1"}) if _, _, err := scheme.ObjectKinds(emptyObj); err != nil { t.Errorf("unexpected error: %v", err) } }) } func TestBadJSONRejection(t *testing.T) { scheme := runtime.NewScheme() codecs := serializer.NewCodecFactory(scheme) info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) jsonserializer := info.Serializer badJSONMissingKind := []byte(`{ }`) if _, err := runtime.Decode(jsonserializer, badJSONMissingKind); err == nil { t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind) } badJSONUnknownType := []byte(`{"kind": "bar"}`) if _, err1 := runtime.Decode(jsonserializer, badJSONUnknownType); err1 == nil { t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType) } } func TestExternalToInternalMapping(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &runtimetesting.InternalOptionalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &runtimetesting.ExternalOptionalExtensionType{}) utilruntime.Must(runtimetesting.RegisterConversions(scheme)) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) table := []struct { obj runtime.Object encoded string }{ { &runtimetesting.InternalOptionalExtensionType{Extension: nil}, `{"kind":"OptionalExtensionType","apiVersion":"` + externalGV.String() + `"}`, }, } for i, item := range table { gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) { t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) } } } func TestExtensionMapping(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("ExtensionType"), &runtimetesting.InternalExtensionType{}) scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &runtimetesting.InternalOptionalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("ExtensionType"), &runtimetesting.ExternalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &runtimetesting.ExternalOptionalExtensionType{}) // register external first when the object is the same in both schemes, so ObjectVersionAndKind reports the // external version. scheme.AddKnownTypeWithName(externalGV.WithKind("A"), &runtimetesting.ExtensionA{}) scheme.AddKnownTypeWithName(externalGV.WithKind("B"), &runtimetesting.ExtensionB{}) scheme.AddKnownTypeWithName(internalGV.WithKind("A"), &runtimetesting.ExtensionA{}) scheme.AddKnownTypeWithName(internalGV.WithKind("B"), &runtimetesting.ExtensionB{}) utilruntime.Must(runtimetesting.RegisterConversions(scheme)) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) table := []struct { obj runtime.Object expected runtime.Object encoded string }{ { &runtimetesting.InternalExtensionType{ Extension: runtime.NewEncodable(codec, &runtimetesting.ExtensionA{TestString: "foo"}), }, &runtimetesting.InternalExtensionType{ Extension: &runtime.Unknown{ Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), ContentType: runtime.ContentTypeJSON, }, }, // apiVersion is set in the serialized object for easier consumption by clients `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}} `, }, { &runtimetesting.InternalExtensionType{Extension: runtime.NewEncodable(codec, &runtimetesting.ExtensionB{TestString: "bar"})}, &runtimetesting.InternalExtensionType{ Extension: &runtime.Unknown{ Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), ContentType: runtime.ContentTypeJSON, }, }, // apiVersion is set in the serialized object for easier consumption by clients `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}} `, }, { &runtimetesting.InternalExtensionType{Extension: nil}, &runtimetesting.InternalExtensionType{ Extension: nil, }, `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":null} `, }, } for i, item := range table { gotEncoded, err := runtime.Encode(codec, item.obj) if err != nil { t.Errorf("unexpected error '%v' (%#v)", err, item.obj) } else if e, a := item.encoded, string(gotEncoded); e != a { t.Errorf("expected\n%#v\ngot\n%#v\n", e, a) } gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.expected, gotDecoded; !reflect.DeepEqual(e, a) { t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) } } } func TestEncode(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} internalGVK := internalGV.WithKind("Simple") externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} externalGVK := externalGV.WithKind("Simple") scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) utilruntime.Must(runtimetesting.RegisterConversions(scheme)) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) test := &runtimetesting.InternalSimple{ TestString: "I'm the same", } obj := runtime.Object(test) data, err := runtime.Encode(codec, obj) obj2, gvk, err2 := codec.Decode(data, nil, nil) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'", err, err2) } if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { t.Fatalf("Got wrong type") } if !reflect.DeepEqual(obj2, test) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) } if *gvk != externalGVK { t.Errorf("unexpected gvk returned by decode: %#v", *gvk) } } func TestUnversionedTypes(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} internalGVK := internalGV.WithKind("Simple") externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} externalGVK := externalGV.WithKind("Simple") otherGV := schema.GroupVersion{Group: "group", Version: "other"} scheme := runtime.NewScheme() scheme.AddUnversionedTypes(externalGV, &runtimetesting.InternalSimple{}) scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) scheme.AddKnownTypeWithName(otherGV.WithKind("Simple"), &runtimetesting.ExternalSimple{}) utilruntime.Must(runtimetesting.RegisterConversions(scheme)) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) if unv, ok := scheme.IsUnversioned(&runtimetesting.InternalSimple{}); !unv || !ok { t.Fatalf("type not unversioned and in scheme: %t %t", unv, ok) } kinds, _, err := scheme.ObjectKinds(&runtimetesting.InternalSimple{}) if err != nil { t.Fatal(err) } kind := kinds[0] if kind != externalGV.WithKind("InternalSimple") { t.Fatalf("unexpected: %#v", kind) } test := &runtimetesting.InternalSimple{ TestString: "I'm the same", } obj := runtime.Object(test) data, err := runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } obj2, gvk, err := codec.Decode(data, nil, nil) if err != nil { t.Fatal(err) } if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { t.Fatalf("Got wrong type") } if !reflect.DeepEqual(obj2, test) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) } // object is serialized as an unversioned object (in the group and version it was defined in) if *gvk != externalGV.WithKind("InternalSimple") { t.Errorf("unexpected gvk returned by decode: %#v", *gvk) } // when serialized to a different group, the object is kept in its preferred name codec = serializer.NewCodecFactory(scheme).LegacyCodec(otherGV) data, err = runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } if string(data) != `{"apiVersion":"test.group/testExternal","kind":"InternalSimple","testString":"I'm the same"}`+"\n" { t.Errorf("unexpected data: %s", data) } } // Returns a new Scheme set up with the test objects. func GetTestScheme() *runtime.Scheme { internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Version: "v1"} alternateExternalGV := schema.GroupVersion{Group: "custom", Version: "v1"} alternateInternalGV := schema.GroupVersion{Group: "custom", Version: runtime.APIVersionInternal} differentExternalGV := schema.GroupVersion{Group: "other", Version: "v2"} s := runtime.NewScheme() // Ordinarily, we wouldn't add TestType2, but because this is a test and // both types are from the same package, we need to get it into the system // so that converter will match it with ExternalType2. s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}, &runtimetesting.TestType2{}, &runtimetesting.ExternalInternalSame{}) s.AddKnownTypes(externalGV, &runtimetesting.ExternalInternalSame{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &runtimetesting.ExternalTestType2{}) s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType4"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(alternateInternalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType5"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(differentExternalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) s.AddUnversionedTypes(externalGV, &runtimetesting.UnversionedType{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) return s } func TestKnownTypes(t *testing.T) { s := GetTestScheme() if len(s.KnownTypes(schema.GroupVersion{Group: "group", Version: "v2"})) != 0 { t.Errorf("should have no known types for v2") } types := s.KnownTypes(schema.GroupVersion{Version: "v1"}) for _, s := range []string{"TestType1", "TestType2", "TestType3", "ExternalInternalSame"} { if _, ok := types[s]; !ok { t.Errorf("missing type %q", s) } } } func TestAddKnownTypesIdemPotent(t *testing.T) { s := runtime.NewScheme() gv := schema.GroupVersion{Group: "foo", Version: "v1"} s.AddKnownTypes(gv, &runtimetesting.InternalSimple{}) s.AddKnownTypes(gv, &runtimetesting.InternalSimple{}) if len(s.KnownTypes(gv)) != 1 { t.Errorf("expected only one %v type after double registration", gv) } if len(s.AllKnownTypes()) != 1 { t.Errorf("expected only one type after double registration") } s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) if len(s.KnownTypes(gv)) != 1 { t.Errorf("expected only one %v type after double registration with custom name", gv) } if len(s.AllKnownTypes()) != 1 { t.Errorf("expected only one type after double registration with custom name") } s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) if len(s.KnownTypes(gv)) != 1 { t.Errorf("expected only one %v type after double registration with custom name", gv) } if len(s.AllKnownTypes()) != 1 { t.Errorf("expected only one type after double registration with custom name") } kinds, _, err := s.ObjectKinds(&runtimetesting.InternalSimple{}) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(kinds) != 1 { t.Errorf("expected only one kind for InternalSimple after double registration") } } // redefine InternalSimple with the same name, but obviously as a different type than in runtimetesting type InternalSimple struct { runtime.TypeMeta `json:",inline"` TestString string `json:"testString"` } func (s *InternalSimple) DeepCopyObject() runtime.Object { return nil } func TestConflictingAddKnownTypes(t *testing.T) { s := runtime.NewScheme() gv := schema.GroupVersion{Group: "foo", Version: "v1"} panicked := make(chan bool) go func() { defer func() { if recover() != nil { panicked <- true } }() s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.ExternalSimple{}) panicked <- false }() if !<-panicked { t.Errorf("Expected AddKnownTypesWithName to panic with conflicting type registrations") } go func() { defer func() { if recover() != nil { panicked <- true } }() s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) s.AddUnversionedTypes(gv, &InternalSimple{}) panicked <- false }() if !<-panicked { t.Errorf("Expected AddUnversionedTypes to panic with conflicting type registrations") } } func TestConvertToVersionBasic(t *testing.T) { s := GetTestScheme() tt := &runtimetesting.TestType1{A: "I'm not a pointer object"} other, err := s.ConvertToVersion(tt, schema.GroupVersion{Version: "v1"}) if err != nil { t.Fatalf("Failure: %v", err) } converted, ok := other.(*runtimetesting.ExternalTestType1) if !ok { t.Fatalf("Got wrong type: %T", other) } if tt.A != converted.A { t.Fatalf("Failed to convert object correctly: %#v", converted) } } type testGroupVersioner struct { target schema.GroupVersionKind ok bool } func (m testGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { return m.target, m.ok } func (m testGroupVersioner) Identifier() string { return "testGroupVersioner" } func TestConvertToVersion(t *testing.T) { testCases := []struct { scheme *runtime.Scheme in runtime.Object gv runtime.GroupVersioner same bool out runtime.Object errFn func(error) bool }{ // errors if the type is not registered in the scheme { scheme: GetTestScheme(), in: &runtimetesting.UnknownType{}, errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) }, }, // errors if the group versioner returns no target { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: testGroupVersioner{}, errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // converts to internal { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: schema.GroupVersion{Version: runtime.APIVersionInternal}, out: &runtimetesting.TestType1{A: "test"}, }, // converts from unstructured to internal { scheme: GetTestScheme(), in: &runtimetesting.Unstructured{Object: map[string]interface{}{ "apiVersion": "custom/v1", "kind": "TestType3", "A": "test", }}, gv: schema.GroupVersion{Version: runtime.APIVersionInternal}, out: &runtimetesting.TestType1{A: "test"}, }, // converts from unstructured to external { scheme: GetTestScheme(), in: &runtimetesting.Unstructured{Object: map[string]interface{}{ "apiVersion": "custom/v1", "kind": "TestType3", "A": "test", }}, gv: schema.GroupVersion{Group: "custom", Version: "v1"}, out: &runtimetesting.ExternalTestType1{MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test"}, }, // prefers the best match { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: runtime.APIVersionInternal}, {Version: "v1"}}, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // unversioned type returned as-is { scheme: GetTestScheme(), in: &runtimetesting.UnversionedType{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &runtimetesting.UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // unversioned type returned when not included in the target types { scheme: GetTestScheme(), in: &runtimetesting.UnversionedType{A: "test"}, gv: schema.GroupVersions{{Group: "other", Version: "v2"}}, same: true, out: &runtimetesting.UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // detected as already being in the target version { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: runtime.APIVersionInternal}}, same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: runtime.APIVersionInternal}}, same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Version: "v1"}}, same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}}, same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}}, same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group) { scheme: GetTestScheme(), in: &runtimetesting.TestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "custom", Version: "v1"}, schema.GroupKind{Group: "other"}, schema.GroupKind{Kind: "TestType5"}), errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &runtimetesting.ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), same: true, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // group versioner can choose a particular target kind for a given input when kind is the same across group versions { scheme: GetTestScheme(), in: &runtimetesting.TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Version: "v1", Kind: "TestType3"}}, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // group versioner can choose a different kind { scheme: GetTestScheme(), in: &runtimetesting.TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}}, out: &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, } for i, test := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { original := test.in.DeepCopyObject() out, err := test.scheme.ConvertToVersion(test.in, test.gv) switch { case test.errFn != nil: if !test.errFn(err) { t.Fatalf("unexpected error: %v", err) } return case err != nil: t.Fatalf("unexpected error: %v", err) } if out == test.in { t.Fatalf("ConvertToVersion should always copy out: %#v", out) } if test.same { if !reflect.DeepEqual(original, test.in) { t.Fatalf("unexpected mutation of input: %s", cmp.Diff(original, test.in)) } if !reflect.DeepEqual(out, test.out) { t.Fatalf("unexpected out: %s", cmp.Diff(out, test.out)) } unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) if err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(unsafe, test.out) { t.Fatalf("unexpected unsafe: %s", cmp.Diff(unsafe, test.out)) } if unsafe != test.in { t.Fatalf("UnsafeConvertToVersion should return same object: %#v", unsafe) } return } if !reflect.DeepEqual(out, test.out) { t.Fatalf("unexpected out: %s", cmp.Diff(out, test.out)) } }) } } func TestConvert(t *testing.T) { testCases := []struct { scheme *runtime.Scheme in runtime.Object into runtime.Object gv runtime.GroupVersioner out runtime.Object errFn func(error) bool }{ // converts from internal to unstructured, given a target version { scheme: GetTestScheme(), in: &runtimetesting.TestType1{A: "test"}, into: &runtimetesting.Unstructured{}, out: &runtimetesting.Unstructured{Object: map[string]interface{}{ "myVersionKey": "custom/v1", "myKindKey": "TestType3", "A": "test", }}, gv: schema.GroupVersion{Group: "custom", Version: "v1"}, }, } for i, test := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { err := test.scheme.Convert(test.in, test.into, test.gv) switch { case test.errFn != nil: if !test.errFn(err) { t.Fatalf("unexpected error: %v", err) } return case err != nil: t.Fatalf("unexpected error: %v", err) return } if !reflect.DeepEqual(test.into, test.out) { t.Fatalf("unexpected out: %s", cmp.Diff(test.into, test.out)) } }) } } func TestMetaValues(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "externalVersion"} s := runtime.NewScheme() s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &runtimetesting.InternalSimple{}) s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &runtimetesting.ExternalSimple{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) conversions := &testConversions{ internalToExternalCalls: 0, externalToInternalCalls: 0, } // Register functions to verify that scope.Meta() gets set correctly. utilruntime.Must(conversions.registerConversions(s)) simple := &runtimetesting.InternalSimple{ TestString: "foo", } out, err := s.ConvertToVersion(simple, externalGV) if err != nil { t.Fatalf("unexpected error: %v", err) } internal, err := s.ConvertToVersion(out, internalGV) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := simple, internal; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } if e, a := 1, conversions.internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := 1, conversions.externalToInternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } } func TestMetaValuesUnregisteredConvert(t *testing.T) { type InternalSimple struct { Version string `json:"apiVersion,omitempty"` Kind string `json:"kind,omitempty"` TestString string `json:"testString"` } type ExternalSimple struct { Version string `json:"apiVersion,omitempty"` Kind string `json:"kind,omitempty"` TestString string `json:"testString"` } s := runtime.NewScheme() // We deliberately don't register the types. internalToExternalCalls := 0 // Register functions to verify that scope.Meta() gets set correctly. convertSimple := func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { out.TestString = in.TestString internalToExternalCalls++ return nil } if err := s.AddConversionFunc((*InternalSimple)(nil), (*ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertSimple(a.(*InternalSimple), b.(*ExternalSimple), scope) }); err != nil { t.Fatalf("unexpected error: %v", err) } simple := &InternalSimple{TestString: "foo"} external := &ExternalSimple{} if err := s.Convert(simple, external, nil); err != nil { t.Fatalf("Unexpected error: %v", err) } if e, a := simple.TestString, external.TestString; e != a { t.Errorf("Expected %v, got %v", e, a) } // Verify that our conversion handler got called. if e, a := 1, internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/000077500000000000000000000000001453143165200226445ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/codec_factory.go000066400000000000000000000311101453143165200257730ustar00rootroot00000000000000/* 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 serializer import ( "mime" "strings" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" "k8s.io/apimachinery/pkg/runtime/serializer/versioning" ) // serializerExtensions are for serializers that are conditionally compiled in var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){} type serializerType struct { AcceptContentTypes []string ContentType string FileExtensions []string // EncodesAsText should be true if this content type can be represented safely in UTF-8 EncodesAsText bool Serializer runtime.Serializer PrettySerializer runtime.Serializer StrictSerializer runtime.Serializer AcceptStreamContentTypes []string StreamContentType string Framer runtime.Framer StreamSerializer runtime.Serializer } func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, options CodecFactoryOptions) []serializerType { jsonSerializer := json.NewSerializerWithOptions( mf, scheme, scheme, json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict}, ) jsonSerializerType := serializerType{ AcceptContentTypes: []string{runtime.ContentTypeJSON}, ContentType: runtime.ContentTypeJSON, FileExtensions: []string{"json"}, EncodesAsText: true, Serializer: jsonSerializer, Framer: json.Framer, StreamSerializer: jsonSerializer, } if options.Pretty { jsonSerializerType.PrettySerializer = json.NewSerializerWithOptions( mf, scheme, scheme, json.SerializerOptions{Yaml: false, Pretty: true, Strict: options.Strict}, ) } strictJSONSerializer := json.NewSerializerWithOptions( mf, scheme, scheme, json.SerializerOptions{Yaml: false, Pretty: false, Strict: true}, ) jsonSerializerType.StrictSerializer = strictJSONSerializer yamlSerializer := json.NewSerializerWithOptions( mf, scheme, scheme, json.SerializerOptions{Yaml: true, Pretty: false, Strict: options.Strict}, ) strictYAMLSerializer := json.NewSerializerWithOptions( mf, scheme, scheme, json.SerializerOptions{Yaml: true, Pretty: false, Strict: true}, ) protoSerializer := protobuf.NewSerializer(scheme, scheme) protoRawSerializer := protobuf.NewRawSerializer(scheme, scheme) serializers := []serializerType{ jsonSerializerType, { AcceptContentTypes: []string{runtime.ContentTypeYAML}, ContentType: runtime.ContentTypeYAML, FileExtensions: []string{"yaml"}, EncodesAsText: true, Serializer: yamlSerializer, StrictSerializer: strictYAMLSerializer, }, { AcceptContentTypes: []string{runtime.ContentTypeProtobuf}, ContentType: runtime.ContentTypeProtobuf, FileExtensions: []string{"pb"}, Serializer: protoSerializer, // note, strict decoding is unsupported for protobuf, // fall back to regular serializing StrictSerializer: protoSerializer, Framer: protobuf.LengthDelimitedFramer, StreamSerializer: protoRawSerializer, }, } for _, fn := range serializerExtensions { if serializer, ok := fn(scheme); ok { serializers = append(serializers, serializer) } } return serializers } // CodecFactory provides methods for retrieving codecs and serializers for specific // versions and content types. type CodecFactory struct { scheme *runtime.Scheme universal runtime.Decoder accepts []runtime.SerializerInfo legacySerializer runtime.Serializer } // CodecFactoryOptions holds the options for configuring CodecFactory behavior type CodecFactoryOptions struct { // Strict configures all serializers in strict mode Strict bool // Pretty includes a pretty serializer along with the non-pretty one Pretty bool } // CodecFactoryOptionsMutator takes a pointer to an options struct and then modifies it. // Functions implementing this type can be passed to the NewCodecFactory() constructor. type CodecFactoryOptionsMutator func(*CodecFactoryOptions) // EnablePretty enables including a pretty serializer along with the non-pretty one func EnablePretty(options *CodecFactoryOptions) { options.Pretty = true } // DisablePretty disables including a pretty serializer along with the non-pretty one func DisablePretty(options *CodecFactoryOptions) { options.Pretty = false } // EnableStrict enables configuring all serializers in strict mode func EnableStrict(options *CodecFactoryOptions) { options.Strict = true } // DisableStrict disables configuring all serializers in strict mode func DisableStrict(options *CodecFactoryOptions) { options.Strict = false } // NewCodecFactory provides methods for retrieving serializers for the supported wire formats // and conversion wrappers to define preferred internal and external versions. In the future, // as the internal version is used less, callers may instead use a defaulting serializer and // only convert objects which are shared internally (Status, common API machinery). // // Mutators can be passed to change the CodecFactoryOptions before construction of the factory. // It is recommended to explicitly pass mutators instead of relying on defaults. // By default, Pretty is enabled -- this is conformant with previously supported behavior. // // TODO: allow other codecs to be compiled in? // TODO: accept a scheme interface func NewCodecFactory(scheme *runtime.Scheme, mutators ...CodecFactoryOptionsMutator) CodecFactory { options := CodecFactoryOptions{Pretty: true} for _, fn := range mutators { fn(&options) } serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory, options) return newCodecFactory(scheme, serializers) } // newCodecFactory is a helper for testing that allows a different metafactory to be specified. func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory { decoders := make([]runtime.Decoder, 0, len(serializers)) var accepts []runtime.SerializerInfo alreadyAccepted := make(map[string]struct{}) var legacySerializer runtime.Serializer for _, d := range serializers { decoders = append(decoders, d.Serializer) for _, mediaType := range d.AcceptContentTypes { if _, ok := alreadyAccepted[mediaType]; ok { continue } alreadyAccepted[mediaType] = struct{}{} info := runtime.SerializerInfo{ MediaType: d.ContentType, EncodesAsText: d.EncodesAsText, Serializer: d.Serializer, PrettySerializer: d.PrettySerializer, StrictSerializer: d.StrictSerializer, } mediaType, _, err := mime.ParseMediaType(info.MediaType) if err != nil { panic(err) } parts := strings.SplitN(mediaType, "/", 2) info.MediaTypeType = parts[0] info.MediaTypeSubType = parts[1] if d.StreamSerializer != nil { info.StreamSerializer = &runtime.StreamSerializerInfo{ Serializer: d.StreamSerializer, EncodesAsText: d.EncodesAsText, Framer: d.Framer, } } accepts = append(accepts, info) if mediaType == runtime.ContentTypeJSON { legacySerializer = d.Serializer } } } if legacySerializer == nil { legacySerializer = serializers[0].Serializer } return CodecFactory{ scheme: scheme, universal: recognizer.NewDecoder(decoders...), accepts: accepts, legacySerializer: legacySerializer, } } // WithoutConversion returns a NegotiatedSerializer that performs no conversion, even if the // caller requests it. func (f CodecFactory) WithoutConversion() runtime.NegotiatedSerializer { return WithoutConversionCodecFactory{f} } // SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { return f.accepts } // LegacyCodec encodes output to a given API versions, and decodes output into the internal form from // any recognized source. The returned codec will always encode output to JSON. If a type is not // found in the list of versions an error will be returned. // // This method is deprecated - clients and servers should negotiate a serializer by mime-type and // invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder(). // // TODO: make this call exist only in pkg/api, and initialize it with the set of default versions. // All other callers will be forced to request a Codec directly. func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec { return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner) } // UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies // runtime.Object. It does not perform conversion. It does not perform defaulting. func (f CodecFactory) UniversalDeserializer() runtime.Decoder { return f.universal } // UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used // by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes // objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate // versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified, // unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs // defaulting. // // TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form // TODO: only accept a group versioner func (f CodecFactory) UniversalDecoder(versions ...schema.GroupVersion) runtime.Decoder { var versioner runtime.GroupVersioner if len(versions) == 0 { versioner = runtime.InternalGroupVersioner } else { versioner = schema.GroupVersions(versions) } return f.CodecForVersions(nil, f.universal, nil, versioner) } // CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list, // it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not // converted. If encode or decode are nil, no conversion is performed. func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec { // TODO: these are for backcompat, remove them in the future if encode == nil { encode = runtime.DisabledGroupVersioner } if decode == nil { decode = runtime.InternalGroupVersioner } return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode) } // DecoderToVersion returns a decoder that targets the provided group version. func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { return f.CodecForVersions(nil, decoder, nil, gv) } // EncoderForVersion returns an encoder that targets the provided group version. func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { return f.CodecForVersions(encoder, nil, gv, nil) } // WithoutConversionCodecFactory is a CodecFactory that will explicitly ignore requests to perform conversion. // This wrapper is used while code migrates away from using conversion (such as external clients) and in the future // will be unnecessary when we change the signature of NegotiatedSerializer. type WithoutConversionCodecFactory struct { CodecFactory } // EncoderForVersion returns an encoder that does not do conversion, but does set the group version kind of the object // when serialized. func (f WithoutConversionCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder { return runtime.WithVersionEncoder{ Version: version, Encoder: serializer, ObjectTyper: f.CodecFactory.scheme, } } // DecoderToVersion returns an decoder that does not do conversion. func (f WithoutConversionCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { return runtime.WithoutVersionDecoder{ Decoder: serializer, } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/codec_test.go000066400000000000000000000322511453143165200253120ustar00rootroot00000000000000/* 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 serializer import ( "encoding/json" "fmt" "log" "os" "reflect" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" "k8s.io/apimachinery/pkg/util/diff" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "github.com/google/go-cmp/cmp" fuzz "github.com/google/gofuzz" flag "github.com/spf13/pflag" "sigs.k8s.io/yaml" ) var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.") type testMetaFactory struct{} func (testMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { findKind := struct { APIVersion string `json:"myVersionKey,omitempty"` ObjectKind string `json:"myKindKey,omitempty"` }{} // yaml is a superset of json, so we use it to decode here. That way, // we understand both. if err := yaml.Unmarshal(data, &findKind); err != nil { return nil, fmt.Errorf("couldn't get version/kind: %v", err) } gv, err := schema.ParseGroupVersion(findKind.APIVersion) if err != nil { return nil, err } return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.ObjectKind}, nil } // TestObjectFuzzer can randomly populate all the above objects. var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs( func(j *runtimetesting.MyWeirdCustomEmbeddedVersionKindField, c fuzz.Continue) { c.FuzzNoCustom(j) j.APIVersion = "" j.ObjectKind = "" }, ) // Returns a new Scheme set up with the test objects. func GetTestScheme() (*runtime.Scheme, runtime.Codec) { internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Version: "v1"} externalGV2 := schema.GroupVersion{Version: "v2"} s := runtime.NewScheme() // Ordinarily, we wouldn't add TestType2, but because this is a test and // both types are from the same package, we need to get it into the system // so that converter will match it with ExternalType2. s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}, &runtimetesting.TestType2{}, &runtimetesting.ExternalInternalSame{}) s.AddKnownTypes(externalGV, &runtimetesting.ExternalInternalSame{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &runtimetesting.ExternalTestType2{}) s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(externalGV2.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) s.AddUnversionedTypes(externalGV, &metav1.Status{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})) codec := cf.LegacyCodec(schema.GroupVersion{Version: "v1"}) return s, codec } var semantic = conversion.EqualitiesOrDie( func(a, b runtimetesting.MyWeirdCustomEmbeddedVersionKindField) bool { a.APIVersion, a.ObjectKind = "", "" b.APIVersion, b.ObjectKind = "", "" return a == b }, ) func runTest(t *testing.T, source interface{}) { name := reflect.TypeOf(source).Elem().Name() TestObjectFuzzer.Fuzz(source) _, codec := GetTestScheme() data, err := runtime.Encode(codec, source.(runtime.Object)) if err != nil { t.Errorf("%v: %v (%#v)", name, err, source) return } obj2, err := runtime.Decode(codec, data) if err != nil { t.Errorf("%v: %v (%v)", name, err, string(data)) return } if !semantic.DeepEqual(source, obj2) { t.Errorf("1: %v: diff: %v", name, diff.ObjectGoPrintSideBySide(source, obj2)) return } obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface() if err := runtime.DecodeInto(codec, data, obj3.(runtime.Object)); err != nil { t.Errorf("2: %v: %v", name, err) return } if !semantic.DeepEqual(source, obj3) { t.Errorf("3: %v: diff: %v", name, cmp.Diff(source, obj3)) return } } func TestTypes(t *testing.T) { table := []interface{}{ &runtimetesting.TestType1{}, &runtimetesting.ExternalInternalSame{}, } for _, item := range table { // Try a few times, since runTest uses random values. for i := 0; i < *fuzzIters; i++ { runTest(t, item) } } } func TestVersionedEncoding(t *testing.T) { s, _ := GetTestScheme() cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})) info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON) encoder := info.Serializer codec := cf.EncoderForVersion(encoder, schema.GroupVersion{Version: "v2"}) out, err := runtime.Encode(codec, &runtimetesting.TestType1{}) if err != nil { t.Fatal(err) } if string(out) != `{"myVersionKey":"v2","myKindKey":"TestType1"}`+"\n" { t.Fatal(string(out)) } codec = cf.EncoderForVersion(encoder, schema.GroupVersion{Version: "v3"}) _, err = runtime.Encode(codec, &runtimetesting.TestType1{}) if err == nil { t.Fatal(err) } // unversioned encode with no versions is written directly to wire codec = cf.EncoderForVersion(encoder, runtime.InternalGroupVersioner) out, err = runtime.Encode(codec, &runtimetesting.TestType1{}) if err != nil { t.Fatal(err) } if string(out) != `{}`+"\n" { t.Fatal(string(out)) } } func TestMultipleNames(t *testing.T) { _, codec := GetTestScheme() obj, _, err := codec.Decode([]byte(`{"myKindKey":"TestType3","myVersionKey":"v1","A":"value"}`), nil, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } internal := obj.(*runtimetesting.TestType1) if internal.A != "value" { t.Fatalf("unexpected decoded object: %#v", internal) } out, err := runtime.Encode(codec, internal) if err != nil { t.Fatalf("unexpected error: %v", err) } if !strings.Contains(string(out), `"myKindKey":"TestType1"`) { t.Errorf("unexpected encoded output: %s", string(out)) } } func TestStrictOption(t *testing.T) { s, _ := GetTestScheme() duplicateKeys := `{"myKindKey":"TestType3","myVersionKey":"v1","myVersionKey":"v1","A":"value"}` strictCodec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})).LegacyCodec() _, _, err := strictCodec.Decode([]byte(duplicateKeys), nil, nil) if !runtime.IsStrictDecodingError(err) { t.Fatalf("StrictDecodingError not returned on object with duplicate keys: %v, type: %v", err, reflect.TypeOf(err)) } nonStrictCodec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: false})).LegacyCodec() _, _, err = nonStrictCodec.Decode([]byte(duplicateKeys), nil, nil) if runtime.IsStrictDecodingError(err) { t.Fatalf("Non-Strict decoder returned a StrictDecodingError: %v", err) } } func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) { internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Version: "v1"} s := runtime.NewScheme() // create two names internally, with TestType1 being preferred s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &runtimetesting.TestType1{}) s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &runtimetesting.TestType1{}) // create two names externally, with TestType1 being preferred s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &runtimetesting.ExternalTestType1{}) if err := runtimetesting.RegisterConversions(s); err != nil { t.Fatalf("unexpected error; %v", err) } ext := &runtimetesting.ExternalTestType1{} ext.APIVersion = "v1" ext.ObjectKind = "OtherType1" ext.A = "test" data, err := json.Marshal(ext) if err != nil { t.Fatalf("unexpected error: %v", err) } expect := &runtimetesting.TestType1{A: "test"} codec := newCodecFactory( s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true}), ).LegacyCodec(schema.GroupVersion{Version: "v1"}) obj, err := runtime.Decode(codec, data) if err != nil { t.Fatalf("unexpected error: %v", err) } if !semantic.DeepEqual(expect, obj) { t.Errorf("unexpected object: %#v", obj) } into := &runtimetesting.TestType1{} if err := runtime.DecodeInto(codec, data, into); err != nil { t.Fatalf("unexpected error: %v", err) } if !semantic.DeepEqual(expect, into) { t.Errorf("unexpected object: %#v", obj) } } func TestEncode_Ptr(t *testing.T) { _, codec := GetTestScheme() tt := &runtimetesting.TestType1{A: "I am a pointer object"} data, err := runtime.Encode(codec, tt) obj2, err2 := runtime.Decode(codec, data) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'\n%s", err, err2, data) } if _, ok := obj2.(*runtimetesting.TestType1); !ok { t.Fatalf("Got wrong type") } if !semantic.DeepEqual(obj2, tt) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", tt, obj2) } } func TestBadJSONRejection(t *testing.T) { log.SetOutput(os.Stderr) _, codec := GetTestScheme() badJSONs := [][]byte{ []byte(`{"myVersionKey":"v1"}`), // Missing kind []byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind []byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version []byte(`{"myKindKey":"TestType1"}`), // Missing version } for _, b := range badJSONs { if _, err := runtime.Decode(codec, b); err == nil { t.Errorf("Did not reject bad json: %s", string(b)) } } badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`) if err := runtime.DecodeInto(codec, badJSONKindMismatch, &runtimetesting.TestType1{}); err == nil { t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch) } if err := runtime.DecodeInto(codec, []byte(``), &runtimetesting.TestType1{}); err != nil { t.Errorf("Should allow empty decode: %v", err) } if _, _, err := codec.Decode([]byte(``), &schema.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err == nil { t.Errorf("Did not give error for empty data with only kind default") } if _, _, err := codec.Decode([]byte(`{"myVersionKey":"v1"}`), &schema.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err != nil { t.Errorf("Gave error for version and kind default") } if _, _, err := codec.Decode([]byte(`{"myKindKey":"ExternalInternalSame"}`), &schema.GroupVersionKind{Version: "v1"}, nil); err != nil { t.Errorf("Gave error for version and kind default") } if _, _, err := codec.Decode([]byte(``), &schema.GroupVersionKind{Kind: "ExternalInternalSame", Version: "v1"}, nil); err != nil { t.Errorf("Gave error for version and kind defaulted: %v", err) } if _, err := runtime.Decode(codec, []byte(``)); err == nil { t.Errorf("Did not give error for empty data") } } // Returns a new Scheme set up with the test objects needed by TestDirectCodec. func GetDirectCodecTestScheme() *runtime.Scheme { internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Version: "v1"} s := runtime.NewScheme() // Ordinarily, we wouldn't add TestType2, but because this is a test and // both types are from the same package, we need to get it into the system // so that converter will match it with ExternalType2. s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}) s.AddKnownTypes(externalGV, &runtimetesting.ExternalTestType1{}) s.AddUnversionedTypes(externalGV, &metav1.Status{}) utilruntime.Must(runtimetesting.RegisterConversions(s)) return s } func TestDirectCodec(t *testing.T) { s := GetDirectCodecTestScheme() cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})) info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON) serializer := info.Serializer df := cf.WithoutConversion() ignoredGV, err := schema.ParseGroupVersion("ignored group/ignored version") if err != nil { t.Fatal(err) } directEncoder := df.EncoderForVersion(serializer, ignoredGV) directDecoder := df.DecoderToVersion(serializer, ignoredGV) out, err := runtime.Encode(directEncoder, &runtimetesting.ExternalTestType1{}) if err != nil { t.Fatal(err) } if string(out) != `{"myVersionKey":"v1","myKindKey":"ExternalTestType1"}`+"\n" { t.Fatal(string(out)) } a, _, err := directDecoder.Decode(out, nil, nil) if err != nil { t.Fatalf("error on Decode: %v", err) } e := &runtimetesting.ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{ APIVersion: "v1", ObjectKind: "ExternalTestType1", }, } if !semantic.DeepEqual(e, a) { t.Fatalf("expect %v, got %v", e, a) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/encoder_with_allocator_test.go000066400000000000000000000071471453143165200307550ustar00rootroot00000000000000/* Copyright 2022 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 serializer import ( "crypto/rand" "io/ioutil" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" ) func BenchmarkProtobufEncoder(b *testing.B) { benchmarkEncodeFor(b, protobuf.NewSerializer(nil, nil)) } func BenchmarkProtobufEncodeWithAllocator(b *testing.B) { benchmarkEncodeWithAllocatorFor(b, protobuf.NewSerializer(nil, nil)) } func BenchmarkRawProtobufEncoder(b *testing.B) { benchmarkEncodeFor(b, protobuf.NewRawSerializer(nil, nil)) } func BenchmarkRawProtobufEncodeWithAllocator(b *testing.B) { benchmarkEncodeWithAllocatorFor(b, protobuf.NewRawSerializer(nil, nil)) } func benchmarkEncodeFor(b *testing.B, target runtime.Encoder) { for _, tc := range benchTestCases() { b.Run(tc.name, func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { err := target.Encode(tc.obj, ioutil.Discard) if err != nil { b.Fatal(err) } } }) } } func benchmarkEncodeWithAllocatorFor(b *testing.B, target runtime.EncoderWithAllocator) { for _, tc := range benchTestCases() { b.Run(tc.name, func(b *testing.B) { b.ReportAllocs() allocator := &runtime.Allocator{} for n := 0; n < b.N; n++ { err := target.EncodeWithAllocator(tc.obj, ioutil.Discard, allocator) if err != nil { b.Fatal(err) } } }) } } type benchTestCase struct { name string obj runtime.Object } func benchTestCases() []benchTestCase { return []benchTestCase{ { name: "an obj with 1kB payload", obj: func() runtime.Object { carpPayload := make([]byte, 1000) // 1 kB if _, err := rand.Read(carpPayload); err != nil { panic(err) } return carpWithPayload(carpPayload) }(), }, { name: "an obj with 10kB payload", obj: func() runtime.Object { carpPayload := make([]byte, 10000) // 10 kB if _, err := rand.Read(carpPayload); err != nil { panic(err) } return carpWithPayload(carpPayload) }(), }, { name: "an obj with 100kB payload", obj: func() runtime.Object { carpPayload := make([]byte, 100000) // 100 kB if _, err := rand.Read(carpPayload); err != nil { panic(err) } return carpWithPayload(carpPayload) }(), }, { name: "an obj with 1MB payload", obj: func() runtime.Object { carpPayload := make([]byte, 1000000) // 1 MB if _, err := rand.Read(carpPayload); err != nil { panic(err) } return carpWithPayload(carpPayload) }(), }, } } func carpWithPayload(carpPayload []byte) *testapigroupv1.Carp { gvk := &schema.GroupVersionKind{Group: "group", Version: "version", Kind: "Carp"} return &testapigroupv1.Carp{ TypeMeta: metav1.TypeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}, ObjectMeta: metav1.ObjectMeta{ Name: "name", Namespace: "namespace", }, Spec: testapigroupv1.CarpSpec{ Subdomain: "carp.k8s.io", NodeSelector: map[string]string{"payload": string(carpPayload)}, }, } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/json/000077500000000000000000000000001453143165200236155ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/json/json.go000066400000000000000000000300321453143165200251130ustar00rootroot00000000000000/* 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 json import ( "encoding/json" "io" "strconv" kjson "sigs.k8s.io/json" "sigs.k8s.io/yaml" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" "k8s.io/apimachinery/pkg/util/framer" utilyaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/klog/v2" ) // NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer // is not nil, the object has the group, version, and kind fields set. // Deprecated: use NewSerializerWithOptions instead. func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, pretty bool) *Serializer { return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{false, pretty, false}) } // NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer // is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that // matches JSON, and will error if constructs are used that do not serialize to JSON. // Deprecated: use NewSerializerWithOptions instead. func NewYAMLSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer { return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{true, false, false}) } // NewSerializerWithOptions creates a JSON/YAML serializer that handles encoding versioned objects into the proper JSON/YAML // form. If typer is not nil, the object has the group, version, and kind fields set. Options are copied into the Serializer // and are immutable. func NewSerializerWithOptions(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, options SerializerOptions) *Serializer { return &Serializer{ meta: meta, creater: creater, typer: typer, options: options, identifier: identifier(options), } } // identifier computes Identifier of Encoder based on the given options. func identifier(options SerializerOptions) runtime.Identifier { result := map[string]string{ "name": "json", "yaml": strconv.FormatBool(options.Yaml), "pretty": strconv.FormatBool(options.Pretty), "strict": strconv.FormatBool(options.Strict), } identifier, err := json.Marshal(result) if err != nil { klog.Fatalf("Failed marshaling identifier for json Serializer: %v", err) } return runtime.Identifier(identifier) } // SerializerOptions holds the options which are used to configure a JSON/YAML serializer. // example: // (1) To configure a JSON serializer, set `Yaml` to `false`. // (2) To configure a YAML serializer, set `Yaml` to `true`. // (3) To configure a strict serializer that can return strictDecodingError, set `Strict` to `true`. type SerializerOptions struct { // Yaml: configures the Serializer to work with JSON(false) or YAML(true). // When `Yaml` is enabled, this serializer only supports the subset of YAML that // matches JSON, and will error if constructs are used that do not serialize to JSON. Yaml bool // Pretty: configures a JSON enabled Serializer(`Yaml: false`) to produce human-readable output. // This option is silently ignored when `Yaml` is `true`. Pretty bool // Strict: configures the Serializer to return strictDecodingError's when duplicate fields are present decoding JSON or YAML. // Note that enabling this option is not as performant as the non-strict variant, and should not be used in fast paths. Strict bool } // Serializer handles encoding versioned objects into the proper JSON form type Serializer struct { meta MetaFactory options SerializerOptions creater runtime.ObjectCreater typer runtime.ObjectTyper identifier runtime.Identifier } // Serializer implements Serializer var _ runtime.Serializer = &Serializer{} var _ recognizer.RecognizingDecoder = &Serializer{} // gvkWithDefaults returns group kind and version defaulting from provided default func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind { if len(actual.Kind) == 0 { actual.Kind = defaultGVK.Kind } if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = defaultGVK.Group actual.Version = defaultGVK.Version } if len(actual.Version) == 0 && actual.Group == defaultGVK.Group { actual.Version = defaultGVK.Version } return actual } // Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then // load that data into an object matching the desired schema kind or the provided into. // If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed. // If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling. // If into is provided and the original data is not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. // If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk) // On success or most errors, the method will return the calculated schema kind. // The gvk calculate priority will be originalData > default gvk > into func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { data := originalData if s.options.Yaml { altered, err := yaml.YAMLToJSON(data) if err != nil { return nil, nil, err } data = altered } actual, err := s.meta.Interpret(data) if err != nil { return nil, nil, err } if gvk != nil { *actual = gvkWithDefaults(*actual, *gvk) } if unk, ok := into.(*runtime.Unknown); ok && unk != nil { unk.Raw = originalData unk.ContentType = runtime.ContentTypeJSON unk.GetObjectKind().SetGroupVersionKind(*actual) return unk, actual, nil } if into != nil { _, isUnstructured := into.(runtime.Unstructured) types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err), isUnstructured: strictErrs, err := s.unmarshal(into, data, originalData) if err != nil { return nil, actual, err } // when decoding directly into a provided unstructured object, // extract the actual gvk decoded from the provided data, // and ensure it is non-empty. if isUnstructured { *actual = into.GetObjectKind().GroupVersionKind() if len(actual.Kind) == 0 { return nil, actual, runtime.NewMissingKindErr(string(originalData)) } // TODO(109023): require apiVersion here as well once unstructuredJSONScheme#Decode does } if len(strictErrs) > 0 { return into, actual, runtime.NewStrictDecodingError(strictErrs) } return into, actual, nil case err != nil: return nil, actual, err default: *actual = gvkWithDefaults(*actual, types[0]) } } if len(actual.Kind) == 0 { return nil, actual, runtime.NewMissingKindErr(string(originalData)) } if len(actual.Version) == 0 { return nil, actual, runtime.NewMissingVersionErr(string(originalData)) } // use the target if necessary obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into) if err != nil { return nil, actual, err } strictErrs, err := s.unmarshal(obj, data, originalData) if err != nil { return nil, actual, err } else if len(strictErrs) > 0 { return obj, actual, runtime.NewStrictDecodingError(strictErrs) } return obj, actual, nil } // Encode serializes the provided object to the given writer. func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { if co, ok := obj.(runtime.CacheableObject); ok { return co.CacheEncode(s.Identifier(), s.doEncode, w) } return s.doEncode(obj, w) } func (s *Serializer) doEncode(obj runtime.Object, w io.Writer) error { if s.options.Yaml { json, err := json.Marshal(obj) if err != nil { return err } data, err := yaml.JSONToYAML(json) if err != nil { return err } _, err = w.Write(data) return err } if s.options.Pretty { data, err := json.MarshalIndent(obj, "", " ") if err != nil { return err } _, err = w.Write(data) return err } encoder := json.NewEncoder(w) return encoder.Encode(obj) } // IsStrict indicates whether the serializer // uses strict decoding or not func (s *Serializer) IsStrict() bool { return s.options.Strict } func (s *Serializer) unmarshal(into runtime.Object, data, originalData []byte) (strictErrs []error, err error) { // If the deserializer is non-strict, return here. if !s.options.Strict { if err := kjson.UnmarshalCaseSensitivePreserveInts(data, into); err != nil { return nil, err } return nil, nil } if s.options.Yaml { // In strict mode pass the original data through the YAMLToJSONStrict converter. // This is done to catch duplicate fields in YAML that would have been dropped in the original YAMLToJSON conversion. // TODO: rework YAMLToJSONStrict to return warnings about duplicate fields without terminating so we don't have to do this twice. _, err := yaml.YAMLToJSONStrict(originalData) if err != nil { strictErrs = append(strictErrs, err) } } var strictJSONErrs []error if u, isUnstructured := into.(runtime.Unstructured); isUnstructured { // Unstructured is a custom unmarshaler that gets delegated // to, so in order to detect strict JSON errors we need // to unmarshal directly into the object. m := map[string]interface{}{} strictJSONErrs, err = kjson.UnmarshalStrict(data, &m) u.SetUnstructuredContent(m) } else { strictJSONErrs, err = kjson.UnmarshalStrict(data, into) } if err != nil { // fatal decoding error, not due to strictness return nil, err } strictErrs = append(strictErrs, strictJSONErrs...) return strictErrs, nil } // Identifier implements runtime.Encoder interface. func (s *Serializer) Identifier() runtime.Identifier { return s.identifier } // RecognizesData implements the RecognizingDecoder interface. func (s *Serializer) RecognizesData(data []byte) (ok, unknown bool, err error) { if s.options.Yaml { // we could potentially look for '---' return false, true, nil } return utilyaml.IsJSONBuffer(data), false, nil } // Framer is the default JSON framing behavior, with newlines delimiting individual objects. var Framer = jsonFramer{} type jsonFramer struct{} // NewFrameWriter implements stream framing for this serializer func (jsonFramer) NewFrameWriter(w io.Writer) io.Writer { // we can write JSON objects directly to the writer, because they are self-framing return w } // NewFrameReader implements stream framing for this serializer func (jsonFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { // we need to extract the JSON chunks of data to pass to Decode() return framer.NewJSONFramedReader(r) } // YAMLFramer is the default JSON framing behavior, with newlines delimiting individual objects. var YAMLFramer = yamlFramer{} type yamlFramer struct{} // NewFrameWriter implements stream framing for this serializer func (yamlFramer) NewFrameWriter(w io.Writer) io.Writer { return yamlFrameWriter{w} } // NewFrameReader implements stream framing for this serializer func (yamlFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { // extract the YAML document chunks directly return utilyaml.NewDocumentDecoder(r) } type yamlFrameWriter struct { w io.Writer } // Write separates each document with the YAML document separator (`---` followed by line // break). Writers must write well formed YAML documents (include a final line break). func (w yamlFrameWriter) Write(data []byte) (n int, err error) { if _, err := w.w.Write([]byte("---\n")); err != nil { return 0, err } return w.w.Write(data) } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/json/json_limit_test.go000066400000000000000000000107171453143165200273600ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package json import ( gojson "encoding/json" "strings" "testing" utiljson "k8s.io/apimachinery/pkg/util/json" ) type testcase struct { name string data []byte checkErr func(t testing.TB, err error) benchmark bool } func testcases() []testcase { // verify we got an error of some kind nonNilError := func(t testing.TB, err error) { if err == nil { t.Errorf("expected error, got none") } } // verify the parse completed, either with success or a max depth error successOrMaxDepthError := func(t testing.TB, err error) { if err != nil && !strings.Contains(err.Error(), "max depth") { t.Errorf("expected success or error containing 'max depth', got: %v", err) } } return []testcase{ { name: "3MB of deeply nested slices", checkErr: successOrMaxDepthError, data: []byte(`{"a":` + strings.Repeat(`[`, 3*1024*1024/2) + strings.Repeat(`]`, 3*1024*1024/2) + "}"), }, { name: "3MB of unbalanced nested slices", checkErr: nonNilError, data: []byte(`{"a":` + strings.Repeat(`[`, 3*1024*1024)), }, { name: "3MB of deeply nested maps", checkErr: successOrMaxDepthError, data: []byte(strings.Repeat(`{"":`, 3*1024*1024/5/2) + "{}" + strings.Repeat(`}`, 3*1024*1024/5/2)), }, { name: "3MB of unbalanced nested maps", checkErr: nonNilError, data: []byte(strings.Repeat(`{"":`, 3*1024*1024/5)), }, { name: "3MB of empty slices", data: []byte(`{"a":[` + strings.Repeat(`[],`, 3*1024*1024/3-2) + `[]]}`), benchmark: true, }, { name: "3MB of slices", data: []byte(`{"a":[` + strings.Repeat(`[0],`, 3*1024*1024/4-2) + `[0]]}`), benchmark: true, }, { name: "3MB of empty maps", data: []byte(`{"a":[` + strings.Repeat(`{},`, 3*1024*1024/3-2) + `{}]}`), benchmark: true, }, { name: "3MB of maps", data: []byte(`{"a":[` + strings.Repeat(`{"a":0},`, 3*1024*1024/8-2) + `{"a":0}]}`), benchmark: true, }, { name: "3MB of ints", data: []byte(`{"a":[` + strings.Repeat(`0,`, 3*1024*1024/2-2) + `0]}`), benchmark: true, }, { name: "3MB of floats", data: []byte(`{"a":[` + strings.Repeat(`0.0,`, 3*1024*1024/4-2) + `0.0]}`), benchmark: true, }, { name: "3MB of bools", data: []byte(`{"a":[` + strings.Repeat(`true,`, 3*1024*1024/5-2) + `true]}`), benchmark: true, }, { name: "3MB of empty strings", data: []byte(`{"a":[` + strings.Repeat(`"",`, 3*1024*1024/3-2) + `""]}`), benchmark: true, }, { name: "3MB of strings", data: []byte(`{"a":[` + strings.Repeat(`"abcdefghijklmnopqrstuvwxyz012",`, 3*1024*1024/30-2) + `"abcdefghijklmnopqrstuvwxyz012"]}`), benchmark: true, }, { name: "3MB of nulls", data: []byte(`{"a":[` + strings.Repeat(`null,`, 3*1024*1024/5-2) + `null]}`), benchmark: true, }, } } var decoders = map[string]func([]byte, interface{}) error{ "gojson": gojson.Unmarshal, "utiljson": utiljson.Unmarshal, } func TestJSONLimits(t *testing.T) { for _, tc := range testcases() { if tc.benchmark { continue } t.Run(tc.name, func(t *testing.T) { for decoderName, decoder := range decoders { t.Run(decoderName, func(t *testing.T) { v := map[string]interface{}{} err := decoder(tc.data, &v) if tc.checkErr != nil { tc.checkErr(t, err) } else if err != nil { t.Errorf("unexpected error: %v", err) } }) } }) } } func BenchmarkJSONLimits(b *testing.B) { for _, tc := range testcases() { b.Run(tc.name, func(b *testing.B) { for decoderName, decoder := range decoders { b.Run(decoderName, func(b *testing.B) { for i := 0; i < b.N; i++ { v := map[string]interface{}{} err := decoder(tc.data, &v) if tc.checkErr != nil { tc.checkErr(b, err) } else if err != nil { b.Errorf("unexpected error: %v", err) } } }) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/json/json_test.go000066400000000000000000001110201453143165200261470ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package json_test import ( "fmt" "reflect" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" "k8s.io/apimachinery/pkg/util/diff" ) type testDecodable struct { metav1.TypeMeta `json:",inline"` Other string Value int `json:"value"` Spec DecodableSpec `json:"spec"` Interface interface{} `json:"interface"` } // DecodableSpec has 15 fields. type DecodableSpec struct { A int `json:"A"` B int `json:"B"` C int `json:"C"` D int `json:"D"` E int `json:"E"` F int `json:"F"` G int `json:"G"` H int `json:"h"` I int `json:"i"` J int `json:"j"` K int `json:"k"` L int `json:"l"` M int `json:"m"` N int `json:"n"` O int `json:"o"` } func (d *testDecodable) DeepCopyObject() runtime.Object { if d == nil { return nil } out := new(testDecodable) d.DeepCopyInto(out) return out } func (d *testDecodable) DeepCopyInto(out *testDecodable) { *out = *d out.Other = d.Other out.Value = d.Value out.Spec = d.Spec out.Interface = d.Interface return } type testDecodeCoercion struct { metav1.TypeMeta `json:",inline"` Bool bool `json:"bool"` Int int `json:"int"` Int32 int `json:"int32"` Int64 int `json:"int64"` Float32 float32 `json:"float32"` Float64 float64 `json:"float64"` String string `json:"string"` Struct testDecodable `json:"struct"` Array []string `json:"array"` Map map[string]string `json:"map"` } func (d *testDecodeCoercion) DeepCopyObject() runtime.Object { if d == nil { return nil } out := new(testDecodeCoercion) d.DeepCopyInto(out) return out } func (d *testDecodeCoercion) DeepCopyInto(out *testDecodeCoercion) { *out = *d return } func TestDecode(t *testing.T) { type testCase struct { creater runtime.ObjectCreater typer runtime.ObjectTyper yaml bool pretty bool strict bool data []byte defaultGVK *schema.GroupVersionKind into runtime.Object errFn func(error) bool expectedObject runtime.Object expectedGVK *schema.GroupVersionKind } testCases := []testCase{ // missing metadata without into, typed creater { data: []byte("{}"), expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte("{}"), expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"kind":"Foo"}`), expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, }, { data: []byte(`{"kind":"Foo"}`), expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, strict: true, }, { data: []byte(`{"apiVersion":"foo/v1"}`), expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte(`{"apiVersion":"foo/v1"}`), expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "/v1", Kind: "Foo"}}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "/v1", Kind: "Foo"}}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, strict: true, }, // missing metadata with unstructured into { data: []byte("{}"), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte("{}"), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion }, { data: []byte(`{"kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, strict: true, // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion }, { data: []byte(`{"apiVersion":"foo/v1"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte(`{"apiVersion":"foo/v1"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, strict: true, }, // missing metadata with unstructured into providing metadata { data: []byte("{}"), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte("{}"), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion }, { data: []byte(`{"kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, strict: true, // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion }, { data: []byte(`{"apiVersion":"foo/v1"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte(`{"apiVersion":"foo/v1"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, strict: true, }, // missing metadata without into, unstructured creater { data: []byte("{}"), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte("{}"), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, }, { data: []byte(`{"kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, strict: true, }, { data: []byte(`{"apiVersion":"foo/v1"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, }, { data: []byte(`{"apiVersion":"foo/v1"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, strict: true, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, }, { data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, creater: &mockCreater{obj: &unstructured.Unstructured{}}, expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, strict: true, }, // creator errors { data: []byte("{}"), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{err: fmt.Errorf("fake error")}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return err.Error() == "fake error" }, }, { data: []byte("{}"), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{err: fmt.Errorf("fake error")}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return err.Error() == "fake error" }, }, // creator typed { data: []byte("{}"), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, }, { data: []byte("{}"), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, strict: true, }, // version without group is not defaulted { data: []byte(`{"apiVersion":"blah"}`), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "", Version: "blah"}, }, // group without version is defaulted { data: []byte(`{"apiVersion":"other/"}`), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "other/"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, }, // group version, kind is defaulted { data: []byte(`{"apiVersion":"other1/blah1"}`), defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "other1/blah1"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other1", Version: "blah1"}, }, // gvk all provided then not defaulted at all { data: []byte(`{"kind":"Test","apiVersion":"other/blah"}`), defaultGVK: &schema.GroupVersionKind{Kind: "Test1", Group: "other1", Version: "blah1"}, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, }, //gvk defaulting if kind not provided in data and defaultGVK use into's kind { data: []byte(`{"apiVersion":"b1/c1"}`), into: &testDecodable{TypeMeta: metav1.TypeMeta{Kind: "a3", APIVersion: "b1/c1"}}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}}, defaultGVK: nil, creater: &mockCreater{obj: &testDecodable{}}, expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{Kind: "a3", APIVersion: "b1/c1"}}, expectedGVK: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}, }, // accept runtime.Unknown as into and bypass creator { data: []byte(`{}`), into: &runtime.Unknown{}, expectedGVK: &schema.GroupVersionKind{}, expectedObject: &runtime.Unknown{ Raw: []byte(`{}`), ContentType: runtime.ContentTypeJSON, }, }, { data: []byte(`{"test":"object"}`), into: &runtime.Unknown{}, expectedGVK: &schema.GroupVersionKind{}, expectedObject: &runtime.Unknown{ Raw: []byte(`{"test":"object"}`), ContentType: runtime.ContentTypeJSON, }, }, { data: []byte(`{"test":"object"}`), into: &runtime.Unknown{}, defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, Raw: []byte(`{"test":"object"}`), ContentType: runtime.ContentTypeJSON, }, }, // unregistered objects can be decoded into directly { data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`), into: &testDecodable{}, typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, Other: "test", Value: 1, }, }, // registered types get defaulted by the into object kind { data: []byte(`{"value":1,"Other":"test"}`), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ Other: "test", Value: 1, }, }, // registered types get defaulted by the into object kind even without version, but return an error { data: []byte(`{"value":1,"Other":"test"}`), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: ""}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: ""}, errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, expectedObject: &testDecodable{ Other: "test", Value: 1, }, }, // Error on invalid number { data: []byte(`{"kind":"Test","apiVersion":"other/blah","interface":1e1000}`), creater: &mockCreater{obj: &testDecodable{}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `json: cannot unmarshal number 1e1000 into Go struct field testDecodable.interface of type float64`) }, }, // Unmarshalling is case-sensitive { // "VaLue" should have been "value" data: []byte(`{"kind":"Test","apiVersion":"other/blah","VaLue":1,"Other":"test"}`), into: &testDecodable{}, typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, Other: "test", }, }, // Unmarshalling is case-sensitive for big struct. { // "b" should have been "B", "I" should have been "i" data: []byte(`{"kind":"Test","apiVersion":"other/blah","spec": {"A": 1, "b": 2, "h": 3, "I": 4}}`), into: &testDecodable{}, typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, Spec: DecodableSpec{A: 1, H: 3}, }, }, // Unknown fields should return an error from the strict JSON deserializer. { data: []byte(`{"unknown": 1}`), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `unknown field "unknown"`) }, strict: true, }, // Unknown fields should return an error from the strict YAML deserializer. { data: []byte("unknown: 1\n"), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `unknown field "unknown"`) }, yaml: true, strict: true, }, // Duplicate fields should return an error from the strict JSON deserializer. { data: []byte(`{"value":1,"value":1}`), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `duplicate field "value"`) }, strict: true, }, // Duplicate fields should return an error from the strict YAML deserializer. { data: []byte("value: 1\n" + "value: 1\n"), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `"value" already set in map`) }, yaml: true, strict: true, }, // Duplicate fields should return an error from the strict JSON deserializer for unstructured. { data: []byte(`{"kind":"Custom","value":1,"value":1}`), into: &unstructured.Unstructured{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Custom"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `duplicate field "value"`) }, strict: true, }, // Duplicate fields should return an error from the strict YAML deserializer for unstructured. { data: []byte("kind: Custom\n" + "value: 1\n" + "value: 1\n"), into: &unstructured.Unstructured{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Custom"}, errFn: func(err error) bool { return strings.Contains(err.Error(), `"value" already set in map`) }, yaml: true, strict: true, }, // Strict JSON decode into unregistered objects directly. { data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`), into: &testDecodable{}, typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, Other: "test", Value: 1, }, strict: true, }, // Strict YAML decode into unregistered objects directly. { data: []byte("kind: Test\n" + "apiVersion: other/blah\n" + "value: 1\n" + "Other: test\n"), into: &testDecodable{}, typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, Other: "test", Value: 1, }, yaml: true, strict: true, }, // Valid strict JSON decode without GVK. { data: []byte(`{"value":1234}`), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ Value: 1234, }, strict: true, }, // Valid strict YAML decode without GVK. { data: []byte("value: 1234\n"), into: &testDecodable{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodable{ Value: 1234, }, yaml: true, strict: true, }, // Invalid strict JSON, results in json parse error: { data: []byte("foo"), into: &unstructured.Unstructured{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, errFn: func(err error) bool { return strings.Contains(err.Error(), `json parse error: invalid character 'o'`) }, strict: true, }, // empty JSON strict, results in missing kind error { data: []byte("{}"), into: &unstructured.Unstructured{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{}, errFn: func(err error) bool { return strings.Contains(err.Error(), `Object 'Kind' is missing`) }, strict: true, }, // coerce from null { data: []byte(`{"bool":null,"int":null,"int32":null,"int64":null,"float32":null,"float64":null,"string":null,"array":null,"map":null,"struct":null}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{}, strict: true, }, { data: []byte(`{"bool":null,"int":null,"int32":null,"int64":null,"float32":null,"float64":null,"string":null,"array":null,"map":null,"struct":null}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{}, yaml: true, strict: true, }, // coerce from string { data: []byte(`{"string":""}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{}, strict: true, }, { data: []byte(`{"string":""}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{}, yaml: true, strict: true, }, // coerce from array { data: []byte(`{"array":[]}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Array: []string{}}, strict: true, }, { data: []byte(`{"array":[]}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Array: []string{}}, yaml: true, strict: true, }, // coerce from map { data: []byte(`{"map":{},"struct":{}}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Map: map[string]string{}}, strict: true, }, { data: []byte(`{"map":{},"struct":{}}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Map: map[string]string{}}, yaml: true, strict: true, }, // coerce from int { data: []byte(`{"int":1,"int32":1,"int64":1,"float32":1,"float64":1}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Int: 1, Int32: 1, Int64: 1, Float32: 1, Float64: 1}, strict: true, }, { data: []byte(`{"int":1,"int32":1,"int64":1,"float32":1,"float64":1}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Int: 1, Int32: 1, Int64: 1, Float32: 1, Float64: 1}, yaml: true, strict: true, }, // coerce from float { data: []byte(`{"float32":1.0,"float64":1.0}`), into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Float32: 1, Float64: 1}, strict: true, }, { data: []byte(`{"int":1.0,"int32":1.0,"int64":1.0,"float32":1.0,"float64":1.0}`), // floating point gets dropped in yaml -> json step into: &testDecodeCoercion{}, typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &testDecodeCoercion{Int: 1, Int32: 1, Int64: 1, Float32: 1, Float64: 1}, yaml: true, strict: true, }, } logTestCase := func(t *testing.T, tc testCase) { t.Logf("data=%s\n\tinto=%T, yaml=%v, strict=%v", string(tc.data), tc.into, tc.yaml, tc.strict) } for i, test := range testCases { var s runtime.Serializer if test.yaml { s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, json.SerializerOptions{Yaml: test.yaml, Pretty: false, Strict: test.strict}) } else { s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, json.SerializerOptions{Yaml: test.yaml, Pretty: test.pretty, Strict: test.strict}) } obj, gvk, err := s.Decode([]byte(test.data), test.defaultGVK, test.into) if !reflect.DeepEqual(test.expectedGVK, gvk) { logTestCase(t, test) t.Errorf("%d: unexpected GVK: %v", i, gvk) } switch { case err == nil && test.errFn != nil: logTestCase(t, test) t.Errorf("%d: failed: not getting the expected error", i) continue case err != nil && test.errFn == nil: logTestCase(t, test) t.Errorf("%d: failed: %v", i, err) continue case err != nil: if !test.errFn(err) { logTestCase(t, test) t.Errorf("%d: failed: %v", i, err) } if !runtime.IsStrictDecodingError(err) && obj != nil { logTestCase(t, test) t.Errorf("%d: should have returned nil object", i) } continue } if test.into != nil && test.into != obj { logTestCase(t, test) t.Errorf("%d: expected into to be returned: %v", i, obj) continue } if !reflect.DeepEqual(test.expectedObject, obj) { logTestCase(t, test) t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) } } } func TestCacheableObject(t *testing.T) { gvk := schema.GroupVersionKind{Group: "group", Version: "version", Kind: "MockCacheableObject"} creater := &mockCreater{obj: &runtimetesting.MockCacheableObject{}} typer := &mockTyper{gvk: &gvk} serializer := json.NewSerializer(json.DefaultMetaFactory, creater, typer, false) runtimetesting.CacheableObjectTest(t, serializer) } type mockCreater struct { apiVersion string kind string err error obj runtime.Object } func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { c.apiVersion, c.kind = kind.GroupVersion().String(), kind.Kind return c.obj, c.err } type mockTyper struct { gvk *schema.GroupVersionKind err error } func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { if t.gvk == nil { return nil, false, t.err } return []schema.GroupVersionKind{*t.gvk}, false, t.err } func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { return false } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/json/meta.go000066400000000000000000000042631453143165200250770ustar00rootroot00000000000000/* 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 json import ( "encoding/json" "fmt" "k8s.io/apimachinery/pkg/runtime/schema" ) // MetaFactory is used to store and retrieve the version and kind // information for JSON objects in a serializer. type MetaFactory interface { // Interpret should return the version and kind of the wire-format of // the object. Interpret(data []byte) (*schema.GroupVersionKind, error) } // DefaultMetaFactory is a default factory for versioning objects in JSON. The object // in memory and in the default JSON serialization will use the "kind" and "apiVersion" // fields. var DefaultMetaFactory = SimpleMetaFactory{} // SimpleMetaFactory provides default methods for retrieving the type and version of objects // that are identified with an "apiVersion" and "kind" fields in their JSON // serialization. It may be parameterized with the names of the fields in memory, or an // optional list of base structs to search for those fields in memory. type SimpleMetaFactory struct { } // Interpret will return the APIVersion and Kind of the JSON wire-format // encoding of an object, or an error. func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { findKind := struct { // +optional APIVersion string `json:"apiVersion,omitempty"` // +optional Kind string `json:"kind,omitempty"` }{} if err := json.Unmarshal(data, &findKind); err != nil { return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err) } gv, err := schema.ParseGroupVersion(findKind.APIVersion) if err != nil { return nil, err } return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/json/meta_test.go000066400000000000000000000023451453143165200261350ustar00rootroot00000000000000/* 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 json import "testing" func TestSimpleMetaFactoryInterpret(t *testing.T) { factory := SimpleMetaFactory{} gvk, err := factory.Interpret([]byte(`{"apiVersion":"1","kind":"object"}`)) if err != nil { t.Fatalf("unexpected error: %v", err) } if gvk.Version != "1" || gvk.Kind != "object" { t.Errorf("unexpected interpret: %#v", gvk) } // no kind or version gvk, err = factory.Interpret([]byte(`{}`)) if err != nil { t.Fatalf("unexpected error: %v", err) } if gvk.Version != "" || gvk.Kind != "" { t.Errorf("unexpected interpret: %#v", gvk) } // unparsable _, err = factory.Interpret([]byte(`{`)) if err == nil { t.Errorf("unexpected non-error") } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/negotiated_codec.go000066400000000000000000000025421453143165200264560ustar00rootroot00000000000000/* Copyright 2016 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 serializer import ( "k8s.io/apimachinery/pkg/runtime" ) // TODO: We should split negotiated serializers that we can change versions on from those we can change // serialization formats on type negotiatedSerializerWrapper struct { info runtime.SerializerInfo } func NegotiatedSerializerWrapper(info runtime.SerializerInfo) runtime.NegotiatedSerializer { return &negotiatedSerializerWrapper{info} } func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []runtime.SerializerInfo { return []runtime.SerializerInfo{n.info} } func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder { return e } func (n *negotiatedSerializerWrapper) DecoderToVersion(d runtime.Decoder, _gv runtime.GroupVersioner) runtime.Decoder { return d } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/protobuf/000077500000000000000000000000001453143165200245045ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/protobuf/doc.go000066400000000000000000000013321453143165200255770ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package protobuf provides a Kubernetes serializer for the protobuf format. package protobuf // import "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/protobuf/protobuf.go000066400000000000000000000434301453143165200266770ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package protobuf import ( "bytes" "fmt" "io" "net/http" "reflect" "github.com/gogo/protobuf/proto" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" "k8s.io/apimachinery/pkg/util/framer" "k8s.io/klog/v2" ) var ( // protoEncodingPrefix serves as a magic number for an encoded protobuf message on this serializer. All // proto messages serialized by this schema will be preceded by the bytes 0x6b 0x38 0x73, with the fourth // byte being reserved for the encoding style. The only encoding style defined is 0x00, which means that // the rest of the byte stream is a message of type k8s.io.kubernetes.pkg.runtime.Unknown (proto2). // // See k8s.io/apimachinery/pkg/runtime/generated.proto for details of the runtime.Unknown message. // // This encoding scheme is experimental, and is subject to change at any time. protoEncodingPrefix = []byte{0x6b, 0x38, 0x73, 0x00} ) type errNotMarshalable struct { t reflect.Type } func (e errNotMarshalable) Error() string { return fmt.Sprintf("object %v does not implement the protobuf marshalling interface and cannot be encoded to a protobuf message", e.t) } func (e errNotMarshalable) Status() metav1.Status { return metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusNotAcceptable, Reason: metav1.StatusReason("NotAcceptable"), Message: e.Error(), } } // IsNotMarshalable checks the type of error, returns a boolean true if error is not nil and not marshalable false otherwise func IsNotMarshalable(err error) bool { _, ok := err.(errNotMarshalable) return err != nil && ok } // NewSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If a typer // is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written // as-is (any type info passed with the object will be used). func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer { return &Serializer{ prefix: protoEncodingPrefix, creater: creater, typer: typer, } } // Serializer handles encoding versioned objects into the proper wire form type Serializer struct { prefix []byte creater runtime.ObjectCreater typer runtime.ObjectTyper } var _ runtime.Serializer = &Serializer{} var _ runtime.EncoderWithAllocator = &Serializer{} var _ recognizer.RecognizingDecoder = &Serializer{} const serializerIdentifier runtime.Identifier = "protobuf" // Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default // gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, // the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will // be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is // not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most // errors, the method will return the calculated schema kind. func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { prefixLen := len(s.prefix) switch { case len(originalData) == 0: // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty data") case len(originalData) < prefixLen || !bytes.Equal(s.prefix, originalData[:prefixLen]): return nil, nil, fmt.Errorf("provided data does not appear to be a protobuf message, expected prefix %v", s.prefix) case len(originalData) == prefixLen: // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty body") } data := originalData[prefixLen:] unk := runtime.Unknown{} if err := unk.Unmarshal(data); err != nil { return nil, nil, err } actual := unk.GroupVersionKind() copyKindDefaults(&actual, gvk) if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { *intoUnknown = unk if ok, _, _ := s.RecognizesData(unk.Raw); ok { intoUnknown.ContentType = runtime.ContentTypeProtobuf } return intoUnknown, &actual, nil } if into != nil { types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err): pb, ok := into.(proto.Message) if !ok { return nil, &actual, errNotMarshalable{reflect.TypeOf(into)} } if err := proto.Unmarshal(unk.Raw, pb); err != nil { return nil, &actual, err } return into, &actual, nil case err != nil: return nil, &actual, err default: copyKindDefaults(&actual, &types[0]) // if the result of defaulting did not set a version or group, ensure that at least group is set // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group // of into is set if there is no better information from the caller or object. if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = types[0].Group } } } if len(actual.Kind) == 0 { return nil, &actual, runtime.NewMissingKindErr(fmt.Sprintf("%#v", unk.TypeMeta)) } if len(actual.Version) == 0 { return nil, &actual, runtime.NewMissingVersionErr(fmt.Sprintf("%#v", unk.TypeMeta)) } return unmarshalToObject(s.typer, s.creater, &actual, into, unk.Raw) } // EncodeWithAllocator writes an object to the provided writer. // In addition, it allows for providing a memory allocator for efficient memory usage during object serialization. func (s *Serializer) EncodeWithAllocator(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { return s.encode(obj, w, memAlloc) } // Encode serializes the provided object to the given writer. func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { return s.encode(obj, w, &runtime.SimpleAllocator{}) } func (s *Serializer) encode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { if co, ok := obj.(runtime.CacheableObject); ok { return co.CacheEncode(s.Identifier(), func(obj runtime.Object, w io.Writer) error { return s.doEncode(obj, w, memAlloc) }, w) } return s.doEncode(obj, w, memAlloc) } func (s *Serializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { if memAlloc == nil { klog.Error("a mandatory memory allocator wasn't provided, this might have a negative impact on performance, check invocations of EncodeWithAllocator method, falling back on runtime.SimpleAllocator") memAlloc = &runtime.SimpleAllocator{} } prefixSize := uint64(len(s.prefix)) var unk runtime.Unknown switch t := obj.(type) { case *runtime.Unknown: estimatedSize := prefixSize + uint64(t.Size()) data := memAlloc.Allocate(estimatedSize) i, err := t.MarshalTo(data[prefixSize:]) if err != nil { return err } copy(data, s.prefix) _, err = w.Write(data[:prefixSize+uint64(i)]) return err default: kind := obj.GetObjectKind().GroupVersionKind() unk = runtime.Unknown{ TypeMeta: runtime.TypeMeta{ Kind: kind.Kind, APIVersion: kind.GroupVersion().String(), }, } } switch t := obj.(type) { case bufferedMarshaller: // this path performs a single allocation during write only when the Allocator wasn't provided // it also requires the caller to implement the more efficient Size and MarshalToSizedBuffer methods encodedSize := uint64(t.Size()) estimatedSize := prefixSize + estimateUnknownSize(&unk, encodedSize) data := memAlloc.Allocate(estimatedSize) i, err := unk.NestedMarshalTo(data[prefixSize:], t, encodedSize) if err != nil { return err } copy(data, s.prefix) _, err = w.Write(data[:prefixSize+uint64(i)]) return err case proto.Marshaler: // this path performs extra allocations data, err := t.Marshal() if err != nil { return err } unk.Raw = data estimatedSize := prefixSize + uint64(unk.Size()) data = memAlloc.Allocate(estimatedSize) i, err := unk.MarshalTo(data[prefixSize:]) if err != nil { return err } copy(data, s.prefix) _, err = w.Write(data[:prefixSize+uint64(i)]) return err default: // TODO: marshal with a different content type and serializer (JSON for third party objects) return errNotMarshalable{reflect.TypeOf(obj)} } } // Identifier implements runtime.Encoder interface. func (s *Serializer) Identifier() runtime.Identifier { return serializerIdentifier } // RecognizesData implements the RecognizingDecoder interface. func (s *Serializer) RecognizesData(data []byte) (bool, bool, error) { return bytes.HasPrefix(data, s.prefix), false, nil } // copyKindDefaults defaults dst to the value in src if dst does not have a value set. func copyKindDefaults(dst, src *schema.GroupVersionKind) { if src == nil { return } // apply kind and version defaulting from provided default if len(dst.Kind) == 0 { dst.Kind = src.Kind } if len(dst.Version) == 0 && len(src.Version) > 0 { dst.Group = src.Group dst.Version = src.Version } } // bufferedMarshaller describes a more efficient marshalling interface that can avoid allocating multiple // byte buffers by pre-calculating the size of the final buffer needed. type bufferedMarshaller interface { proto.Sizer runtime.ProtobufMarshaller } // Like bufferedMarshaller, but is able to marshal backwards, which is more efficient since it doesn't call Size() as frequently. type bufferedReverseMarshaller interface { proto.Sizer runtime.ProtobufReverseMarshaller } // estimateUnknownSize returns the expected bytes consumed by a given runtime.Unknown // object with a nil RawJSON struct and the expected size of the provided buffer. The // returned size will not be correct if RawJSOn is set on unk. func estimateUnknownSize(unk *runtime.Unknown, byteSize uint64) uint64 { size := uint64(unk.Size()) // protobuf uses 1 byte for the tag, a varint for the length of the array (at most 8 bytes - uint64 - here), // and the size of the array. size += 1 + 8 + byteSize return size } // NewRawSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If typer // is not nil, the object has the group, version, and kind fields set. This serializer does not provide type information for the // encoded object, and thus is not self describing (callers must know what type is being described in order to decode). // // This encoding scheme is experimental, and is subject to change at any time. func NewRawSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper) *RawSerializer { return &RawSerializer{ creater: creater, typer: typer, } } // RawSerializer encodes and decodes objects without adding a runtime.Unknown wrapper (objects are encoded without identifying // type). type RawSerializer struct { creater runtime.ObjectCreater typer runtime.ObjectTyper } var _ runtime.Serializer = &RawSerializer{} const rawSerializerIdentifier runtime.Identifier = "raw-protobuf" // Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default // gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, // the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will // be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is // not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most // errors, the method will return the calculated schema kind. func (s *RawSerializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { if into == nil { return nil, nil, fmt.Errorf("this serializer requires an object to decode into: %#v", s) } if len(originalData) == 0 { // TODO: treat like decoding {} from JSON with defaulting return nil, nil, fmt.Errorf("empty data") } data := originalData actual := &schema.GroupVersionKind{} copyKindDefaults(actual, gvk) if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { intoUnknown.Raw = data intoUnknown.ContentEncoding = "" intoUnknown.ContentType = runtime.ContentTypeProtobuf intoUnknown.SetGroupVersionKind(*actual) return intoUnknown, actual, nil } types, _, err := s.typer.ObjectKinds(into) switch { case runtime.IsNotRegisteredError(err): pb, ok := into.(proto.Message) if !ok { return nil, actual, errNotMarshalable{reflect.TypeOf(into)} } if err := proto.Unmarshal(data, pb); err != nil { return nil, actual, err } return into, actual, nil case err != nil: return nil, actual, err default: copyKindDefaults(actual, &types[0]) // if the result of defaulting did not set a version or group, ensure that at least group is set // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group // of into is set if there is no better information from the caller or object. if len(actual.Version) == 0 && len(actual.Group) == 0 { actual.Group = types[0].Group } } if len(actual.Kind) == 0 { return nil, actual, runtime.NewMissingKindErr("") } if len(actual.Version) == 0 { return nil, actual, runtime.NewMissingVersionErr("") } return unmarshalToObject(s.typer, s.creater, actual, into, data) } // unmarshalToObject is the common code between decode in the raw and normal serializer. func unmarshalToObject(typer runtime.ObjectTyper, creater runtime.ObjectCreater, actual *schema.GroupVersionKind, into runtime.Object, data []byte) (runtime.Object, *schema.GroupVersionKind, error) { // use the target if necessary obj, err := runtime.UseOrCreateObject(typer, creater, *actual, into) if err != nil { return nil, actual, err } pb, ok := obj.(proto.Message) if !ok { return nil, actual, errNotMarshalable{reflect.TypeOf(obj)} } if err := proto.Unmarshal(data, pb); err != nil { return nil, actual, err } if actual != nil { obj.GetObjectKind().SetGroupVersionKind(*actual) } return obj, actual, nil } // Encode serializes the provided object to the given writer. Overrides is ignored. func (s *RawSerializer) Encode(obj runtime.Object, w io.Writer) error { return s.encode(obj, w, &runtime.SimpleAllocator{}) } // EncodeWithAllocator writes an object to the provided writer. // In addition, it allows for providing a memory allocator for efficient memory usage during object serialization. func (s *RawSerializer) EncodeWithAllocator(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { return s.encode(obj, w, memAlloc) } func (s *RawSerializer) encode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { if co, ok := obj.(runtime.CacheableObject); ok { return co.CacheEncode(s.Identifier(), func(obj runtime.Object, w io.Writer) error { return s.doEncode(obj, w, memAlloc) }, w) } return s.doEncode(obj, w, memAlloc) } func (s *RawSerializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { if memAlloc == nil { klog.Error("a mandatory memory allocator wasn't provided, this might have a negative impact on performance, check invocations of EncodeWithAllocator method, falling back on runtime.SimpleAllocator") memAlloc = &runtime.SimpleAllocator{} } switch t := obj.(type) { case bufferedReverseMarshaller: // this path performs a single allocation during write only when the Allocator wasn't provided // it also requires the caller to implement the more efficient Size and MarshalToSizedBuffer methods encodedSize := uint64(t.Size()) data := memAlloc.Allocate(encodedSize) n, err := t.MarshalToSizedBuffer(data) if err != nil { return err } _, err = w.Write(data[:n]) return err case bufferedMarshaller: // this path performs a single allocation during write only when the Allocator wasn't provided // it also requires the caller to implement the more efficient Size and MarshalTo methods encodedSize := uint64(t.Size()) data := memAlloc.Allocate(encodedSize) n, err := t.MarshalTo(data) if err != nil { return err } _, err = w.Write(data[:n]) return err case proto.Marshaler: // this path performs extra allocations data, err := t.Marshal() if err != nil { return err } _, err = w.Write(data) return err default: return errNotMarshalable{reflect.TypeOf(obj)} } } // Identifier implements runtime.Encoder interface. func (s *RawSerializer) Identifier() runtime.Identifier { return rawSerializerIdentifier } // LengthDelimitedFramer is exported variable of type lengthDelimitedFramer var LengthDelimitedFramer = lengthDelimitedFramer{} // Provides length delimited frame reader and writer methods type lengthDelimitedFramer struct{} // NewFrameWriter implements stream framing for this serializer func (lengthDelimitedFramer) NewFrameWriter(w io.Writer) io.Writer { return framer.NewLengthDelimitedFrameWriter(w) } // NewFrameReader implements stream framing for this serializer func (lengthDelimitedFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { return framer.NewLengthDelimitedFrameReader(r) } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/protobuf/protobuf_test.go000066400000000000000000000120021453143165200277250ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package protobuf import ( "bytes" "reflect" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" ) func TestCacheableObject(t *testing.T) { gvk := schema.GroupVersionKind{Group: "group", Version: "version", Kind: "MockCacheableObject"} creater := &mockCreater{obj: &runtimetesting.MockCacheableObject{}} typer := &mockTyper{gvk: &gvk} encoders := []runtime.Encoder{ NewSerializer(creater, typer), NewRawSerializer(creater, typer), } for _, encoder := range encoders { runtimetesting.CacheableObjectTest(t, encoder) } } type mockCreater struct { apiVersion string kind string err error obj runtime.Object } func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { c.apiVersion, c.kind = kind.GroupVersion().String(), kind.Kind return c.obj, c.err } type mockTyper struct { gvk *schema.GroupVersionKind err error } func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { if t.gvk == nil { return nil, false, t.err } return []schema.GroupVersionKind{*t.gvk}, false, t.err } func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { return false } func TestSerializerEncodeWithAllocator(t *testing.T) { testCases := []struct { name string obj runtime.Object }{ { name: "encode a bufferedMarshaller obj", obj: &testapigroupv1.Carp{ TypeMeta: metav1.TypeMeta{APIVersion: "group/version", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ Name: "name", Namespace: "namespace", }, Spec: testapigroupv1.CarpSpec{ Subdomain: "carp.k8s.io", }, }, }, { name: "encode a runtime.Unknown obj", obj: &runtime.Unknown{TypeMeta: runtime.TypeMeta{APIVersion: "group/version", Kind: "Unknown"}, Raw: []byte("hello world")}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { target := NewSerializer(nil, nil) writer := &bytes.Buffer{} if err := target.Encode(tc.obj, writer); err != nil { t.Fatal(err) } writer2 := &bytes.Buffer{} alloc := &testAllocator{} if err := target.EncodeWithAllocator(tc.obj, writer2, alloc); err != nil { t.Fatal(err) } if alloc.allocateCount != 1 { t.Fatalf("expected the Allocate method to be called exactly 1 but it was executed: %v times ", alloc.allocateCount) } // to ensure compatibility of the new method with the old one, serialized data must be equal // also we are not testing decoding since "roundtripping" is tested elsewhere for all known types if !reflect.DeepEqual(writer.Bytes(), writer2.Bytes()) { t.Fatal("data mismatch, data serialized with the Encode method is different than serialized with the EncodeWithAllocator method") } }) } } func TestRawSerializerEncodeWithAllocator(t *testing.T) { testCases := []struct { name string obj runtime.Object }{ { name: "encode a bufferedReverseMarshaller obj", obj: &testapigroupv1.Carp{ TypeMeta: metav1.TypeMeta{APIVersion: "group/version", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ Name: "name", Namespace: "namespace", }, Spec: testapigroupv1.CarpSpec{ Subdomain: "carp.k8s.io", }, }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { writer := &bytes.Buffer{} target := NewRawSerializer(nil, nil) if err := target.Encode(tc.obj, writer); err != nil { t.Fatal(err) } writer2 := &bytes.Buffer{} alloc := &testAllocator{} if err := target.EncodeWithAllocator(tc.obj, writer2, alloc); err != nil { t.Fatal(err) } if alloc.allocateCount != 1 { t.Fatalf("expected the Allocate method to be called exactly 1 but it was executed: %v times ", alloc.allocateCount) } // to ensure compatibility of the new method with the old one, serialized data must be equal // also we are not testing decoding since "roundtripping" is tested elsewhere for all known types if !reflect.DeepEqual(writer.Bytes(), writer2.Bytes()) { t.Fatal("data mismatch, data serialized with the Encode method is different than serialized with the EncodeWithAllocator method") } }) } } type testAllocator struct { buf []byte allocateCount int } func (ta *testAllocator) Allocate(n uint64) []byte { ta.buf = make([]byte, n) ta.allocateCount++ return ta.buf } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/recognizer/000077500000000000000000000000001453143165200250135ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/recognizer/recognizer.go000066400000000000000000000070631453143165200275170ustar00rootroot00000000000000/* 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 recognizer import ( "fmt" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) type RecognizingDecoder interface { runtime.Decoder // RecognizesData should return true if the input provided in the provided reader // belongs to this decoder, or an error if the data could not be read or is ambiguous. // Unknown is true if the data could not be determined to match the decoder type. // Decoders should assume that they can read as much of peek as they need (as the caller // provides) and may return unknown if the data provided is not sufficient to make a // a determination. When peek returns EOF that may mean the end of the input or the // end of buffered input - recognizers should return the best guess at that time. RecognizesData(peek []byte) (ok, unknown bool, err error) } // NewDecoder creates a decoder that will attempt multiple decoders in an order defined // by: // // 1. The decoder implements RecognizingDecoder and identifies the data // 2. All other decoders, and any decoder that returned true for unknown. // // The order passed to the constructor is preserved within those priorities. func NewDecoder(decoders ...runtime.Decoder) runtime.Decoder { return &decoder{ decoders: decoders, } } type decoder struct { decoders []runtime.Decoder } var _ RecognizingDecoder = &decoder{} func (d *decoder) RecognizesData(data []byte) (bool, bool, error) { var ( lastErr error anyUnknown bool ) for _, r := range d.decoders { switch t := r.(type) { case RecognizingDecoder: ok, unknown, err := t.RecognizesData(data) if err != nil { lastErr = err continue } anyUnknown = anyUnknown || unknown if !ok { continue } return true, false, nil } } return false, anyUnknown, lastErr } func (d *decoder) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { var ( lastErr error skipped []runtime.Decoder ) // try recognizers, record any decoders we need to give a chance later for _, r := range d.decoders { switch t := r.(type) { case RecognizingDecoder: ok, unknown, err := t.RecognizesData(data) if err != nil { lastErr = err continue } if unknown { skipped = append(skipped, t) continue } if !ok { continue } return r.Decode(data, gvk, into) default: skipped = append(skipped, t) } } // try recognizers that returned unknown or didn't recognize their data for _, r := range skipped { out, actual, err := r.Decode(data, gvk, into) if err != nil { // if we got an object back from the decoder, and the // error was a strict decoding error (e.g. unknown or // duplicate fields), we still consider the recognizer // to have understood the object if out == nil || !runtime.IsStrictDecodingError(err) { lastErr = err continue } } return out, actual, err } if lastErr == nil { lastErr = fmt.Errorf("no serialization format matched the provided data") } return nil, nil, lastErr } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/recognizer/testing/000077500000000000000000000000001453143165200264705ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/recognizer/testing/recognizer_test.go000066400000000000000000000027201453143165200322260ustar00rootroot00000000000000/* 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 testing import ( "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" ) type A struct{} func (A) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (a A) DeepCopyObject() runtime.Object { return a } func TestRecognizer(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(schema.GroupVersion{Version: "v1"}, &A{}) d := recognizer.NewDecoder( json.NewSerializer(json.DefaultMetaFactory, s, s, false), json.NewYAMLSerializer(json.DefaultMetaFactory, s, s), ) out, _, err := d.Decode([]byte(` kind: A apiVersion: v1 `), nil, nil) if err != nil { t.Fatal(err) } t.Logf("%#v", out) out, _, err = d.Decode([]byte(` { "kind":"A", "apiVersion":"v1" } `), nil, nil) if err != nil { t.Fatal(err) } t.Logf("%#v", out) } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/sparse_test.go000066400000000000000000000046061453143165200255350ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package serializer import ( "testing" "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) type FakeV1Obj struct { metav1.TypeMeta metav1.ObjectMeta } func (*FakeV1Obj) DeepCopyObject() runtime.Object { panic("not supported") } type FakeV2DifferentObj struct { metav1.TypeMeta metav1.ObjectMeta } func (*FakeV2DifferentObj) DeepCopyObject() runtime.Object { panic("not supported") } func TestSparse(t *testing.T) { v1 := schema.GroupVersion{Group: "mygroup", Version: "v1"} v2 := schema.GroupVersion{Group: "mygroup", Version: "v2"} scheme := runtime.NewScheme() scheme.AddKnownTypes(v1, &FakeV1Obj{}) scheme.AddKnownTypes(v2, &FakeV2DifferentObj{}) codecs := NewCodecFactory(scheme) srcObj1 := &FakeV1Obj{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} srcObj2 := &FakeV2DifferentObj{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} encoder := codecs.LegacyCodec(v2, v1) decoder := codecs.UniversalDecoder(v2, v1) srcObj1Bytes, err := runtime.Encode(encoder, srcObj1) if err != nil { t.Fatal(err) } t.Log(string(srcObj1Bytes)) srcObj2Bytes, err := runtime.Encode(encoder, srcObj2) if err != nil { t.Fatal(err) } t.Log(string(srcObj2Bytes)) uncastDstObj1, err := runtime.Decode(decoder, srcObj1Bytes) if err != nil { t.Fatal(err) } uncastDstObj2, err := runtime.Decode(decoder, srcObj2Bytes) if err != nil { t.Fatal(err) } // clear typemeta uncastDstObj1.(*FakeV1Obj).TypeMeta = metav1.TypeMeta{} uncastDstObj2.(*FakeV2DifferentObj).TypeMeta = metav1.TypeMeta{} if !equality.Semantic.DeepEqual(srcObj1, uncastDstObj1) { t.Fatal(cmp.Diff(srcObj1, uncastDstObj1)) } if !equality.Semantic.DeepEqual(srcObj2, uncastDstObj2) { t.Fatal(cmp.Diff(srcObj2, uncastDstObj2)) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/streaming/000077500000000000000000000000001453143165200246355ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/streaming/streaming.go000066400000000000000000000072421453143165200271620ustar00rootroot00000000000000/* Copyright 2015 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 streaming implements encoder and decoder for streams // of runtime.Objects over io.Writer/Readers. package streaming import ( "bytes" "fmt" "io" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // Encoder is a runtime.Encoder on a stream. type Encoder interface { // Encode will write the provided object to the stream or return an error. It obeys the same // contract as runtime.VersionedEncoder. Encode(obj runtime.Object) error } // Decoder is a runtime.Decoder from a stream. type Decoder interface { // Decode will return io.EOF when no more objects are available. Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) // Close closes the underlying stream. Close() error } // Serializer is a factory for creating encoders and decoders that work over streams. type Serializer interface { NewEncoder(w io.Writer) Encoder NewDecoder(r io.ReadCloser) Decoder } type decoder struct { reader io.ReadCloser decoder runtime.Decoder buf []byte maxBytes int resetRead bool } // NewDecoder creates a streaming decoder that reads object chunks from r and decodes them with d. // The reader is expected to return ErrShortRead if the provided buffer is not large enough to read // an entire object. func NewDecoder(r io.ReadCloser, d runtime.Decoder) Decoder { return &decoder{ reader: r, decoder: d, buf: make([]byte, 1024), maxBytes: 16 * 1024 * 1024, } } var ErrObjectTooLarge = fmt.Errorf("object to decode was longer than maximum allowed size") // Decode reads the next object from the stream and decodes it. func (d *decoder) Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { base := 0 for { n, err := d.reader.Read(d.buf[base:]) if err == io.ErrShortBuffer { if n == 0 { return nil, nil, fmt.Errorf("got short buffer with n=0, base=%d, cap=%d", base, cap(d.buf)) } if d.resetRead { continue } // double the buffer size up to maxBytes if len(d.buf) < d.maxBytes { base += n d.buf = append(d.buf, make([]byte, len(d.buf))...) continue } // must read the rest of the frame (until we stop getting ErrShortBuffer) d.resetRead = true return nil, nil, ErrObjectTooLarge } if err != nil { return nil, nil, err } if d.resetRead { // now that we have drained the large read, continue d.resetRead = false continue } base += n break } return d.decoder.Decode(d.buf[:base], defaults, into) } func (d *decoder) Close() error { return d.reader.Close() } type encoder struct { writer io.Writer encoder runtime.Encoder buf *bytes.Buffer } // NewEncoder returns a new streaming encoder. func NewEncoder(w io.Writer, e runtime.Encoder) Encoder { return &encoder{ writer: w, encoder: e, buf: &bytes.Buffer{}, } } // Encode writes the provided object to the nested writer. func (e *encoder) Encode(obj runtime.Object) error { if err := e.encoder.Encode(obj, e.buf); err != nil { return err } _, err := e.writer.Write(e.buf.Bytes()) e.buf.Reset() return err } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/streaming/streaming_test.go000066400000000000000000000043641453143165200302230ustar00rootroot00000000000000/* Copyright 2016 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 streaming import ( "bytes" "io" "io/ioutil" "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/framer" ) type fakeDecoder struct { got []byte obj runtime.Object err error } func (d *fakeDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { d.got = data return d.obj, nil, d.err } func TestEmptyDecoder(t *testing.T) { buf := bytes.NewBuffer([]byte{}) d := &fakeDecoder{} _, _, err := NewDecoder(ioutil.NopCloser(buf), d).Decode(nil, nil) if err != io.EOF { t.Fatal(err) } } func TestDecoder(t *testing.T) { frames := [][]byte{ make([]byte, 1025), make([]byte, 1024*5), make([]byte, 1024*1024*17), make([]byte, 1025), } pr, pw := io.Pipe() fw := framer.NewLengthDelimitedFrameWriter(pw) go func() { for i := range frames { fw.Write(frames[i]) } pw.Close() }() r := framer.NewLengthDelimitedFrameReader(pr) d := &fakeDecoder{} dec := NewDecoder(r, d) if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[0]) { t.Fatalf("unexpected %v %v", err, len(d.got)) } if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[1]) { t.Fatalf("unexpected %v %v", err, len(d.got)) } if _, _, err := dec.Decode(nil, nil); err != ErrObjectTooLarge || !bytes.Equal(d.got, frames[1]) { t.Fatalf("unexpected %v %v", err, len(d.got)) } if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[3]) { t.Fatalf("unexpected %v %v", err, len(d.got)) } if _, _, err := dec.Decode(nil, nil); err != io.EOF { t.Fatalf("unexpected %v %v", err, len(d.got)) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/versioning/000077500000000000000000000000001453143165200250275ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/versioning/versioning.go000066400000000000000000000234111453143165200275420ustar00rootroot00000000000000/* 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 versioning import ( "encoding/json" "io" "reflect" "sync" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" ) // NewDefaultingCodecForScheme is a convenience method for callers that are using a scheme. func NewDefaultingCodecForScheme( // TODO: I should be a scheme interface? scheme *runtime.Scheme, encoder runtime.Encoder, decoder runtime.Decoder, encodeVersion runtime.GroupVersioner, decodeVersion runtime.GroupVersioner, ) runtime.Codec { return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion, scheme.Name()) } // NewCodec takes objects in their internal versions and converts them to external versions before // serializing them. It assumes the serializer provided to it only deals with external versions. // This class is also a serializer, but is generally used with a specific version. func NewCodec( encoder runtime.Encoder, decoder runtime.Decoder, convertor runtime.ObjectConvertor, creater runtime.ObjectCreater, typer runtime.ObjectTyper, defaulter runtime.ObjectDefaulter, encodeVersion runtime.GroupVersioner, decodeVersion runtime.GroupVersioner, originalSchemeName string, ) runtime.Codec { internal := &codec{ encoder: encoder, decoder: decoder, convertor: convertor, creater: creater, typer: typer, defaulter: defaulter, encodeVersion: encodeVersion, decodeVersion: decodeVersion, identifier: identifier(encodeVersion, encoder), originalSchemeName: originalSchemeName, } return internal } type codec struct { encoder runtime.Encoder decoder runtime.Decoder convertor runtime.ObjectConvertor creater runtime.ObjectCreater typer runtime.ObjectTyper defaulter runtime.ObjectDefaulter encodeVersion runtime.GroupVersioner decodeVersion runtime.GroupVersioner identifier runtime.Identifier // originalSchemeName is optional, but when filled in it holds the name of the scheme from which this codec originates originalSchemeName string } var _ runtime.EncoderWithAllocator = &codec{} var identifiersMap sync.Map type codecIdentifier struct { EncodeGV string `json:"encodeGV,omitempty"` Encoder string `json:"encoder,omitempty"` Name string `json:"name,omitempty"` } // identifier computes Identifier of Encoder based on codec parameters. func identifier(encodeGV runtime.GroupVersioner, encoder runtime.Encoder) runtime.Identifier { result := codecIdentifier{ Name: "versioning", } if encodeGV != nil { result.EncodeGV = encodeGV.Identifier() } if encoder != nil { result.Encoder = string(encoder.Identifier()) } if id, ok := identifiersMap.Load(result); ok { return id.(runtime.Identifier) } identifier, err := json.Marshal(result) if err != nil { klog.Fatalf("Failed marshaling identifier for codec: %v", err) } identifiersMap.Store(result, runtime.Identifier(identifier)) return runtime.Identifier(identifier) } // Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is // successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an // into that matches the serialized version. func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { // If the into object is unstructured and expresses an opinion about its group/version, // create a new instance of the type so we always exercise the conversion path (skips short-circuiting on `into == obj`) decodeInto := into if into != nil { if _, ok := into.(runtime.Unstructured); ok && !into.GetObjectKind().GroupVersionKind().GroupVersion().Empty() { decodeInto = reflect.New(reflect.TypeOf(into).Elem()).Interface().(runtime.Object) } } var strictDecodingErrs []error obj, gvk, err := c.decoder.Decode(data, defaultGVK, decodeInto) if err != nil { if strictErr, ok := runtime.AsStrictDecodingError(err); obj != nil && ok { // save the strictDecodingError and let the caller decide what to do with it strictDecodingErrs = append(strictDecodingErrs, strictErr.Errors()...) } else { return nil, gvk, err } } if d, ok := obj.(runtime.NestedObjectDecoder); ok { if err := d.DecodeNestedObjects(runtime.WithoutVersionDecoder{Decoder: c.decoder}); err != nil { if strictErr, ok := runtime.AsStrictDecodingError(err); ok { // save the strictDecodingError let and the caller decide what to do with it strictDecodingErrs = append(strictDecodingErrs, strictErr.Errors()...) } else { return nil, gvk, err } } } // aggregate the strict decoding errors into one var strictDecodingErr error if len(strictDecodingErrs) > 0 { strictDecodingErr = runtime.NewStrictDecodingError(strictDecodingErrs) } // if we specify a target, use generic conversion. if into != nil { // perform defaulting if requested if c.defaulter != nil { c.defaulter.Default(obj) } // Short-circuit conversion if the into object is same object if into == obj { return into, gvk, strictDecodingErr } if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil { return nil, gvk, err } return into, gvk, strictDecodingErr } // perform defaulting if requested if c.defaulter != nil { c.defaulter.Default(obj) } out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion) if err != nil { return nil, gvk, err } return out, gvk, strictDecodingErr } // EncodeWithAllocator ensures the provided object is output in the appropriate group and version, invoking // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. // In addition, it allows for providing a memory allocator for efficient memory usage during object serialization. func (c *codec) EncodeWithAllocator(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { return c.encode(obj, w, memAlloc) } // Encode ensures the provided object is output in the appropriate group and version, invoking // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. func (c *codec) Encode(obj runtime.Object, w io.Writer) error { return c.encode(obj, w, nil) } func (c *codec) encode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { if co, ok := obj.(runtime.CacheableObject); ok { return co.CacheEncode(c.Identifier(), func(obj runtime.Object, w io.Writer) error { return c.doEncode(obj, w, memAlloc) }, w) } return c.doEncode(obj, w, memAlloc) } func (c *codec) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { encodeFn := c.encoder.Encode if memAlloc != nil { if encoder, supportsAllocator := c.encoder.(runtime.EncoderWithAllocator); supportsAllocator { encodeFn = func(obj runtime.Object, w io.Writer) error { return encoder.EncodeWithAllocator(obj, w, memAlloc) } } else { klog.V(6).Infof("a memory allocator was provided but the encoder %s doesn't implement the runtime.EncoderWithAllocator, using regular encoder.Encode method", c.encoder.Identifier()) } } switch obj := obj.(type) { case *runtime.Unknown: return encodeFn(obj, w) case runtime.Unstructured: // An unstructured list can contain objects of multiple group version kinds. don't short-circuit just // because the top-level type matches our desired destination type. actually send the object to the converter // to give it a chance to convert the list items if needed. if _, ok := obj.(*unstructured.UnstructuredList); !ok { // avoid conversion roundtrip if GVK is the right one already or is empty (yes, this is a hack, but the old behaviour we rely on in kubectl) objGVK := obj.GetObjectKind().GroupVersionKind() if len(objGVK.Version) == 0 { return encodeFn(obj, w) } targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK}) if !ok { return runtime.NewNotRegisteredGVKErrForTarget(c.originalSchemeName, objGVK, c.encodeVersion) } if targetGVK == objGVK { return encodeFn(obj, w) } } } gvks, isUnversioned, err := c.typer.ObjectKinds(obj) if err != nil { return err } objectKind := obj.GetObjectKind() old := objectKind.GroupVersionKind() // restore the old GVK after encoding defer objectKind.SetGroupVersionKind(old) if c.encodeVersion == nil || isUnversioned { if e, ok := obj.(runtime.NestedObjectEncoder); ok { if err := e.EncodeNestedObjects(runtime.WithVersionEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { return err } } objectKind.SetGroupVersionKind(gvks[0]) return encodeFn(obj, w) } // Perform a conversion if necessary out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion) if err != nil { return err } if e, ok := out.(runtime.NestedObjectEncoder); ok { if err := e.EncodeNestedObjects(runtime.WithVersionEncoder{Version: c.encodeVersion, Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { return err } } // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object return encodeFn(out, w) } // Identifier implements runtime.Encoder interface. func (c *codec) Identifier() runtime.Identifier { return c.identifier } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/versioning/versioning_test.go000066400000000000000000000310631453143165200306030ustar00rootroot00000000000000/* Copyright 2015 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 versioning import ( "fmt" "io" "io/ioutil" "reflect" "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" "k8s.io/apimachinery/pkg/util/diff" ) type testDecodable struct { Other string Value int `json:"value"` gvk schema.GroupVersionKind } func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d } func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } func (d *testDecodable) DeepCopyObject() runtime.Object { // no real deepcopy because these tests check for pointer equality return d } type testNestedDecodable struct { Other string Value int `json:"value"` gvk schema.GroupVersionKind nestedCalled bool nestedErr error } func (d *testNestedDecodable) GetObjectKind() schema.ObjectKind { return d } func (d *testNestedDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } func (d *testNestedDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } func (d *testNestedDecodable) DeepCopyObject() runtime.Object { // no real deepcopy because these tests check for pointer equality return d } func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error { d.nestedCalled = true return d.nestedErr } func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error { d.nestedCalled = true return d.nestedErr } func TestNestedDecode(t *testing.T) { n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} decoder := &mockSerializer{obj: n} codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr { t.Errorf("unexpected error: %v", err) } if !n.nestedCalled { t.Errorf("did not invoke nested decoder") } } func TestNestedDecodeStrictDecodingError(t *testing.T) { strictErr := runtime.NewStrictDecodingError([]error{fmt.Errorf("duplicate field")}) n := &testNestedDecodable{nestedErr: strictErr} decoder := &mockSerializer{obj: n} codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") o, _, err := codec.Decode([]byte(`{}`), nil, n) if strictErr, ok := runtime.AsStrictDecodingError(err); !ok || err != strictErr { t.Errorf("unexpected error: %v", err) } if o != n { t.Errorf("did not successfully decode with strict decoding error: %v", o) } if !n.nestedCalled { t.Errorf("did not invoke nested decoder") } } func TestNestedEncode(t *testing.T) { n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")} encoder := &mockSerializer{obj: n} codec := NewCodec( encoder, nil, &checkConvertor{obj: n2, groupVersion: schema.GroupVersion{Group: "other"}}, nil, &mockTyper{gvks: []schema.GroupVersionKind{{Kind: "test"}}}, nil, schema.GroupVersion{Group: "other"}, nil, "TestNestedEncode", ) if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr { t.Errorf("unexpected error: %v", err) } if n.nestedCalled || !n2.nestedCalled { t.Errorf("did not invoke correct nested decoder") } } func TestNestedEncodeError(t *testing.T) { n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to encode")} gvk1 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v1"} gvk2 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v2"} n.SetGroupVersionKind(gvk1) encoder := &mockSerializer{obj: n} codec := NewCodec( encoder, nil, &mockConvertor{}, nil, &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, nil, schema.GroupVersion{Group: "other", Version: "v2"}, nil, "TestNestedEncodeError", ) if err := codec.Encode(n, ioutil.Discard); err != n.nestedErr { t.Errorf("unexpected error: %v", err) } if n.GroupVersionKind() != gvk1 { t.Errorf("unexpected gvk of input object: %v", n.GroupVersionKind()) } } func TestDecode(t *testing.T) { gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"} decodable1 := &testDecodable{} decodable2 := &testDecodable{} decodable3 := &testDecodable{} testCases := []struct { serializer runtime.Serializer convertor runtime.ObjectConvertor creater runtime.ObjectCreater typer runtime.ObjectTyper defaulter runtime.ObjectDefaulter yaml bool pretty bool encodes, decodes runtime.GroupVersioner defaultGVK *schema.GroupVersionKind into runtime.Object errFn func(error) bool expectedObject runtime.Object sameObject runtime.Object expectedGVK *schema.GroupVersionKind }{ { serializer: &mockSerializer{actual: gvk1}, convertor: &checkConvertor{groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, expectedGVK: gvk1, decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, }, { serializer: &mockSerializer{actual: gvk1, obj: decodable1}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, expectedGVK: gvk1, sameObject: decodable2, decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, }, // defaultGVK.Group is allowed to force a conversion to the destination group { serializer: &mockSerializer{actual: gvk1, obj: decodable1}, defaultGVK: &schema.GroupVersionKind{Group: "force"}, convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}}, expectedGVK: gvk1, sameObject: decodable2, decodes: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}, }, // uses direct conversion for into when objects differ { into: decodable3, serializer: &mockSerializer{actual: gvk1, obj: decodable1}, convertor: &checkConvertor{in: decodable1, obj: decodable3, directConvert: true}, expectedGVK: gvk1, sameObject: decodable3, }, // decode into the same version as the serialized object { decodes: schema.GroupVersions{gvk1.GroupVersion()}, serializer: &mockSerializer{actual: gvk1, obj: decodable1}, convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, expectedGVK: gvk1, expectedObject: decodable1, }, } for i, test := range testCases { t.Logf("%d", i) s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i)) obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into) if !reflect.DeepEqual(test.expectedGVK, gvk) { t.Errorf("%d: unexpected GVK: %v", i, gvk) } switch { case err == nil && test.errFn != nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil && test.errFn == nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil: if !test.errFn(err) { t.Errorf("%d: failed: %v", i, err) } if obj != nil { t.Errorf("%d: should have returned nil object", i) } continue } if test.into != nil && test.into != obj { t.Errorf("%d: expected into to be returned: %v", i, obj) continue } switch { case test.expectedObject != nil: if !reflect.DeepEqual(test.expectedObject, obj) { t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) } case test.sameObject != nil: if test.sameObject != obj { t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj)) } case obj != nil: t.Errorf("%d: unexpected object: %#v", i, obj) } } } type checkConvertor struct { err error in, obj runtime.Object groupVersion runtime.GroupVersioner directConvert bool } func (c *checkConvertor) Convert(in, out, context interface{}) error { if !c.directConvert { return fmt.Errorf("unexpected call to Convert") } if c.in != nil && c.in != in { return fmt.Errorf("unexpected in: %s", in) } if c.obj != nil && c.obj != out { return fmt.Errorf("unexpected out: %s", out) } return c.err } func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { if c.directConvert { return nil, fmt.Errorf("unexpected call to ConvertToVersion") } if c.in != nil && c.in != in { return nil, fmt.Errorf("unexpected in: %s", in) } if !reflect.DeepEqual(c.groupVersion, outVersion) { return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion) } return c.obj, c.err } func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") } type mockConvertor struct { } func (c *mockConvertor) Convert(in, out, context interface{}) error { return fmt.Errorf("unexpect call to Convert") } func (c *mockConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { objectKind := in.GetObjectKind() inGVK := objectKind.GroupVersionKind() if out, ok := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{inGVK}); ok { objectKind.SetGroupVersionKind(out) } else { return nil, fmt.Errorf("unexpected conversion") } return in, nil } func (c *mockConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") } type mockSerializer struct { err error obj runtime.Object encodingObjGVK schema.GroupVersionKind defaults, actual *schema.GroupVersionKind into runtime.Object } func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { s.defaults = defaults s.into = into return s.obj, s.actual, s.err } func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error { s.obj = obj s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind() return s.err } func (s *mockSerializer) Identifier() runtime.Identifier { return runtime.Identifier("mock") } type mockTyper struct { gvks []schema.GroupVersionKind unversioned bool err error } func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { return t.gvks, t.unversioned, t.err } func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { return true } func TestDirectCodecEncode(t *testing.T) { serializer := mockSerializer{} typer := mockTyper{ gvks: []schema.GroupVersionKind{ { Group: "wrong_group", Kind: "some_kind", }, { Group: "expected_group", Kind: "some_kind", }, }, } c := runtime.WithVersionEncoder{ Version: schema.GroupVersion{Group: "expected_group"}, Encoder: &serializer, ObjectTyper: &typer, } c.Encode(&testDecodable{}, ioutil.Discard) if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a { t.Errorf("expected group to be %v, got %v", e, a) } } func TestCacheableObject(t *testing.T) { gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"} gvk2 := schema.GroupVersionKind{Group: "group", Version: "version2", Kind: "MockCacheableObject"} encoder := NewCodec( &mockSerializer{}, &mockSerializer{}, &mockConvertor{}, nil, &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, nil, gvk1.GroupVersion(), gvk2.GroupVersion(), "TestCacheableObject") runtimetesting.CacheableObjectTest(t, encoder) } func BenchmarkIdentifier(b *testing.B) { encoder := &mockSerializer{} gv := schema.GroupVersion{Group: "group", Version: "version"} for i := 0; i < b.N; i++ { id := identifier(gv, encoder) // Avoid optimizing by compiler. if id[0] != '{' { b.Errorf("unexpected identifier: %s", id) } } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/versioning/versioning_unstructured_test.go000066400000000000000000000255471453143165200334440ustar00rootroot00000000000000/* Copyright 2015 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 versioning import ( "fmt" "io/ioutil" "testing" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) func buildUnstructuredDecodable(gvk schema.GroupVersionKind) runtime.Object { obj := &unstructured.Unstructured{} obj.SetGroupVersionKind(gvk) return obj } func buildUnstructuredListDecodable(gvk schema.GroupVersionKind) runtime.Object { obj := &unstructured.UnstructuredList{} obj.SetGroupVersionKind(gvk) return obj } func TestEncodeUnstructured(t *testing.T) { v1GVK := schema.GroupVersionKind{ Group: "crispy", Version: "v1", Kind: "Noxu", } v2GVK := schema.GroupVersionKind{ Group: "crispy", Version: "v2", Kind: "Noxu", } elseGVK := schema.GroupVersionKind{ Group: "crispy2", Version: "else", Kind: "Noxu", } elseUnstructuredDecodable := buildUnstructuredDecodable(elseGVK) elseUnstructuredDecodableList := buildUnstructuredListDecodable(elseGVK) v1UnstructuredDecodable := buildUnstructuredDecodable(v1GVK) v1UnstructuredDecodableList := buildUnstructuredListDecodable(v1GVK) v2UnstructuredDecodable := buildUnstructuredDecodable(v2GVK) testCases := []struct { name string convertor runtime.ObjectConvertor targetVersion runtime.GroupVersioner outObj runtime.Object typer runtime.ObjectTyper errFunc func(error) bool expectedObj runtime.Object }{ { name: "encode v1 unstructured with v2 encode version", typer: &mockTyper{ gvks: []schema.GroupVersionKind{v1GVK}, }, outObj: v1UnstructuredDecodable, targetVersion: v2GVK.GroupVersion(), convertor: &checkConvertor{ obj: v2UnstructuredDecodable, groupVersion: v2GVK.GroupVersion(), }, expectedObj: v2UnstructuredDecodable, }, { name: "both typer and conversion are bypassed when unstructured gvk matches encode gvk", typer: &mockTyper{ err: fmt.Errorf("unexpected typer call"), }, outObj: v1UnstructuredDecodable, targetVersion: v1GVK.GroupVersion(), convertor: &checkConvertor{ err: fmt.Errorf("unexpected conversion happened"), }, expectedObj: v1UnstructuredDecodable, }, { name: "encode will fail when unstructured object's gvk and encode gvk mismatches", outObj: elseUnstructuredDecodable, targetVersion: v1GVK.GroupVersion(), errFunc: func(err error) bool { return assert.Equal(t, runtime.NewNotRegisteredGVKErrForTarget("noxu-scheme", elseGVK, v1GVK.GroupVersion()), err) }, }, { name: "encode with unstructured list's gvk regardless of its elements' gvk", outObj: elseUnstructuredDecodableList, targetVersion: elseGVK.GroupVersion(), }, { name: "typer fail to recognize unstructured object gvk will fail the encoding", outObj: elseUnstructuredDecodable, targetVersion: v1GVK.GroupVersion(), typer: &mockTyper{ err: fmt.Errorf("invalid obj gvk"), }, }, { name: "encoding unstructured object without encode version will fallback to typer suggested version", targetVersion: v1GVK.GroupVersion(), convertor: &checkConvertor{ obj: v1UnstructuredDecodableList, groupVersion: v1GVK.GroupVersion(), }, outObj: elseUnstructuredDecodable, typer: &mockTyper{ gvks: []schema.GroupVersionKind{v1GVK}, }, }, } for _, testCase := range testCases { serializer := &mockSerializer{} codec := NewCodec(serializer, serializer, testCase.convertor, nil, testCase.typer, nil, testCase.targetVersion, nil, "noxu-scheme") err := codec.Encode(testCase.outObj, ioutil.Discard) if testCase.errFunc != nil { if !testCase.errFunc(err) { t.Errorf("%v: failed: %v", testCase.name, err) } return } assert.NoError(t, err) assert.Equal(t, testCase.expectedObj, serializer.obj) } } type errNotRecognizedGVK struct { failedGVK schema.GroupVersionKind claimingGVKs []schema.GroupVersionKind } func (e errNotRecognizedGVK) Error() string { return fmt.Sprintf("unrecognized gvk %v, should be one of %v", e.failedGVK, e.claimingGVKs) } type mockUnstructuredNopConvertor struct { claimingGVKs []schema.GroupVersionKind } func (c *mockUnstructuredNopConvertor) recognizeGVK(gvkToCheck schema.GroupVersionKind) error { matched := false for _, gvk := range c.claimingGVKs { if gvk == gvkToCheck { matched = true } } if !matched { return errNotRecognizedGVK{ failedGVK: gvkToCheck, claimingGVKs: c.claimingGVKs, } } return nil } func (c *mockUnstructuredNopConvertor) Convert(in, out, context interface{}) error { inObj := in.(*unstructured.Unstructured) outObj := out.(*unstructured.Unstructured) if err := c.recognizeGVK(outObj.GroupVersionKind()); err != nil { return err } outGVK := outObj.GetObjectKind().GroupVersionKind() *outObj = *inObj.DeepCopy() outObj.GetObjectKind().SetGroupVersionKind(outGVK) return nil } func (c *mockUnstructuredNopConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (runtime.Object, error) { out := in.DeepCopyObject() targetGVK, matched := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{in.GetObjectKind().GroupVersionKind()}) if !matched { return nil, fmt.Errorf("attempt to convert to mismatched gv %v", outVersion) } if err := c.recognizeGVK(out.GetObjectKind().GroupVersionKind()); err != nil { return nil, err } out.GetObjectKind().SetGroupVersionKind(targetGVK) return out, nil } func (c *mockUnstructuredNopConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") } func TestDecodeUnstructured(t *testing.T) { internalGVK := schema.GroupVersionKind{ Group: "crispy", Version: runtime.APIVersionInternal, Kind: "Noxu", } v1GVK := schema.GroupVersionKind{ Group: "crispy", Version: "v1", Kind: "Noxu", } v2GVK := schema.GroupVersionKind{ Group: "crispy", Version: "v2", Kind: "Noxu", } internalUnstructuredDecodable := buildUnstructuredDecodable(internalGVK) v1UnstructuredDecodable := buildUnstructuredDecodable(v1GVK) v2UnstructuredDecodable := buildUnstructuredDecodable(v2GVK) testCases := []struct { name string serializer runtime.Serializer convertor runtime.ObjectConvertor suggestedConvertVersion runtime.GroupVersioner defaultGVK *schema.GroupVersionKind intoObj runtime.Object errFunc func(error) bool expectedGVKOfSerializedData *schema.GroupVersionKind expectedOut runtime.Object }{ { name: "decode v1 unstructured into non-nil v2 unstructured", serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, convertor: &mockUnstructuredNopConvertor{ claimingGVKs: []schema.GroupVersionKind{ v1GVK, v2GVK, }, }, suggestedConvertVersion: v2GVK.GroupVersion(), intoObj: v2UnstructuredDecodable, expectedGVKOfSerializedData: &v1GVK, expectedOut: v2UnstructuredDecodable, }, { name: "decode v1 unstructured into nil object with v2 version", serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, convertor: &mockUnstructuredNopConvertor{ claimingGVKs: []schema.GroupVersionKind{ v1GVK, v2GVK, }, }, suggestedConvertVersion: v2GVK.GroupVersion(), intoObj: nil, expectedGVKOfSerializedData: &v1GVK, expectedOut: v2UnstructuredDecodable, }, { name: "decode v1 unstructured into non-nil internal unstructured", serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, convertor: &mockUnstructuredNopConvertor{ claimingGVKs: []schema.GroupVersionKind{ v1GVK, v2GVK, }, }, suggestedConvertVersion: internalGVK.GroupVersion(), intoObj: internalUnstructuredDecodable, errFunc: func(err error) bool { notRecognized, ok := err.(errNotRecognizedGVK) if !ok { return false } return assert.Equal(t, notRecognized.failedGVK, internalGVK) }, }, { name: "decode v1 unstructured into nil object with internal version", serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, convertor: &mockUnstructuredNopConvertor{ claimingGVKs: []schema.GroupVersionKind{ v1GVK, v2GVK, }, }, suggestedConvertVersion: internalGVK.GroupVersion(), intoObj: nil, errFunc: func(err error) bool { notRecognized, ok := err.(errNotRecognizedGVK) if !ok { return false } return assert.Equal(t, notRecognized.failedGVK, internalGVK) }, }, { name: "skip conversion if serializer returns the same unstructured as into", serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, convertor: &checkConvertor{ err: fmt.Errorf("unexpected conversion happened"), }, suggestedConvertVersion: internalGVK.GroupVersion(), intoObj: v1UnstructuredDecodable, expectedGVKOfSerializedData: &v1GVK, expectedOut: v1UnstructuredDecodable, }, { name: "invalid convert version makes decoding unstructured fail", serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, convertor: &checkConvertor{ in: v1UnstructuredDecodable, groupVersion: internalGVK.GroupVersion(), err: fmt.Errorf("no matching decode version"), }, suggestedConvertVersion: internalGVK.GroupVersion(), errFunc: func(err error) bool { return assert.Equal(t, err, fmt.Errorf("no matching decode version")) }, }, } for _, testCase := range testCases { codec := NewCodec(testCase.serializer, testCase.serializer, testCase.convertor, nil, nil, nil, nil, testCase.suggestedConvertVersion, "noxu-scheme") actualObj, actualSerializedGVK, err := codec.Decode([]byte(`{}`), testCase.defaultGVK, testCase.intoObj) if testCase.errFunc != nil { if !testCase.errFunc(err) { t.Errorf("%v: failed: %v", testCase.name, err) } return } assert.NoError(t, err) assert.Equal(t, testCase.expectedOut, actualObj, "%v failed", testCase.name) assert.Equal(t, testCase.expectedGVKOfSerializedData, actualSerializedGVK, "%v failed", testCase.name) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/yaml/000077500000000000000000000000001453143165200236065ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/yaml/meta.go000066400000000000000000000034731453143165200250720ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package yaml import ( "fmt" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/yaml" ) // DefaultMetaFactory is a default factory for versioning objects in JSON or // YAML. The object in memory and in the default serialization will use the // "kind" and "apiVersion" fields. var DefaultMetaFactory = SimpleMetaFactory{} // SimpleMetaFactory provides default methods for retrieving the type and version of objects // that are identified with an "apiVersion" and "kind" fields in their JSON // serialization. It may be parameterized with the names of the fields in memory, or an // optional list of base structs to search for those fields in memory. type SimpleMetaFactory struct{} // Interpret will return the APIVersion and Kind of the JSON wire-format // encoding of an object, or an error. func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { gvk := runtime.TypeMeta{} if err := yaml.Unmarshal(data, &gvk); err != nil { return nil, fmt.Errorf("could not interpret GroupVersionKind; unmarshal error: %v", err) } gv, err := schema.ParseGroupVersion(gvk.APIVersion) if err != nil { return nil, err } return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind}, nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/yaml/meta_test.go000066400000000000000000000102661453143165200261270ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package yaml import ( "reflect" "testing" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestInterpret(t *testing.T) { testCases := []struct { name string input string expected *schema.GroupVersionKind errFn func(error) bool }{ { name: "YAMLSuccessfullyInterpretVK", input: `apiVersion: v1 kind: Service`, expected: &schema.GroupVersionKind{Version: "v1", Kind: "Service"}, }, { name: "YAMLSuccessfullyInterpretGVK", input: `apiVersion: core/v2 kind: Deployment`, expected: &schema.GroupVersionKind{Group: "core", Version: "v2", Kind: "Deployment"}, }, { name: "YAMLSuccessfullyInterpretV", input: `apiVersion: v1`, expected: &schema.GroupVersionKind{Version: "v1"}, }, { name: "YAMLSuccessfullyInterpretK", input: `kind: Service`, expected: &schema.GroupVersionKind{Kind: "Service"}, }, { name: "YAMLSuccessfullyInterpretEmptyString", input: ``, expected: &schema.GroupVersionKind{}, }, { name: "YAMLSuccessfullyInterpretEmptyDoc", input: `---`, expected: &schema.GroupVersionKind{}, }, { name: "YAMLSuccessfullyInterpretMultiDoc", input: `--- apiVersion: v1 kind: Service --- apiVersion: v2 kind: Deployment`, expected: &schema.GroupVersionKind{Version: "v1", Kind: "Service"}, }, { name: "YAMLSuccessfullyInterpretOnlyG", input: `apiVersion: core/`, expected: &schema.GroupVersionKind{Group: "core"}, }, { name: "YAMLSuccessfullyWrongFormat", input: `foo: bar`, expected: &schema.GroupVersionKind{}, }, { name: "YAMLFailInterpretWrongSyntax", input: `foo`, errFn: func(err error) bool { return err != nil }, }, { name: "JSONSuccessfullyInterpretVK", input: `{"apiVersion": "v3", "kind": "DaemonSet"}`, expected: &schema.GroupVersionKind{Version: "v3", Kind: "DaemonSet"}, }, { name: "JSONSuccessfullyInterpretGVK", input: `{"apiVersion": "core/v2", "kind": "Deployment"}`, expected: &schema.GroupVersionKind{Group: "core", Version: "v2", Kind: "Deployment"}, }, { name: "JSONSuccessfullyInterpretV", input: `{"apiVersion": "v1"}`, expected: &schema.GroupVersionKind{Version: "v1"}, }, { name: "JSONSuccessfullyInterpretK", input: `{"kind": "Service"}`, expected: &schema.GroupVersionKind{Kind: "Service"}, }, { name: "JSONSuccessfullyInterpretEmptyString", input: ``, expected: &schema.GroupVersionKind{}, }, { name: "JSONSuccessfullyInterpretEmptyObject", input: `{}`, expected: &schema.GroupVersionKind{}, }, { name: "JSONSuccessfullyInterpretMultiDoc", input: `{"apiVersion": "v1", "kind": "Service"}, {"apiVersion": "v2", "kind": "Deployment"}`, expected: &schema.GroupVersionKind{Version: "v1", Kind: "Service"}, }, { name: "JSONSuccessfullyWrongFormat", input: `{"foo": "bar"}`, expected: &schema.GroupVersionKind{}, }, { name: "JSONFailInterpretArray", input: `[]`, errFn: func(err error) bool { return err != nil }, }, { name: "JSONFailInterpretWrongSyntax", input: `{"foo"`, errFn: func(err error) bool { return err != nil }, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actual, err := DefaultMetaFactory.Interpret([]byte(test.input)) switch { case test.errFn != nil: if !test.errFn(err) { t.Errorf("unexpected error: %v", err) } case err != nil: t.Errorf("unexpected error: %v", err) case !reflect.DeepEqual(test.expected, actual): t.Errorf("outcome mismatch -- expected: %#v, actual: %#v", test.expected, actual, ) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/yaml/yaml.go000066400000000000000000000026371453143165200251070ustar00rootroot00000000000000/* 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 yaml import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" ) // yamlSerializer converts YAML passed to the Decoder methods to JSON. type yamlSerializer struct { // the nested serializer runtime.Serializer } // yamlSerializer implements Serializer var _ runtime.Serializer = yamlSerializer{} // NewDecodingSerializer adds YAML decoding support to a serializer that supports JSON. func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer { return &yamlSerializer{jsonSerializer} } func (c yamlSerializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { out, err := yaml.ToJSON(data) if err != nil { return nil, nil, err } data = out return c.Serializer.Decode(data, gvk, into) } golang-k8s-apimachinery-0.29.0/pkg/runtime/serializer/yaml/yaml_test.go000066400000000000000000000345221453143165200261440ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package yaml import ( "strings" "testing" "k8s.io/apimachinery/pkg/util/yaml" sigsyaml "sigs.k8s.io/yaml" ) type testcase struct { name string data []byte error string benchmark bool } func testcases() []testcase { return []testcase{ { name: "arrays of string aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a ["webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb"] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), }, { name: "arrays of empty string aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a ["","","","","","","","",""] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "arrays of null aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a [null,null,null,null,null,null,null,null,null] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "arrays of zero int aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a [0,0,0,0,0,0,0,0,0] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "arrays of zero float aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "arrays of big float aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a [1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "arrays of bool aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a [true,true,true,true,true,true,true,true,true] b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "map key aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a {"verylongkey1":"","verylongkey2":"","verylongkey3":"","verylongkey4":"","verylongkey5":"","verylongkey6":"","verylongkey7":"","verylongkey8":"","verylongkey9":""} b: &b {"verylongkey1":*a,"verylongkey2":*a,"verylongkey3":*a,"verylongkey4":*a,"verylongkey5":*a,"verylongkey6":*a,"verylongkey7":*a,"verylongkey8":*a,"verylongkey9":*a} c: &c {"verylongkey1":*b,"verylongkey2":*b,"verylongkey3":*b,"verylongkey4":*b,"verylongkey5":*b,"verylongkey6":*b,"verylongkey7":*b,"verylongkey8":*b,"verylongkey9":*b} d: &d {"verylongkey1":*c,"verylongkey2":*c,"verylongkey3":*c,"verylongkey4":*c,"verylongkey5":*c,"verylongkey6":*c,"verylongkey7":*c,"verylongkey8":*c,"verylongkey9":*c} e: &e {"verylongkey1":*d,"verylongkey2":*d,"verylongkey3":*d,"verylongkey4":*d,"verylongkey5":*d,"verylongkey6":*d,"verylongkey7":*d,"verylongkey8":*d,"verylongkey9":*d} f: &f {"verylongkey1":*e,"verylongkey2":*e,"verylongkey3":*e,"verylongkey4":*e,"verylongkey5":*e,"verylongkey6":*e,"verylongkey7":*e,"verylongkey8":*e,"verylongkey9":*e} g: &g {"verylongkey1":*f,"verylongkey2":*f,"verylongkey3":*f,"verylongkey4":*f,"verylongkey5":*f,"verylongkey6":*f,"verylongkey7":*f,"verylongkey8":*f,"verylongkey9":*f} h: &h {"verylongkey1":*g,"verylongkey2":*g,"verylongkey3":*g,"verylongkey4":*g,"verylongkey5":*g,"verylongkey6":*g,"verylongkey7":*g,"verylongkey8":*g,"verylongkey9":*g} i: &i {"verylongkey1":*h,"verylongkey2":*h,"verylongkey3":*h,"verylongkey4":*h,"verylongkey5":*h,"verylongkey6":*h,"verylongkey7":*h,"verylongkey8":*h,"verylongkey9":*h} kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "map value aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a {"1":"verylongmapvalue","2":"verylongmapvalue","3":"verylongmapvalue","4":"verylongmapvalue","5":"verylongmapvalue","6":"verylongmapvalue","7":"verylongmapvalue","8":"verylongmapvalue","9":"verylongmapvalue"} b: &b {"1":*a,"2":*a,"3":*a,"4":*a,"5":*a,"6":*a,"7":*a,"8":*a,"9":*a} c: &c {"1":*b,"2":*b,"3":*b,"4":*b,"5":*b,"6":*b,"7":*b,"8":*b,"9":*b} d: &d {"1":*c,"2":*c,"3":*c,"4":*c,"5":*c,"6":*c,"7":*c,"8":*c,"9":*c} e: &e {"1":*d,"2":*d,"3":*d,"4":*d,"5":*d,"6":*d,"7":*d,"8":*d,"9":*d} f: &f {"1":*e,"2":*e,"3":*e,"4":*e,"5":*e,"6":*e,"7":*e,"8":*e,"9":*e} g: &g {"1":*f,"2":*f,"3":*f,"4":*f,"5":*f,"6":*f,"7":*f,"8":*f,"9":*f} h: &h {"1":*g,"2":*g,"3":*g,"4":*g,"5":*g,"6":*g,"7":*g,"8":*g,"9":*g} i: &i {"1":*h,"2":*h,"3":*h,"4":*h,"5":*h,"6":*h,"7":*h,"8":*h,"9":*h} kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "nested map aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a {"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} b: &b {"1":*a,"2":*a,"3":*a,"4":*a,"5":*a,"6":*a,"7":*a,"8":*a,"9":*a} c: &c {"1":*b,"2":*b,"3":*b,"4":*b,"5":*b,"6":*b,"7":*b,"8":*b,"9":*b} d: &d {"1":*c,"2":*c,"3":*c,"4":*c,"5":*c,"6":*c,"7":*c,"8":*c,"9":*c} e: &e {"1":*d,"2":*d,"3":*d,"4":*d,"5":*d,"6":*d,"7":*d,"8":*d,"9":*d} f: &f {"1":*e,"2":*e,"3":*e,"4":*e,"5":*e,"6":*e,"7":*e,"8":*e,"9":*e} g: &g {"1":*f,"2":*f,"3":*f,"4":*f,"5":*f,"6":*f,"7":*f,"8":*f,"9":*f} h: &h {"1":*g,"2":*g,"3":*g,"4":*g,"5":*g,"6":*g,"7":*g,"8":*g,"9":*g} i: &i {"1":*h,"2":*h,"3":*h,"4":*h,"5":*h,"6":*h,"7":*h,"8":*h,"9":*h} kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "nested slice aliases", error: "excessive aliasing", data: []byte(` apiVersion: v1 data: a: &a [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[""]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] b: &b [[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]]] c: &c [[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]]] d: &d [[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]]] e: &e [[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]]] f: &f [[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]]] g: &g [[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]]] h: &h [[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]]] i: &i [[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]]] kind: ConfigMap metadata: name: yaml-bomb namespace: default `), benchmark: true, }, { name: "3MB map without alias", data: []byte(`a: &a [{a}` + strings.Repeat(`,{a}`, 3*1024*1024/4) + `]`), benchmark: true, }, { name: "3MB map with alias", error: "excessive aliasing", data: []byte(` a: &a [{a}` + strings.Repeat(`,{a}`, 3*1024*1024/4) + `] b: &b [*a]`), benchmark: true, }, { name: "deeply nested slices", error: "max depth", data: []byte(strings.Repeat(`[`, 3*1024*1024)), }, { name: "deeply nested maps", error: "max depth", data: []byte("x: " + strings.Repeat(`{`, 3*1024*1024)), }, { name: "deeply nested indents", error: "max depth", data: []byte(strings.Repeat(`- `, 3*1024*1024)), }, { name: "3MB of 1000-indent lines", data: []byte(strings.Repeat(strings.Repeat(`- `, 1000)+"\n", 3*1024/2)), benchmark: true, }, { name: "3MB of empty slices", data: []byte(`[` + strings.Repeat(`[],`, 3*1024*1024/3-2) + `[]]`), benchmark: true, }, { name: "3MB of slices", data: []byte(`[` + strings.Repeat(`[0],`, 3*1024*1024/4-2) + `[0]]`), benchmark: true, }, { name: "3MB of empty maps", data: []byte(`[` + strings.Repeat(`{},`, 3*1024*1024/3-2) + `{}]`), benchmark: true, }, { name: "3MB of maps", data: []byte(`[` + strings.Repeat(`{a},`, 3*1024*1024/4-2) + `{a}]`), benchmark: true, }, { name: "3MB of ints", data: []byte(`[` + strings.Repeat(`0,`, 3*1024*1024/2-2) + `0]`), benchmark: true, }, { name: "3MB of floats", data: []byte(`[` + strings.Repeat(`0.0,`, 3*1024*1024/4-2) + `0.0]`), benchmark: true, }, { name: "3MB of bools", data: []byte(`[` + strings.Repeat(`true,`, 3*1024*1024/5-2) + `true]`), benchmark: true, }, { name: "3MB of empty strings", data: []byte(`[` + strings.Repeat(`"",`, 3*1024*1024/3-2) + `""]`), benchmark: true, }, { name: "3MB of strings", data: []byte(`[` + strings.Repeat(`"abcdefghijklmnopqrstuvwxyz012",`, 3*1024*1024/30-2) + `"abcdefghijklmnopqrstuvwxyz012"]`), benchmark: true, }, { name: "3MB of nulls", data: []byte(`[` + strings.Repeat(`null,`, 3*1024*1024/5-2) + `null]`), benchmark: true, }, } } var decoders = map[string]func([]byte) ([]byte, error){ "sigsyaml": sigsyaml.YAMLToJSON, "utilyaml": yaml.ToJSON, } func TestYAMLLimits(t *testing.T) { for _, tc := range testcases() { if tc.benchmark { continue } t.Run(tc.name, func(t *testing.T) { for decoderName, decoder := range decoders { t.Run(decoderName, func(t *testing.T) { _, err := decoder(tc.data) if len(tc.error) == 0 { if err != nil { t.Errorf("unexpected error: %v", err) } } else { if err == nil || !strings.Contains(err.Error(), tc.error) { t.Errorf("expected %q error, got %v", tc.error, err) } } }) } }) } } func BenchmarkYAMLLimits(b *testing.B) { for _, tc := range testcases() { b.Run(tc.name, func(b *testing.B) { for decoderName, decoder := range decoders { b.Run(decoderName, func(b *testing.B) { for i := 0; i < b.N; i++ { _, err := decoder(tc.data) if len(tc.error) == 0 { if err != nil { b.Errorf("unexpected error: %v", err) } } else { if err == nil || !strings.Contains(err.Error(), tc.error) { b.Errorf("expected %q error, got %v", tc.error, err) } } } }) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/splice.go000066400000000000000000000033371453143165200223070ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime import ( "bytes" "io" ) // Splice is the interface that wraps the Splice method. // // Splice moves data from given slice without copying the underlying data for // efficiency purpose. Therefore, the caller should make sure the underlying // data is not changed later. type Splice interface { Splice([]byte) io.Writer Reset() Bytes() []byte } // A spliceBuffer implements Splice and io.Writer interfaces. type spliceBuffer struct { raw []byte buf *bytes.Buffer } func NewSpliceBuffer() Splice { return &spliceBuffer{} } // Splice implements the Splice interface. func (sb *spliceBuffer) Splice(raw []byte) { sb.raw = raw } // Write implements the io.Writer interface. func (sb *spliceBuffer) Write(p []byte) (n int, err error) { if sb.buf == nil { sb.buf = &bytes.Buffer{} } return sb.buf.Write(p) } // Reset resets the buffer to be empty. func (sb *spliceBuffer) Reset() { if sb.buf != nil { sb.buf.Reset() } sb.raw = nil } // Bytes returns the data held by the buffer. func (sb *spliceBuffer) Bytes() []byte { if sb.buf != nil && len(sb.buf.Bytes()) > 0 { return sb.buf.Bytes() } if sb.raw != nil { return sb.raw } return []byte{} } golang-k8s-apimachinery-0.29.0/pkg/runtime/splice_test.go000066400000000000000000000050311453143165200233370ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package runtime_test import ( "bytes" "testing" "k8s.io/apimachinery/pkg/runtime" ) func TestSpliceBuffer(t *testing.T) { testBytes0 := []byte{0x01, 0x02, 0x03, 0x04} testBytes1 := []byte{0x04, 0x03, 0x02, 0x02} testCases := []struct { name string run func(sb runtime.Splice, buf *bytes.Buffer) }{ { name: "Basic Write", run: func(sb runtime.Splice, buf *bytes.Buffer) { sb.Write(testBytes0) buf.Write(testBytes0) }, }, { name: "Multiple Writes", run: func(sb runtime.Splice, buf *bytes.Buffer) { for _, b := range testBytes0 { sb.Write([]byte{b}) buf.Write([]byte{b}) } }, }, { name: "Write and Reset", run: func(sb runtime.Splice, buf *bytes.Buffer) { sb.Write(testBytes0) buf.Write(testBytes0) sb.Reset() buf.Reset() }, }, { name: "Write/Splice", run: func(sb runtime.Splice, buf *bytes.Buffer) { sb.Splice(testBytes0) buf.Write(testBytes0) }, }, { name: "Write/Splice and Reset", run: func(sb runtime.Splice, buf *bytes.Buffer) { sb.Splice(testBytes0) buf.Write(testBytes0) sb.Reset() buf.Reset() }, }, { name: "Write/Splice, Reset, Write/Splice", run: func(sb runtime.Splice, buf *bytes.Buffer) { sb.Splice(testBytes0) buf.Write(testBytes0) sb.Reset() buf.Reset() sb.Splice(testBytes1) buf.Write(testBytes1) }, }, { name: "Write, Reset, Splice", run: func(sb runtime.Splice, buf *bytes.Buffer) { sb.Write(testBytes0) buf.Write(testBytes0) sb.Reset() buf.Reset() sb.Splice(testBytes1) buf.Write(testBytes1) }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { sb := runtime.NewSpliceBuffer() buf := &bytes.Buffer{} tt.run(sb, buf) if sb.Bytes() == nil { t.Errorf("Unexpected nil") } if string(sb.Bytes()) != string(buf.Bytes()) { t.Errorf("Expected sb.Bytes() == %q, buf.Bytes() == %q", sb.Bytes(), buf.Bytes()) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/swagger_doc_generator.go000066400000000000000000000160311453143165200253550ustar00rootroot00000000000000/* Copyright 2015 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 runtime import ( "bytes" "fmt" "go/ast" "go/doc" "go/parser" "go/token" "io" "reflect" "strings" ) // Pair of strings. We keed the name of fields and the doc type Pair struct { Name, Doc string } // KubeTypes is an array to represent all available types in a parsed file. [0] is for the type itself type KubeTypes []Pair func astFrom(filePath string) *doc.Package { fset := token.NewFileSet() m := make(map[string]*ast.File) f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) if err != nil { fmt.Println(err) return nil } m[filePath] = f apkg, _ := ast.NewPackage(fset, m, nil, nil) return doc.New(apkg, "", 0) } func fmtRawDoc(rawDoc string) string { var buffer bytes.Buffer delPrevChar := func() { if buffer.Len() > 0 { buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n" } } // Ignore all lines after --- rawDoc = strings.Split(rawDoc, "---")[0] for _, line := range strings.Split(rawDoc, "\n") { line = strings.TrimRight(line, " ") leading := strings.TrimLeft(line, " ") switch { case len(line) == 0: // Keep paragraphs delPrevChar() buffer.WriteString("\n\n") case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs case strings.HasPrefix(leading, "+"): // Ignore instructions to the generators default: if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { delPrevChar() line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..." } else { line += " " } buffer.WriteString(line) } } postDoc := strings.TrimRight(buffer.String(), "\n") postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to " postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape " postDoc = strings.Replace(postDoc, "\n", "\\n", -1) postDoc = strings.Replace(postDoc, "\t", "\\t", -1) return postDoc } // fieldName returns the name of the field as it should appear in JSON format // "-" indicates that this field is not part of the JSON representation func fieldName(field *ast.Field) string { jsonTag := "" if field.Tag != nil { jsonTag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("json") // Delete first and last quotation if strings.Contains(jsonTag, "inline") { return "-" } } jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-" if jsonTag == "" { if field.Names != nil { return field.Names[0].Name } return field.Type.(*ast.Ident).Name } return jsonTag } // A buffer of lines that will be written. type bufferedLine struct { line string indentation int } type buffer struct { lines []bufferedLine } func newBuffer() *buffer { return &buffer{ lines: make([]bufferedLine, 0), } } func (b *buffer) addLine(line string, indent int) { b.lines = append(b.lines, bufferedLine{line, indent}) } func (b *buffer) flushLines(w io.Writer) error { for _, line := range b.lines { indentation := strings.Repeat("\t", line.indentation) fullLine := fmt.Sprintf("%s%s", indentation, line.line) if _, err := io.WriteString(w, fullLine); err != nil { return err } } return nil } func writeFuncHeader(b *buffer, structName string, indent int) { s := fmt.Sprintf("var map_%s = map[string]string {\n", structName) b.addLine(s, indent) } func writeFuncFooter(b *buffer, structName string, indent int) { b.addLine("}\n", indent) // Closes the map definition s := fmt.Sprintf("func (%s) SwaggerDoc() map[string]string {\n", structName) b.addLine(s, indent) s = fmt.Sprintf("return map_%s\n", structName) b.addLine(s, indent+1) b.addLine("}\n", indent) // Closes the function definition } func writeMapBody(b *buffer, kubeType []Pair, indent int) { format := "\"%s\": \"%s\",\n" for _, pair := range kubeType { s := fmt.Sprintf(format, pair.Name, pair.Doc) b.addLine(s, indent+2) } } // ParseDocumentationFrom gets all types' documentation and returns them as an // array. Each type is again represented as an array (we have to use arrays as we // need to be sure for the order of the fields). This function returns fields and // struct definitions that have no documentation as {name, ""}. func ParseDocumentationFrom(src string) []KubeTypes { var docForTypes []KubeTypes pkg := astFrom(src) for _, kubType := range pkg.Types { if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok { var ks KubeTypes ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc)}) for _, field := range structType.Fields.List { if n := fieldName(field); n != "-" { fieldDoc := fmtRawDoc(field.Doc.Text()) ks = append(ks, Pair{n, fieldDoc}) } } docForTypes = append(docForTypes, ks) } } return docForTypes } // WriteSwaggerDocFunc writes a declaration of a function as a string. This function is used in // Swagger as a documentation source for structs and theirs fields func WriteSwaggerDocFunc(kubeTypes []KubeTypes, w io.Writer) error { for _, kubeType := range kubeTypes { structName := kubeType[0].Name kubeType[0].Name = "" // Ignore empty documentation docfulTypes := make(KubeTypes, 0, len(kubeType)) for _, pair := range kubeType { if pair.Doc != "" { docfulTypes = append(docfulTypes, pair) } } if len(docfulTypes) == 0 { continue // If no fields and the struct have documentation, skip the function definition } indent := 0 buffer := newBuffer() writeFuncHeader(buffer, structName, indent) writeMapBody(buffer, docfulTypes, indent) writeFuncFooter(buffer, structName, indent) buffer.addLine("\n", 0) if err := buffer.flushLines(w); err != nil { return err } } return nil } // VerifySwaggerDocsExist writes in a io.Writer a list of structs and fields that // are missing of documentation. func VerifySwaggerDocsExist(kubeTypes []KubeTypes, w io.Writer) (int, error) { missingDocs := 0 buffer := newBuffer() for _, kubeType := range kubeTypes { structName := kubeType[0].Name if kubeType[0].Doc == "" { format := "Missing documentation for the struct itself: %s\n" s := fmt.Sprintf(format, structName) buffer.addLine(s, 0) missingDocs++ } kubeType = kubeType[1:] // Skip struct definition for _, pair := range kubeType { // Iterate only the fields if pair.Doc == "" { format := "In struct: %s, field documentation is missing: %s\n" s := fmt.Sprintf(format, structName, pair.Name) buffer.addLine(s, 0) missingDocs++ } } } if err := buffer.flushLines(w); err != nil { return -1, err } return missingDocs, nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/swagger_doc_generator_test.go000066400000000000000000000022221453143165200264110ustar00rootroot00000000000000/* Copyright 2015 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 runtime import ( "testing" ) func TestFmtRawDoc(t *testing.T) { tests := []struct { t, expected string }{ {"aaa\n --- asd\n TODO: tooooodo\n toooodoooooo\n", "aaa"}, {"aaa\nasd\n TODO: tooooodo\nbbbb\n --- toooodoooooo\n", "aaa asd bbbb"}, {" TODO: tooooodo\n", ""}, {"Par1\n\nPar2\n\n", "Par1\\n\\nPar2"}, {"", ""}, {" ", ""}, {" \n", ""}, {" \n\n ", ""}, {"Example:\n\tl1\n\t\tl2\n", "Example:\\n\\tl1\\n\\t\\tl2"}, } for _, test := range tests { if o := fmtRawDoc(test.t); o != test.expected { t.Fatalf("Expected: %q, got %q", test.expected, o) } } } golang-k8s-apimachinery-0.29.0/pkg/runtime/testing/000077500000000000000000000000001453143165200221505ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/runtime/testing/cacheable_object.go000066400000000000000000000141661453143165200257240ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testing import ( "bytes" "fmt" "io" "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // nonCacheableTestObject implements json.Marshaler and proto.Marshaler interfaces // for mocking purpose. // +k8s:deepcopy-gen=false type noncacheableTestObject struct { gvk schema.GroupVersionKind } // MarshalJSON implements json.Marshaler interface. func (*noncacheableTestObject) MarshalJSON() ([]byte, error) { return []byte("\"json-result\""), nil } // Marshal implements proto.Marshaler interface. func (*noncacheableTestObject) Marshal() ([]byte, error) { return []byte("\"proto-result\""), nil } // DeepCopyObject implements runtime.Object interface. func (*noncacheableTestObject) DeepCopyObject() runtime.Object { panic("DeepCopy unimplemented for noncacheableTestObject") } // GetObjectKind implements runtime.Object interface. func (o *noncacheableTestObject) GetObjectKind() schema.ObjectKind { return o } // GroupVersionKind implements schema.ObjectKind interface. func (o *noncacheableTestObject) GroupVersionKind() schema.GroupVersionKind { return o.gvk } // SetGroupVersionKind implements schema.ObjectKind interface. func (o *noncacheableTestObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { o.gvk = gvk } var _ runtime.CacheableObject = &MockCacheableObject{} // MochCacheableObject is used to test CacheableObject interface. // +k8s:deepcopy-gen=false type MockCacheableObject struct { gvk schema.GroupVersionKind t *testing.T runEncode bool returnSelf bool expectedResult string expectedError error intercepted []runtime.Identifier } // DeepCopyObject implements runtime.Object interface. func (m *MockCacheableObject) DeepCopyObject() runtime.Object { panic("DeepCopy unimplemented for MockCacheableObject") } // GetObjectKind implements runtime.Object interface. func (m *MockCacheableObject) GetObjectKind() schema.ObjectKind { return m } // GroupVersionKind implements schema.ObjectKind interface. func (m *MockCacheableObject) GroupVersionKind() schema.GroupVersionKind { return m.gvk } // SetGroupVersionKind implements schema.ObjectKind interface. func (m *MockCacheableObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { m.gvk = gvk } // Marshal implements proto.Marshaler interface. // This is implemented to avoid errors from protobuf serializer. func (*MockCacheableObject) Marshal() ([]byte, error) { return []byte("\"proto-result\""), nil } // CacheEncode implements runtime.CacheableObject interface. func (m *MockCacheableObject) CacheEncode(id runtime.Identifier, encode func(runtime.Object, io.Writer) error, w io.Writer) error { m.intercepted = append(m.intercepted, id) if m.runEncode { return encode(m.GetObject(), w) } if _, err := w.Write([]byte(m.expectedResult)); err != nil { m.t.Errorf("couldn't write to io.Writer: %v", err) } return m.expectedError } // GetObject implements runtime.CacheableObject interface. func (m *MockCacheableObject) GetObject() runtime.Object { if m.returnSelf { return m } gvk := schema.GroupVersionKind{Group: "group", Version: "version", Kind: "noncacheableTestObject"} return &noncacheableTestObject{gvk: gvk} } func (m *MockCacheableObject) interceptedCalls() []runtime.Identifier { return m.intercepted } type testBuffer struct { writer io.Writer t *testing.T object *MockCacheableObject } // Write implements io.Writer interface. func (b *testBuffer) Write(p []byte) (int, error) { // Before writing any byte, check if has already // intercepted any CacheEncode operation. if len(b.object.interceptedCalls()) == 0 { b.t.Errorf("writing to buffer without handling MockCacheableObject") } return b.writer.Write(p) } // CacheableObjectTest implements a test that should be run for every // runtime.Encoder interface implementation. // It checks whether CacheableObject is properly supported by it. func CacheableObjectTest(t *testing.T, e runtime.Encoder) { gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"} testCases := []struct { desc string runEncode bool returnSelf bool expectedResult string expectedError error }{ { desc: "delegate", runEncode: true, }, { desc: "delegate return self", runEncode: true, returnSelf: true, }, { desc: "cached success", runEncode: false, expectedResult: "result", expectedError: nil, }, { desc: "cached failure", runEncode: false, expectedResult: "", expectedError: fmt.Errorf("encoding error"), }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { obj := &MockCacheableObject{ gvk: gvk1, t: t, runEncode: test.runEncode, returnSelf: test.returnSelf, expectedResult: test.expectedResult, expectedError: test.expectedError, } buffer := bytes.NewBuffer(nil) w := &testBuffer{ writer: buffer, t: t, object: obj, } if err := e.Encode(obj, w); err != test.expectedError { t.Errorf("unexpected error: %v, expected: %v", err, test.expectedError) } if !test.runEncode { if result := buffer.String(); result != test.expectedResult { t.Errorf("unexpected result: %s, expected: %s", result, test.expectedResult) } } intercepted := obj.interceptedCalls() if len(intercepted) != 1 { t.Fatalf("unexpected number of intercepted calls: %v", intercepted) } if intercepted[0] != e.Identifier() { t.Errorf("unexpected intercepted call: %v, expected: %v", intercepted, e.Identifier()) } }) } } golang-k8s-apimachinery-0.29.0/pkg/runtime/testing/conversion.go000066400000000000000000000244331453143165200246720ustar00rootroot00000000000000/* Copyright 2020 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 testing import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" ) func convertEmbeddedTestToEmbeddedTestExternal(in *EmbeddedTest, out *EmbeddedTestExternal, s conversion.Scope) error { out.TypeMeta = in.TypeMeta out.ID = in.ID if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Object, &out.Object, s); err != nil { return err } if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.EmptyObject, &out.EmptyObject, s); err != nil { return err } return nil } func convertEmbeddedTestExternalToEmbeddedTest(in *EmbeddedTestExternal, out *EmbeddedTest, s conversion.Scope) error { out.TypeMeta = in.TypeMeta out.ID = in.ID if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Object, &out.Object, s); err != nil { return err } if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.EmptyObject, &out.EmptyObject, s); err != nil { return err } return nil } func convertObjectTestToObjectTestExternal(in *ObjectTest, out *ObjectTestExternal, s conversion.Scope) error { out.TypeMeta = in.TypeMeta out.ID = in.ID if in.Items != nil { out.Items = make([]runtime.RawExtension, len(in.Items)) for i := range in.Items { if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Items[i], &out.Items[i], s); err != nil { return err } } } else { out.Items = nil } return nil } func convertObjectTestExternalToObjectTest(in *ObjectTestExternal, out *ObjectTest, s conversion.Scope) error { out.TypeMeta = in.TypeMeta out.ID = in.ID if in.Items != nil { out.Items = make([]runtime.Object, len(in.Items)) for i := range in.Items { if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Items[i], &out.Items[i], s); err != nil { return err } } } else { out.Items = nil } return nil } func convertInternalSimpleToExternalSimple(in *InternalSimple, out *ExternalSimple, s conversion.Scope) error { out.TypeMeta = in.TypeMeta out.TestString = in.TestString return nil } func convertExternalSimpleToInternalSimple(in *ExternalSimple, out *InternalSimple, s conversion.Scope) error { out.TypeMeta = in.TypeMeta out.TestString = in.TestString return nil } func convertInternalExtensionTypeToExternalExtensionType(in *InternalExtensionType, out *ExternalExtensionType, s conversion.Scope) error { out.TypeMeta = in.TypeMeta if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Extension, &out.Extension, s); err != nil { return err } return nil } func convertExternalExtensionTypeToInternalExtensionType(in *ExternalExtensionType, out *InternalExtensionType, s conversion.Scope) error { out.TypeMeta = in.TypeMeta if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Extension, &out.Extension, s); err != nil { return err } return nil } func convertInternalOptionalExtensionTypeToExternalOptionalExtensionType(in *InternalOptionalExtensionType, out *ExternalOptionalExtensionType, s conversion.Scope) error { out.TypeMeta = in.TypeMeta if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Extension, &out.Extension, s); err != nil { return err } return nil } func convertExternalOptionalExtensionTypeToInternalOptionalExtensionType(in *ExternalOptionalExtensionType, out *InternalOptionalExtensionType, s conversion.Scope) error { out.TypeMeta = in.TypeMeta if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Extension, &out.Extension, s); err != nil { return err } return nil } func convertTestType1ToExternalTestType1(in *TestType1, out *ExternalTestType1, s conversion.Scope) error { out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField out.A = in.A out.B = in.B out.C = in.C out.D = in.D out.E = in.E out.F = in.F out.G = in.G out.H = in.H out.I = in.I out.J = in.J out.K = in.K out.L = in.L out.M = in.M if in.N != nil { out.N = make(map[string]ExternalTestType2) for key := range in.N { in, tmp := in.N[key], ExternalTestType2{} if err := convertTestType2ToExternalTestType2(&in, &tmp, s); err != nil { return err } out.N[key] = tmp } } else { out.N = nil } if in.O != nil { out.O = new(ExternalTestType2) if err := convertTestType2ToExternalTestType2(in.O, out.O, s); err != nil { return err } } else { out.O = nil } if in.P != nil { out.P = make([]ExternalTestType2, len(in.P)) for i := range in.P { if err := convertTestType2ToExternalTestType2(&in.P[i], &out.P[i], s); err != nil { return err } } } return nil } func convertExternalTestType1ToTestType1(in *ExternalTestType1, out *TestType1, s conversion.Scope) error { out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField out.A = in.A out.B = in.B out.C = in.C out.D = in.D out.E = in.E out.F = in.F out.G = in.G out.H = in.H out.I = in.I out.J = in.J out.K = in.K out.L = in.L out.M = in.M if in.N != nil { out.N = make(map[string]TestType2) for key := range in.N { in, tmp := in.N[key], TestType2{} if err := convertExternalTestType2ToTestType2(&in, &tmp, s); err != nil { return err } out.N[key] = tmp } } else { out.N = nil } if in.O != nil { out.O = new(TestType2) if err := convertExternalTestType2ToTestType2(in.O, out.O, s); err != nil { return err } } else { out.O = nil } if in.P != nil { out.P = make([]TestType2, len(in.P)) for i := range in.P { if err := convertExternalTestType2ToTestType2(&in.P[i], &out.P[i], s); err != nil { return err } } } return nil } func convertTestType2ToExternalTestType2(in *TestType2, out *ExternalTestType2, s conversion.Scope) error { out.A = in.A out.B = in.B return nil } func convertExternalTestType2ToTestType2(in *ExternalTestType2, out *TestType2, s conversion.Scope) error { out.A = in.A out.B = in.B return nil } func RegisterConversions(s *runtime.Scheme) error { if err := s.AddConversionFunc((*EmbeddedTest)(nil), (*EmbeddedTestExternal)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertEmbeddedTestToEmbeddedTestExternal(a.(*EmbeddedTest), b.(*EmbeddedTestExternal), scope) }); err != nil { return err } if err := s.AddConversionFunc((*EmbeddedTestExternal)(nil), (*EmbeddedTest)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertEmbeddedTestExternalToEmbeddedTest(a.(*EmbeddedTestExternal), b.(*EmbeddedTest), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ObjectTest)(nil), (*ObjectTestExternal)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertObjectTestToObjectTestExternal(a.(*ObjectTest), b.(*ObjectTestExternal), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ObjectTestExternal)(nil), (*ObjectTest)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertObjectTestExternalToObjectTest(a.(*ObjectTestExternal), b.(*ObjectTest), scope) }); err != nil { return err } if err := s.AddConversionFunc((*InternalSimple)(nil), (*ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertInternalSimpleToExternalSimple(a.(*InternalSimple), b.(*ExternalSimple), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ExternalSimple)(nil), (*InternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertExternalSimpleToInternalSimple(a.(*ExternalSimple), b.(*InternalSimple), scope) }); err != nil { return err } if err := s.AddConversionFunc((*InternalExtensionType)(nil), (*ExternalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertInternalExtensionTypeToExternalExtensionType(a.(*InternalExtensionType), b.(*ExternalExtensionType), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ExternalExtensionType)(nil), (*InternalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertExternalExtensionTypeToInternalExtensionType(a.(*ExternalExtensionType), b.(*InternalExtensionType), scope) }); err != nil { return err } if err := s.AddConversionFunc((*InternalOptionalExtensionType)(nil), (*ExternalOptionalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertInternalOptionalExtensionTypeToExternalOptionalExtensionType(a.(*InternalOptionalExtensionType), b.(*ExternalOptionalExtensionType), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ExternalOptionalExtensionType)(nil), (*InternalOptionalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertExternalOptionalExtensionTypeToInternalOptionalExtensionType(a.(*ExternalOptionalExtensionType), b.(*InternalOptionalExtensionType), scope) }); err != nil { return err } if err := s.AddConversionFunc((*TestType1)(nil), (*ExternalTestType1)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertTestType1ToExternalTestType1(a.(*TestType1), b.(*ExternalTestType1), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ExternalTestType1)(nil), (*TestType1)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertExternalTestType1ToTestType1(a.(*ExternalTestType1), b.(*TestType1), scope) }); err != nil { return err } if err := s.AddConversionFunc((*TestType2)(nil), (*ExternalTestType2)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertTestType2ToExternalTestType2(a.(*TestType2), b.(*ExternalTestType2), scope) }); err != nil { return err } if err := s.AddConversionFunc((*ExternalTestType2)(nil), (*TestType2)(nil), func(a, b interface{}, scope conversion.Scope) error { return convertExternalTestType2ToTestType2(a.(*ExternalTestType2), b.(*TestType2), scope) }); err != nil { return err } return nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/testing/doc.go000066400000000000000000000012351453143165200232450ustar00rootroot00000000000000/* Copyright 2016 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. */ // +k8s:deepcopy-gen=package package testing // import "k8s.io/apimachinery/pkg/runtime/testing" golang-k8s-apimachinery-0.29.0/pkg/runtime/testing/types.go000066400000000000000000000267641453143165200236620ustar00rootroot00000000000000/* Copyright 2016 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 testing import ( "fmt" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/json" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type EmbeddedTest struct { runtime.TypeMeta ID string Object runtime.Object EmptyObject runtime.Object } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type EmbeddedTestExternal struct { runtime.TypeMeta `json:",inline"` ID string `json:"id,omitempty"` Object runtime.RawExtension `json:"object,omitempty"` EmptyObject runtime.RawExtension `json:"emptyObject,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ObjectTest struct { runtime.TypeMeta ID string Items []runtime.Object } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ObjectTestExternal struct { runtime.TypeMeta `yaml:",inline" json:",inline"` ID string `json:"id,omitempty"` Items []runtime.RawExtension `json:"items,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type InternalSimple struct { runtime.TypeMeta `json:",inline"` TestString string `json:"testString"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalSimple struct { runtime.TypeMeta `json:",inline"` TestString string `json:"testString"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExtensionA struct { runtime.TypeMeta `json:",inline"` TestString string `json:"testString"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExtensionB struct { runtime.TypeMeta `json:",inline"` TestString string `json:"testString"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalExtensionType struct { runtime.TypeMeta `json:",inline"` Extension runtime.RawExtension `json:"extension"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type InternalExtensionType struct { runtime.TypeMeta `json:",inline"` Extension runtime.Object `json:"extension"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalOptionalExtensionType struct { runtime.TypeMeta `json:",inline"` Extension runtime.RawExtension `json:"extension,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type InternalOptionalExtensionType struct { runtime.TypeMeta `json:",inline"` Extension runtime.Object `json:"extension,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type InternalComplex struct { runtime.TypeMeta String string Integer int Integer64 int64 Int64 int64 Bool bool } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalComplex struct { runtime.TypeMeta `json:",inline"` String string `json:"string" description:"testing"` Integer int `json:"int"` Integer64 int64 `json:",omitempty"` Int64 int64 Bool bool `json:"bool"` } // Test a weird version/kind embedding format. // +k8s:deepcopy-gen=false type MyWeirdCustomEmbeddedVersionKindField struct { ID string `json:"ID,omitempty"` APIVersion string `json:"myVersionKey,omitempty"` ObjectKind string `json:"myKindKey,omitempty"` Z string `json:"Z,omitempty"` Y uint64 `json:"Y,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type TestType1 struct { MyWeirdCustomEmbeddedVersionKindField `json:",inline"` A string `json:"A,omitempty"` B int `json:"B,omitempty"` C int8 `json:"C,omitempty"` D int16 `json:"D,omitempty"` E int32 `json:"E,omitempty"` F int64 `json:"F,omitempty"` G uint `json:"G,omitempty"` H uint8 `json:"H,omitempty"` I uint16 `json:"I,omitempty"` J uint32 `json:"J,omitempty"` K uint64 `json:"K,omitempty"` L bool `json:"L,omitempty"` M map[string]int `json:"M,omitempty"` N map[string]TestType2 `json:"N,omitempty"` O *TestType2 `json:"O,omitempty"` P []TestType2 `json:"Q,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type TestType2 struct { A string `json:"A,omitempty"` B int `json:"B,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalTestType2 struct { A string `json:"A,omitempty"` B int `json:"B,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalTestType1 struct { MyWeirdCustomEmbeddedVersionKindField `json:",inline"` A string `json:"A,omitempty"` B int `json:"B,omitempty"` C int8 `json:"C,omitempty"` D int16 `json:"D,omitempty"` E int32 `json:"E,omitempty"` F int64 `json:"F,omitempty"` G uint `json:"G,omitempty"` H uint8 `json:"H,omitempty"` I uint16 `json:"I,omitempty"` J uint32 `json:"J,omitempty"` K uint64 `json:"K,omitempty"` L bool `json:"L,omitempty"` M map[string]int `json:"M,omitempty"` N map[string]ExternalTestType2 `json:"N,omitempty"` O *ExternalTestType2 `json:"O,omitempty"` P []ExternalTestType2 `json:"Q,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ExternalInternalSame struct { MyWeirdCustomEmbeddedVersionKindField `json:",inline"` A TestType2 `json:"A,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type UnversionedType struct { MyWeirdCustomEmbeddedVersionKindField `json:",inline"` A string `json:"A,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type UnknownType struct { MyWeirdCustomEmbeddedVersionKindField `json:",inline"` A string `json:"A,omitempty"` } func (obj *MyWeirdCustomEmbeddedVersionKindField) GetObjectKind() schema.ObjectKind { return obj } func (obj *MyWeirdCustomEmbeddedVersionKindField) SetGroupVersionKind(gvk schema.GroupVersionKind) { obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind() } func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() schema.GroupVersionKind { return schema.FromAPIVersionAndKind(obj.APIVersion, obj.ObjectKind) } func (obj *TestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *ExternalTestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } // +k8s:deepcopy-gen=false type Unstructured struct { // Object is a JSON compatible map with string, float, int, bool, []interface{}, or // map[string]interface{} // children. Object map[string]interface{} } var _ runtime.Unstructured = &Unstructured{} func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } func (obj *Unstructured) IsList() bool { if obj.Object != nil { _, ok := obj.Object["items"] return ok } return false } func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { if obj.Object == nil { return fmt.Errorf("content is not a list") } field, ok := obj.Object["items"] if !ok { return fmt.Errorf("content is not a list") } items, ok := field.([]interface{}) if !ok { return nil } for _, item := range items { child, ok := item.(map[string]interface{}) if !ok { return fmt.Errorf("items member is not an object") } if err := fn(&Unstructured{Object: child}); err != nil { return err } } return nil } func (obj *Unstructured) EachListItemWithAlloc(fn func(runtime.Object) error) error { // EachListItem has allocated a new Object for the user, we can use it directly. return obj.EachListItem(fn) } func (obj *Unstructured) NewEmptyInstance() runtime.Unstructured { out := new(Unstructured) if obj != nil { out.SetGroupVersionKind(obj.GroupVersionKind()) } return out } func (obj *Unstructured) UnstructuredContent() map[string]interface{} { if obj.Object == nil { return make(map[string]interface{}) } return obj.Object } func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) { obj.Object = content } // MarshalJSON ensures that the unstructured object produces proper // JSON when passed to Go's standard JSON library. func (u *Unstructured) MarshalJSON() ([]byte, error) { return json.Marshal(u.Object) } // UnmarshalJSON ensures that the unstructured object properly decodes // JSON when passed to Go's standard JSON library. func (u *Unstructured) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &u.Object) } func (in *Unstructured) DeepCopyObject() runtime.Object { return in.DeepCopy() } func (in *Unstructured) DeepCopy() *Unstructured { if in == nil { return nil } out := new(Unstructured) *out = *in out.Object = runtime.DeepCopyJSON(in.Object) return out } func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { apiVersion, ok := u.Object["apiVersion"].(string) if !ok { return schema.GroupVersionKind{} } gv, err := schema.ParseGroupVersion(apiVersion) if err != nil { return schema.GroupVersionKind{} } kind, ok := u.Object["kind"].(string) if ok { return gv.WithKind(kind) } return schema.GroupVersionKind{} } func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) { if u.Object == nil { u.Object = make(map[string]interface{}) } u.Object["apiVersion"] = gvk.GroupVersion().String() u.Object["kind"] = gvk.Kind } golang-k8s-apimachinery-0.29.0/pkg/runtime/testing/zz_generated.deepcopy.go000066400000000000000000000434371453143165200270020ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package testing import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EmbeddedTest) DeepCopyInto(out *EmbeddedTest) { *out = *in out.TypeMeta = in.TypeMeta if in.Object != nil { out.Object = in.Object.DeepCopyObject() } if in.EmptyObject != nil { out.EmptyObject = in.EmptyObject.DeepCopyObject() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmbeddedTest. func (in *EmbeddedTest) DeepCopy() *EmbeddedTest { if in == nil { return nil } out := new(EmbeddedTest) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *EmbeddedTest) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EmbeddedTestExternal) DeepCopyInto(out *EmbeddedTestExternal) { *out = *in out.TypeMeta = in.TypeMeta in.Object.DeepCopyInto(&out.Object) in.EmptyObject.DeepCopyInto(&out.EmptyObject) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmbeddedTestExternal. func (in *EmbeddedTestExternal) DeepCopy() *EmbeddedTestExternal { if in == nil { return nil } out := new(EmbeddedTestExternal) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *EmbeddedTestExternal) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExtensionA) DeepCopyInto(out *ExtensionA) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionA. func (in *ExtensionA) DeepCopy() *ExtensionA { if in == nil { return nil } out := new(ExtensionA) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExtensionA) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExtensionB) DeepCopyInto(out *ExtensionB) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionB. func (in *ExtensionB) DeepCopy() *ExtensionB { if in == nil { return nil } out := new(ExtensionB) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExtensionB) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalComplex) DeepCopyInto(out *ExternalComplex) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalComplex. func (in *ExternalComplex) DeepCopy() *ExternalComplex { if in == nil { return nil } out := new(ExternalComplex) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalComplex) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalExtensionType) DeepCopyInto(out *ExternalExtensionType) { *out = *in out.TypeMeta = in.TypeMeta in.Extension.DeepCopyInto(&out.Extension) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalExtensionType. func (in *ExternalExtensionType) DeepCopy() *ExternalExtensionType { if in == nil { return nil } out := new(ExternalExtensionType) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalExtensionType) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalInternalSame) DeepCopyInto(out *ExternalInternalSame) { *out = *in out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField out.A = in.A return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalInternalSame. func (in *ExternalInternalSame) DeepCopy() *ExternalInternalSame { if in == nil { return nil } out := new(ExternalInternalSame) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalInternalSame) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalOptionalExtensionType) DeepCopyInto(out *ExternalOptionalExtensionType) { *out = *in out.TypeMeta = in.TypeMeta in.Extension.DeepCopyInto(&out.Extension) return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalOptionalExtensionType. func (in *ExternalOptionalExtensionType) DeepCopy() *ExternalOptionalExtensionType { if in == nil { return nil } out := new(ExternalOptionalExtensionType) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalOptionalExtensionType) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalSimple) DeepCopyInto(out *ExternalSimple) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSimple. func (in *ExternalSimple) DeepCopy() *ExternalSimple { if in == nil { return nil } out := new(ExternalSimple) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalSimple) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalTestType1) DeepCopyInto(out *ExternalTestType1) { *out = *in out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField if in.M != nil { in, out := &in.M, &out.M *out = make(map[string]int, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.N != nil { in, out := &in.N, &out.N *out = make(map[string]ExternalTestType2, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.O != nil { in, out := &in.O, &out.O *out = new(ExternalTestType2) **out = **in } if in.P != nil { in, out := &in.P, &out.P *out = make([]ExternalTestType2, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalTestType1. func (in *ExternalTestType1) DeepCopy() *ExternalTestType1 { if in == nil { return nil } out := new(ExternalTestType1) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalTestType1) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalTestType2) DeepCopyInto(out *ExternalTestType2) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalTestType2. func (in *ExternalTestType2) DeepCopy() *ExternalTestType2 { if in == nil { return nil } out := new(ExternalTestType2) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ExternalTestType2) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InternalComplex) DeepCopyInto(out *InternalComplex) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalComplex. func (in *InternalComplex) DeepCopy() *InternalComplex { if in == nil { return nil } out := new(InternalComplex) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *InternalComplex) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InternalExtensionType) DeepCopyInto(out *InternalExtensionType) { *out = *in out.TypeMeta = in.TypeMeta if in.Extension != nil { out.Extension = in.Extension.DeepCopyObject() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalExtensionType. func (in *InternalExtensionType) DeepCopy() *InternalExtensionType { if in == nil { return nil } out := new(InternalExtensionType) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *InternalExtensionType) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InternalOptionalExtensionType) DeepCopyInto(out *InternalOptionalExtensionType) { *out = *in out.TypeMeta = in.TypeMeta if in.Extension != nil { out.Extension = in.Extension.DeepCopyObject() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalOptionalExtensionType. func (in *InternalOptionalExtensionType) DeepCopy() *InternalOptionalExtensionType { if in == nil { return nil } out := new(InternalOptionalExtensionType) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *InternalOptionalExtensionType) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InternalSimple) DeepCopyInto(out *InternalSimple) { *out = *in out.TypeMeta = in.TypeMeta return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalSimple. func (in *InternalSimple) DeepCopy() *InternalSimple { if in == nil { return nil } out := new(InternalSimple) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *InternalSimple) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectTest) DeepCopyInto(out *ObjectTest) { *out = *in out.TypeMeta = in.TypeMeta if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.Object, len(*in)) for i := range *in { if (*in)[i] != nil { (*out)[i] = (*in)[i].DeepCopyObject() } } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectTest. func (in *ObjectTest) DeepCopy() *ObjectTest { if in == nil { return nil } out := new(ObjectTest) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ObjectTest) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectTestExternal) DeepCopyInto(out *ObjectTestExternal) { *out = *in out.TypeMeta = in.TypeMeta if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.RawExtension, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectTestExternal. func (in *ObjectTestExternal) DeepCopy() *ObjectTestExternal { if in == nil { return nil } out := new(ObjectTestExternal) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ObjectTestExternal) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TestType1) DeepCopyInto(out *TestType1) { *out = *in out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField if in.M != nil { in, out := &in.M, &out.M *out = make(map[string]int, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.N != nil { in, out := &in.N, &out.N *out = make(map[string]TestType2, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.O != nil { in, out := &in.O, &out.O *out = new(TestType2) **out = **in } if in.P != nil { in, out := &in.P, &out.P *out = make([]TestType2, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType1. func (in *TestType1) DeepCopy() *TestType1 { if in == nil { return nil } out := new(TestType1) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *TestType1) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TestType2) DeepCopyInto(out *TestType2) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType2. func (in *TestType2) DeepCopy() *TestType2 { if in == nil { return nil } out := new(TestType2) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *TestType2) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnknownType) DeepCopyInto(out *UnknownType) { *out = *in out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnknownType. func (in *UnknownType) DeepCopy() *UnknownType { if in == nil { return nil } out := new(UnknownType) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *UnknownType) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnversionedType) DeepCopyInto(out *UnversionedType) { *out = *in out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnversionedType. func (in *UnversionedType) DeepCopy() *UnversionedType { if in == nil { return nil } out := new(UnversionedType) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *UnversionedType) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/types.go000066400000000000000000000113301453143165200221640ustar00rootroot00000000000000/* 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 runtime // Note that the types provided in this file are not versioned and are intended to be // safe to use from within all versions of every API object. // TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, // like this: // // type MyAwesomeAPIObject struct { // runtime.TypeMeta `json:",inline"` // ... // other fields // } // // func (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind // // TypeMeta is provided here for convenience. You may use it directly from this package or define // your own with the same fields. // // +k8s:deepcopy-gen=false // +protobuf=true // +k8s:openapi-gen=true type TypeMeta struct { // +optional APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"` // +optional Kind string `json:"kind,omitempty" yaml:"kind,omitempty" protobuf:"bytes,2,opt,name=kind"` } const ( ContentTypeJSON string = "application/json" ContentTypeYAML string = "application/yaml" ContentTypeProtobuf string = "application/vnd.kubernetes.protobuf" ) // RawExtension is used to hold extensions in external versions. // // To use this, make a field which has RawExtension as its type in your external, versioned // struct, and Object in your internal struct. You also need to register your // various plugin types. // // // Internal package: // // type MyAPIObject struct { // runtime.TypeMeta `json:",inline"` // MyPlugin runtime.Object `json:"myPlugin"` // } // // type PluginA struct { // AOption string `json:"aOption"` // } // // // External package: // // type MyAPIObject struct { // runtime.TypeMeta `json:",inline"` // MyPlugin runtime.RawExtension `json:"myPlugin"` // } // // type PluginA struct { // AOption string `json:"aOption"` // } // // // On the wire, the JSON will look something like this: // // { // "kind":"MyAPIObject", // "apiVersion":"v1", // "myPlugin": { // "kind":"PluginA", // "aOption":"foo", // }, // } // // So what happens? Decode first uses json or yaml to unmarshal the serialized data into // your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. // The next step is to copy (using pkg/conversion) into the internal struct. The runtime // package's DefaultScheme has conversion functions installed which will unpack the // JSON stored in RawExtension, turning it into the correct object type, and storing it // in the Object. (TODO: In the case where the object is of an unknown type, a // runtime.Unknown object will be created and stored.) // // +k8s:deepcopy-gen=true // +protobuf=true // +k8s:openapi-gen=true type RawExtension struct { // Raw is the underlying serialization of this object. // // TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data. Raw []byte `json:"-" protobuf:"bytes,1,opt,name=raw"` // Object can hold a representation of this extension - useful for working with versioned // structs. Object Object `json:"-"` } // Unknown allows api objects with unknown types to be passed-through. This can be used // to deal with the API objects from a plug-in. Unknown objects still have functioning // TypeMeta features-- kind, version, etc. // TODO: Make this object have easy access to field based accessors and settors for // metadata and field mutatation. // // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +protobuf=true // +k8s:openapi-gen=true type Unknown struct { TypeMeta `json:",inline" protobuf:"bytes,1,opt,name=typeMeta"` // Raw will hold the complete serialized object which couldn't be matched // with a registered type. Most likely, nothing should be done with this // except for passing it through the system. Raw []byte `json:"-" protobuf:"bytes,2,opt,name=raw"` // ContentEncoding is encoding used to encode 'Raw' data. // Unspecified means no encoding. ContentEncoding string `protobuf:"bytes,3,opt,name=contentEncoding"` // ContentType is serialization method used to serialize 'Raw'. // Unspecified means ContentTypeJSON. ContentType string `protobuf:"bytes,4,opt,name=contentType"` } golang-k8s-apimachinery-0.29.0/pkg/runtime/types_proto.go000066400000000000000000000052141453143165200234130ustar00rootroot00000000000000/* Copyright 2015 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 runtime import ( "fmt" ) type ProtobufMarshaller interface { MarshalTo(data []byte) (int, error) } type ProtobufReverseMarshaller interface { MarshalToSizedBuffer(data []byte) (int, error) } // NestedMarshalTo allows a caller to avoid extra allocations during serialization of an Unknown // that will contain an object that implements ProtobufMarshaller or ProtobufReverseMarshaller. func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) { // Calculate the full size of the message. msgSize := m.Size() if b != nil { msgSize += int(size) + sovGenerated(size) + 1 } // Reverse marshal the fields of m. i := msgSize i -= len(m.ContentType) copy(data[i:], m.ContentType) i = encodeVarintGenerated(data, i, uint64(len(m.ContentType))) i-- data[i] = 0x22 i -= len(m.ContentEncoding) copy(data[i:], m.ContentEncoding) i = encodeVarintGenerated(data, i, uint64(len(m.ContentEncoding))) i-- data[i] = 0x1a if b != nil { if r, ok := b.(ProtobufReverseMarshaller); ok { n1, err := r.MarshalToSizedBuffer(data[:i]) if err != nil { return 0, err } i -= int(size) if uint64(n1) != size { // programmer error: the Size() method for protobuf does not match the results of LashramOt, which means the proto // struct returned would be wrong. return 0, fmt.Errorf("the Size() value of %T was %d, but NestedMarshalTo wrote %d bytes to data", b, size, n1) } } else { i -= int(size) n1, err := b.MarshalTo(data[i:]) if err != nil { return 0, err } if uint64(n1) != size { // programmer error: the Size() method for protobuf does not match the results of MarshalTo, which means the proto // struct returned would be wrong. return 0, fmt.Errorf("the Size() value of %T was %d, but NestedMarshalTo wrote %d bytes to data", b, size, n1) } } i = encodeVarintGenerated(data, i, size) i-- data[i] = 0x12 } n2, err := m.TypeMeta.MarshalToSizedBuffer(data[:i]) if err != nil { return 0, err } i -= n2 i = encodeVarintGenerated(data, i, uint64(n2)) i-- data[i] = 0xa return msgSize - i, nil } golang-k8s-apimachinery-0.29.0/pkg/runtime/zz_generated.deepcopy.go000066400000000000000000000040261453143165200253140ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package runtime // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RawExtension) DeepCopyInto(out *RawExtension) { *out = *in if in.Raw != nil { in, out := &in.Raw, &out.Raw *out = make([]byte, len(*in)) copy(*out, *in) } if in.Object != nil { out.Object = in.Object.DeepCopyObject() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawExtension. func (in *RawExtension) DeepCopy() *RawExtension { if in == nil { return nil } out := new(RawExtension) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Unknown) DeepCopyInto(out *Unknown) { *out = *in out.TypeMeta = in.TypeMeta if in.Raw != nil { in, out := &in.Raw, &out.Raw *out = make([]byte, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Unknown. func (in *Unknown) DeepCopy() *Unknown { if in == nil { return nil } out := new(Unknown) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new Object. func (in *Unknown) DeepCopyObject() Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/selection/000077500000000000000000000000001453143165200207755ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/selection/operator.go000066400000000000000000000020031453143165200231520ustar00rootroot00000000000000/* Copyright 2016 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 selection // Operator represents a key/field's relationship to value(s). // See labels.Requirement and fields.Requirement for more details. type Operator string const ( DoesNotExist Operator = "!" Equals Operator = "=" DoubleEquals Operator = "==" In Operator = "in" NotEquals Operator = "!=" NotIn Operator = "notin" Exists Operator = "exists" GreaterThan Operator = "gt" LessThan Operator = "lt" ) golang-k8s-apimachinery-0.29.0/pkg/test/000077500000000000000000000000001453143165200177675ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/test/api_meta_help_test.go000066400000000000000000000210361453143165200241460ustar00rootroot00000000000000/* 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 test import ( "reflect" "testing" "github.com/google/go-cmp/cmp" fuzz "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/api/meta" metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/testapigroup" v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" ) func TestIsList(t *testing.T) { tests := []struct { obj runtime.Object isList bool }{ {&testapigroup.CarpList{}, true}, {&testapigroup.Carp{}, false}, } for _, item := range tests { if e, a := item.isList, meta.IsListType(item.obj); e != a { t.Errorf("%v: Expected %v, got %v", reflect.TypeOf(item.obj), e, a) } } } func TestExtractList(t *testing.T) { list1 := []runtime.Object{ &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, } list2 := &ListV1{ Items: []runtime.RawExtension{ {Raw: []byte("foo")}, {Raw: []byte("bar")}, {Object: &v1.Carp{ObjectMeta: metav1.ObjectMeta{Name: "other"}}}, }, } list3 := &fakePtrValueList{ Items: []*testapigroup.Carp{ {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, }, } list4 := &testapigroup.CarpList{ Items: []testapigroup.Carp{ {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, {ObjectMeta: metav1.ObjectMeta{Name: "3"}}, }, } testCases := []struct { in runtime.Object out []interface{} equal bool }{ { in: &List{}, out: []interface{}{}, }, { in: &ListV1{}, out: []interface{}{}, }, { in: &List{Items: list1}, out: []interface{}{list1[0], list1[1]}, }, { in: list2, out: []interface{}{&runtime.Unknown{Raw: list2.Items[0].Raw}, &runtime.Unknown{Raw: list2.Items[1].Raw}, list2.Items[2].Object}, equal: true, }, { in: list3, out: []interface{}{list3.Items[0], list3.Items[1]}, }, { in: list4, out: []interface{}{&list4.Items[0], &list4.Items[1], &list4.Items[2]}, }, } for i, test := range testCases { list, err := meta.ExtractList(test.in) if err != nil { t.Fatalf("%d: extract: Unexpected error %v", i, err) } if e, a := len(test.out), len(list); e != a { t.Fatalf("%d: extract: Expected %v, got %v", i, e, a) } for j, e := range test.out { if e != list[j] { if !test.equal { t.Fatalf("%d: extract: Expected list[%d] to be %#v, but found %#v", i, j, e, list[j]) } if !reflect.DeepEqual(e, list[j]) { t.Fatalf("%d: extract: Expected list[%d] to be %#v, but found %#v", i, j, e, list[j]) } } } } } func TestEachListItem(t *testing.T) { list1 := []runtime.Object{ &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, } list2 := &ListV1{ Items: []runtime.RawExtension{ {Raw: []byte("foo")}, {Raw: []byte("bar")}, {Object: &v1.Carp{ObjectMeta: metav1.ObjectMeta{Name: "other"}}}, }, } list3 := &fakePtrValueList{ Items: []*testapigroup.Carp{ {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, }, } list4 := &testapigroup.CarpList{ Items: []testapigroup.Carp{ {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, {ObjectMeta: metav1.ObjectMeta{Name: "3"}}, }, } testCases := []struct { in runtime.Object out []interface{} }{ { in: &List{}, out: []interface{}{}, }, { in: &ListV1{}, out: []interface{}{}, }, { in: &List{Items: list1}, out: []interface{}{list1[0], list1[1]}, }, { in: list2, out: []interface{}{nil, nil, list2.Items[2].Object}, }, { in: list3, out: []interface{}{list3.Items[0], list3.Items[1]}, }, { in: list4, out: []interface{}{&list4.Items[0], &list4.Items[1], &list4.Items[2]}, }, } for i, test := range testCases { list := []runtime.Object{} err := meta.EachListItem(test.in, func(obj runtime.Object) error { list = append(list, obj) return nil }) if err != nil { t.Fatalf("%d: each: Unexpected error %v", i, err) } if e, a := len(test.out), len(list); e != a { t.Fatalf("%d: each: Expected %v, got %v", i, e, a) } for j, e := range test.out { if e != list[j] { t.Fatalf("%d: each: Expected list[%d] to be %#v, but found %#v", i, j, e, list[j]) } } } } type fakePtrInterfaceList struct { Items *[]runtime.Object } func (obj fakePtrInterfaceList) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj fakePtrInterfaceList) DeepCopyObject() runtime.Object { panic("fakePtrInterfaceList does not support DeepCopy") } func TestExtractListOfInterfacePtrs(t *testing.T) { pl := &fakePtrInterfaceList{ Items: &[]runtime.Object{}, } list, err := meta.ExtractList(pl) if err != nil { t.Fatalf("Unexpected error %v", err) } if len(list) > 0 { t.Fatalf("Expected empty list, got %#v", list) } } type fakePtrValueList struct { Items []*testapigroup.Carp } func (obj fakePtrValueList) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *fakePtrValueList) DeepCopyObject() runtime.Object { if obj == nil { return nil } clone := fakePtrValueList{ Items: make([]*testapigroup.Carp, len(obj.Items)), } for i, carp := range obj.Items { clone.Items[i] = carp.DeepCopy() } return &clone } func TestSetList(t *testing.T) { pl := &testapigroup.CarpList{} list := []runtime.Object{ &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "3"}}, } err := meta.SetList(pl, list) if err != nil { t.Fatalf("Unexpected error %v", err) } if e, a := len(list), len(pl.Items); e != a { t.Fatalf("Expected %v, got %v", e, a) } for i := range list { if e, a := list[i].(*testapigroup.Carp).Name, pl.Items[i].Name; e != a { t.Fatalf("Expected %v, got %v", e, a) } } } func TestSetListToRuntimeObjectArray(t *testing.T) { pl := &List{} list := []runtime.Object{ &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "3"}}, } err := meta.SetList(pl, list) if err != nil { t.Fatalf("Unexpected error %v", err) } if e, a := len(list), len(pl.Items); e != a { t.Fatalf("Expected %v, got %v", e, a) } for i := range list { if e, a := list[i], pl.Items[i]; e != a { t.Fatalf("%d: unmatched: %s", i, cmp.Diff(e, a)) } } } func TestSetListToMatchingType(t *testing.T) { pl := &unstructured.UnstructuredList{} list := []runtime.Object{ &unstructured.Unstructured{Object: map[string]interface{}{"foo": 1}}, &unstructured.Unstructured{Object: map[string]interface{}{"foo": 2}}, &unstructured.Unstructured{Object: map[string]interface{}{"foo": 3}}, } err := meta.SetList(pl, list) if err != nil { t.Fatalf("Unexpected error %v", err) } if e, a := len(list), len(pl.Items); e != a { t.Fatalf("Expected %v, got %v", e, a) } for i := range list { if e, a := list[i], &pl.Items[i]; !reflect.DeepEqual(e, a) { t.Fatalf("%d: unmatched: %s", i, cmp.Diff(e, a)) } } } func TestSetExtractListRoundTrip(t *testing.T) { scheme := runtime.NewScheme() codecs := serializer.NewCodecFactory(scheme) fuzzer := fuzz.New().NilChance(0).NumElements(1, 5).Funcs(metafuzzer.Funcs(codecs)...).MaxDepth(10) for i := 0; i < 5; i++ { start := &testapigroup.CarpList{} fuzzer.Fuzz(&start.Items) list, err := meta.ExtractList(start) if err != nil { t.Errorf("Unexpected error %v", err) continue } got := &testapigroup.CarpList{} err = meta.SetList(got, list) if err != nil { t.Errorf("Unexpected error %v", err) continue } if e, a := start, got; !reflect.DeepEqual(e, a) { t.Fatalf("Expected %#v, got %#v", e, a) } } } golang-k8s-apimachinery-0.29.0/pkg/test/api_meta_meta_test.go000066400000000000000000000317051453143165200241500ustar00rootroot00000000000000/* 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 test import ( "reflect" "testing" fuzz "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/api/meta" metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" ) func TestAPIObjectMeta(t *testing.T) { j := &testapigroup.Carp{ TypeMeta: metav1.TypeMeta{APIVersion: "/a", Kind: "b"}, ObjectMeta: metav1.ObjectMeta{ Namespace: "bar", Name: "foo", GenerateName: "prefix", UID: "uid", ResourceVersion: "1", SelfLink: "some/place/only/we/know", Labels: map[string]string{"foo": "bar"}, Annotations: map[string]string{"x": "y"}, Finalizers: []string{ "finalizer.1", "finalizer.2", }, }, } var _ metav1.Object = &j.ObjectMeta var _ metav1.ObjectMetaAccessor = j accessor, err := meta.Accessor(j) if err != nil { t.Fatalf("unexpected error: %v", err) } if accessor != metav1.Object(j) { t.Fatalf("should have returned the same pointer: %#v\n\n%#v", accessor, j) } if e, a := "bar", accessor.GetNamespace(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "foo", accessor.GetName(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "prefix", accessor.GetGenerateName(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "uid", string(accessor.GetUID()); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "1", accessor.GetResourceVersion(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "some/place/only/we/know", accessor.GetSelfLink(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := []string{"finalizer.1", "finalizer.2"}, accessor.GetFinalizers(); !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } typeAccessor, err := meta.TypeAccessor(j) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := "a", typeAccessor.GetAPIVersion(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "b", typeAccessor.GetKind(); e != a { t.Errorf("expected %v, got %v", e, a) } accessor.SetNamespace("baz") accessor.SetName("bar") accessor.SetGenerateName("generate") accessor.SetUID("other") typeAccessor.SetAPIVersion("c") typeAccessor.SetKind("d") accessor.SetResourceVersion("2") accessor.SetSelfLink("google.com") accessor.SetFinalizers([]string{"finalizer.3"}) // Prove that accessor changes the original object. if e, a := "baz", j.Namespace; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "bar", j.Name; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "generate", j.GenerateName; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := types.UID("other"), j.UID; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "c", j.APIVersion; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "d", j.Kind; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "2", j.ResourceVersion; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "google.com", j.SelfLink; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := []string{"finalizer.3"}, j.Finalizers; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } typeAccessor.SetAPIVersion("d") typeAccessor.SetKind("e") if e, a := "d", j.APIVersion; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "e", j.Kind; e != a { t.Errorf("expected %v, got %v", e, a) } } func TestGenericTypeMeta(t *testing.T) { type TypeMeta struct { Kind string `json:"kind,omitempty"` Namespace string `json:"namespace,omitempty"` Name string `json:"name,omitempty"` GenerateName string `json:"generateName,omitempty"` UID string `json:"uid,omitempty"` CreationTimestamp metav1.Time `json:"creationTimestamp,omitempty"` SelfLink string `json:"selfLink,omitempty"` ResourceVersion string `json:"resourceVersion,omitempty"` APIVersion string `json:"apiVersion,omitempty"` Labels map[string]string `json:"labels,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty"` Finalizers []string `json:"finalizers,omitempty"` } j := struct{ TypeMeta }{TypeMeta{APIVersion: "a", Kind: "b"}} typeAccessor, err := meta.TypeAccessor(&j) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := "a", typeAccessor.GetAPIVersion(); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "b", typeAccessor.GetKind(); e != a { t.Errorf("expected %v, got %v", e, a) } typeAccessor.SetAPIVersion("c") typeAccessor.SetKind("d") if e, a := "c", j.APIVersion; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "d", j.Kind; e != a { t.Errorf("expected %v, got %v", e, a) } typeAccessor.SetAPIVersion("d") typeAccessor.SetKind("e") if e, a := "d", j.APIVersion; e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := "e", j.Kind; e != a { t.Errorf("expected %v, got %v", e, a) } } type InternalTypeMeta struct { Kind string `json:"kind,omitempty"` Namespace string `json:"namespace,omitempty"` Name string `json:"name,omitempty"` GenerateName string `json:"generateName,omitempty"` UID string `json:"uid,omitempty"` CreationTimestamp metav1.Time `json:"creationTimestamp,omitempty"` SelfLink string `json:"selfLink,omitempty"` ResourceVersion string `json:"resourceVersion,omitempty"` Continue string `json:"next,omitempty"` RemainingItemCount *int64 `json:"remainingItemCount,omitempty"` APIVersion string `json:"apiVersion,omitempty"` Labels map[string]string `json:"labels,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` Finalizers []string `json:"finalizers,omitempty"` OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty"` } func (m *InternalTypeMeta) GetResourceVersion() string { return m.ResourceVersion } func (m *InternalTypeMeta) SetResourceVersion(rv string) { m.ResourceVersion = rv } func (m *InternalTypeMeta) GetSelfLink() string { return m.SelfLink } func (m *InternalTypeMeta) SetSelfLink(link string) { m.SelfLink = link } func (m *InternalTypeMeta) GetContinue() string { return m.Continue } func (m *InternalTypeMeta) SetContinue(c string) { m.Continue = c } func (m *InternalTypeMeta) GetRemainingItemCount() *int64 { return m.RemainingItemCount } func (m *InternalTypeMeta) SetRemainingItemCount(c *int64) { m.RemainingItemCount = c } type MyAPIObject struct { TypeMeta InternalTypeMeta `json:",inline"` } func (obj *MyAPIObject) GetListMeta() metav1.ListInterface { return &obj.TypeMeta } func (obj *MyAPIObject) GetObjectKind() schema.ObjectKind { return obj } func (obj *MyAPIObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind() } func (obj *MyAPIObject) GroupVersionKind() schema.GroupVersionKind { return schema.FromAPIVersionAndKind(obj.TypeMeta.APIVersion, obj.TypeMeta.Kind) } func (obj *MyAPIObject) DeepCopyObject() runtime.Object { panic("MyAPIObject does not support DeepCopy") } type MyIncorrectlyMarkedAsAPIObject struct{} func (obj *MyIncorrectlyMarkedAsAPIObject) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *MyIncorrectlyMarkedAsAPIObject) DeepCopyObject() runtime.Object { panic("MyIncorrectlyMarkedAsAPIObject does not support DeepCopy") } func TestResourceVersionerOfAPI(t *testing.T) { type T struct { runtime.Object Expected string } testCases := map[string]T{ "empty api object": {&MyAPIObject{}, ""}, "api object with version": {&MyAPIObject{TypeMeta: InternalTypeMeta{ResourceVersion: "1"}}, "1"}, "pointer to api object with version": {&MyAPIObject{TypeMeta: InternalTypeMeta{ResourceVersion: "1"}}, "1"}, } versioning := meta.NewAccessor() for key, testCase := range testCases { actual, err := versioning.ResourceVersion(testCase.Object) if err != nil { t.Errorf("%s: unexpected error %#v", key, err) } if actual != testCase.Expected { t.Errorf("%s: expected %v, got %v", key, testCase.Expected, actual) } } failingCases := map[string]struct { runtime.Object Expected string }{ "not a valid object to try": {&MyIncorrectlyMarkedAsAPIObject{}, "1"}, } for key, testCase := range failingCases { _, err := versioning.ResourceVersion(testCase.Object) if err == nil { t.Errorf("%s: expected error, got nil", key) } } setCases := map[string]struct { runtime.Object Expected string }{ "pointer to api object with version": {&MyAPIObject{TypeMeta: InternalTypeMeta{ResourceVersion: "1"}}, "1"}, } for key, testCase := range setCases { if err := versioning.SetResourceVersion(testCase.Object, "5"); err != nil { t.Errorf("%s: unexpected error %#v", key, err) } actual, err := versioning.ResourceVersion(testCase.Object) if err != nil { t.Errorf("%s: unexpected error %#v", key, err) } if actual != "5" { t.Errorf("%s: expected %v, got %v", key, "5", actual) } } } type MyAPIObject2 struct { metav1.TypeMeta metav1.ObjectMeta } func getObjectMetaAndOwnerReferences() (myAPIObject2 MyAPIObject2, metaOwnerReferences []metav1.OwnerReference) { scheme := runtime.NewScheme() codecs := serializer.NewCodecFactory(scheme) fuzz.New().NilChance(.5).NumElements(1, 5).Funcs(metafuzzer.Funcs(codecs)...).MaxDepth(10).Fuzz(&myAPIObject2) references := myAPIObject2.ObjectMeta.OwnerReferences // This is necessary for the test to pass because the getter will return a // non-nil slice. metaOwnerReferences = make([]metav1.OwnerReference, 0) for i := 0; i < len(references); i++ { metaOwnerReferences = append(metaOwnerReferences, metav1.OwnerReference{ Kind: references[i].Kind, Name: references[i].Name, UID: references[i].UID, APIVersion: references[i].APIVersion, Controller: references[i].Controller, BlockOwnerDeletion: references[i].BlockOwnerDeletion, }) } if len(references) == 0 { // This is necessary for the test to pass because the setter will make a // non-nil slice. myAPIObject2.ObjectMeta.OwnerReferences = make([]metav1.OwnerReference, 0) } return myAPIObject2, metaOwnerReferences } func testGetOwnerReferences(t *testing.T) { obj, expected := getObjectMetaAndOwnerReferences() accessor, err := meta.Accessor(&obj) if err != nil { t.Error(err) } references := accessor.GetOwnerReferences() if !reflect.DeepEqual(references, expected) { t.Errorf("expect %#v\n got %#v", expected, references) } } func testSetOwnerReferences(t *testing.T) { expected, references := getObjectMetaAndOwnerReferences() obj := MyAPIObject2{} accessor, err := meta.Accessor(&obj) if err != nil { t.Error(err) } accessor.SetOwnerReferences(references) if e, a := expected.ObjectMeta.OwnerReferences, obj.ObjectMeta.OwnerReferences; !reflect.DeepEqual(e, a) { t.Errorf("expect %#v\n got %#v", e, a) } } func TestAccessOwnerReferences(t *testing.T) { fuzzIter := 5 for i := 0; i < fuzzIter; i++ { testGetOwnerReferences(t) testSetOwnerReferences(t) } } // BenchmarkAccessorSetFastPath shows the interface fast path func BenchmarkAccessorSetFastPath(b *testing.B) { obj := &testapigroup.Carp{ TypeMeta: metav1.TypeMeta{APIVersion: "/a", Kind: "b"}, ObjectMeta: metav1.ObjectMeta{ Namespace: "bar", Name: "foo", GenerateName: "prefix", UID: "uid", ResourceVersion: "1", SelfLink: "some/place/only/we/know", Labels: map[string]string{"foo": "bar"}, Annotations: map[string]string{"x": "y"}, }, } b.ResetTimer() for i := 0; i < b.N; i++ { acc, err := meta.Accessor(obj) if err != nil { b.Fatal(err) } acc.SetNamespace("something") } b.StopTimer() } golang-k8s-apimachinery-0.29.0/pkg/test/apis_meta_v1_unstructed_unstructure_test.go000066400000000000000000000424001453143165200306700ustar00rootroot00000000000000/* Copyright 2015 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 test import ( "fmt" "reflect" "strconv" "strings" "testing" "time" apitesting "k8s.io/apimachinery/pkg/api/apitesting" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ) func TestDecodeUnstructured(t *testing.T) { groupVersionString := "v1" rawJson := fmt.Sprintf(`{"kind":"Pod","apiVersion":"%s","metadata":{"name":"test"}}`, groupVersionString) pl := &List{ Items: []runtime.Object{ &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, &runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: groupVersionString}, Raw: []byte(rawJson), ContentType: runtime.ContentTypeJSON, }, &runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "", APIVersion: groupVersionString}, Raw: []byte(rawJson), ContentType: runtime.ContentTypeJSON, }, &unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "Foo", "apiVersion": "Bar", "test": "value", }, }, }, } if errs := runtime.DecodeList(pl.Items, unstructured.UnstructuredJSONScheme); len(errs) == 1 { t.Fatalf("unexpected error %v", errs) } if pod, ok := pl.Items[1].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { t.Errorf("object not converted: %#v", pl.Items[1]) } if pod, ok := pl.Items[2].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { t.Errorf("object not converted: %#v", pl.Items[2]) } } func TestDecode(t *testing.T) { tcs := []struct { json []byte want runtime.Object }{ { json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`), want: &unstructured.Unstructured{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"}, }, }, { json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`), want: &unstructured.UnstructuredList{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, Items: []unstructured.Unstructured{}, }, }, { json: []byte(`{"items": [{"metadata": {"name": "object1", "deletionGracePeriodSeconds": 10}, "apiVersion": "test", "kind": "test_kind"}, {"metadata": {"name": "object2"}, "apiVersion": "test", "kind": "test_kind"}], "apiVersion": "test", "kind": "test_list"}`), want: &unstructured.UnstructuredList{ Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, Items: []unstructured.Unstructured{ { Object: map[string]interface{}{ "metadata": map[string]interface{}{"name": "object1", "deletionGracePeriodSeconds": int64(10)}, "apiVersion": "test", "kind": "test_kind", }, }, { Object: map[string]interface{}{ "metadata": map[string]interface{}{"name": "object2"}, "apiVersion": "test", "kind": "test_kind", }, }, }, }, }, } for _, tc := range tcs { got, _, err := unstructured.UnstructuredJSONScheme.Decode(tc.json, nil, nil) if err != nil { t.Errorf("Unexpected error for %q: %v", string(tc.json), err) continue } if !reflect.DeepEqual(got, tc.want) { t.Errorf("Decode(%q) want: %v\ngot: %v", string(tc.json), tc.want, got) } } } func TestUnstructuredGetters(t *testing.T) { trueVar := true ten := int64(10) unstruct := unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", "metadata": map[string]interface{}{ "name": "test_name", "namespace": "test_namespace", "generateName": "test_generateName", "uid": "test_uid", "resourceVersion": "test_resourceVersion", "generation": ten, "deletionGracePeriodSeconds": ten, "selfLink": "test_selfLink", "creationTimestamp": "2009-11-10T23:00:00Z", "deletionTimestamp": "2010-11-10T23:00:00Z", "labels": map[string]interface{}{ "test_label": "test_value", }, "annotations": map[string]interface{}{ "test_annotation": "test_value", }, "ownerReferences": []interface{}{ map[string]interface{}{ "kind": "Pod", "name": "poda", "apiVersion": "v1", "uid": "1", }, map[string]interface{}{ "kind": "Pod", "name": "podb", "apiVersion": "v1", "uid": "2", // though these fields are of type *bool, but when // decoded from JSON, they are unmarshalled as bool. "controller": true, "blockOwnerDeletion": true, }, }, "finalizers": []interface{}{ "finalizer.1", "finalizer.2", }, }, }, } if got, want := unstruct.GetAPIVersion(), "test_version"; got != want { t.Errorf("GetAPIVersions() = %s, want %s", got, want) } if got, want := unstruct.GetKind(), "test_kind"; got != want { t.Errorf("GetKind() = %s, want %s", got, want) } if got, want := unstruct.GetNamespace(), "test_namespace"; got != want { t.Errorf("GetNamespace() = %s, want %s", got, want) } if got, want := unstruct.GetName(), "test_name"; got != want { t.Errorf("GetName() = %s, want %s", got, want) } if got, want := unstruct.GetGenerateName(), "test_generateName"; got != want { t.Errorf("GetGenerateName() = %s, want %s", got, want) } if got, want := unstruct.GetUID(), types.UID("test_uid"); got != want { t.Errorf("GetUID() = %s, want %s", got, want) } if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want { t.Errorf("GetResourceVersion() = %s, want %s", got, want) } if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want { t.Errorf("GetSelfLink() = %s, want %s", got, want) } if got, want := unstruct.GetCreationTimestamp(), metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC); !got.Equal(&want) { t.Errorf("GetCreationTimestamp() = %s, want %s", got, want) } if got, want := unstruct.GetDeletionTimestamp(), metav1.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC); got == nil || !got.Equal(&want) { t.Errorf("GetDeletionTimestamp() = %s, want %s", got, want) } if got, want := unstruct.GetLabels(), map[string]string{"test_label": "test_value"}; !reflect.DeepEqual(got, want) { t.Errorf("GetLabels() = %s, want %s", got, want) } if got, want := unstruct.GetAnnotations(), map[string]string{"test_annotation": "test_value"}; !reflect.DeepEqual(got, want) { t.Errorf("GetAnnotations() = %s, want %s", got, want) } refs := unstruct.GetOwnerReferences() expectedOwnerReferences := []metav1.OwnerReference{ { Kind: "Pod", Name: "poda", APIVersion: "v1", UID: "1", }, { Kind: "Pod", Name: "podb", APIVersion: "v1", UID: "2", Controller: &trueVar, BlockOwnerDeletion: &trueVar, }, } if got, want := refs, expectedOwnerReferences; !reflect.DeepEqual(got, want) { t.Errorf("GetOwnerReferences()=%v, want %v", got, want) } if got, want := unstruct.GetFinalizers(), []string{"finalizer.1", "finalizer.2"}; !reflect.DeepEqual(got, want) { t.Errorf("GetFinalizers()=%v, want %v", got, want) } if got, want := unstruct.GetDeletionGracePeriodSeconds(), &ten; !reflect.DeepEqual(got, want) { t.Errorf("GetDeletionGracePeriodSeconds()=%v, want %v", got, want) } if got, want := unstruct.GetGeneration(), ten; !reflect.DeepEqual(got, want) { t.Errorf("GetGeneration()=%v, want %v", got, want) } } func TestUnstructuredSetters(t *testing.T) { unstruct := unstructured.Unstructured{} trueVar := true ten := int64(10) want := unstructured.Unstructured{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", "metadata": map[string]interface{}{ "name": "test_name", "namespace": "test_namespace", "generateName": "test_generateName", "uid": "test_uid", "resourceVersion": "test_resourceVersion", "selfLink": "test_selfLink", "creationTimestamp": "2009-11-10T23:00:00Z", "deletionTimestamp": "2010-11-10T23:00:00Z", "deletionGracePeriodSeconds": ten, "generation": ten, "labels": map[string]interface{}{ "test_label": "test_value", }, "annotations": map[string]interface{}{ "test_annotation": "test_value", }, "ownerReferences": []interface{}{ map[string]interface{}{ "kind": "Pod", "name": "poda", "apiVersion": "v1", "uid": "1", }, map[string]interface{}{ "kind": "Pod", "name": "podb", "apiVersion": "v1", "uid": "2", "controller": true, "blockOwnerDeletion": true, }, }, "finalizers": []interface{}{ "finalizer.1", "finalizer.2", }, }, }, } unstruct.SetAPIVersion("test_version") unstruct.SetKind("test_kind") unstruct.SetNamespace("test_namespace") unstruct.SetName("test_name") unstruct.SetGenerateName("test_generateName") unstruct.SetUID(types.UID("test_uid")) unstruct.SetResourceVersion("test_resourceVersion") unstruct.SetSelfLink("test_selfLink") unstruct.SetCreationTimestamp(metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) date := metav1.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC) unstruct.SetDeletionTimestamp(&date) unstruct.SetLabels(map[string]string{"test_label": "test_value"}) unstruct.SetAnnotations(map[string]string{"test_annotation": "test_value"}) newOwnerReferences := []metav1.OwnerReference{ { Kind: "Pod", Name: "poda", APIVersion: "v1", UID: "1", }, { Kind: "Pod", Name: "podb", APIVersion: "v1", UID: "2", Controller: &trueVar, BlockOwnerDeletion: &trueVar, }, } unstruct.SetOwnerReferences(newOwnerReferences) unstruct.SetFinalizers([]string{"finalizer.1", "finalizer.2"}) unstruct.SetDeletionGracePeriodSeconds(&ten) unstruct.SetGeneration(ten) if !reflect.DeepEqual(unstruct, want) { t.Errorf("Wanted: \n%s\n Got:\n%s", want, unstruct) } } func TestOwnerReferences(t *testing.T) { t.Parallel() trueVar := true falseVar := false refs := []metav1.OwnerReference{ { APIVersion: "v2", Kind: "K2", Name: "n2", UID: types.UID("abc1"), }, { APIVersion: "v1", Kind: "K1", Name: "n1", UID: types.UID("abc2"), Controller: &trueVar, BlockOwnerDeletion: &falseVar, }, { APIVersion: "v3", Kind: "K3", Name: "n3", UID: types.UID("abc3"), Controller: &falseVar, BlockOwnerDeletion: &trueVar, }, } for i, ref := range refs { ref := ref t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() u1 := unstructured.Unstructured{ Object: make(map[string]interface{}), } refsX := []metav1.OwnerReference{ref} u1.SetOwnerReferences(refsX) have := u1.GetOwnerReferences() if !reflect.DeepEqual(have, refsX) { t.Errorf("Object references are not the same: %#v != %#v", have, refsX) } }) } } func TestUnstructuredListGetters(t *testing.T) { unstruct := unstructured.UnstructuredList{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", "metadata": map[string]interface{}{ "resourceVersion": "test_resourceVersion", "selfLink": "test_selfLink", }, }, } if got, want := unstruct.GetAPIVersion(), "test_version"; got != want { t.Errorf("GetAPIVersions() = %s, want %s", got, want) } if got, want := unstruct.GetKind(), "test_kind"; got != want { t.Errorf("GetKind() = %s, want %s", got, want) } if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want { t.Errorf("GetResourceVersion() = %s, want %s", got, want) } if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want { t.Errorf("GetSelfLink() = %s, want %s", got, want) } } func TestUnstructuredListSetters(t *testing.T) { unstruct := unstructured.UnstructuredList{} want := unstructured.UnstructuredList{ Object: map[string]interface{}{ "kind": "test_kind", "apiVersion": "test_version", "metadata": map[string]interface{}{ "resourceVersion": "test_resourceVersion", "selfLink": "test_selfLink", }, }, } unstruct.SetAPIVersion("test_version") unstruct.SetKind("test_kind") unstruct.SetResourceVersion("test_resourceVersion") unstruct.SetSelfLink("test_selfLink") if !reflect.DeepEqual(unstruct, want) { t.Errorf("Wanted: \n%s\n Got:\n%s", unstruct, want) } } func TestDecodeNumbers(t *testing.T) { // Start with a valid pod originalJSON := []byte(`{ "kind":"Carp", "apiVersion":"v1", "metadata":{"name":"pod","namespace":"foo"}, "spec":{ "containers":[{"name":"container","image":"container"}], "activeDeadlineSeconds":1000030003 } }`) pod := &testapigroup.Carp{} _, codecs := TestScheme() codec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}) err := runtime.DecodeInto(codec, originalJSON, pod) if err != nil { t.Fatalf("unexpected error: %v", err) } // Round-trip with unstructured codec unstructuredObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON) if err != nil { t.Fatalf("unexpected error: %v", err) } roundtripJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstructuredObj) if err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure we serialize back out in int form if !strings.Contains(string(roundtripJSON), `"activeDeadlineSeconds":1000030003`) { t.Errorf("Expected %s, got %s", `"activeDeadlineSeconds":1000030003`, string(roundtripJSON)) } // Decode with structured codec again obj2, err := runtime.Decode(codec, roundtripJSON) if err != nil { t.Fatalf("unexpected error: %v", err) } // ensure pod is still valid pod2, ok := obj2.(*testapigroup.Carp) if !ok { t.Fatalf("expected an *api.Pod, got %#v", obj2) } // ensure round-trip preserved large integers if !reflect.DeepEqual(pod, pod2) { t.Fatalf("Expected\n\t%#v, got \n\t%#v", pod, pod2) } } // TestAccessorMethods does opaque roundtrip testing against an Unstructured // instance's Object methods to ensure that what is "Set" matches what you // subsequently "Get" without any assertions against internal state. func TestAccessorMethods(t *testing.T) { int64p := func(i int) *int64 { v := int64(i) return &v } tests := []struct { accessor string val interface{} nilVal reflect.Value }{ {accessor: "Namespace", val: "foo"}, {accessor: "Name", val: "bar"}, {accessor: "GenerateName", val: "baz"}, {accessor: "UID", val: types.UID("uid")}, {accessor: "ResourceVersion", val: "1"}, {accessor: "Generation", val: int64(5)}, {accessor: "SelfLink", val: "/foo"}, // TODO: Handle timestamps, which are being marshalled as UTC and // unmarshalled as Local. // https://github.com/kubernetes/kubernetes/issues/21402 // {accessor: "CreationTimestamp", val: someTime}, // {accessor: "DeletionTimestamp", val: someTimeP}, {accessor: "DeletionTimestamp", nilVal: reflect.ValueOf((*metav1.Time)(nil))}, {accessor: "DeletionGracePeriodSeconds", val: int64p(10)}, {accessor: "DeletionGracePeriodSeconds", val: int64p(0)}, {accessor: "DeletionGracePeriodSeconds", nilVal: reflect.ValueOf((*int64)(nil))}, {accessor: "Labels", val: map[string]string{"foo": "bar"}}, {accessor: "Annotations", val: map[string]string{"foo": "bar"}}, {accessor: "Finalizers", val: []string{"foo"}}, {accessor: "OwnerReferences", val: []metav1.OwnerReference{{Name: "foo"}}}, } for i, test := range tests { t.Logf("evaluating test %d (%s)", i, test.accessor) u := &unstructured.Unstructured{} setter := reflect.ValueOf(u).MethodByName("Set" + test.accessor) getter := reflect.ValueOf(u).MethodByName("Get" + test.accessor) args := []reflect.Value{} if test.val != nil { args = append(args, reflect.ValueOf(test.val)) } else { args = append(args, test.nilVal) } setter.Call(args) ret := getter.Call([]reflect.Value{}) actual := ret[0].Interface() var expected interface{} if test.val != nil { expected = test.val } else { expected = test.nilVal.Interface() } if e, a := expected, actual; !reflect.DeepEqual(e, a) { t.Fatalf("%s: expected %v (%T), got %v (%T)", test.accessor, e, e, a, a) } } } golang-k8s-apimachinery-0.29.0/pkg/test/runtime_helper_test.go000066400000000000000000000027561453143165200244110ustar00rootroot00000000000000/* 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 test import ( "testing" apitesting "k8s.io/apimachinery/pkg/api/apitesting" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/runtime" ) func TestDecodeList(t *testing.T) { pl := List{ Items: []runtime.Object{ &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, &runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "Carp", APIVersion: "v1"}, Raw: []byte(`{"kind":"Carp","apiVersion":"` + "v1" + `","metadata":{"name":"test"}}`), ContentType: runtime.ContentTypeJSON, }, }, } _, codecs := TestScheme() Codec := apitesting.TestCodec(codecs, testapigroup.SchemeGroupVersion) if errs := runtime.DecodeList(pl.Items, Codec); len(errs) != 0 { t.Fatalf("unexpected error %v", errs) } if pod, ok := pl.Items[1].(*testapigroup.Carp); !ok || pod.Name != "test" { t.Errorf("object not converted: %#v", pl.Items[1]) } } golang-k8s-apimachinery-0.29.0/pkg/test/runtime_serializer_protobuf_protobuf_test.go000066400000000000000000000216601453143165200311360ustar00rootroot00000000000000/* Copyright 2015 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 test import ( "bytes" "encoding/hex" "fmt" "reflect" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" ) type testObject struct { gvk schema.GroupVersionKind } func (d *testObject) GetObjectKind() schema.ObjectKind { return d } func (d *testObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } func (d *testObject) GroupVersionKind() schema.GroupVersionKind { return d.gvk } func (d *testObject) DeepCopyObject() runtime.Object { panic("testObject does not support DeepCopy") } type testMarshalable struct { testObject data []byte err error } func (d *testMarshalable) Marshal() ([]byte, error) { return d.data, d.err } func (d *testMarshalable) DeepCopyObject() runtime.Object { panic("testMarshalable does not support DeepCopy") } type testBufferedMarshalable struct { testObject data []byte err error } func (d *testBufferedMarshalable) Marshal() ([]byte, error) { return nil, fmt.Errorf("not invokable") } func (d *testBufferedMarshalable) MarshalTo(data []byte) (int, error) { copy(data, d.data) return len(d.data), d.err } func (d *testBufferedMarshalable) Size() int { return len(d.data) } func (d *testBufferedMarshalable) DeepCopyObject() runtime.Object { panic("testBufferedMarshalable does not support DeepCopy") } func TestRecognize(t *testing.T) { s := protobuf.NewSerializer(nil, nil) ignores := [][]byte{ nil, {}, []byte("k8s"), {0x6b, 0x38, 0x73, 0x01}, } for i, data := range ignores { if ok, _, err := s.RecognizesData(data); err != nil || ok { t.Errorf("%d: should not recognize data: %v", i, err) } } recognizes := [][]byte{ {0x6b, 0x38, 0x73, 0x00}, {0x6b, 0x38, 0x73, 0x00, 0x01}, } for i, data := range recognizes { if ok, _, err := s.RecognizesData(data); err != nil || !ok { t.Errorf("%d: should recognize data: %v", i, err) } } } func TestEncode(t *testing.T) { obj1 := &testMarshalable{testObject: testObject{}, data: []byte{}} wire1 := []byte{ 0x6b, 0x38, 0x73, 0x00, // prefix 0x0a, 0x04, 0x0a, 0x00, // apiversion 0x12, 0x00, // kind 0x12, 0x00, // data 0x1a, 0x00, // content-type 0x22, 0x00, // content-encoding } obj2 := &testMarshalable{ testObject: testObject{gvk: schema.GroupVersionKind{Kind: "test", Group: "other", Version: "version"}}, data: []byte{0x01, 0x02, 0x03}, } wire2 := []byte{ 0x6b, 0x38, 0x73, 0x00, // prefix 0x0a, 0x15, 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind 0x12, 0x03, 0x01, 0x02, 0x03, // data 0x1a, 0x00, // content-type 0x22, 0x00, // content-encoding } err1 := fmt.Errorf("a test error") testCases := []struct { obj runtime.Object data []byte errFn func(error) bool }{ { obj: &testObject{}, errFn: protobuf.IsNotMarshalable, }, { obj: obj1, data: wire1, }, { obj: &testMarshalable{testObject: obj1.testObject, err: err1}, errFn: func(err error) bool { return err == err1 }, }, { // if this test fails, writing the "fast path" marshal is not the same as the "slow path" obj: &testBufferedMarshalable{testObject: obj1.testObject, data: obj1.data}, data: wire1, }, { obj: obj2, data: wire2, }, { // if this test fails, writing the "fast path" marshal is not the same as the "slow path" obj: &testBufferedMarshalable{testObject: obj2.testObject, data: obj2.data}, data: wire2, }, { obj: &testBufferedMarshalable{testObject: obj1.testObject, err: err1}, errFn: func(err error) bool { return err == err1 }, }, } for i, test := range testCases { s := protobuf.NewSerializer(nil, nil) data, err := runtime.Encode(s, test.obj) switch { case err == nil && test.errFn != nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil && test.errFn == nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil: if !test.errFn(err) { t.Errorf("%d: failed: %v", i, err) } if data != nil { t.Errorf("%d: should not have returned nil data", i) } continue } if test.data != nil && !bytes.Equal(test.data, data) { t.Errorf("%d: unexpected data:\n%s", i, hex.Dump(data)) continue } if ok, _, err := s.RecognizesData(data); !ok || err != nil { t.Errorf("%d: did not recognize data generated by call: %v", i, err) } } } func TestProtobufDecode(t *testing.T) { wire1 := []byte{ 0x6b, 0x38, 0x73, 0x00, // prefix 0x0a, 0x04, 0x0a, 0x00, // apiversion 0x12, 0x00, // kind 0x12, 0x00, // data 0x1a, 0x00, // content-type 0x22, 0x00, // content-encoding } wire2 := []byte{ 0x6b, 0x38, 0x73, 0x00, // prefix 0x0a, 0x15, 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind 0x12, 0x07, 0x6b, 0x38, 0x73, 0x00, 0x01, 0x02, 0x03, // data 0x1a, 0x00, // content-type 0x22, 0x00, // content-encoding } testCases := []struct { obj runtime.Object data []byte errFn func(error) bool }{ { obj: &runtime.Unknown{}, errFn: func(err error) bool { return err.Error() == "empty data" }, }, { data: []byte{0x6b}, errFn: func(err error) bool { return strings.Contains(err.Error(), "does not appear to be a protobuf message") }, }, { obj: &runtime.Unknown{ Raw: []byte{}, }, data: wire1, }, { obj: &runtime.Unknown{ TypeMeta: runtime.TypeMeta{ APIVersion: "other/version", Kind: "test", }, // content type is set because the prefix matches the content ContentType: runtime.ContentTypeProtobuf, Raw: []byte{0x6b, 0x38, 0x73, 0x00, 0x01, 0x02, 0x03}, }, data: wire2, }, } for i, test := range testCases { s := protobuf.NewSerializer(nil, nil) unk := &runtime.Unknown{} err := runtime.DecodeInto(s, test.data, unk) switch { case err == nil && test.errFn != nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil && test.errFn == nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil: if !test.errFn(err) { t.Errorf("%d: failed: %v", i, err) } continue } if !reflect.DeepEqual(unk, test.obj) { t.Errorf("%d: unexpected object:\n%#v", i, unk) continue } } } func TestDecodeObjects(t *testing.T) { obj1 := &v1.Carp{ ObjectMeta: metav1.ObjectMeta{ Name: "cool", }, Spec: v1.CarpSpec{ Hostname: "coolhost", }, } obj1wire, err := obj1.Marshal() if err != nil { t.Fatal(err) } wire1, err := (&runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "Carp", APIVersion: "v1"}, Raw: obj1wire, }).Marshal() if err != nil { t.Fatal(err) } unk2 := &runtime.Unknown{ TypeMeta: runtime.TypeMeta{Kind: "Carp", APIVersion: "v1"}, } wire2 := make([]byte, len(wire1)*2) n, err := unk2.NestedMarshalTo(wire2, obj1, uint64(obj1.Size())) if err != nil { t.Fatal(err) } if n != len(wire1) || !bytes.Equal(wire1, wire2[:n]) { t.Fatalf("unexpected wire:\n%s", hex.Dump(wire2[:n])) } wire1 = append([]byte{0x6b, 0x38, 0x73, 0x00}, wire1...) obj1WithKind := obj1.DeepCopyObject() obj1WithKind.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Carp"}) testCases := []struct { obj runtime.Object data []byte errFn func(error) bool }{ { obj: obj1WithKind, data: wire1, }, } scheme := runtime.NewScheme() for i, test := range testCases { scheme.AddKnownTypes(schema.GroupVersion{Version: "v1"}, &v1.Carp{}) require.NoError(t, v1.AddToScheme(scheme)) s := protobuf.NewSerializer(scheme, scheme) obj, err := runtime.Decode(s, test.data) switch { case err == nil && test.errFn != nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil && test.errFn == nil: t.Errorf("%d: failed: %v", i, err) continue case err != nil: if !test.errFn(err) { t.Errorf("%d: failed: %v", i, err) } if obj != nil { t.Errorf("%d: should not have returned an object", i) } continue } if !apiequality.Semantic.DeepEqual(obj, test.obj) { t.Errorf("%d: unexpected object:\n%s", i, cmp.Diff(test.obj, obj)) continue } } } golang-k8s-apimachinery-0.29.0/pkg/test/runtime_unversioned_test.go000066400000000000000000000055341453143165200254700ustar00rootroot00000000000000/* Copyright 2015 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 test import ( "encoding/json" "reflect" "testing" // TODO: Ideally we should create the necessary package structure in e.g., // pkg/conversion/test/... instead of importing pkg/api here. apitesting "k8s.io/apimachinery/pkg/api/apitesting" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) func TestV1EncodeDecodeStatus(t *testing.T) { status := &metav1.Status{ Status: metav1.StatusFailure, Code: 200, Reason: metav1.StatusReasonUnknown, Message: "", } _, codecs := TestScheme() codec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}) encoded, err := runtime.Encode(codec, status) if err != nil { t.Errorf("unexpected error: %v", err) } typeMeta := metav1.TypeMeta{} if err := json.Unmarshal(encoded, &typeMeta); err != nil { t.Errorf("unexpected error: %v", err) } if typeMeta.Kind != "Status" { t.Errorf("Kind is not set to \"Status\". Got %v", string(encoded)) } if typeMeta.APIVersion != "v1" { t.Errorf("APIVersion is not set to \"v1\". Got %v", string(encoded)) } decoded, err := runtime.Decode(codec, encoded) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(status, decoded) { t.Errorf("expected: %v, got: %v", status, decoded) } } func TestExperimentalEncodeDecodeStatus(t *testing.T) { status := &metav1.Status{ Status: metav1.StatusFailure, Code: 200, Reason: metav1.StatusReasonUnknown, Message: "", } _, codecs := TestScheme() expCodec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}) encoded, err := runtime.Encode(expCodec, status) if err != nil { t.Errorf("unexpected error: %v", err) } typeMeta := metav1.TypeMeta{} if err := json.Unmarshal(encoded, &typeMeta); err != nil { t.Errorf("unexpected error: %v", err) } if typeMeta.Kind != "Status" { t.Errorf("Kind is not set to \"Status\". Got %s", encoded) } if typeMeta.APIVersion != "v1" { t.Errorf("APIVersion is not set to \"\". Got %s", encoded) } decoded, err := runtime.Decode(expCodec, encoded) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(status, decoded) { t.Errorf("expected: %v, got: %v", status, decoded) } } golang-k8s-apimachinery-0.29.0/pkg/test/util.go000066400000000000000000000041151453143165200212740ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package test import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/testapigroup" "k8s.io/apimachinery/pkg/apis/testapigroup/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" apiserializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // List and ListV1 should be kept in sync with k8s.io/kubernetes/pkg/api#List // and k8s.io/api/core/v1#List. // // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type List struct { metav1.TypeMeta metav1.ListMeta Items []runtime.Object } // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type ListV1 struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` Items []runtime.RawExtension `json:"items" protobuf:"bytes,2,rep,name=items"` } func TestScheme() (*runtime.Scheme, apiserializer.CodecFactory) { internalGV := schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "", Version: "v1"} scheme := runtime.NewScheme() scheme.AddKnownTypes(internalGV, &testapigroup.Carp{}, &testapigroup.CarpList{}, &List{}, ) scheme.AddKnownTypes(externalGV, &v1.Carp{}, &v1.CarpList{}, &List{}, ) utilruntime.Must(testapigroup.AddToScheme(scheme)) utilruntime.Must(v1.AddToScheme(scheme)) codecs := apiserializer.NewCodecFactory(scheme) return scheme, codecs } golang-k8s-apimachinery-0.29.0/pkg/test/zz_generated.deepcopy.go000066400000000000000000000047211453143165200246120ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package test import ( runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *List) DeepCopyInto(out *List) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.Object, len(*in)) for i := range *in { if (*in)[i] != nil { (*out)[i] = (*in)[i].DeepCopyObject() } } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. func (in *List) DeepCopy() *List { if in == nil { return nil } out := new(List) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *List) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListV1) DeepCopyInto(out *ListV1) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]runtime.RawExtension, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListV1. func (in *ListV1) DeepCopy() *ListV1 { if in == nil { return nil } out := new(ListV1) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. func (in *ListV1) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } golang-k8s-apimachinery-0.29.0/pkg/types/000077500000000000000000000000001453143165200201545ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/types/doc.go000066400000000000000000000013011453143165200212430ustar00rootroot00000000000000/* Copyright 2015 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 types implements various generic types used throughout kubernetes. package types // import "k8s.io/apimachinery/pkg/types" golang-k8s-apimachinery-0.29.0/pkg/types/namespacedname.go000066400000000000000000000031101453143165200234370ustar00rootroot00000000000000/* Copyright 2015 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 types // NamespacedName comprises a resource name, with a mandatory namespace, // rendered as "/". Being a type captures intent and // helps make sure that UIDs, namespaced names and non-namespaced names // do not get conflated in code. For most use cases, namespace and name // will already have been format validated at the API entry point, so we // don't do that here. Where that's not the case (e.g. in testing), // consider using NamespacedNameOrDie() in testing.go in this package. type NamespacedName struct { Namespace string Name string } const ( Separator = '/' ) // String returns the general purpose string representation func (n NamespacedName) String() string { return n.Namespace + string(Separator) + n.Name } // MarshalLog emits a struct containing required key/value pair func (n NamespacedName) MarshalLog() interface{} { return struct { Name string `json:"name"` Namespace string `json:"namespace,omitempty"` }{ Name: n.Name, Namespace: n.Namespace, } } golang-k8s-apimachinery-0.29.0/pkg/types/nodename.go000066400000000000000000000037461453143165200223030ustar00rootroot00000000000000/* Copyright 2015 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 types // NodeName is a type that holds a api.Node's Name identifier. // Being a type captures intent and helps make sure that the node name // is not confused with similar concepts (the hostname, the cloud provider id, // the cloud provider name etc) // // To clarify the various types: // // - Node.Name is the Name field of the Node in the API. This should be stored in a NodeName. // Unfortunately, because Name is part of ObjectMeta, we can't store it as a NodeName at the API level. // // - Hostname is the hostname of the local machine (from uname -n). // However, some components allow the user to pass in a --hostname-override flag, // which will override this in most places. In the absence of anything more meaningful, // kubelet will use Hostname as the Node.Name when it creates the Node. // // * The cloudproviders have the own names: GCE has InstanceName, AWS has InstanceId. // // For GCE, InstanceName is the Name of an Instance object in the GCE API. On GCE, Instance.Name becomes the // Hostname, and thus it makes sense also to use it as the Node.Name. But that is GCE specific, and it is up // to the cloudprovider how to do this mapping. // // For AWS, the InstanceID is not yet suitable for use as a Node.Name, so we actually use the // PrivateDnsName for the Node.Name. And this is _not_ always the same as the hostname: if // we are using a custom DHCP domain it won't be. type NodeName string golang-k8s-apimachinery-0.29.0/pkg/types/patch.go000066400000000000000000000020611453143165200216010ustar00rootroot00000000000000/* Copyright 2015 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 types // Similarly to above, these are constants to support HTTP PATCH utilized by // both the client and server that didn't make sense for a whole package to be // dedicated to. type PatchType string const ( JSONPatchType PatchType = "application/json-patch+json" MergePatchType PatchType = "application/merge-patch+json" StrategicMergePatchType PatchType = "application/strategic-merge-patch+json" ApplyPatchType PatchType = "application/apply-patch+yaml" ) golang-k8s-apimachinery-0.29.0/pkg/types/uid.go000066400000000000000000000014711453143165200212670ustar00rootroot00000000000000/* Copyright 2015 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 types // UID is a type that holds unique ID values, including UUIDs. Because we // don't ONLY use UUIDs, this is an alias to string. Being a type captures // intent and helps make sure that UIDs and names do not get conflated. type UID string golang-k8s-apimachinery-0.29.0/pkg/util/000077500000000000000000000000001453143165200177655ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/OWNERS000066400000000000000000000001131453143165200207200ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners approvers: - apelisse golang-k8s-apimachinery-0.29.0/pkg/util/cache/000077500000000000000000000000001453143165200210305ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/cache/expiring.go000066400000000000000000000131151453143165200232050ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cache import ( "container/heap" "sync" "time" "k8s.io/utils/clock" ) // NewExpiring returns an initialized expiring cache. func NewExpiring() *Expiring { return NewExpiringWithClock(clock.RealClock{}) } // NewExpiringWithClock is like NewExpiring but allows passing in a custom // clock for testing. func NewExpiringWithClock(clock clock.Clock) *Expiring { return &Expiring{ clock: clock, cache: make(map[interface{}]entry), } } // Expiring is a map whose entries expire after a per-entry timeout. type Expiring struct { // AllowExpiredGet causes the expiration check to be skipped on Get. // It should only be used when a key always corresponds to the exact same value. // Thus when this field is true, expired keys are considered valid // until the next call to Set (which causes the GC to run). // It may not be changed concurrently with calls to Get. AllowExpiredGet bool clock clock.Clock // mu protects the below fields mu sync.RWMutex // cache is the internal map that backs the cache. cache map[interface{}]entry // generation is used as a cheap resource version for cache entries. Cleanups // are scheduled with a key and generation. When the cleanup runs, it first // compares its generation with the current generation of the entry. It // deletes the entry iff the generation matches. This prevents cleanups // scheduled for earlier versions of an entry from deleting later versions of // an entry when Set() is called multiple times with the same key. // // The integer value of the generation of an entry is meaningless. generation uint64 heap expiringHeap } type entry struct { val interface{} expiry time.Time generation uint64 } // Get looks up an entry in the cache. func (c *Expiring) Get(key interface{}) (val interface{}, ok bool) { c.mu.RLock() defer c.mu.RUnlock() e, ok := c.cache[key] if !ok { return nil, false } if !c.AllowExpiredGet && !c.clock.Now().Before(e.expiry) { return nil, false } return e.val, true } // Set sets a key/value/expiry entry in the map, overwriting any previous entry // with the same key. The entry expires at the given expiry time, but its TTL // may be lengthened or shortened by additional calls to Set(). Garbage // collection of expired entries occurs during calls to Set(), however calls to // Get() will not return expired entries that have not yet been garbage // collected. func (c *Expiring) Set(key interface{}, val interface{}, ttl time.Duration) { now := c.clock.Now() expiry := now.Add(ttl) c.mu.Lock() defer c.mu.Unlock() c.generation++ c.cache[key] = entry{ val: val, expiry: expiry, generation: c.generation, } // Run GC inline before pushing the new entry. c.gc(now) heap.Push(&c.heap, &expiringHeapEntry{ key: key, expiry: expiry, generation: c.generation, }) } // Delete deletes an entry in the map. func (c *Expiring) Delete(key interface{}) { c.mu.Lock() defer c.mu.Unlock() c.del(key, 0) } // del deletes the entry for the given key. The generation argument is the // generation of the entry that should be deleted. If the generation has been // changed (e.g. if a set has occurred on an existing element but the old // cleanup still runs), this is a noop. If the generation argument is 0, the // entry's generation is ignored and the entry is deleted. // // del must be called under the write lock. func (c *Expiring) del(key interface{}, generation uint64) { e, ok := c.cache[key] if !ok { return } if generation != 0 && generation != e.generation { return } delete(c.cache, key) } // Len returns the number of items in the cache. func (c *Expiring) Len() int { c.mu.RLock() defer c.mu.RUnlock() return len(c.cache) } func (c *Expiring) gc(now time.Time) { for { // Return from gc if the heap is empty or the next element is not yet // expired. // // heap[0] is a peek at the next element in the heap, which is not obvious // from looking at the (*expiringHeap).Pop() implementation below. // heap.Pop() swaps the first entry with the last entry of the heap, then // calls (*expiringHeap).Pop() which returns the last element. if len(c.heap) == 0 || now.Before(c.heap[0].expiry) { return } cleanup := heap.Pop(&c.heap).(*expiringHeapEntry) c.del(cleanup.key, cleanup.generation) } } type expiringHeapEntry struct { key interface{} expiry time.Time generation uint64 } // expiringHeap is a min-heap ordered by expiration time of its entries. The // expiring cache uses this as a priority queue to efficiently organize entries // which will be garbage collected once they expire. type expiringHeap []*expiringHeapEntry var _ heap.Interface = &expiringHeap{} func (cq expiringHeap) Len() int { return len(cq) } func (cq expiringHeap) Less(i, j int) bool { return cq[i].expiry.Before(cq[j].expiry) } func (cq expiringHeap) Swap(i, j int) { cq[i], cq[j] = cq[j], cq[i] } func (cq *expiringHeap) Push(c interface{}) { *cq = append(*cq, c.(*expiringHeapEntry)) } func (cq *expiringHeap) Pop() interface{} { c := (*cq)[cq.Len()-1] *cq = (*cq)[:cq.Len()-1] return c } golang-k8s-apimachinery-0.29.0/pkg/util/cache/expiring_test.go000066400000000000000000000165141453143165200242520ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cache import ( "context" "math/rand" "sync" "testing" "time" "github.com/google/uuid" testingclock "k8s.io/utils/clock/testing" ) func TestExpiringCache(t *testing.T) { cache := NewExpiring() if result, ok := cache.Get("foo"); ok || result != nil { t.Errorf("Expected null, false, got %#v, %v", result, ok) } record1 := "bob" record2 := "alice" // when empty, record is stored cache.Set("foo", record1, time.Hour) if result, ok := cache.Get("foo"); !ok || result != record1 { t.Errorf("Expected %#v, true, got %#v, %v", record1, result, ok) } // newer record overrides cache.Set("foo", record2, time.Hour) if result, ok := cache.Get("foo"); !ok || result != record2 { t.Errorf("Expected %#v, true, got %#v, %v", record2, result, ok) } // delete the current value cache.Delete("foo") if result, ok := cache.Get("foo"); ok || result != nil { t.Errorf("Expected null, false, got %#v, %v", result, ok) } } func TestExpiration(t *testing.T) { fc := &testingclock.FakeClock{} c := NewExpiringWithClock(fc) c.Set("a", "a", time.Second) fc.Step(500 * time.Millisecond) if _, ok := c.Get("a"); !ok { t.Fatalf("we should have found a key") } fc.Step(time.Second) if _, ok := c.Get("a"); ok { t.Fatalf("we should not have found a key") } c.Set("a", "a", time.Second) fc.Step(500 * time.Millisecond) if _, ok := c.Get("a"); !ok { t.Fatalf("we should have found a key") } // reset should restart the ttl c.Set("a", "a", time.Second) fc.Step(750 * time.Millisecond) if _, ok := c.Get("a"); !ok { t.Fatalf("we should have found a key") } // Simulate a race between a reset and cleanup. Assert that del doesn't // remove the key. c.Set("a", "a", time.Second) e := c.cache["a"] e.generation++ e.expiry = e.expiry.Add(1 * time.Second) c.cache["a"] = e fc.Step(1 * time.Second) if _, ok := c.Get("a"); !ok { t.Fatalf("we should have found a key") } // Check getting an expired key with and without AllowExpiredGet c.Set("b", "b", time.Second) fc.Step(2 * time.Second) if _, ok := c.Get("b"); ok { t.Fatalf("we should not have found b key") } if count := c.Len(); count != 2 { // b is still in the cache t.Errorf("expected two items got: %d", count) } c.AllowExpiredGet = true if _, ok := c.Get("b"); !ok { t.Fatalf("we should have found b key") } if count := c.Len(); count != 2 { // b is still in the cache t.Errorf("expected two items got: %d", count) } c.Set("c", "c", time.Second) // set some unrelated key to run gc if count := c.Len(); count != 2 { // only a and c in the cache now t.Errorf("expected two items got: %d", count) } if _, ok := c.Get("b"); ok { t.Fatalf("we should not have found b key") } if _, ok := c.Get("a"); !ok { t.Fatalf("we should have found a key") } if _, ok := c.Get("c"); !ok { t.Fatalf("we should have found c key") } } func TestGarbageCollection(t *testing.T) { fc := &testingclock.FakeClock{} type entry struct { key, val string ttl time.Duration } tests := []struct { name string now time.Time set []entry want map[string]string }{ { name: "two entries just set", now: fc.Now().Add(0 * time.Second), set: []entry{ {"a", "aa", 1 * time.Second}, {"b", "bb", 2 * time.Second}, }, want: map[string]string{ "a": "aa", "b": "bb", }, }, { name: "first entry expired now", now: fc.Now().Add(1 * time.Second), set: []entry{ {"a", "aa", 1 * time.Second}, {"b", "bb", 2 * time.Second}, }, want: map[string]string{ "b": "bb", }, }, { name: "first entry expired half a second ago", now: fc.Now().Add(1500 * time.Millisecond), set: []entry{ {"a", "aa", 1 * time.Second}, {"b", "bb", 2 * time.Second}, }, want: map[string]string{ "b": "bb", }, }, { name: "three entries weird order", now: fc.Now().Add(1 * time.Second), set: []entry{ {"c", "cc", 3 * time.Second}, {"a", "aa", 1 * time.Second}, {"b", "bb", 2 * time.Second}, }, want: map[string]string{ "b": "bb", "c": "cc", }, }, { name: "expire multiple entries in one cycle", now: fc.Now().Add(2500 * time.Millisecond), set: []entry{ {"a", "aa", 1 * time.Second}, {"b", "bb", 2 * time.Second}, {"c", "cc", 3 * time.Second}, }, want: map[string]string{ "c": "cc", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := NewExpiringWithClock(fc) for _, e := range test.set { c.Set(e.key, e.val, e.ttl) } c.gc(test.now) for k, want := range test.want { got, ok := c.Get(k) if !ok { t.Errorf("expected cache to have entry for key=%q but found none", k) continue } if got != want { t.Errorf("unexpected value for key=%q: got=%q, want=%q", k, got, want) } } if got, want := c.Len(), len(test.want); got != want { t.Errorf("unexpected cache size: got=%d, want=%d", got, want) } }) } } func BenchmarkExpiringCacheContention(b *testing.B) { b.Run("evict_probablility=100%", func(b *testing.B) { benchmarkExpiringCacheContention(b, 1) }) b.Run("evict_probablility=10%", func(b *testing.B) { benchmarkExpiringCacheContention(b, 0.1) }) b.Run("evict_probablility=1%", func(b *testing.B) { benchmarkExpiringCacheContention(b, 0.01) }) } func benchmarkExpiringCacheContention(b *testing.B, prob float64) { const numKeys = 1 << 16 cache := NewExpiring() keys := []string{} for i := 0; i < numKeys; i++ { key := uuid.New().String() keys = append(keys, key) } b.ResetTimer() b.SetParallelism(256) b.RunParallel(func(pb *testing.PB) { rand := rand.New(rand.NewSource(rand.Int63())) for pb.Next() { i := rand.Int31() key := keys[i%numKeys] _, ok := cache.Get(key) if ok { // compare lower bits of sampled i to decide whether we should evict. if rand.Float64() < prob { cache.Delete(key) } } else { cache.Set(key, struct{}{}, 50*time.Millisecond) } } }) } func TestStressExpiringCache(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() const numKeys = 1 << 16 cache := NewExpiring() keys := []string{} for i := 0; i < numKeys; i++ { key := uuid.New().String() keys = append(keys, key) } var wg sync.WaitGroup for i := 0; i < 256; i++ { wg.Add(1) go func() { defer wg.Done() rand := rand.New(rand.NewSource(rand.Int63())) for { select { case <-ctx.Done(): return default: } key := keys[rand.Intn(numKeys)] if _, ok := cache.Get(key); !ok { cache.Set(key, struct{}{}, 50*time.Millisecond) } } }() } wg.Wait() // trigger a GC with a set and check the cache size. time.Sleep(60 * time.Millisecond) cache.Set("trigger", "gc", time.Second) if cache.Len() != 1 { t.Errorf("unexpected cache size: got=%d, want=1", cache.Len()) } } golang-k8s-apimachinery-0.29.0/pkg/util/cache/lruexpirecache.go000066400000000000000000000105161453143165200243650ustar00rootroot00000000000000/* Copyright 2016 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 cache import ( "container/list" "sync" "time" ) // Clock defines an interface for obtaining the current time type Clock interface { Now() time.Time } // realClock implements the Clock interface by calling time.Now() type realClock struct{} func (realClock) Now() time.Time { return time.Now() } // LRUExpireCache is a cache that ensures the mostly recently accessed keys are returned with // a ttl beyond which keys are forcibly expired. type LRUExpireCache struct { // clock is used to obtain the current time clock Clock lock sync.Mutex maxSize int evictionList list.List entries map[interface{}]*list.Element } // NewLRUExpireCache creates an expiring cache with the given size func NewLRUExpireCache(maxSize int) *LRUExpireCache { return NewLRUExpireCacheWithClock(maxSize, realClock{}) } // NewLRUExpireCacheWithClock creates an expiring cache with the given size, using the specified clock to obtain the current time. func NewLRUExpireCacheWithClock(maxSize int, clock Clock) *LRUExpireCache { if maxSize <= 0 { panic("maxSize must be > 0") } return &LRUExpireCache{ clock: clock, maxSize: maxSize, entries: map[interface{}]*list.Element{}, } } type cacheEntry struct { key interface{} value interface{} expireTime time.Time } // Add adds the value to the cache at key with the specified maximum duration. func (c *LRUExpireCache) Add(key interface{}, value interface{}, ttl time.Duration) { c.lock.Lock() defer c.lock.Unlock() // Key already exists oldElement, ok := c.entries[key] if ok { c.evictionList.MoveToFront(oldElement) oldElement.Value.(*cacheEntry).value = value oldElement.Value.(*cacheEntry).expireTime = c.clock.Now().Add(ttl) return } // Make space if necessary if c.evictionList.Len() >= c.maxSize { toEvict := c.evictionList.Back() c.evictionList.Remove(toEvict) delete(c.entries, toEvict.Value.(*cacheEntry).key) } // Add new entry entry := &cacheEntry{ key: key, value: value, expireTime: c.clock.Now().Add(ttl), } element := c.evictionList.PushFront(entry) c.entries[key] = element } // Get returns the value at the specified key from the cache if it exists and is not // expired, or returns false. func (c *LRUExpireCache) Get(key interface{}) (interface{}, bool) { c.lock.Lock() defer c.lock.Unlock() element, ok := c.entries[key] if !ok { return nil, false } if c.clock.Now().After(element.Value.(*cacheEntry).expireTime) { c.evictionList.Remove(element) delete(c.entries, key) return nil, false } c.evictionList.MoveToFront(element) return element.Value.(*cacheEntry).value, true } // Remove removes the specified key from the cache if it exists func (c *LRUExpireCache) Remove(key interface{}) { c.lock.Lock() defer c.lock.Unlock() element, ok := c.entries[key] if !ok { return } c.evictionList.Remove(element) delete(c.entries, key) } // RemoveAll removes all keys that match predicate. func (c *LRUExpireCache) RemoveAll(predicate func(key any) bool) { c.lock.Lock() defer c.lock.Unlock() for key, element := range c.entries { if predicate(key) { c.evictionList.Remove(element) delete(c.entries, key) } } } // Keys returns all unexpired keys in the cache. // // Keep in mind that subsequent calls to Get() for any of the returned keys // might return "not found". // // Keys are returned ordered from least recently used to most recently used. func (c *LRUExpireCache) Keys() []interface{} { c.lock.Lock() defer c.lock.Unlock() now := c.clock.Now() val := make([]interface{}, 0, c.evictionList.Len()) for element := c.evictionList.Back(); element != nil; element = element.Prev() { // Only return unexpired keys if !now.After(element.Value.(*cacheEntry).expireTime) { val = append(val, element.Value.(*cacheEntry).key) } } return val } golang-k8s-apimachinery-0.29.0/pkg/util/cache/lruexpirecache_test.go000066400000000000000000000104541453143165200254250ustar00rootroot00000000000000/* Copyright 2016 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 cache import ( "testing" "time" "github.com/google/go-cmp/cmp" testingclock "k8s.io/utils/clock/testing" ) func expectEntry(t *testing.T, c *LRUExpireCache, key interface{}, value interface{}) { t.Helper() result, ok := c.Get(key) if !ok || result != value { t.Errorf("Expected cache[%v]: %v, got %v", key, value, result) } } func expectNotEntry(t *testing.T, c *LRUExpireCache, key interface{}) { t.Helper() if result, ok := c.Get(key); ok { t.Errorf("Expected cache[%v] to be empty, got %v", key, result) } } // Note: Check keys before checking individual entries, because Get() changes // the eviction list. func assertKeys(t *testing.T, gotKeys, wantKeys []interface{}) { t.Helper() if diff := cmp.Diff(gotKeys, wantKeys); diff != "" { t.Errorf("Wrong result for keys: diff (-got +want):\n%s", diff) } } func TestSimpleGet(t *testing.T) { c := NewLRUExpireCache(10) c.Add("long-lived", "12345", 10*time.Hour) assertKeys(t, c.Keys(), []interface{}{"long-lived"}) expectEntry(t, c, "long-lived", "12345") } func TestSimpleRemove(t *testing.T) { c := NewLRUExpireCache(10) c.Add("long-lived", "12345", 10*time.Hour) c.Remove("long-lived") assertKeys(t, c.Keys(), []interface{}{}) expectNotEntry(t, c, "long-lived") } func TestSimpleRemoveAll(t *testing.T) { c := NewLRUExpireCache(10) c.Add("long-lived", "12345", 10*time.Hour) c.Add("other-long-lived", "12345", 10*time.Hour) c.RemoveAll(func(k any) bool { return k.(string) == "long-lived" }) assertKeys(t, c.Keys(), []any{"other-long-lived"}) expectNotEntry(t, c, "long-lived") expectEntry(t, c, "other-long-lived", "12345") } func TestExpiredGet(t *testing.T) { fakeClock := testingclock.NewFakeClock(time.Now()) c := NewLRUExpireCacheWithClock(10, fakeClock) c.Add("short-lived", "12345", 1*time.Millisecond) // ensure the entry expired fakeClock.Step(2 * time.Millisecond) // Keys() should not return expired keys. assertKeys(t, c.Keys(), []interface{}{}) expectNotEntry(t, c, "short-lived") } func TestLRUOverflow(t *testing.T) { c := NewLRUExpireCache(4) c.Add("elem1", "1", 10*time.Hour) c.Add("elem2", "2", 10*time.Hour) c.Add("elem3", "3", 10*time.Hour) c.Add("elem4", "4", 10*time.Hour) c.Add("elem5", "5", 10*time.Hour) assertKeys(t, c.Keys(), []interface{}{"elem2", "elem3", "elem4", "elem5"}) expectNotEntry(t, c, "elem1") expectEntry(t, c, "elem2", "2") expectEntry(t, c, "elem3", "3") expectEntry(t, c, "elem4", "4") expectEntry(t, c, "elem5", "5") } func TestAddBringsToFront(t *testing.T) { c := NewLRUExpireCache(4) c.Add("elem1", "1", 10*time.Hour) c.Add("elem2", "2", 10*time.Hour) c.Add("elem3", "3", 10*time.Hour) c.Add("elem4", "4", 10*time.Hour) c.Add("elem1", "1-new", 10*time.Hour) c.Add("elem5", "5", 10*time.Hour) assertKeys(t, c.Keys(), []interface{}{"elem3", "elem4", "elem1", "elem5"}) expectNotEntry(t, c, "elem2") expectEntry(t, c, "elem1", "1-new") expectEntry(t, c, "elem3", "3") expectEntry(t, c, "elem4", "4") expectEntry(t, c, "elem5", "5") } func TestGetBringsToFront(t *testing.T) { c := NewLRUExpireCache(4) c.Add("elem1", "1", 10*time.Hour) c.Add("elem2", "2", 10*time.Hour) c.Add("elem3", "3", 10*time.Hour) c.Add("elem4", "4", 10*time.Hour) c.Get("elem1") c.Add("elem5", "5", 10*time.Hour) assertKeys(t, c.Keys(), []interface{}{"elem3", "elem4", "elem1", "elem5"}) expectNotEntry(t, c, "elem2") expectEntry(t, c, "elem1", "1") expectEntry(t, c, "elem3", "3") expectEntry(t, c, "elem4", "4") expectEntry(t, c, "elem5", "5") } func TestLRUKeys(t *testing.T) { c := NewLRUExpireCache(4) c.Add("elem1", "1", 10*time.Hour) c.Add("elem2", "2", 10*time.Hour) c.Add("elem3", "3", 10*time.Hour) c.Add("elem4", "4", 10*time.Hour) assertKeys(t, c.Keys(), []interface{}{"elem1", "elem2", "elem3", "elem4"}) } golang-k8s-apimachinery-0.29.0/pkg/util/diff/000077500000000000000000000000001453143165200206755ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/diff/diff.go000066400000000000000000000066071453143165200221450ustar00rootroot00000000000000/* 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 diff import ( "bytes" "fmt" "reflect" "strings" "text/tabwriter" "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/util/dump" ) func legacyDiff(a, b interface{}) string { return cmp.Diff(a, b) } // StringDiff diffs a and b and returns a human readable diff. // DEPRECATED: use github.com/google/go-cmp/cmp.Diff func StringDiff(a, b string) string { return legacyDiff(a, b) } // ObjectDiff prints the diff of two go objects and fails if the objects // contain unhandled unexported fields. // DEPRECATED: use github.com/google/go-cmp/cmp.Diff func ObjectDiff(a, b interface{}) string { return legacyDiff(a, b) } // ObjectGoPrintDiff prints the diff of two go objects and fails if the objects // contain unhandled unexported fields. // DEPRECATED: use github.com/google/go-cmp/cmp.Diff func ObjectGoPrintDiff(a, b interface{}) string { return legacyDiff(a, b) } // ObjectReflectDiff prints the diff of two go objects and fails if the objects // contain unhandled unexported fields. // DEPRECATED: use github.com/google/go-cmp/cmp.Diff func ObjectReflectDiff(a, b interface{}) string { return legacyDiff(a, b) } // ObjectGoPrintSideBySide prints a and b as textual dumps side by side, // enabling easy visual scanning for mismatches. func ObjectGoPrintSideBySide(a, b interface{}) string { sA := dump.Pretty(a) sB := dump.Pretty(b) linesA := strings.Split(sA, "\n") linesB := strings.Split(sB, "\n") width := 0 for _, s := range linesA { l := len(s) if l > width { width = l } } for _, s := range linesB { l := len(s) if l > width { width = l } } buf := &bytes.Buffer{} w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0) max := len(linesA) if len(linesB) > max { max = len(linesB) } for i := 0; i < max; i++ { var a, b string if i < len(linesA) { a = linesA[i] } if i < len(linesB) { b = linesB[i] } fmt.Fprintf(w, "%s\t%s\n", a, b) } w.Flush() return buf.String() } // IgnoreUnset is an option that ignores fields that are unset on the right // hand side of a comparison. This is useful in testing to assert that an // object is a derivative. func IgnoreUnset() cmp.Option { return cmp.Options{ // ignore unset fields in v2 cmp.FilterPath(func(path cmp.Path) bool { _, v2 := path.Last().Values() switch v2.Kind() { case reflect.Slice, reflect.Map: if v2.IsNil() || v2.Len() == 0 { return true } case reflect.String: if v2.Len() == 0 { return true } case reflect.Interface, reflect.Pointer: if v2.IsNil() { return true } } return false }, cmp.Ignore()), // ignore map entries that aren't set in v2 cmp.FilterPath(func(path cmp.Path) bool { switch i := path.Last().(type) { case cmp.MapIndex: if _, v2 := i.Values(); !v2.IsValid() { fmt.Println("E") return true } } return false }, cmp.Ignore()), } } golang-k8s-apimachinery-0.29.0/pkg/util/dump/000077500000000000000000000000001453143165200207325ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/dump/dump.go000066400000000000000000000032341453143165200222300ustar00rootroot00000000000000/* Copyright 2021 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 dump import ( "github.com/davecgh/go-spew/spew" ) var prettyPrintConfig = &spew.ConfigState{ Indent: " ", DisableMethods: true, DisablePointerAddresses: true, DisableCapacities: true, } // The config MUST NOT be changed because that could change the result of a hash operation var prettyPrintConfigForHash = &spew.ConfigState{ Indent: " ", SortKeys: true, DisableMethods: true, SpewKeys: true, DisablePointerAddresses: true, DisableCapacities: true, } // Pretty wrap the spew.Sdump with Indent, and disabled methods like error() and String() // The output may change over time, so for guaranteed output please take more direct control func Pretty(a interface{}) string { return prettyPrintConfig.Sdump(a) } // ForHash keeps the original Spew.Sprintf format to ensure the same checksum func ForHash(a interface{}) string { return prettyPrintConfigForHash.Sprintf("%#v", a) } // OneLine outputs the object in one line func OneLine(a interface{}) string { return prettyPrintConfig.Sprintf("%#v", a) } golang-k8s-apimachinery-0.29.0/pkg/util/dump/dump_test.go000066400000000000000000000244121453143165200232700ustar00rootroot00000000000000/* Copyright 2021 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 dump import ( "fmt" "testing" ) func ptrint(i int) *int { return &i } func ptrstr(s string) *string { return &s } // custom type to test Stringer interface on non-pointer receiver. type customString string // String implements the Stringer interface for testing invocation func (s customString) String() string { return "custom string " + string(s) } // custom type to test error interface on non-pointer receiver. type customError int // Error implements the error interface for testing invocation func (e customError) Error() string { return fmt.Sprintf("custom error: %d", int(e)) } // custom type to test Stringer interface on pointer receiver. type pCustomString string // String implements the Stringer interface for testing invocation func (s *pCustomString) String() string { return "custom string " + string(*s) } // custom type to test error interface on pointer receiver. type pCustomError int // Error implements the error interface for testing invocation func (e *pCustomError) Error() string { return fmt.Sprintf("custom error: %d", int(*e)) } // embed is used to test embedded structures. type embed struct { s string } // embedwrap is used to test embedded structures. type embedwrap struct { embed e *embed } func TestPretty(t *testing.T) { tcs := customString("test") tpcs := pCustomString("&test") tce := customError(0) tpce := pCustomError(0) teb := embed{"test"} tebw := embedwrap{teb, &teb} testCases := []struct { a interface{} want string }{ {int8(93), "(int8) 93\n"}, {int16(93), "(int16) 93\n"}, {int32(93), "(int32) 93\n"}, {int64(93), "(int64) 93\n"}, {int(-93), "(int) -93\n"}, {int8(-93), "(int8) -93\n"}, {int16(-93), "(int16) -93\n"}, {int32(-93), "(int32) -93\n"}, {int64(-93), "(int64) -93\n"}, {uint(93), "(uint) 93\n"}, {uint8(93), "(uint8) 93\n"}, {uint16(93), "(uint16) 93\n"}, {uint32(93), "(uint32) 93\n"}, {uint64(93), "(uint64) 93\n"}, {uintptr(93), "(uintptr) 0x5d\n"}, {ptrint(93), "(*int)(93)\n"}, {float32(93.76), "(float32) 93.76\n"}, {float64(93.76), "(float64) 93.76\n"}, {complex64(93i), "(complex64) (0+93i)\n"}, {complex128(93i), "(complex128) (0+93i)\n"}, {bool(true), "(bool) true\n"}, {bool(false), "(bool) false\n"}, {string("test"), "(string) (len=4) \"test\"\n"}, {ptrstr("test"), "(*string)((len=4) \"test\")\n"}, {[1]string{"arr"}, "([1]string) (len=1) {\n (string) (len=3) \"arr\"\n}\n"}, {[]string{"slice"}, "([]string) (len=1) {\n (string) (len=5) \"slice\"\n}\n"}, {tcs, "(dump.customString) (len=4) \"test\"\n"}, {&tcs, "(*dump.customString)((len=4) \"test\")\n"}, {tpcs, "(dump.pCustomString) (len=5) \"&test\"\n"}, {&tpcs, "(*dump.pCustomString)((len=5) \"&test\")\n"}, {tce, "(dump.customError) 0\n"}, {&tce, "(*dump.customError)(0)\n"}, {tpce, "(dump.pCustomError) 0\n"}, {&tpce, "(*dump.pCustomError)(0)\n"}, { struct { arr [1]string slice []string m map[string]int }{ [1]string{"arr"}, []string{"slice"}, map[string]int{"one": 1}, }, "(struct { arr [1]string; slice []string; m map[string]int }) {\n arr: ([1]string) (len=1) {\n (string) (len=3) \"arr\"\n },\n slice: ([]string) (len=1) {\n (string) (len=5) \"slice\"\n },\n m: (map[string]int) (len=1) {\n (string) (len=3) \"one\": (int) 1\n }\n}\n", }, {teb, "(dump.embed) {\n s: (string) (len=4) \"test\"\n}\n"}, {tebw, "(dump.embedwrap) {\n embed: (dump.embed) {\n s: (string) (len=4) \"test\"\n },\n e: (*dump.embed)({\n s: (string) (len=4) \"test\"\n })\n}\n"}, {map[string]int{}, "(map[string]int) {\n}\n"}, {map[string]int{"one": 1}, "(map[string]int) (len=1) {\n (string) (len=3) \"one\": (int) 1\n}\n"}, {map[string]interface{}{"one": 1}, "(map[string]interface {}) (len=1) {\n (string) (len=3) \"one\": (int) 1\n}\n"}, {map[string]customString{"key": tcs}, "(map[string]dump.customString) (len=1) {\n (string) (len=3) \"key\": (dump.customString) (len=4) \"test\"\n}\n"}, {map[string]customError{"key": tce}, "(map[string]dump.customError) (len=1) {\n (string) (len=3) \"key\": (dump.customError) 0\n}\n"}, {map[string]embed{"key": teb}, "(map[string]dump.embed) (len=1) {\n (string) (len=3) \"key\": (dump.embed) {\n s: (string) (len=4) \"test\"\n }\n}\n"}, {map[string]embedwrap{"key": tebw}, "(map[string]dump.embedwrap) (len=1) {\n (string) (len=3) \"key\": (dump.embedwrap) {\n embed: (dump.embed) {\n s: (string) (len=4) \"test\"\n },\n e: (*dump.embed)({\n s: (string) (len=4) \"test\"\n })\n }\n}\n"}, } for i, tc := range testCases { s := Pretty(tc.a) if tc.want != s { t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, tc.want, s) } } } func TestForHash(t *testing.T) { tcs := customString("test") tpcs := pCustomString("&test") tce := customError(0) tpce := pCustomError(0) teb := embed{"test"} tebw := embedwrap{teb, &teb} testCases := []struct { a interface{} want string }{ {int8(93), "(int8)93"}, {int16(93), "(int16)93"}, {int32(93), "(int32)93"}, {int64(93), "(int64)93"}, {int(-93), "(int)-93"}, {int8(-93), "(int8)-93"}, {int16(-93), "(int16)-93"}, {int32(-93), "(int32)-93"}, {int64(-93), "(int64)-93"}, {uint(93), "(uint)93"}, {uint8(93), "(uint8)93"}, {uint16(93), "(uint16)93"}, {uint32(93), "(uint32)93"}, {uint64(93), "(uint64)93"}, {uintptr(93), "(uintptr)0x5d"}, {ptrint(93), "(*int)93"}, {float32(93.76), "(float32)93.76"}, {float64(93.76), "(float64)93.76"}, {complex64(93i), "(complex64)(0+93i)"}, {complex128(93i), "(complex128)(0+93i)"}, {bool(true), "(bool)true"}, {bool(false), "(bool)false"}, {string("test"), "(string)test"}, {ptrstr("test"), "(*string)test"}, {[1]string{"arr"}, "([1]string)[arr]"}, {[]string{"slice"}, "([]string)[slice]"}, {tcs, "(dump.customString)test"}, {&tcs, "(*dump.customString)test"}, {tpcs, "(dump.pCustomString)&test"}, {&tpcs, "(*dump.pCustomString)&test"}, {tce, "(dump.customError)0"}, {&tce, "(*dump.customError)0"}, {tpce, "(dump.pCustomError)0"}, {&tpce, "(*dump.pCustomError)0"}, { struct { arr [1]string slice []string m map[string]int }{ [1]string{"arr"}, []string{"slice"}, map[string]int{"one": 1}, }, "(struct { arr [1]string; slice []string; m map[string]int }){arr:([1]string)[arr] slice:([]string)[slice] m:(map[string]int)map[one:1]}", }, {teb, "(dump.embed){s:(string)test}"}, {tebw, "(dump.embedwrap){embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}"}, {map[string]int{}, "(map[string]int)map[]"}, {map[string]int{"one": 1, "two": 2}, "(map[string]int)map[one:1 two:2]"}, {map[string]interface{}{"one": 1}, "(map[string]interface {})map[one:(int)1]"}, {map[string]customString{"key": tcs}, "(map[string]dump.customString)map[key:test]"}, {map[string]customError{"key": tce}, "(map[string]dump.customError)map[key:0]"}, {map[string]embed{"key": teb}, "(map[string]dump.embed)map[key:{s:(string)test}]"}, {map[string]embedwrap{"key": tebw}, "(map[string]dump.embedwrap)map[key:{embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}]"}, } for i, tc := range testCases { s := ForHash(tc.a) if tc.want != s { t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, tc.want, s) } } } func TestOneLine(t *testing.T) { tcs := customString("test") tpcs := pCustomString("&test") tce := customError(0) tpce := pCustomError(0) teb := embed{"test"} tebw := embedwrap{teb, &teb} testCases := []struct { a interface{} want string }{ {int8(93), "(int8)93"}, {int16(93), "(int16)93"}, {int32(93), "(int32)93"}, {int64(93), "(int64)93"}, {int(-93), "(int)-93"}, {int8(-93), "(int8)-93"}, {int16(-93), "(int16)-93"}, {int32(-93), "(int32)-93"}, {int64(-93), "(int64)-93"}, {uint(93), "(uint)93"}, {uint8(93), "(uint8)93"}, {uint16(93), "(uint16)93"}, {uint32(93), "(uint32)93"}, {uint64(93), "(uint64)93"}, {uintptr(93), "(uintptr)0x5d"}, {ptrint(93), "(*int)93"}, {float32(93.76), "(float32)93.76"}, {float64(93.76), "(float64)93.76"}, {complex64(93i), "(complex64)(0+93i)"}, {complex128(93i), "(complex128)(0+93i)"}, {bool(true), "(bool)true"}, {bool(false), "(bool)false"}, {string("test"), "(string)test"}, {ptrstr("test"), "(*string)test"}, {[1]string{"arr"}, "([1]string)[arr]"}, {[]string{"slice"}, "([]string)[slice]"}, {tcs, "(dump.customString)test"}, {&tcs, "(*dump.customString)test"}, {tpcs, "(dump.pCustomString)&test"}, {&tpcs, "(*dump.pCustomString)&test"}, {tce, "(dump.customError)0"}, {&tce, "(*dump.customError)0"}, {tpce, "(dump.pCustomError)0"}, {&tpce, "(*dump.pCustomError)0"}, { struct { arr [1]string slice []string m map[string]int }{ [1]string{"arr"}, []string{"slice"}, map[string]int{"one": 1}, }, "(struct { arr [1]string; slice []string; m map[string]int }){arr:([1]string)[arr] slice:([]string)[slice] m:(map[string]int)map[one:1]}", }, {teb, "(dump.embed){s:(string)test}"}, {tebw, "(dump.embedwrap){embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}"}, {map[string]int{}, "(map[string]int)map[]"}, {map[string]int{"one": 1}, "(map[string]int)map[one:1]"}, {map[string]interface{}{"one": 1}, "(map[string]interface {})map[one:(int)1]"}, {map[string]customString{"key": tcs}, "(map[string]dump.customString)map[key:test]"}, {map[string]customError{"key": tce}, "(map[string]dump.customError)map[key:0]"}, {map[string]embed{"key": teb}, "(map[string]dump.embed)map[key:{s:(string)test}]"}, {map[string]embedwrap{"key": tebw}, "(map[string]dump.embedwrap)map[key:{embed:(dump.embed){s:(string)test} e:(*dump.embed){s:(string)test}}]"}, } for i, tc := range testCases { s := OneLine(tc.a) if tc.want != s { t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, tc.want, s) } } } golang-k8s-apimachinery-0.29.0/pkg/util/duration/000077500000000000000000000000001453143165200216125ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/duration/duration.go000066400000000000000000000054431453143165200237740ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package duration import ( "fmt" "time" ) // ShortHumanDuration returns a succinct representation of the provided duration // with limited precision for consumption by humans. func ShortHumanDuration(d time.Duration) string { // Allow deviation no more than 2 seconds(excluded) to tolerate machine time // inconsistence, it can be considered as almost now. if seconds := int(d.Seconds()); seconds < -1 { return "" } else if seconds < 0 { return "0s" } else if seconds < 60 { return fmt.Sprintf("%ds", seconds) } else if minutes := int(d.Minutes()); minutes < 60 { return fmt.Sprintf("%dm", minutes) } else if hours := int(d.Hours()); hours < 24 { return fmt.Sprintf("%dh", hours) } else if hours < 24*365 { return fmt.Sprintf("%dd", hours/24) } return fmt.Sprintf("%dy", int(d.Hours()/24/365)) } // HumanDuration returns a succinct representation of the provided duration // with limited precision for consumption by humans. It provides ~2-3 significant // figures of duration. func HumanDuration(d time.Duration) string { // Allow deviation no more than 2 seconds(excluded) to tolerate machine time // inconsistence, it can be considered as almost now. if seconds := int(d.Seconds()); seconds < -1 { return "" } else if seconds < 0 { return "0s" } else if seconds < 60*2 { return fmt.Sprintf("%ds", seconds) } minutes := int(d / time.Minute) if minutes < 10 { s := int(d/time.Second) % 60 if s == 0 { return fmt.Sprintf("%dm", minutes) } return fmt.Sprintf("%dm%ds", minutes, s) } else if minutes < 60*3 { return fmt.Sprintf("%dm", minutes) } hours := int(d / time.Hour) if hours < 8 { m := int(d/time.Minute) % 60 if m == 0 { return fmt.Sprintf("%dh", hours) } return fmt.Sprintf("%dh%dm", hours, m) } else if hours < 48 { return fmt.Sprintf("%dh", hours) } else if hours < 24*8 { h := hours % 24 if h == 0 { return fmt.Sprintf("%dd", hours/24) } return fmt.Sprintf("%dd%dh", hours/24, h) } else if hours < 24*365*2 { return fmt.Sprintf("%dd", hours/24) } else if hours < 24*365*8 { dy := int(hours/24) % 365 if dy == 0 { return fmt.Sprintf("%dy", hours/24/365) } return fmt.Sprintf("%dy%dd", hours/24/365, dy) } return fmt.Sprintf("%dy", int(hours/24/365)) } golang-k8s-apimachinery-0.29.0/pkg/util/duration/duration_test.go000066400000000000000000000110061453143165200250230ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package duration import ( "testing" "time" ) func TestHumanDuration(t *testing.T) { tests := []struct { d time.Duration want string }{ {d: time.Second, want: "1s"}, {d: 70 * time.Second, want: "70s"}, {d: 190 * time.Second, want: "3m10s"}, {d: 70 * time.Minute, want: "70m"}, {d: 47 * time.Hour, want: "47h"}, {d: 49 * time.Hour, want: "2d1h"}, {d: (8*24 + 2) * time.Hour, want: "8d"}, {d: (367 * 24) * time.Hour, want: "367d"}, {d: (365*2*24 + 25) * time.Hour, want: "2y1d"}, {d: (365*8*24 + 2) * time.Hour, want: "8y"}, } for _, tt := range tests { t.Run(tt.d.String(), func(t *testing.T) { if got := HumanDuration(tt.d); got != tt.want { t.Errorf("HumanDuration() = %v, want %v", got, tt.want) } }) } } func TestHumanDurationBoundaries(t *testing.T) { tests := []struct { d time.Duration want string }{ {d: -2 * time.Second, want: ""}, {d: -2*time.Second + 1, want: "0s"}, {d: 0, want: "0s"}, {d: time.Second - time.Millisecond, want: "0s"}, {d: 2*time.Minute - time.Millisecond, want: "119s"}, {d: 2 * time.Minute, want: "2m"}, {d: 2*time.Minute + time.Second, want: "2m1s"}, {d: 10*time.Minute - time.Millisecond, want: "9m59s"}, {d: 10 * time.Minute, want: "10m"}, {d: 10*time.Minute + time.Second, want: "10m"}, {d: 3*time.Hour - time.Millisecond, want: "179m"}, {d: 3 * time.Hour, want: "3h"}, {d: 3*time.Hour + time.Minute, want: "3h1m"}, {d: 8*time.Hour - time.Millisecond, want: "7h59m"}, {d: 8 * time.Hour, want: "8h"}, {d: 8*time.Hour + 59*time.Minute, want: "8h"}, {d: 2*24*time.Hour - time.Millisecond, want: "47h"}, {d: 2 * 24 * time.Hour, want: "2d"}, {d: 2*24*time.Hour + time.Hour, want: "2d1h"}, {d: 8*24*time.Hour - time.Millisecond, want: "7d23h"}, {d: 8 * 24 * time.Hour, want: "8d"}, {d: 8*24*time.Hour + 23*time.Hour, want: "8d"}, {d: 2*365*24*time.Hour - time.Millisecond, want: "729d"}, {d: 2 * 365 * 24 * time.Hour, want: "2y"}, {d: 2*365*24*time.Hour + 23*time.Hour, want: "2y"}, {d: 2*365*24*time.Hour + 23*time.Hour + 59*time.Minute, want: "2y"}, {d: 2*365*24*time.Hour + 24*time.Hour - time.Millisecond, want: "2y"}, {d: 2*365*24*time.Hour + 24*time.Hour, want: "2y1d"}, {d: 3 * 365 * 24 * time.Hour, want: "3y"}, {d: 4 * 365 * 24 * time.Hour, want: "4y"}, {d: 5 * 365 * 24 * time.Hour, want: "5y"}, {d: 6 * 365 * 24 * time.Hour, want: "6y"}, {d: 7 * 365 * 24 * time.Hour, want: "7y"}, {d: 8*365*24*time.Hour - time.Millisecond, want: "7y364d"}, {d: 8 * 365 * 24 * time.Hour, want: "8y"}, {d: 8*365*24*time.Hour + 364*24*time.Hour, want: "8y"}, {d: 9 * 365 * 24 * time.Hour, want: "9y"}, } for _, tt := range tests { t.Run(tt.d.String(), func(t *testing.T) { if got := HumanDuration(tt.d); got != tt.want { t.Errorf("HumanDuration() = %v, want %v", got, tt.want) } }) } } func TestShortHumanDurationBoundaries(t *testing.T) { tests := []struct { d time.Duration want string }{ {d: -2 * time.Second, want: ""}, {d: -2*time.Second + 1, want: "0s"}, {d: 0, want: "0s"}, {d: time.Second - time.Millisecond, want: "0s"}, {d: time.Second, want: "1s"}, {d: 2*time.Second - time.Millisecond, want: "1s"}, {d: time.Minute - time.Millisecond, want: "59s"}, {d: time.Minute, want: "1m"}, {d: 2*time.Minute - time.Millisecond, want: "1m"}, {d: time.Hour - time.Millisecond, want: "59m"}, {d: time.Hour, want: "1h"}, {d: 2*time.Hour - time.Millisecond, want: "1h"}, {d: 24*time.Hour - time.Millisecond, want: "23h"}, {d: 24 * time.Hour, want: "1d"}, {d: 2*24*time.Hour - time.Millisecond, want: "1d"}, {d: 365*24*time.Hour - time.Millisecond, want: "364d"}, {d: 365 * 24 * time.Hour, want: "1y"}, {d: 2*365*24*time.Hour - time.Millisecond, want: "1y"}, {d: 2 * 365 * 24 * time.Hour, want: "2y"}, } for _, tt := range tests { t.Run(tt.d.String(), func(t *testing.T) { if got := ShortHumanDuration(tt.d); got != tt.want { t.Errorf("ShortHumanDuration() = %v, want %v", got, tt.want) } }) } } golang-k8s-apimachinery-0.29.0/pkg/util/errors/000077500000000000000000000000001453143165200213015ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/errors/doc.go000066400000000000000000000013121453143165200223720ustar00rootroot00000000000000/* Copyright 2015 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 errors implements various utility functions and types around errors. package errors // import "k8s.io/apimachinery/pkg/util/errors" golang-k8s-apimachinery-0.29.0/pkg/util/errors/errors.go000066400000000000000000000145171453143165200231540ustar00rootroot00000000000000/* Copyright 2015 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 errors import ( "errors" "fmt" "k8s.io/apimachinery/pkg/util/sets" ) // MessageCountMap contains occurrence for each error message. type MessageCountMap map[string]int // Aggregate represents an object that contains multiple errors, but does not // necessarily have singular semantic meaning. // The aggregate can be used with `errors.Is()` to check for the occurrence of // a specific error type. // Errors.As() is not supported, because the caller presumably cares about a // specific error of potentially multiple that match the given type. type Aggregate interface { error Errors() []error Is(error) bool } // NewAggregate converts a slice of errors into an Aggregate interface, which // is itself an implementation of the error interface. If the slice is empty, // this returns nil. // It will check if any of the element of input error list is nil, to avoid // nil pointer panic when call Error(). func NewAggregate(errlist []error) Aggregate { if len(errlist) == 0 { return nil } // In case of input error list contains nil var errs []error for _, e := range errlist { if e != nil { errs = append(errs, e) } } if len(errs) == 0 { return nil } return aggregate(errs) } // This helper implements the error and Errors interfaces. Keeping it private // prevents people from making an aggregate of 0 errors, which is not // an error, but does satisfy the error interface. type aggregate []error // Error is part of the error interface. func (agg aggregate) Error() string { if len(agg) == 0 { // This should never happen, really. return "" } if len(agg) == 1 { return agg[0].Error() } seenerrs := sets.NewString() result := "" agg.visit(func(err error) bool { msg := err.Error() if seenerrs.Has(msg) { return false } seenerrs.Insert(msg) if len(seenerrs) > 1 { result += ", " } result += msg return false }) if len(seenerrs) == 1 { return result } return "[" + result + "]" } func (agg aggregate) Is(target error) bool { return agg.visit(func(err error) bool { return errors.Is(err, target) }) } func (agg aggregate) visit(f func(err error) bool) bool { for _, err := range agg { switch err := err.(type) { case aggregate: if match := err.visit(f); match { return match } case Aggregate: for _, nestedErr := range err.Errors() { if match := f(nestedErr); match { return match } } default: if match := f(err); match { return match } } } return false } // Errors is part of the Aggregate interface. func (agg aggregate) Errors() []error { return []error(agg) } // Matcher is used to match errors. Returns true if the error matches. type Matcher func(error) bool // FilterOut removes all errors that match any of the matchers from the input // error. If the input is a singular error, only that error is tested. If the // input implements the Aggregate interface, the list of errors will be // processed recursively. // // This can be used, for example, to remove known-OK errors (such as io.EOF or // os.PathNotFound) from a list of errors. func FilterOut(err error, fns ...Matcher) error { if err == nil { return nil } if agg, ok := err.(Aggregate); ok { return NewAggregate(filterErrors(agg.Errors(), fns...)) } if !matchesError(err, fns...) { return err } return nil } // matchesError returns true if any Matcher returns true func matchesError(err error, fns ...Matcher) bool { for _, fn := range fns { if fn(err) { return true } } return false } // filterErrors returns any errors (or nested errors, if the list contains // nested Errors) for which all fns return false. If no errors // remain a nil list is returned. The resulting slice will have all // nested slices flattened as a side effect. func filterErrors(list []error, fns ...Matcher) []error { result := []error{} for _, err := range list { r := FilterOut(err, fns...) if r != nil { result = append(result, r) } } return result } // Flatten takes an Aggregate, which may hold other Aggregates in arbitrary // nesting, and flattens them all into a single Aggregate, recursively. func Flatten(agg Aggregate) Aggregate { result := []error{} if agg == nil { return nil } for _, err := range agg.Errors() { if a, ok := err.(Aggregate); ok { r := Flatten(a) if r != nil { result = append(result, r.Errors()...) } } else { if err != nil { result = append(result, err) } } } return NewAggregate(result) } // CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate { if m == nil { return nil } result := make([]error, 0, len(m)) for errStr, count := range m { var countStr string if count > 1 { countStr = fmt.Sprintf(" (repeated %v times)", count) } result = append(result, fmt.Errorf("%v%v", errStr, countStr)) } return NewAggregate(result) } // Reduce will return err or nil, if err is an Aggregate and only has one item, // the first item in the aggregate. func Reduce(err error) error { if agg, ok := err.(Aggregate); ok && err != nil { switch len(agg.Errors()) { case 1: return agg.Errors()[0] case 0: return nil } } return err } // AggregateGoroutines runs the provided functions in parallel, stuffing all // non-nil errors into the returned Aggregate. // Returns nil if all the functions complete successfully. func AggregateGoroutines(funcs ...func() error) Aggregate { errChan := make(chan error, len(funcs)) for _, f := range funcs { go func(f func() error) { errChan <- f() }(f) } errs := make([]error, 0) for i := 0; i < cap(errChan); i++ { if err := <-errChan; err != nil { errs = append(errs, err) } } return NewAggregate(errs) } // ErrPreconditionViolated is returned when the precondition is violated var ErrPreconditionViolated = errors.New("precondition is violated") golang-k8s-apimachinery-0.29.0/pkg/util/errors/errors_test.go000066400000000000000000000312351453143165200242070ustar00rootroot00000000000000/* Copyright 2015 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 errors import ( "errors" "fmt" "reflect" "sort" "testing" ) func TestEmptyAggregate(t *testing.T) { var slice []error var agg Aggregate var err error agg = NewAggregate(slice) if agg != nil { t.Errorf("expected nil, got %#v", agg) } err = NewAggregate(slice) if err != nil { t.Errorf("expected nil, got %#v", err) } // This is not normally possible, but pedantry demands I test it. agg = aggregate(slice) // empty aggregate if s := agg.Error(); s != "" { t.Errorf("expected empty string, got %q", s) } if s := agg.Errors(); len(s) != 0 { t.Errorf("expected empty slice, got %#v", s) } err = agg.(error) if s := err.Error(); s != "" { t.Errorf("expected empty string, got %q", s) } } func TestAggregateWithNil(t *testing.T) { var slice []error slice = []error{nil} var agg Aggregate var err error agg = NewAggregate(slice) if agg != nil { t.Errorf("expected nil, got %#v", agg) } err = NewAggregate(slice) if err != nil { t.Errorf("expected nil, got %#v", err) } // Append a non-nil error slice = append(slice, fmt.Errorf("err")) agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "err" { t.Errorf("expected 'err', got %q", s) } if s := agg.Errors(); len(s) != 1 { t.Errorf("expected one-element slice, got %#v", s) } if s := agg.Errors()[0].Error(); s != "err" { t.Errorf("expected 'err', got %q", s) } err = agg.(error) if err == nil { t.Errorf("expected non-nil") } if s := err.Error(); s != "err" { t.Errorf("expected 'err', got %q", s) } } func TestSingularAggregate(t *testing.T) { var slice = []error{fmt.Errorf("err")} var agg Aggregate var err error agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "err" { t.Errorf("expected 'err', got %q", s) } if s := agg.Errors(); len(s) != 1 { t.Errorf("expected one-element slice, got %#v", s) } if s := agg.Errors()[0].Error(); s != "err" { t.Errorf("expected 'err', got %q", s) } err = agg.(error) if err == nil { t.Errorf("expected non-nil") } if s := err.Error(); s != "err" { t.Errorf("expected 'err', got %q", s) } } func TestPluralAggregate(t *testing.T) { var slice = []error{fmt.Errorf("abc"), fmt.Errorf("123")} var agg Aggregate var err error agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "[abc, 123]" { t.Errorf("expected '[abc, 123]', got %q", s) } if s := agg.Errors(); len(s) != 2 { t.Errorf("expected two-elements slice, got %#v", s) } if s := agg.Errors()[0].Error(); s != "abc" { t.Errorf("expected '[abc, 123]', got %q", s) } err = agg.(error) if err == nil { t.Errorf("expected non-nil") } if s := err.Error(); s != "[abc, 123]" { t.Errorf("expected '[abc, 123]', got %q", s) } } func TestDedupeAggregate(t *testing.T) { var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc")} var agg Aggregate agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "abc" { t.Errorf("expected 'abc', got %q", s) } if s := agg.Errors(); len(s) != 2 { t.Errorf("expected two-elements slice, got %#v", s) } } func TestDedupePluralAggregate(t *testing.T) { var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), fmt.Errorf("123")} var agg Aggregate agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "[abc, 123]" { t.Errorf("expected '[abc, 123]', got %q", s) } if s := agg.Errors(); len(s) != 3 { t.Errorf("expected three-elements slice, got %#v", s) } } func TestFlattenAndDedupeAggregate(t *testing.T) { var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), NewAggregate([]error{fmt.Errorf("abc")})} var agg Aggregate agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "abc" { t.Errorf("expected 'abc', got %q", s) } if s := agg.Errors(); len(s) != 3 { t.Errorf("expected three-elements slice, got %#v", s) } } func TestFlattenAggregate(t *testing.T) { var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), NewAggregate([]error{fmt.Errorf("abc"), fmt.Errorf("def"), NewAggregate([]error{fmt.Errorf("def"), fmt.Errorf("ghi")})})} var agg Aggregate agg = NewAggregate(slice) if agg == nil { t.Errorf("expected non-nil") } if s := agg.Error(); s != "[abc, def, ghi]" { t.Errorf("expected '[abc, def, ghi]', got %q", s) } if s := agg.Errors(); len(s) != 3 { t.Errorf("expected three-elements slice, got %#v", s) } } func TestFilterOut(t *testing.T) { testCases := []struct { err error filter []Matcher expected error }{ { nil, []Matcher{}, nil, }, { aggregate{}, []Matcher{}, nil, }, { aggregate{fmt.Errorf("abc")}, []Matcher{}, aggregate{fmt.Errorf("abc")}, }, { aggregate{fmt.Errorf("abc")}, []Matcher{func(err error) bool { return false }}, aggregate{fmt.Errorf("abc")}, }, { aggregate{fmt.Errorf("abc")}, []Matcher{func(err error) bool { return true }}, nil, }, { aggregate{fmt.Errorf("abc")}, []Matcher{func(err error) bool { return false }, func(err error) bool { return false }}, aggregate{fmt.Errorf("abc")}, }, { aggregate{fmt.Errorf("abc")}, []Matcher{func(err error) bool { return false }, func(err error) bool { return true }}, nil, }, { aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, []Matcher{func(err error) bool { return err.Error() == "def" }}, aggregate{fmt.Errorf("abc"), fmt.Errorf("ghi")}, }, { aggregate{aggregate{fmt.Errorf("abc")}}, []Matcher{}, aggregate{aggregate{fmt.Errorf("abc")}}, }, { aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, []Matcher{}, aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, }, { aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, []Matcher{func(err error) bool { return err.Error() == "def" }}, aggregate{aggregate{fmt.Errorf("abc")}}, }, } for i, testCase := range testCases { err := FilterOut(testCase.err, testCase.filter...) if !reflect.DeepEqual(testCase.expected, err) { t.Errorf("%d: expected %v, got %v", i, testCase.expected, err) } } } func TestFlatten(t *testing.T) { testCases := []struct { agg Aggregate expected Aggregate }{ { nil, nil, }, { aggregate{}, nil, }, { aggregate{fmt.Errorf("abc")}, aggregate{fmt.Errorf("abc")}, }, { aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, }, { aggregate{aggregate{fmt.Errorf("abc")}}, aggregate{fmt.Errorf("abc")}, }, { aggregate{aggregate{aggregate{fmt.Errorf("abc")}}}, aggregate{fmt.Errorf("abc")}, }, { aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, aggregate{fmt.Errorf("abc"), fmt.Errorf("def")}, }, { aggregate{aggregate{aggregate{fmt.Errorf("abc")}, fmt.Errorf("def"), aggregate{fmt.Errorf("ghi")}}}, aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, }, } for i, testCase := range testCases { agg := Flatten(testCase.agg) if !reflect.DeepEqual(testCase.expected, agg) { t.Errorf("%d: expected %v, got %v", i, testCase.expected, agg) } } } func TestCreateAggregateFromMessageCountMap(t *testing.T) { testCases := []struct { name string mcm MessageCountMap expected Aggregate }{ { "input has single instance of one message", MessageCountMap{"abc": 1}, aggregate{fmt.Errorf("abc")}, }, { "input has multiple messages", MessageCountMap{"abc": 2, "ghi": 1}, aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")}, }, { "input has multiple messages", MessageCountMap{"ghi": 1, "abc": 2}, aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")}, }, } var expected, agg []error for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { if testCase.expected != nil { expected = testCase.expected.Errors() sort.Slice(expected, func(i, j int) bool { return expected[i].Error() < expected[j].Error() }) } if testCase.mcm != nil { agg = CreateAggregateFromMessageCountMap(testCase.mcm).Errors() sort.Slice(agg, func(i, j int) bool { return agg[i].Error() < agg[j].Error() }) } if !reflect.DeepEqual(expected, agg) { t.Errorf("expected %v, got %v", expected, agg) } }) } } func TestAggregateGoroutines(t *testing.T) { testCases := []struct { errs []error expected map[string]bool // can't compare directly to Aggregate due to non-deterministic ordering }{ { []error{}, nil, }, { []error{nil}, nil, }, { []error{nil, nil}, nil, }, { []error{fmt.Errorf("1")}, map[string]bool{"1": true}, }, { []error{fmt.Errorf("1"), nil}, map[string]bool{"1": true}, }, { []error{fmt.Errorf("1"), fmt.Errorf("267")}, map[string]bool{"1": true, "267": true}, }, { []error{fmt.Errorf("1"), nil, fmt.Errorf("1234")}, map[string]bool{"1": true, "1234": true}, }, { []error{nil, fmt.Errorf("1"), nil, fmt.Errorf("1234"), fmt.Errorf("22")}, map[string]bool{"1": true, "1234": true, "22": true}, }, } for i, testCase := range testCases { funcs := make([]func() error, len(testCase.errs)) for i := range testCase.errs { err := testCase.errs[i] funcs[i] = func() error { return err } } agg := AggregateGoroutines(funcs...) if agg == nil { if len(testCase.expected) > 0 { t.Errorf("%d: expected %v, got nil", i, testCase.expected) } continue } if len(agg.Errors()) != len(testCase.expected) { t.Errorf("%d: expected %d errors in aggregate, got %v", i, len(testCase.expected), agg) continue } for _, err := range agg.Errors() { if !testCase.expected[err.Error()] { t.Errorf("%d: expected %v, got aggregate containing %v", i, testCase.expected, err) } } } } type alwaysMatchingError struct{} func (_ alwaysMatchingError) Error() string { return "error" } func (_ alwaysMatchingError) Is(_ error) bool { return true } type someError struct{ msg string } func (se someError) Error() string { if se.msg != "" { return se.msg } return "err" } func TestAggregateWithErrorsIs(t *testing.T) { testCases := []struct { name string err error matchAgainst error expectMatch bool }{ { name: "no match", err: aggregate{errors.New("my-error"), errors.New("my-other-error")}, matchAgainst: fmt.Errorf("no entry %s", "here"), }, { name: "match via .Is()", err: aggregate{errors.New("forbidden"), alwaysMatchingError{}}, matchAgainst: errors.New("unauthorized"), expectMatch: true, }, { name: "match via equality", err: aggregate{errors.New("err"), someError{}}, matchAgainst: someError{}, expectMatch: true, }, { name: "match via nested aggregate", err: aggregate{errors.New("closed today"), aggregate{aggregate{someError{}}}}, matchAgainst: someError{}, expectMatch: true, }, { name: "match via wrapped aggregate", err: fmt.Errorf("wrap: %w", aggregate{errors.New("err"), someError{}}), matchAgainst: someError{}, expectMatch: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := errors.Is(tc.err, tc.matchAgainst) if result != tc.expectMatch { t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result) } }) } } type accessTrackingError struct { wasAccessed bool } func (accessTrackingError) Error() string { return "err" } func (ate *accessTrackingError) Is(_ error) bool { ate.wasAccessed = true return true } var _ error = &accessTrackingError{} func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) { errC := aggregate{&accessTrackingError{}, &accessTrackingError{}} _ = errors.Is(errC, &accessTrackingError{}) var numAccessed int for _, err := range errC { if ate := err.(*accessTrackingError); ate.wasAccessed { numAccessed++ } } if numAccessed != 1 { t.Errorf("expected exactly one error to get accessed, got %d", numAccessed) } } golang-k8s-apimachinery-0.29.0/pkg/util/framer/000077500000000000000000000000001453143165200212415ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/framer/framer.go000066400000000000000000000114461453143165200230520ustar00rootroot00000000000000/* Copyright 2015 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 framer implements simple frame decoding techniques for an io.ReadCloser package framer import ( "encoding/binary" "encoding/json" "io" ) type lengthDelimitedFrameWriter struct { w io.Writer h [4]byte } func NewLengthDelimitedFrameWriter(w io.Writer) io.Writer { return &lengthDelimitedFrameWriter{w: w} } // Write writes a single frame to the nested writer, prepending it with the length // in bytes of data (as a 4 byte, bigendian uint32). func (w *lengthDelimitedFrameWriter) Write(data []byte) (int, error) { binary.BigEndian.PutUint32(w.h[:], uint32(len(data))) n, err := w.w.Write(w.h[:]) if err != nil { return 0, err } if n != len(w.h) { return 0, io.ErrShortWrite } return w.w.Write(data) } type lengthDelimitedFrameReader struct { r io.ReadCloser remaining int } // NewLengthDelimitedFrameReader returns an io.Reader that will decode length-prefixed // frames off of a stream. // // The protocol is: // // stream: message ... // message: prefix body // prefix: 4 byte uint32 in BigEndian order, denotes length of body // body: bytes (0..prefix) // // If the buffer passed to Read is not long enough to contain an entire frame, io.ErrShortRead // will be returned along with the number of bytes read. func NewLengthDelimitedFrameReader(r io.ReadCloser) io.ReadCloser { return &lengthDelimitedFrameReader{r: r} } // Read attempts to read an entire frame into data. If that is not possible, io.ErrShortBuffer // is returned and subsequent calls will attempt to read the last frame. A frame is complete when // err is nil. func (r *lengthDelimitedFrameReader) Read(data []byte) (int, error) { if r.remaining <= 0 { header := [4]byte{} n, err := io.ReadAtLeast(r.r, header[:4], 4) if err != nil { return 0, err } if n != 4 { return 0, io.ErrUnexpectedEOF } frameLength := int(binary.BigEndian.Uint32(header[:])) r.remaining = frameLength } expect := r.remaining max := expect if max > len(data) { max = len(data) } n, err := io.ReadAtLeast(r.r, data[:max], int(max)) r.remaining -= n if err == io.ErrShortBuffer || r.remaining > 0 { return n, io.ErrShortBuffer } if err != nil { return n, err } if n != expect { return n, io.ErrUnexpectedEOF } return n, nil } func (r *lengthDelimitedFrameReader) Close() error { return r.r.Close() } type jsonFrameReader struct { r io.ReadCloser decoder *json.Decoder remaining []byte } // NewJSONFramedReader returns an io.Reader that will decode individual JSON objects off // of a wire. // // The boundaries between each frame are valid JSON objects. A JSON parsing error will terminate // the read. func NewJSONFramedReader(r io.ReadCloser) io.ReadCloser { return &jsonFrameReader{ r: r, decoder: json.NewDecoder(r), } } // ReadFrame decodes the next JSON object in the stream, or returns an error. The returned // byte slice will be modified the next time ReadFrame is invoked and should not be altered. func (r *jsonFrameReader) Read(data []byte) (int, error) { // Return whatever remaining data exists from an in progress frame if n := len(r.remaining); n > 0 { if n <= len(data) { //nolint:staticcheck // SA4006,SA4010 underlying array of data is modified here. data = append(data[0:0], r.remaining...) r.remaining = nil return n, nil } n = len(data) //nolint:staticcheck // SA4006,SA4010 underlying array of data is modified here. data = append(data[0:0], r.remaining[:n]...) r.remaining = r.remaining[n:] return n, io.ErrShortBuffer } // RawMessage#Unmarshal appends to data - we reset the slice down to 0 and will either see // data written to data, or be larger than data and a different array. n := len(data) m := json.RawMessage(data[:0]) if err := r.decoder.Decode(&m); err != nil { return 0, err } // If capacity of data is less than length of the message, decoder will allocate a new slice // and set m to it, which means we need to copy the partial result back into data and preserve // the remaining result for subsequent reads. if len(m) > n { //nolint:staticcheck // SA4006,SA4010 underlying array of data is modified here. data = append(data[0:0], m[:n]...) r.remaining = m[n:] return n, io.ErrShortBuffer } return len(m), nil } func (r *jsonFrameReader) Close() error { return r.r.Close() } golang-k8s-apimachinery-0.29.0/pkg/util/framer/framer_test.go000066400000000000000000000133231453143165200241050ustar00rootroot00000000000000/* Copyright 2016 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 framer import ( "bytes" "io" "testing" ) func TestRead(t *testing.T) { data := []byte{ 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x03, 0x05, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, } b := bytes.NewBuffer(data) r := NewLengthDelimitedFrameReader(io.NopCloser(b)) buf := make([]byte, 1) if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x01}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x02}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read the remaining frame buf = make([]byte, 2) if n, err := r.Read(buf); err != nil && n != 2 && bytes.Equal(buf, []byte{0x03, 0x04}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read with buffer equal to frame buf = make([]byte, 3) if n, err := r.Read(buf); err != nil && n != 3 && bytes.Equal(buf, []byte{0x05, 0x06, 0x07}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read empty frame buf = make([]byte, 3) if n, err := r.Read(buf); err != nil && n != 0 && bytes.Equal(buf, []byte{}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read with larger buffer than frame buf = make([]byte, 3) if n, err := r.Read(buf); err != nil && n != 1 && bytes.Equal(buf, []byte{0x08}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read EOF if n, err := r.Read(buf); err != io.EOF && n != 0 { t.Fatalf("unexpected: %v %d", err, n) } } func TestReadLarge(t *testing.T) { data := []byte{ 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x03, 0x05, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, } b := bytes.NewBuffer(data) r := NewLengthDelimitedFrameReader(io.NopCloser(b)) buf := make([]byte, 40) if n, err := r.Read(buf); err != nil && n != 4 && bytes.Equal(buf, []byte{0x01, 0x02, 0x03, 0x04}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } if n, err := r.Read(buf); err != nil && n != 3 && bytes.Equal(buf, []byte{0x05, 0x06, 0x7}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } if n, err := r.Read(buf); err != nil && n != 0 && bytes.Equal(buf, []byte{}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } if n, err := r.Read(buf); err != nil && n != 1 && bytes.Equal(buf, []byte{0x08}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read EOF if n, err := r.Read(buf); err != io.EOF && n != 0 { t.Fatalf("unexpected: %v %d", err, n) } } func TestReadInvalidFrame(t *testing.T) { data := []byte{ 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, } b := bytes.NewBuffer(data) r := NewLengthDelimitedFrameReader(io.NopCloser(b)) buf := make([]byte, 1) if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x01}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read the remaining frame buf = make([]byte, 3) if n, err := r.Read(buf); err != io.ErrUnexpectedEOF && n != 1 && bytes.Equal(buf, []byte{0x02}) { t.Fatalf("unexpected: %v %d %v", err, n, buf) } // read EOF if n, err := r.Read(buf); err != io.EOF && n != 0 { t.Fatalf("unexpected: %v %d", err, n) } } func TestJSONFrameReader(t *testing.T) { b := bytes.NewBufferString("{\"test\":true}\n1\n[\"a\"]") r := NewJSONFramedReader(io.NopCloser(b)) buf := make([]byte, 20) if n, err := r.Read(buf); err != nil || n != 13 || string(buf[:n]) != `{"test":true}` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `1` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != nil || n != 5 || string(buf[:n]) != `["a"]` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != io.EOF || n != 0 { t.Fatalf("unexpected: %v %d %q", err, n, buf) } } func TestJSONFrameReaderShortBuffer(t *testing.T) { b := bytes.NewBufferString("{\"test\":true}\n1\n[\"a\"]") r := NewJSONFramedReader(io.NopCloser(b)) buf := make([]byte, 3) if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `{"t` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `est` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `":t` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `rue` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `}` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `1` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `["a` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != nil || n != 2 || string(buf[:n]) != `"]` { t.Fatalf("unexpected: %v %d %q", err, n, buf) } if n, err := r.Read(buf); err != io.EOF || n != 0 { t.Fatalf("unexpected: %v %d %q", err, n, buf) } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/000077500000000000000000000000001453143165200221605ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/doc.go000066400000000000000000000013661453143165200232620ustar00rootroot00000000000000/* Copyright 2015 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 httpstream adds multiplexed streaming support to HTTP requests and // responses via connection upgrades. package httpstream // import "k8s.io/apimachinery/pkg/util/httpstream" golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/httpstream.go000066400000000000000000000146331453143165200247110ustar00rootroot00000000000000/* Copyright 2015 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 httpstream import ( "errors" "fmt" "io" "net/http" "strings" "time" ) const ( HeaderConnection = "Connection" HeaderUpgrade = "Upgrade" HeaderProtocolVersion = "X-Stream-Protocol-Version" HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions" ) // NewStreamHandler defines a function that is called when a new Stream is // received. If no error is returned, the Stream is accepted; otherwise, // the stream is rejected. After the reply frame has been sent, replySent is closed. type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error // NoOpNewStreamHandler is a stream handler that accepts a new stream and // performs no other logic. func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil } // Dialer knows how to open a streaming connection to a server. type Dialer interface { // Dial opens a streaming connection to a server using one of the protocols // specified (in order of most preferred to least preferred). Dial(protocols ...string) (Connection, string, error) } // UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade // HTTP requests to support multiplexed bidirectional streams. After RoundTrip() // is invoked, if the upgrade is successful, clients may retrieve the upgraded // connection by calling UpgradeRoundTripper.Connection(). type UpgradeRoundTripper interface { http.RoundTripper // NewConnection validates the response and creates a new Connection. NewConnection(resp *http.Response) (Connection, error) } // ResponseUpgrader knows how to upgrade HTTP requests and responses to // add streaming support to them. type ResponseUpgrader interface { // UpgradeResponse upgrades an HTTP response to one that supports multiplexed // streams. newStreamHandler will be called asynchronously whenever the // other end of the upgraded connection creates a new stream. UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection } // Connection represents an upgraded HTTP connection. type Connection interface { // CreateStream creates a new Stream with the supplied headers. CreateStream(headers http.Header) (Stream, error) // Close resets all streams and closes the connection. Close() error // CloseChan returns a channel that is closed when the underlying connection is closed. CloseChan() <-chan bool // SetIdleTimeout sets the amount of time the connection may remain idle before // it is automatically closed. SetIdleTimeout(timeout time.Duration) // RemoveStreams can be used to remove a set of streams from the Connection. RemoveStreams(streams ...Stream) } // Stream represents a bidirectional communications channel that is part of an // upgraded connection. type Stream interface { io.ReadWriteCloser // Reset closes both directions of the stream, indicating that neither client // or server can use it any more. Reset() error // Headers returns the headers used to create the stream. Headers() http.Header // Identifier returns the stream's ID. Identifier() uint32 } // UpgradeFailureError encapsulates the cause for why the streaming // upgrade request failed. Implements error interface. type UpgradeFailureError struct { Cause error } func (u *UpgradeFailureError) Error() string { return fmt.Sprintf("unable to upgrade streaming request: %s", u.Cause) } // IsUpgradeFailure returns true if the passed error is (or wrapped error contains) // the UpgradeFailureError. func IsUpgradeFailure(err error) bool { if err == nil { return false } var upgradeErr *UpgradeFailureError return errors.As(err, &upgradeErr) } // IsUpgradeRequest returns true if the given request is a connection upgrade request func IsUpgradeRequest(req *http.Request) bool { for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] { if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) { return true } } return false } func negotiateProtocol(clientProtocols, serverProtocols []string) string { for i := range clientProtocols { for j := range serverProtocols { if clientProtocols[i] == serverProtocols[j] { return clientProtocols[i] } } } return "" } func commaSeparatedHeaderValues(header []string) []string { var parsedClientProtocols []string for i := range header { for _, clientProtocol := range strings.Split(header[i], ",") { if proto := strings.Trim(clientProtocol, " "); len(proto) > 0 { parsedClientProtocols = append(parsedClientProtocols, proto) } } } return parsedClientProtocols } // Handshake performs a subprotocol negotiation. If the client did request a // subprotocol, Handshake will select the first common value found in // serverProtocols. If a match is found, Handshake adds a response header // indicating the chosen subprotocol. If no match is found, HTTP forbidden is // returned, along with a response header containing the list of protocols the // server can accept. func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) { clientProtocols := commaSeparatedHeaderValues(req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)]) if len(clientProtocols) == 0 { return "", fmt.Errorf("unable to upgrade: %s is required", HeaderProtocolVersion) } if len(serverProtocols) == 0 { panic(fmt.Errorf("unable to upgrade: serverProtocols is required")) } negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols) if len(negotiatedProtocol) == 0 { for i := range serverProtocols { w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i]) } err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols) http.Error(w, err.Error(), http.StatusForbidden) return "", err } w.Header().Add(HeaderProtocolVersion, negotiatedProtocol) return negotiatedProtocol, nil } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/httpstream_test.go000066400000000000000000000107331453143165200257450ustar00rootroot00000000000000/* Copyright 2015 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 httpstream import ( "errors" "fmt" "net/http" "reflect" "testing" ) type responseWriter struct { header http.Header statusCode *int } func newResponseWriter() *responseWriter { return &responseWriter{ header: make(http.Header), } } func (r *responseWriter) Header() http.Header { return r.header } func (r *responseWriter) WriteHeader(code int) { r.statusCode = &code } func (r *responseWriter) Write([]byte) (int, error) { return 0, nil } func TestHandshake(t *testing.T) { tests := map[string]struct { clientProtocols []string serverProtocols []string expectedProtocol string expectError bool }{ "no common protocol": { clientProtocols: []string{"c"}, serverProtocols: []string{"a", "b"}, expectedProtocol: "", expectError: true, }, "no common protocol with comma separated list": { clientProtocols: []string{"c, d"}, serverProtocols: []string{"a", "b"}, expectedProtocol: "", expectError: true, }, "common protocol": { clientProtocols: []string{"b"}, serverProtocols: []string{"a", "b"}, expectedProtocol: "b", }, "common protocol with comma separated list": { clientProtocols: []string{"b, c"}, serverProtocols: []string{"a", "b"}, expectedProtocol: "b", }, } for name, test := range tests { req, err := http.NewRequest("GET", "http://www.example.com/", nil) if err != nil { t.Fatalf("%s: error creating request: %v", name, err) } for _, p := range test.clientProtocols { req.Header.Add(HeaderProtocolVersion, p) } w := newResponseWriter() negotiated, err := Handshake(req, w, test.serverProtocols) // verify negotiated protocol if e, a := test.expectedProtocol, negotiated; e != a { t.Errorf("%s: protocol: expected %q, got %q", name, e, a) } if test.expectError { if err == nil { t.Errorf("%s: expected error but did not get one", name) } if w.statusCode == nil { t.Errorf("%s: expected w.statusCode to be set", name) } else if e, a := http.StatusForbidden, *w.statusCode; e != a { t.Errorf("%s: w.statusCode: expected %d, got %d", name, e, a) } if e, a := test.serverProtocols, w.Header()[HeaderAcceptedProtocolVersions]; !reflect.DeepEqual(e, a) { t.Errorf("%s: accepted server protocols: expected %v, got %v", name, e, a) } continue } if !test.expectError && err != nil { t.Errorf("%s: unexpected error: %v", name, err) continue } if w.statusCode != nil { t.Errorf("%s: unexpected non-nil w.statusCode: %d", name, w.statusCode) } if len(test.expectedProtocol) == 0 { if len(w.Header()[HeaderProtocolVersion]) > 0 { t.Errorf("%s: unexpected protocol version response header: %s", name, w.Header()[HeaderProtocolVersion]) } continue } // verify response headers if e, a := []string{test.expectedProtocol}, w.Header()[HeaderProtocolVersion]; !reflect.DeepEqual(e, a) { t.Errorf("%s: protocol response header: expected %v, got %v", name, e, a) } } } func TestIsUpgradeFailureError(t *testing.T) { testCases := map[string]struct { err error expected bool }{ "nil error should return false": { err: nil, expected: false, }, "Non-upgrade error should return false": { err: fmt.Errorf("this is not an upgrade error"), expected: false, }, "UpgradeFailure error should return true": { err: &UpgradeFailureError{}, expected: true, }, "Wrapped Non-UpgradeFailure error should return false": { err: fmt.Errorf("%s: %w", "first error", errors.New("Non-upgrade error")), expected: false, }, "Wrapped UpgradeFailure error should return true": { err: fmt.Errorf("%s: %w", "first error", &UpgradeFailureError{}), expected: true, }, } for name, test := range testCases { t.Run(name, func(t *testing.T) { actual := IsUpgradeFailure(test.err) if test.expected != actual { t.Errorf("expected upgrade failure %t, got %t", test.expected, actual) } }) } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/000077500000000000000000000000001453143165200231375ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/connection.go000066400000000000000000000153151453143165200256320ustar00rootroot00000000000000/* Copyright 2015 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 spdy import ( "net" "net/http" "sync" "time" "github.com/moby/spdystream" "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/klog/v2" ) // connection maintains state about a spdystream.Connection and its associated // streams. type connection struct { conn *spdystream.Connection streams map[uint32]httpstream.Stream streamLock sync.Mutex newStreamHandler httpstream.NewStreamHandler ping func() (time.Duration, error) } // NewClientConnection creates a new SPDY client connection. func NewClientConnection(conn net.Conn) (httpstream.Connection, error) { return NewClientConnectionWithPings(conn, 0) } // NewClientConnectionWithPings creates a new SPDY client connection. // // If pingPeriod is non-zero, a background goroutine will send periodic Ping // frames to the server. Use this to keep idle connections through certain load // balancers alive longer. func NewClientConnectionWithPings(conn net.Conn, pingPeriod time.Duration) (httpstream.Connection, error) { spdyConn, err := spdystream.NewConnection(conn, false) if err != nil { defer conn.Close() return nil, err } return newConnection(spdyConn, httpstream.NoOpNewStreamHandler, pingPeriod, spdyConn.Ping), nil } // NewServerConnection creates a new SPDY server connection. newStreamHandler // will be invoked when the server receives a newly created stream from the // client. func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) { return NewServerConnectionWithPings(conn, newStreamHandler, 0) } // NewServerConnectionWithPings creates a new SPDY server connection. // newStreamHandler will be invoked when the server receives a newly created // stream from the client. // // If pingPeriod is non-zero, a background goroutine will send periodic Ping // frames to the server. Use this to keep idle connections through certain load // balancers alive longer. func NewServerConnectionWithPings(conn net.Conn, newStreamHandler httpstream.NewStreamHandler, pingPeriod time.Duration) (httpstream.Connection, error) { spdyConn, err := spdystream.NewConnection(conn, true) if err != nil { defer conn.Close() return nil, err } return newConnection(spdyConn, newStreamHandler, pingPeriod, spdyConn.Ping), nil } // newConnection returns a new connection wrapping conn. newStreamHandler // will be invoked when the server receives a newly created stream from the // client. func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler, pingPeriod time.Duration, pingFn func() (time.Duration, error)) httpstream.Connection { c := &connection{ conn: conn, newStreamHandler: newStreamHandler, ping: pingFn, streams: make(map[uint32]httpstream.Stream), } go conn.Serve(c.newSpdyStream) if pingPeriod > 0 && pingFn != nil { go c.sendPings(pingPeriod) } return c } // createStreamResponseTimeout indicates how long to wait for the other side to // acknowledge the new stream before timing out. const createStreamResponseTimeout = 30 * time.Second // Close first sends a reset for all of the connection's streams, and then // closes the underlying spdystream.Connection. func (c *connection) Close() error { c.streamLock.Lock() for _, s := range c.streams { // calling Reset instead of Close ensures that all streams are fully torn down s.Reset() } c.streams = make(map[uint32]httpstream.Stream, 0) c.streamLock.Unlock() // now that all streams are fully torn down, it's safe to call close on the underlying connection, // which should be able to terminate immediately at this point, instead of waiting for any // remaining graceful stream termination. return c.conn.Close() } // RemoveStreams can be used to removes a set of streams from the Connection. func (c *connection) RemoveStreams(streams ...httpstream.Stream) { c.streamLock.Lock() for _, stream := range streams { // It may be possible that the provided stream is nil if timed out. if stream != nil { delete(c.streams, stream.Identifier()) } } c.streamLock.Unlock() } // CreateStream creates a new stream with the specified headers and registers // it with the connection. func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) { stream, err := c.conn.CreateStream(headers, nil, false) if err != nil { return nil, err } if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil { return nil, err } c.registerStream(stream) return stream, nil } // registerStream adds the stream s to the connection's list of streams that // it owns. func (c *connection) registerStream(s httpstream.Stream) { c.streamLock.Lock() c.streams[s.Identifier()] = s c.streamLock.Unlock() } // CloseChan returns a channel that, when closed, indicates that the underlying // spdystream.Connection has been closed. func (c *connection) CloseChan() <-chan bool { return c.conn.CloseChan() } // newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve. // It calls connection's newStreamHandler, giving it the opportunity to accept or reject // the stream. If newStreamHandler returns an error, the stream is rejected. If not, the // stream is accepted and registered with the connection. func (c *connection) newSpdyStream(stream *spdystream.Stream) { replySent := make(chan struct{}) err := c.newStreamHandler(stream, replySent) rejectStream := (err != nil) if rejectStream { klog.Warningf("Stream rejected: %v", err) stream.Reset() return } c.registerStream(stream) stream.SendReply(http.Header{}, rejectStream) close(replySent) } // SetIdleTimeout sets the amount of time the connection may remain idle before // it is automatically closed. func (c *connection) SetIdleTimeout(timeout time.Duration) { c.conn.SetIdleTimeout(timeout) } func (c *connection) sendPings(period time.Duration) { t := time.NewTicker(period) defer t.Stop() for { select { case <-c.conn.CloseChan(): return case <-t.C: } if _, err := c.ping(); err != nil { klog.V(3).Infof("SPDY Ping failed: %v", err) // Continue, in case this is a transient failure. // c.conn.CloseChan above will tell us when the connection is // actually closed. } } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/connection_test.go000066400000000000000000000176141453143165200266750ustar00rootroot00000000000000/* Copyright 2016 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 spdy import ( "fmt" "io" "net" "net/http" "sync" "sync/atomic" "testing" "time" "github.com/moby/spdystream" "k8s.io/apimachinery/pkg/util/httpstream" ) func runProxy(t *testing.T, backendUrl string, proxyUrl chan<- string, proxyDone chan<- struct{}, errCh chan<- error) { listener, err := net.Listen("tcp4", "localhost:0") if err != nil { errCh <- err return } defer listener.Close() proxyUrl <- listener.Addr().String() clientConn, err := listener.Accept() if err != nil { t.Errorf("proxy: error accepting client connection: %v", err) return } backendConn, err := net.Dial("tcp4", backendUrl) if err != nil { t.Errorf("proxy: error dialing backend: %v", err) return } defer backendConn.Close() var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() io.Copy(backendConn, clientConn) }() go func() { defer wg.Done() io.Copy(clientConn, backendConn) }() wg.Wait() proxyDone <- struct{}{} } func runServer(t *testing.T, backendUrl chan<- string, serverDone chan<- struct{}, errCh chan<- error) { listener, err := net.Listen("tcp4", "localhost:0") if err != nil { errCh <- err return } defer listener.Close() backendUrl <- listener.Addr().String() conn, err := listener.Accept() if err != nil { t.Errorf("server: error accepting connection: %v", err) return } streamChan := make(chan httpstream.Stream) replySentChan := make(chan (<-chan struct{})) spdyConn, err := NewServerConnection(conn, func(stream httpstream.Stream, replySent <-chan struct{}) error { streamChan <- stream replySentChan <- replySent return nil }) if err != nil { t.Errorf("server: error creating spdy connection: %v", err) return } stream := <-streamChan replySent := <-replySentChan <-replySent buf := make([]byte, 1) _, err = stream.Read(buf) if err != io.EOF { t.Errorf("server: unexpected read error: %v", err) return } <-spdyConn.CloseChan() raw := spdyConn.(*connection).conn if err := raw.Wait(15 * time.Second); err != nil { t.Errorf("server: timed out waiting for connection closure: %v", err) } serverDone <- struct{}{} } func TestConnectionCloseIsImmediateThroughAProxy(t *testing.T) { errCh := make(chan error) serverDone := make(chan struct{}, 1) backendUrlChan := make(chan string) go runServer(t, backendUrlChan, serverDone, errCh) var backendUrl string select { case err := <-errCh: t.Fatalf("server: error listening: %v", err) case backendUrl = <-backendUrlChan: } proxyDone := make(chan struct{}, 1) proxyUrlChan := make(chan string) go runProxy(t, backendUrl, proxyUrlChan, proxyDone, errCh) var proxyUrl string select { case err := <-errCh: t.Fatalf("error listening: %v", err) case proxyUrl = <-proxyUrlChan: } conn, err := net.Dial("tcp4", proxyUrl) if err != nil { t.Fatalf("client: error connecting to proxy: %v", err) } spdyConn, err := NewClientConnection(conn) if err != nil { t.Fatalf("client: error creating spdy connection: %v", err) } if _, err := spdyConn.CreateStream(http.Header{}); err != nil { t.Fatalf("client: error creating stream: %v", err) } spdyConn.Close() raw := spdyConn.(*connection).conn if err := raw.Wait(15 * time.Second); err != nil { t.Fatalf("client: timed out waiting for connection closure: %v", err) } expired := time.NewTimer(15 * time.Second) defer expired.Stop() i := 0 for { select { case <-expired.C: t.Fatalf("timed out waiting for proxy and/or server closure") case <-serverDone: i++ case <-proxyDone: i++ } if i == 2 { break } } } func TestConnectionPings(t *testing.T) { const pingPeriod = 10 * time.Millisecond timeout := time.After(10 * time.Second) // Set up server connection. listener, err := net.Listen("tcp4", "localhost:0") if err != nil { t.Fatal(err) } defer listener.Close() srvErr := make(chan error, 1) go func() { defer close(srvErr) srvConn, err := listener.Accept() if err != nil { srvErr <- fmt.Errorf("server: error accepting connection: %v", err) return } defer srvConn.Close() spdyConn, err := spdystream.NewConnection(srvConn, true) if err != nil { srvErr <- fmt.Errorf("server: error creating spdy connection: %v", err) return } var pingsSent int64 srvSPDYConn := newConnection( spdyConn, func(stream httpstream.Stream, replySent <-chan struct{}) error { // Echo all the incoming data. go io.Copy(stream, stream) return nil }, pingPeriod, func() (time.Duration, error) { atomic.AddInt64(&pingsSent, 1) return 0, nil }) defer srvSPDYConn.Close() // Wait for the connection to close, to prevent defers from running // early. select { case <-timeout: srvErr <- fmt.Errorf("server: timeout waiting for connection to close") return case <-srvSPDYConn.CloseChan(): } // Count pings sent by the server. gotPings := atomic.LoadInt64(&pingsSent) if gotPings < 1 { t.Errorf("server: failed to send any pings (check logs)") } }() // Set up client connection. clConn, err := net.Dial("tcp4", listener.Addr().String()) if err != nil { t.Fatalf("client: error connecting to proxy: %v", err) } defer clConn.Close() clSPDYConn, err := NewClientConnection(clConn) if err != nil { t.Fatalf("client: error creating spdy connection: %v", err) } defer clSPDYConn.Close() start := time.Now() clSPDYStream, err := clSPDYConn.CreateStream(http.Header{}) if err != nil { t.Fatalf("client: error creating stream: %v", err) } defer clSPDYStream.Close() // Send some data both ways, to make sure pings don't interfere with // regular messages. in := "foo" if _, err := fmt.Fprintln(clSPDYStream, in); err != nil { t.Fatalf("client: error writing data to stream: %v", err) } var out string if _, err := fmt.Fscanln(clSPDYStream, &out); err != nil { t.Fatalf("client: error reading data from stream: %v", err) } if in != out { t.Errorf("client: received data doesn't match sent data: got %q, want %q", out, in) } // Wait for at least 2 pings to get sent each way before closing the // connection. elapsed := time.Since(start) if elapsed < 3*pingPeriod { time.Sleep(3*pingPeriod - elapsed) } clSPDYConn.Close() select { case err, ok := <-srvErr: if ok && err != nil { t.Error(err) } case <-timeout: t.Errorf("timed out waiting for server to exit") } } type fakeStream struct{ id uint32 } func (*fakeStream) Read(p []byte) (int, error) { return 0, nil } func (*fakeStream) Write(p []byte) (int, error) { return 0, nil } func (*fakeStream) Close() error { return nil } func (*fakeStream) Reset() error { return nil } func (*fakeStream) Headers() http.Header { return nil } func (f *fakeStream) Identifier() uint32 { return f.id } func TestConnectionRemoveStreams(t *testing.T) { c := &connection{streams: make(map[uint32]httpstream.Stream)} stream0 := &fakeStream{id: 0} stream1 := &fakeStream{id: 1} stream2 := &fakeStream{id: 2} c.registerStream(stream0) c.registerStream(stream1) if len(c.streams) != 2 { t.Fatalf("should have two streams, has %d", len(c.streams)) } // not exists c.RemoveStreams(stream2) if len(c.streams) != 2 { t.Fatalf("should have two streams, has %d", len(c.streams)) } // remove all existing c.RemoveStreams(stream0, stream1) // remove nil stream should not crash c.RemoveStreams(nil) if len(c.streams) != 0 { t.Fatalf("should not have any streams, has %d", len(c.streams)) } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/roundtripper.go000066400000000000000000000313571453143165200262340ustar00rootroot00000000000000/* Copyright 2015 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 spdy import ( "bufio" "context" "crypto/tls" "encoding/base64" "errors" "fmt" "io" "net" "net/http" "net/http/httputil" "net/url" "strings" "time" "golang.org/x/net/proxy" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/httpstream" utilnet "k8s.io/apimachinery/pkg/util/net" apiproxy "k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apimachinery/third_party/forked/golang/netutil" ) // SpdyRoundTripper knows how to upgrade an HTTP request to one that supports // multiplexed streams. After RoundTrip() is invoked, Conn will be set // and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface. type SpdyRoundTripper struct { //tlsConfig holds the TLS configuration settings to use when connecting //to the remote server. tlsConfig *tls.Config /* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper must be safe for use by multiple concurrent goroutines. If this is absolutely necessary, we could keep a map from http.Request to net.Conn. In practice, a client will create an http.Client, set the transport to a new insteace of SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue. */ // conn is the underlying network connection to the remote server. conn net.Conn // Dialer is the dialer used to connect. Used if non-nil. Dialer *net.Dialer // proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment // Used primarily for mocking the proxy discovery in tests. proxier func(req *http.Request) (*url.URL, error) // pingPeriod is a period for sending Ping frames over established // connections. pingPeriod time.Duration // upgradeTransport is an optional substitute for dialing if present. This field is // mutually exclusive with the "tlsConfig", "Dialer", and "proxier". upgradeTransport http.RoundTripper } var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{} var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{} var _ utilnet.Dialer = &SpdyRoundTripper{} // NewRoundTripper creates a new SpdyRoundTripper that will use the specified // tlsConfig. func NewRoundTripper(tlsConfig *tls.Config) (*SpdyRoundTripper, error) { return NewRoundTripperWithConfig(RoundTripperConfig{ TLS: tlsConfig, UpgradeTransport: nil, }) } // NewRoundTripperWithProxy creates a new SpdyRoundTripper that will use the // specified tlsConfig and proxy func. func NewRoundTripperWithProxy(tlsConfig *tls.Config, proxier func(*http.Request) (*url.URL, error)) (*SpdyRoundTripper, error) { return NewRoundTripperWithConfig(RoundTripperConfig{ TLS: tlsConfig, Proxier: proxier, UpgradeTransport: nil, }) } // NewRoundTripperWithConfig creates a new SpdyRoundTripper with the specified // configuration. Returns an error if the SpdyRoundTripper is misconfigured. func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error) { // Process UpgradeTransport, which is mutually exclusive to TLSConfig and Proxier. if cfg.UpgradeTransport != nil { if cfg.TLS != nil || cfg.Proxier != nil { return nil, fmt.Errorf("SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier") } tlsConfig, err := utilnet.TLSClientConfig(cfg.UpgradeTransport) if err != nil { return nil, fmt.Errorf("SpdyRoundTripper: Unable to retrieve TLSConfig from UpgradeTransport: %v", err) } cfg.TLS = tlsConfig } if cfg.Proxier == nil { cfg.Proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) } return &SpdyRoundTripper{ tlsConfig: cfg.TLS, proxier: cfg.Proxier, pingPeriod: cfg.PingPeriod, upgradeTransport: cfg.UpgradeTransport, }, nil } // RoundTripperConfig is a set of options for an SpdyRoundTripper. type RoundTripperConfig struct { // TLS configuration used by the round tripper if UpgradeTransport not present. TLS *tls.Config // Proxier is a proxy function invoked on each request. Optional. Proxier func(*http.Request) (*url.URL, error) // PingPeriod is a period for sending SPDY Pings on the connection. // Optional. PingPeriod time.Duration // UpgradeTransport is a subtitute transport used for dialing. If set, // this field will be used instead of "TLS" and "Proxier" for connection creation. // Optional. UpgradeTransport http.RoundTripper } // TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during // proxying with a spdy roundtripper. func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config { return s.tlsConfig } // Dial implements k8s.io/apimachinery/pkg/util/net.Dialer. func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { var conn net.Conn var err error if s.upgradeTransport != nil { conn, err = apiproxy.DialURL(req.Context(), req.URL, s.upgradeTransport) } else { conn, err = s.dial(req) } if err != nil { return nil, err } if err := req.Write(conn); err != nil { conn.Close() return nil, err } return conn, nil } // dial dials the host specified by req, using TLS if appropriate, optionally // using a proxy server if one is configured via environment variables. func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { proxyURL, err := s.proxier(req) if err != nil { return nil, err } if proxyURL == nil { return s.dialWithoutProxy(req.Context(), req.URL) } switch proxyURL.Scheme { case "socks5": return s.dialWithSocks5Proxy(req, proxyURL) case "https", "http", "": return s.dialWithHttpProxy(req, proxyURL) } return nil, fmt.Errorf("proxy URL scheme not supported: %s", proxyURL.Scheme) } // dialWithHttpProxy dials the host specified by url through an http or an https proxy. func (s *SpdyRoundTripper) dialWithHttpProxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) { // ensure we use a canonical host with proxyReq targetHost := netutil.CanonicalAddr(req.URL) // proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support proxyReq := http.Request{ Method: "CONNECT", URL: &url.URL{}, Host: targetHost, } proxyReq = *proxyReq.WithContext(req.Context()) if pa := s.proxyAuth(proxyURL); pa != "" { proxyReq.Header = http.Header{} proxyReq.Header.Set("Proxy-Authorization", pa) } proxyDialConn, err := s.dialWithoutProxy(proxyReq.Context(), proxyURL) if err != nil { return nil, err } //nolint:staticcheck // SA1019 ignore deprecated httputil.NewProxyClientConn proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil) response, err := proxyClientConn.Do(&proxyReq) //nolint:staticcheck // SA1019 ignore deprecated httputil.ErrPersistEOF: it might be // returned from the invocation of proxyClientConn.Do if err != nil && err != httputil.ErrPersistEOF { return nil, err } if response != nil && response.StatusCode >= 300 || response.StatusCode < 200 { return nil, fmt.Errorf("CONNECT request to %s returned response: %s", proxyURL.Redacted(), response.Status) } rwc, _ := proxyClientConn.Hijack() if req.URL.Scheme == "https" { return s.tlsConn(proxyReq.Context(), rwc, targetHost) } return rwc, nil } // dialWithSocks5Proxy dials the host specified by url through a socks5 proxy. func (s *SpdyRoundTripper) dialWithSocks5Proxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) { // ensure we use a canonical host with proxyReq targetHost := netutil.CanonicalAddr(req.URL) proxyDialAddr := netutil.CanonicalAddr(proxyURL) var auth *proxy.Auth if proxyURL.User != nil { pass, _ := proxyURL.User.Password() auth = &proxy.Auth{ User: proxyURL.User.Username(), Password: pass, } } dialer := s.Dialer if dialer == nil { dialer = &net.Dialer{ Timeout: 30 * time.Second, } } proxyDialer, err := proxy.SOCKS5("tcp", proxyDialAddr, auth, dialer) if err != nil { return nil, err } // According to the implementation of proxy.SOCKS5, the type assertion will always succeed contextDialer, ok := proxyDialer.(proxy.ContextDialer) if !ok { return nil, errors.New("SOCKS5 Dialer must implement ContextDialer") } proxyDialConn, err := contextDialer.DialContext(req.Context(), "tcp", targetHost) if err != nil { return nil, err } if req.URL.Scheme == "https" { return s.tlsConn(req.Context(), proxyDialConn, targetHost) } return proxyDialConn, nil } // tlsConn returns a TLS client side connection using rwc as the underlying transport. func (s *SpdyRoundTripper) tlsConn(ctx context.Context, rwc net.Conn, targetHost string) (net.Conn, error) { host, _, err := net.SplitHostPort(targetHost) if err != nil { return nil, err } tlsConfig := s.tlsConfig switch { case tlsConfig == nil: tlsConfig = &tls.Config{ServerName: host} case len(tlsConfig.ServerName) == 0: tlsConfig = tlsConfig.Clone() tlsConfig.ServerName = host } tlsConn := tls.Client(rwc, tlsConfig) if err := tlsConn.HandshakeContext(ctx); err != nil { tlsConn.Close() return nil, err } return tlsConn, nil } // dialWithoutProxy dials the host specified by url, using TLS if appropriate. func (s *SpdyRoundTripper) dialWithoutProxy(ctx context.Context, url *url.URL) (net.Conn, error) { dialAddr := netutil.CanonicalAddr(url) dialer := s.Dialer if dialer == nil { dialer = &net.Dialer{} } if url.Scheme == "http" { return dialer.DialContext(ctx, "tcp", dialAddr) } tlsDialer := tls.Dialer{ NetDialer: dialer, Config: s.tlsConfig, } return tlsDialer.DialContext(ctx, "tcp", dialAddr) } // proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string { if proxyURL == nil || proxyURL.User == nil { return "" } username := proxyURL.User.Username() password, _ := proxyURL.User.Password() auth := username + ":" + password return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) } // RoundTrip executes the Request and upgrades it. After a successful upgrade, // clients may call SpdyRoundTripper.Connection() to retrieve the upgraded // connection. func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { req = utilnet.CloneRequest(req) req.Header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade) req.Header.Add(httpstream.HeaderUpgrade, HeaderSpdy31) conn, err := s.Dial(req) if err != nil { return nil, err } responseReader := bufio.NewReader(conn) resp, err := http.ReadResponse(responseReader, nil) if err != nil { conn.Close() return nil, err } s.conn = conn return resp, nil } // NewConnection validates the upgrade response, creating and returning a new // httpstream.Connection if there were no errors. func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) { connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection)) upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade)) if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) { defer resp.Body.Close() responseError := "" responseErrorBytes, err := io.ReadAll(resp.Body) if err != nil { responseError = "unable to read error from server response" } else { // TODO: I don't belong here, I should be abstracted from this class if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil { if status, ok := obj.(*metav1.Status); ok { return nil, &apierrors.StatusError{ErrStatus: *status} } } responseError = string(responseErrorBytes) responseError = strings.TrimSpace(responseError) } return nil, fmt.Errorf("unable to upgrade connection: %s", responseError) } return NewClientConnectionWithPings(s.conn, s.pingPeriod) } // statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection var statusScheme = runtime.NewScheme() // ParameterCodec knows about query parameters used with the meta v1 API spec. var statusCodecs = serializer.NewCodecFactory(statusScheme) func init() { statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion, &metav1.Status{}, ) } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/roundtripper_test.go000066400000000000000000001013651453143165200272700ustar00rootroot00000000000000/* Copyright 2015 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 spdy import ( "context" "crypto/tls" "crypto/x509" "io" "net" "net/http" "net/http/httptest" "net/url" "reflect" "strconv" "strings" "testing" "github.com/armon/go-socks5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/util/httpstream" utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" ) type serverHandlerConfig struct { shouldError bool statusCode int connectionHeader string upgradeHeader string } func serverHandler(t *testing.T, config serverHandlerConfig) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { if config.shouldError { if e, a := httpstream.HeaderUpgrade, req.Header.Get(httpstream.HeaderConnection); e != a { t.Fatalf("expected connection=upgrade header, got '%s", a) } w.Header().Set(httpstream.HeaderConnection, config.connectionHeader) w.Header().Set(httpstream.HeaderUpgrade, config.upgradeHeader) w.WriteHeader(config.statusCode) return } streamCh := make(chan httpstream.Stream) responseUpgrader := NewResponseUpgrader() spdyConn := responseUpgrader.UpgradeResponse(w, req, func(s httpstream.Stream, replySent <-chan struct{}) error { streamCh <- s return nil }) if spdyConn == nil { t.Fatal("unexpected nil spdyConn") } defer spdyConn.Close() stream := <-streamCh io.Copy(stream, stream) } } type serverFunc func(http.Handler) *httptest.Server func httpsServerInvalidHostname(t *testing.T) serverFunc { return func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(exampleCert, exampleKey) if err != nil { t.Errorf("https (invalid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts } } func httpsServerValidHostname(t *testing.T) serverFunc { return func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { t.Errorf("https (valid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts } } func localhostCertPool(t *testing.T) *x509.CertPool { localhostPool := x509.NewCertPool() if !localhostPool.AppendCertsFromPEM(localhostCert) { t.Errorf("error setting up localhostCert pool") } return localhostPool } // be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly. func TestRoundTripAndNewConnection(t *testing.T) { localhostPool := localhostCertPool(t) testCases := map[string]struct { serverFunc func(http.Handler) *httptest.Server proxyServerFunc func(http.Handler) *httptest.Server proxyAuth *url.Userinfo clientTLS *tls.Config serverConnectionHeader string serverUpgradeHeader string serverStatusCode int shouldError bool }{ "no headers": { serverFunc: httptest.NewServer, serverConnectionHeader: "", serverUpgradeHeader: "", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "no upgrade header": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "no connection header": { serverFunc: httptest.NewServer, serverConnectionHeader: "", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "no switching protocol status code": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusForbidden, shouldError: true, }, "http": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "https (invalid hostname + InsecureSkipVerify)": { serverFunc: httpsServerInvalidHostname(t), clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "https (invalid hostname + hostname verification)": { serverFunc: httpsServerInvalidHostname(t), clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "https (valid hostname + RootCAs)": { serverFunc: httpsServerValidHostname(t), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied http->http": { serverFunc: httptest.NewServer, proxyServerFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https (invalid hostname + InsecureSkipVerify) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerInvalidHostname(t), clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https with auth (invalid hostname + InsecureSkipVerify) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerInvalidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https (invalid hostname + hostname verification) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerInvalidHostname(t), clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, // fails because the client doesn't trust the proxy }, "proxied https (valid hostname + RootCAs) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerValidHostname(t), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https with auth (valid hostname + RootCAs) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerValidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname(t), proxyServerFunc: httpsServerInvalidHostname(t), clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, // works because the test proxy ignores TLS errors }, "proxied https with auth (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname(t), proxyServerFunc: httpsServerInvalidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, // works because the test proxy ignores TLS errors }, "proxied https (invalid hostname + hostname verification) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname(t), proxyServerFunc: httpsServerInvalidHostname(t), clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, // fails because the client doesn't trust the proxy }, "proxied https (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": { serverFunc: httpsServerValidHostname(t), proxyServerFunc: httpsServerValidHostname(t), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https with auth (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": { serverFunc: httpsServerValidHostname(t), proxyServerFunc: httpsServerValidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied valid https, proxy auth with chars that percent escape -> valid https": { serverFunc: httpsServerValidHostname(t), proxyServerFunc: httpsServerValidHostname(t), proxyAuth: url.UserPassword("proxy user", "proxypasswd%"), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, } for k, testCase := range testCases { t.Run(k, func(t *testing.T) { server := testCase.serverFunc(serverHandler( t, serverHandlerConfig{ shouldError: testCase.shouldError, statusCode: testCase.serverStatusCode, connectionHeader: testCase.serverConnectionHeader, upgradeHeader: testCase.serverUpgradeHeader, }, )) defer server.Close() t.Logf("Server URL: %v", server.URL) serverURL, err := url.Parse(server.URL) if err != nil { t.Fatalf("error creating request: %s", err) } req, err := http.NewRequest("GET", server.URL, nil) if err != nil { t.Fatalf("error creating request: %s", err) } spdyTransport, err := NewRoundTripper(testCase.clientTLS) if err != nil { t.Fatalf("error creating SpdyRoundTripper: %v", err) } var proxierCalled bool var proxyCalledWithHost string var proxyCalledWithAuth bool var proxyCalledWithAuthHeader string if testCase.proxyServerFunc != nil { proxyHandler := utilnettesting.NewHTTPProxyHandler(t, func(req *http.Request) bool { proxyCalledWithHost = req.Host proxyAuthHeaderName := "Proxy-Authorization" _, proxyCalledWithAuth = req.Header[proxyAuthHeaderName] proxyCalledWithAuthHeader = req.Header.Get(proxyAuthHeaderName) return true }) defer proxyHandler.Wait() proxy := testCase.proxyServerFunc(proxyHandler) defer proxy.Close() t.Logf("Proxy URL: %v", proxy.URL) spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) { proxierCalled = true proxyURL, err := url.Parse(proxy.URL) if err != nil { return nil, err } proxyURL.User = testCase.proxyAuth return proxyURL, nil } } client := &http.Client{Transport: spdyTransport} resp, err := client.Do(req) var conn httpstream.Connection if err == nil { conn, err = spdyTransport.NewConnection(resp) } haveErr := err != nil if e, a := testCase.shouldError, haveErr; e != a { t.Fatalf("shouldError=%t, got %t: %v", e, a, err) } if testCase.shouldError { return } defer conn.Close() if resp.StatusCode != http.StatusSwitchingProtocols { t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode) } stream, err := conn.CreateStream(http.Header{}) if err != nil { t.Fatalf("error creating client stream: %s", err) } n, err := stream.Write([]byte("hello")) if err != nil { t.Fatalf("error writing to stream: %s", err) } if n != 5 { t.Fatalf("expected to write 5 bytes, but actually wrote %d", n) } b := make([]byte, 5) n, err = stream.Read(b) if err != nil { t.Fatalf("error reading from stream: %s", err) } if n != 5 { t.Fatalf("expected to read 5 bytes, but actually read %d", n) } if e, a := "hello", string(b[0:n]); e != a { t.Fatalf("expected '%s', got '%s'", e, a) } if testCase.proxyServerFunc != nil { if !proxierCalled { t.Fatal("expected to use a proxy but proxier in SpdyRoundTripper wasn't called") } if proxyCalledWithHost != serverURL.Host { t.Fatalf("expected to see a call to the proxy for backend %q, got %q", serverURL.Host, proxyCalledWithHost) } } if testCase.proxyAuth != nil { expectedUsername := testCase.proxyAuth.Username() expectedPassword, _ := testCase.proxyAuth.Password() username, password, ok := (&http.Request{Header: http.Header{"Authorization": []string{proxyCalledWithAuthHeader}}}).BasicAuth() if !ok { t.Fatalf("invalid proxy auth header %s", proxyCalledWithAuthHeader) } if username != expectedUsername || password != expectedPassword { t.Fatalf("expected proxy auth \"%s:%s\", got \"%s:%s\"", expectedUsername, expectedPassword, username, password) } } else if proxyCalledWithAuth { t.Fatalf("proxy authorization unexpected, got %q", proxyCalledWithAuthHeader) } }) } } // Tests SpdyRoundTripper constructors func TestRoundTripConstuctor(t *testing.T) { testCases := map[string]struct { tlsConfig *tls.Config proxier func(req *http.Request) (*url.URL, error) upgradeTransport http.RoundTripper expectedTLSConfig *tls.Config errMsg string }{ "Basic TLSConfig; no error": { tlsConfig: &tls.Config{InsecureSkipVerify: true}, expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, upgradeTransport: nil, }, "Basic TLSConfig and Proxier: no error": { tlsConfig: &tls.Config{InsecureSkipVerify: true}, proxier: func(req *http.Request) (*url.URL, error) { return nil, nil }, expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, upgradeTransport: nil, }, "TLSConfig with UpgradeTransport: error": { tlsConfig: &tls.Config{InsecureSkipVerify: true}, upgradeTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, errMsg: "SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier", }, "Proxier with UpgradeTransport: error": { proxier: func(req *http.Request) (*url.URL, error) { return nil, nil }, upgradeTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, errMsg: "SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier", }, "Only UpgradeTransport: no error": { upgradeTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, }, } for name, testCase := range testCases { t.Run(name, func(t *testing.T) { spdyRoundTripper, err := NewRoundTripperWithConfig( RoundTripperConfig{ TLS: testCase.tlsConfig, Proxier: testCase.proxier, UpgradeTransport: testCase.upgradeTransport, }, ) if testCase.errMsg != "" { if err == nil { t.Fatalf("expected error but received none") } if !strings.Contains(err.Error(), testCase.errMsg) { t.Fatalf("expected error message (%s), got (%s)", err.Error(), testCase.errMsg) } } if testCase.errMsg == "" { if err != nil { t.Fatalf("unexpected error received: %v", err) } actualTLSConfig := spdyRoundTripper.TLSClientConfig() if !reflect.DeepEqual(testCase.expectedTLSConfig, actualTLSConfig) { t.Errorf("expected TLSConfig (%v), got (%v)", testCase.expectedTLSConfig, actualTLSConfig) } } }) } } type Interceptor struct { Authorization socks5.AuthContext proxyCalledWithHost *string } func (i *Interceptor) GetAuthContext() (int, map[string]string) { return int(i.Authorization.Method), i.Authorization.Payload } func (i *Interceptor) Rewrite(ctx context.Context, req *socks5.Request) (context.Context, *socks5.AddrSpec) { *i.proxyCalledWithHost = req.DestAddr.Address() i.Authorization = socks5.AuthContext(*req.AuthContext) return ctx, req.DestAddr } // be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly. func TestRoundTripSocks5AndNewConnection(t *testing.T) { localhostPool := localhostCertPool(t) socks5Server := func(creds *socks5.StaticCredentials, interceptor *Interceptor) *socks5.Server { var conf *socks5.Config if creds != nil { authenticator := socks5.UserPassAuthenticator{Credentials: creds} conf = &socks5.Config{ AuthMethods: []socks5.Authenticator{authenticator}, Rewriter: interceptor, } } else { conf = &socks5.Config{Rewriter: interceptor} } ts, err := socks5.New(conf) if err != nil { t.Errorf("failed to create sock5 server: %v", err) } return ts } testCases := map[string]struct { clientTLS *tls.Config proxyAuth *url.Userinfo serverConnectionHeader string serverFunc serverFunc serverStatusCode int serverUpgradeHeader string shouldError bool }{ "proxied without auth -> http": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverStatusCode: http.StatusSwitchingProtocols, serverUpgradeHeader: "SPDY/3.1", shouldError: false, }, "proxied with invalid auth -> http": { serverFunc: httptest.NewServer, proxyAuth: url.UserPassword("invalid", "auth"), serverConnectionHeader: "Upgrade", serverStatusCode: http.StatusSwitchingProtocols, serverUpgradeHeader: "SPDY/3.1", shouldError: true, }, "proxied with valid auth -> http": { serverFunc: httptest.NewServer, proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), serverConnectionHeader: "Upgrade", serverStatusCode: http.StatusSwitchingProtocols, serverUpgradeHeader: "SPDY/3.1", shouldError: false, }, "proxied with valid auth -> https (invalid hostname + InsecureSkipVerify)": { serverFunc: httpsServerInvalidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied with valid auth -> https (invalid hostname + hostname verification)": { serverFunc: httpsServerInvalidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "proxied with valid auth -> https (valid hostname + RootCAs)": { serverFunc: httpsServerValidHostname(t), proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, } for name, testCase := range testCases { t.Run(name, func(t *testing.T) { server := testCase.serverFunc(serverHandler( t, serverHandlerConfig{ shouldError: testCase.shouldError, statusCode: testCase.serverStatusCode, connectionHeader: testCase.serverConnectionHeader, upgradeHeader: testCase.serverUpgradeHeader, }, )) defer server.Close() req, err := http.NewRequest("GET", server.URL, nil) if err != nil { t.Fatalf("error creating request: %s", err) } spdyTransport, err := NewRoundTripper(testCase.clientTLS) if err != nil { t.Fatalf("error creating SpdyRoundTripper: %v", err) } var proxierCalled bool var proxyCalledWithHost string interceptor := &Interceptor{proxyCalledWithHost: &proxyCalledWithHost} proxyHandler := socks5Server(nil, interceptor) if testCase.proxyAuth != nil { proxyHandler = socks5Server(&socks5.StaticCredentials{ "proxyuser": "proxypasswd", // Socks5 server static credentials when client authentication is expected }, interceptor) } closed := make(chan struct{}) isClosed := func() bool { select { case <-closed: return true default: return false } } l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("socks5Server: proxy_test: Listen: %v", err) } defer l.Close() go func(shoulderror bool) { conn, err := l.Accept() if err != nil { if isClosed() { return } t.Errorf("error accepting connection: %s", err) } if err := proxyHandler.ServeConn(conn); err != nil && !shoulderror { // If the connection request is closed before the channel is closed // the test will fail with a ServeConn error. Since the test only return // early if expects shouldError=true, the channel is closed at the end of // the test, just before all the deferred connections Close() are executed. if isClosed() { return } t.Errorf("ServeConn error: %s", err) } }(testCase.shouldError) spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) { proxierCalled = true return &url.URL{ Scheme: "socks5", Host: net.JoinHostPort("127.0.0.1", strconv.Itoa(l.Addr().(*net.TCPAddr).Port)), User: testCase.proxyAuth, }, nil } client := &http.Client{Transport: spdyTransport} resp, err := client.Do(req) haveErr := err != nil if e, a := testCase.shouldError, haveErr; e != a { t.Fatalf("shouldError=%t, got %t: %v", e, a, err) } if testCase.shouldError { return } conn, err := spdyTransport.NewConnection(resp) haveErr = err != nil if e, a := testCase.shouldError, haveErr; e != a { t.Fatalf("shouldError=%t, got %t: %v", e, a, err) } if testCase.shouldError { return } defer conn.Close() if resp.StatusCode != http.StatusSwitchingProtocols { t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode) } stream, err := conn.CreateStream(http.Header{}) if err != nil { t.Fatalf("error creating client stream: %s", err) } n, err := stream.Write([]byte("hello")) if err != nil { t.Fatalf("error writing to stream: %s", err) } if n != 5 { t.Fatalf("expected to write 5 bytes, but actually wrote %d", n) } b := make([]byte, 5) n, err = stream.Read(b) if err != nil { t.Fatalf("error reading from stream: %s", err) } if n != 5 { t.Fatalf("expected to read 5 bytes, but actually read %d", n) } if e, a := "hello", string(b[0:n]); e != a { t.Fatalf("expected '%s', got '%s'", e, a) } if !proxierCalled { t.Fatal("xpected to use a proxy but proxier in SpdyRoundTripper wasn't called") } serverURL, err := url.Parse(server.URL) if err != nil { t.Fatalf("error creating request: %s", err) } if proxyCalledWithHost != serverURL.Host { t.Fatalf("expected to see a call to the proxy for backend %q, got %q", serverURL.Host, proxyCalledWithHost) } authMethod, authUser := interceptor.GetAuthContext() if testCase.proxyAuth != nil { expectedSocks5AuthMethod := 2 expectedSocks5AuthUser := "proxyuser" if expectedSocks5AuthMethod != authMethod { t.Fatalf("socks5 Proxy authorization unexpected, got %d, expected %d", authMethod, expectedSocks5AuthMethod) } if expectedSocks5AuthUser != authUser["Username"] { t.Fatalf("socks5 Proxy authorization user unexpected, got %q, expected %q", authUser["Username"], expectedSocks5AuthUser) } } else { if authMethod != 0 { t.Fatalf("proxy authentication method unexpected, got %d", authMethod) } if len(authUser) != 0 { t.Fatalf("unexpected proxy user: %v", authUser) } } // The channel must be closed before any of the connections are closed close(closed) }) } } func TestRoundTripPassesContextToDialer(t *testing.T) { urls := []string{"http://127.0.0.1:1233/", "https://127.0.0.1:1233/"} for _, u := range urls { t.Run(u, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() req, err := http.NewRequestWithContext(ctx, "GET", u, nil) require.NoError(t, err) spdyTransport, err := NewRoundTripper(&tls.Config{}) if err != nil { t.Fatalf("error creating SpdyRoundTripper: %v", err) } _, err = spdyTransport.Dial(req) assert.EqualError(t, err, "dial tcp 127.0.0.1:1233: operation was canceled") }) } } // exampleCert was generated from crypto/tls/generate_cert.go with the following command: // // go run generate_cert.go --rsa-bits 2048 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var exampleCert = []byte(`-----BEGIN CERTIFICATE----- MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy +i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he 7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+ 2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL 1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP uvEQdw== -----END CERTIFICATE-----`) var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9 p+tnzm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/ JlNy+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj 7aMivXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJ OoUGf8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb +9he7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABAoIBAQClt4CiYaaF5ltx wVDjz6TNcJUBUs3CKE+uWAYFnF5Ii1nyU876Pxj8Aaz9fHZ6Kde0GkwiXY7gFOj1 YHo2tzcELSKS/SEDZcYbYFTGCjq13g1AH74R+SV6WZLn+5m8kPvVrM1ZWap188H5 bmuCkRDqVmIvShkbRW7EwhC35J9fiuW3majC/sjmsxtxyP6geWmu4f5/Ttqahcdb osPZIgIIPzqAkNtkLTi7+meHYI9wlrGhL7XZTwnJ1Oc/Y67zzmbthLYB5YFSLUew rXT58jtSjX4gbiQyheBSrWxW08QE4qYg6jJlAdffHhWv72hJW2MCXhuXp8gJs/Do XLRHGwSBAoGBAMdNtsbe4yae/QeHUPGxNW0ipa0yoTF6i+VYoxvqiRMzDM3+3L8k dgI1rr4330SivqDahMA/odWtM/9rVwJI2B2QhZLMHA0n9ytH007OO9TghgVB12nN xosRYBpKdHXyyvV/MUZl7Jux6zKIzRDWOkF95VVYPcAaxJqd1E5/jJ6JAoGBAN51 QrebA1w/jfydeqQTz1sK01sbO4HYj4qGfo/JarVqGEkm1azeBBPPRnHz3jNKnCkM S4PpqRDased3NIcViXlAgoqPqivZ8mQa/Rb146l7WaTErASHsZ023OGrxsr/Ed6N P3GrmvxVJjebaFNaQ9sP80dLkpgeas0t2TY8iQNRAoGATOcnx8TpUVW3vNfx29DN FLdxxkrq9/SZVn3FMlhlXAsuva3B799ZybB9JNjaRdmmRNsMrkHfaFvU3JHGmRMS kRXa9LHdgRYSwZiNaLMbUyDvlce6HxFPswmZU4u3NGvi9KeHk+pwSgN1BaLTvdNr 1ymE/FF4QlAR3LdZ3JBK6kECgYEA0wW4/CJ31ZIURoW8SNjh4iMqy0nR8SJVR7q9 Y/hU2TKDRyEnoIwaohAFayNCrLUh3W5kVAXa8roB+OgDVAECH5sqOfZ+HorofD19 x8II7ESujLZj1whBXDkm3ovsT7QWZ17lyBZZNvQvBKDPHgKKS8udowv1S4fPGENd wS07a4ECgYEAwLSbmMIVJme0jFjsp5d1wOGA2Qi2ZwGIAVlsbnJtygrU/hSBfnu8 VfyJSCgg3fPe7kChWKlfcOebVKSb68LKRsz1Lz1KdbY0HOJFp/cT4lKmDAlRY9gq LB4rdf46lV0mUkvd2/oofIbTrzukjQSnyfLawb/2uJGV1IkTcZcn9CI= -----END RSA PRIVATE KEY-----`) // localhostCert was generated from crypto/tls/generate_cert.go with the following command: // // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var localhostCert = []byte(`-----BEGIN CERTIFICATE----- MIIDGTCCAgGgAwIBAgIRALL5AZcefF4kkYV1SEG6YrMwDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBALQ/FHcyVwdFHxARbbD2KBtDUT7Eni+8ioNdjtGcmtXqBv45EC1C JOqqGJTroFGJ6Q9kQIZ9FqH5IJR2fOOJD9kOTueG4Vt1JY1rj1Kbpjefu8XleZ5L SBwIWVnN/lEsEbuKmj7N2gLt5AH3zMZiBI1mg1u9Z5ZZHYbCiTpBrwsq6cTlvR9g dyo1YkM5hRESCzsrL0aUByoo0qRMD8ZsgANJwgsiO0/M6idbxDwv1BnGwGmRYvOE Hxpy3v0Jg7GJYrvnpnifJTs4nw91N5X9pXxR7FFzi/6HTYDWRljvTb0w6XciKYAz bWZ0+cJr5F7wB7ovlbm7HrQIR7z7EIIu2d8CAwEAAaNoMGYwDgYDVR0PAQH/BAQD AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI hvcNAQELBQADggEBAFPPWopNEJtIA2VFAQcqN6uJK+JVFOnjGRoCrM6Xgzdm0wxY XCGjsxY5dl+V7KzdGqu858rCaq5osEBqypBpYAnS9C38VyCDA1vPS1PsN8SYv48z DyBwj+7R2qar0ADBhnhWxvYO9M72lN/wuCqFKYMeFSnJdQLv3AsrrHe9lYqOa36s 8wxSwVTFTYXBzljPEnSaaJMPqFD8JXaZK1ryJPkO5OsCNQNGtatNiWAf3DcmwHAT MGYMzP0u4nw47aRz9shB8w+taPKHx2BVwE1m/yp3nHVioOjXqA1fwRQVGclCJSH1 D2iq3hWVHRENgjTjANBPICLo9AZ4JfN6PH19mnU= -----END CERTIFICATE-----`) // localhostKey is the private key for localhostCert. var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAtD8UdzJXB0UfEBFtsPYoG0NRPsSeL7yKg12O0Zya1eoG/jkQ LUIk6qoYlOugUYnpD2RAhn0WofkglHZ844kP2Q5O54bhW3UljWuPUpumN5+7xeV5 nktIHAhZWc3+USwRu4qaPs3aAu3kAffMxmIEjWaDW71nllkdhsKJOkGvCyrpxOW9 H2B3KjViQzmFERILOysvRpQHKijSpEwPxmyAA0nCCyI7T8zqJ1vEPC/UGcbAaZFi 84QfGnLe/QmDsYliu+emeJ8lOzifD3U3lf2lfFHsUXOL/odNgNZGWO9NvTDpdyIp gDNtZnT5wmvkXvAHui+VubsetAhHvPsQgi7Z3wIDAQABAoIBAGmw93IxjYCQ0ncc kSKMJNZfsdtJdaxuNRZ0nNNirhQzR2h403iGaZlEpmdkhzxozsWcto1l+gh+SdFk bTUK4MUZM8FlgO2dEqkLYh5BcMT7ICMZvSfJ4v21E5eqR68XVUqQKoQbNvQyxFk3 EddeEGdNrkb0GDK8DKlBlzAW5ep4gjG85wSTjR+J+muUv3R0BgLBFSuQnIDM/IMB LWqsja/QbtB7yppe7jL5u8UCFdZG8BBKT9fcvFIu5PRLO3MO0uOI7LTc8+W1Xm23 uv+j3SY0+v+6POjK0UlJFFi/wkSPTFIfrQO1qFBkTDQHhQ6q/7GnILYYOiGbIRg2 NNuP52ECgYEAzXEoy50wSYh8xfFaBuxbm3ruuG2W49jgop7ZfoFrPWwOQKAZS441 VIwV4+e5IcA6KkuYbtGSdTYqK1SMkgnUyD/VevwAqH5TJoEIGu0pDuKGwVuwqioZ frCIAV5GllKyUJ55VZNbRr2vY2fCsWbaCSCHETn6C16DNuTCe5C0JBECgYEA4JqY 5GpNbMG8fOt4H7hU0Fbm2yd6SHJcQ3/9iimef7xG6ajxsYrIhg1ft+3IPHMjVI0+ 9brwHDnWg4bOOx/VO4VJBt6Dm/F33bndnZRkuIjfSNpLM51P+EnRdaFVHOJHwKqx uF69kihifCAG7YATgCveeXImzBUSyZUz9UrETu8CgYARNBimdFNG1RcdvEg9rC0/ p9u1tfecvNySwZqU7WF9kz7eSonTueTdX521qAHowaAdSpdJMGODTTXaywm6cPhQ jIfj9JZZhbqQzt1O4+08Qdvm9TamCUB5S28YLjza+bHU7nBaqixKkDfPqzCyilpX yVGGL8SwjwmN3zop/sQXAQKBgC0JMsESQ6YcDsRpnrOVjYQc+LtW5iEitTdfsaID iGGKihmOI7B66IxgoCHMTws39wycKdSyADVYr5e97xpR3rrJlgQHmBIrz+Iow7Q2 LiAGaec8xjl6QK/DdXmFuQBKqyKJ14rljFODP4QuE9WJid94bGqjpf3j99ltznZP 4J8HAoGAJb4eb4lu4UGwifDzqfAPzLGCoi0fE1/hSx34lfuLcc1G+LEu9YDKoOVJ 9suOh0b5K/bfEy9KrVMBBriduvdaERSD8S3pkIQaitIz0B029AbE4FLFf9lKQpP2 KR8NJEkK99Vh/tew6jAMll70xFrE7aF8VLXJVE7w4sQzuvHxl9Q= -----END RSA PRIVATE KEY----- `) golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/upgrade.go000066400000000000000000000077641453143165200251330ustar00rootroot00000000000000/* Copyright 2015 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 spdy import ( "bufio" "fmt" "io" "net" "net/http" "strings" "sync/atomic" "time" "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/runtime" ) const HeaderSpdy31 = "SPDY/3.1" // responseUpgrader knows how to upgrade HTTP responses. It // implements the httpstream.ResponseUpgrader interface. type responseUpgrader struct { pingPeriod time.Duration } // connWrapper is used to wrap a hijacked connection and its bufio.Reader. All // calls will be handled directly by the underlying net.Conn with the exception // of Read and Close calls, which will consider data in the bufio.Reader. This // ensures that data already inside the used bufio.Reader instance is also // read. type connWrapper struct { net.Conn closed int32 bufReader *bufio.Reader } func (w *connWrapper) Read(b []byte) (n int, err error) { if atomic.LoadInt32(&w.closed) == 1 { return 0, io.EOF } return w.bufReader.Read(b) } func (w *connWrapper) Close() error { err := w.Conn.Close() atomic.StoreInt32(&w.closed, 1) return err } // NewResponseUpgrader returns a new httpstream.ResponseUpgrader that is // capable of upgrading HTTP responses using SPDY/3.1 via the // spdystream package. func NewResponseUpgrader() httpstream.ResponseUpgrader { return NewResponseUpgraderWithPings(0) } // NewResponseUpgraderWithPings returns a new httpstream.ResponseUpgrader that // is capable of upgrading HTTP responses using SPDY/3.1 via the spdystream // package. // // If pingPeriod is non-zero, for each incoming connection a background // goroutine will send periodic Ping frames to the server. Use this to keep // idle connections through certain load balancers alive longer. func NewResponseUpgraderWithPings(pingPeriod time.Duration) httpstream.ResponseUpgrader { return responseUpgrader{pingPeriod: pingPeriod} } // UpgradeResponse upgrades an HTTP response to one that supports multiplexed // streams. newStreamHandler will be called synchronously whenever the // other end of the upgraded connection creates a new stream. func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection { connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection)) upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade)) if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) { errorMsg := fmt.Sprintf("unable to upgrade: missing upgrade headers in request: %#v", req.Header) http.Error(w, errorMsg, http.StatusBadRequest) return nil } hijacker, ok := w.(http.Hijacker) if !ok { errorMsg := "unable to upgrade: unable to hijack response" http.Error(w, errorMsg, http.StatusInternalServerError) return nil } w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade) w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31) w.WriteHeader(http.StatusSwitchingProtocols) conn, bufrw, err := hijacker.Hijack() if err != nil { runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err)) return nil } connWithBuf := &connWrapper{Conn: conn, bufReader: bufrw.Reader} spdyConn, err := NewServerConnectionWithPings(connWithBuf, newStreamHandler, u.pingPeriod) if err != nil { runtime.HandleError(fmt.Errorf("unable to upgrade: error creating SPDY server connection: %v", err)) return nil } return spdyConn } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/spdy/upgrade_test.go000066400000000000000000000043701453143165200261600ustar00rootroot00000000000000/* Copyright 2015 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 spdy import ( "net/http" "net/http/httptest" "testing" ) func TestUpgradeResponse(t *testing.T) { testCases := []struct { connectionHeader string upgradeHeader string shouldError bool }{ { connectionHeader: "", upgradeHeader: "", shouldError: true, }, { connectionHeader: "Upgrade", upgradeHeader: "", shouldError: true, }, { connectionHeader: "", upgradeHeader: "SPDY/3.1", shouldError: true, }, { connectionHeader: "Upgrade", upgradeHeader: "SPDY/3.1", shouldError: false, }, } for i, testCase := range testCases { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { upgrader := NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, nil) haveErr := conn == nil if e, a := testCase.shouldError, haveErr; e != a { t.Fatalf("%d: expected shouldErr=%t, got %t", i, testCase.shouldError, haveErr) } if haveErr { return } if conn == nil { t.Fatalf("%d: unexpected nil conn", i) } defer conn.Close() })) defer server.Close() req, err := http.NewRequest("GET", server.URL, nil) if err != nil { t.Fatalf("%d: error creating request: %s", i, err) } req.Header.Set("Connection", testCase.connectionHeader) req.Header.Set("Upgrade", testCase.upgradeHeader) client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("%d: unexpected non-nil err from client.Do: %s", i, err) } if testCase.shouldError { continue } if resp.StatusCode != http.StatusSwitchingProtocols { t.Fatalf("%d: expected status 101 switching protocols, got %d", i, resp.StatusCode) } } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/wsstream/000077500000000000000000000000001453143165200240255ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/wsstream/conn.go000066400000000000000000000330011453143165200253060ustar00rootroot00000000000000/* Copyright 2015 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 wsstream import ( "encoding/base64" "fmt" "io" "net/http" "strings" "time" "golang.org/x/net/websocket" "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/klog/v2" ) const WebSocketProtocolHeader = "Sec-Websocket-Protocol" // The Websocket subprotocol "channel.k8s.io" prepends each binary message with a byte indicating // the channel number (zero indexed) the message was sent on. Messages in both directions should // prefix their messages with this channel byte. When used for remote execution, the channel numbers // are by convention defined to match the POSIX file-descriptors assigned to STDIN, STDOUT, and STDERR // (0, 1, and 2). No other conversion is performed on the raw subprotocol - writes are sent as they // are received by the server. // // Example client session: // // CONNECT http://server.com with subprotocol "channel.k8s.io" // WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN) // READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT) // CLOSE const ChannelWebSocketProtocol = "channel.k8s.io" // The Websocket subprotocol "base64.channel.k8s.io" base64 encodes each message with a character // indicating the channel number (zero indexed) the message was sent on. Messages in both directions // should prefix their messages with this channel char. When used for remote execution, the channel // numbers are by convention defined to match the POSIX file-descriptors assigned to STDIN, STDOUT, // and STDERR ('0', '1', and '2'). The data received on the server is base64 decoded (and must be // be valid) and data written by the server to the client is base64 encoded. // // Example client session: // // CONNECT http://server.com with subprotocol "base64.channel.k8s.io" // WRITE []byte{48, 90, 109, 57, 118, 67, 103, 111, 61} # send "foo\n" (base64: "Zm9vCgo=") on channel '0' (STDIN) // READ []byte{49, 67, 103, 61, 61} # receive "\n" (base64: "Cg==") on channel '1' (STDOUT) // CLOSE const Base64ChannelWebSocketProtocol = "base64.channel.k8s.io" type codecType int const ( rawCodec codecType = iota base64Codec ) type ChannelType int const ( IgnoreChannel ChannelType = iota ReadChannel WriteChannel ReadWriteChannel ) // IsWebSocketRequest returns true if the incoming request contains connection upgrade headers // for WebSockets. func IsWebSocketRequest(req *http.Request) bool { if !strings.EqualFold(req.Header.Get("Upgrade"), "websocket") { return false } return httpstream.IsUpgradeRequest(req) } // IsWebSocketRequestWithStreamCloseProtocol returns true if the request contains headers // identifying that it is requesting a websocket upgrade with a remotecommand protocol // version that supports the "CLOSE" signal; false otherwise. func IsWebSocketRequestWithStreamCloseProtocol(req *http.Request) bool { if !IsWebSocketRequest(req) { return false } requestedProtocols := strings.TrimSpace(req.Header.Get(WebSocketProtocolHeader)) for _, requestedProtocol := range strings.Split(requestedProtocols, ",") { if protocolSupportsStreamClose(strings.TrimSpace(requestedProtocol)) { return true } } return false } // IgnoreReceives reads from a WebSocket until it is closed, then returns. If timeout is set, the // read and write deadlines are pushed every time a new message is received. func IgnoreReceives(ws *websocket.Conn, timeout time.Duration) { defer runtime.HandleCrash() var data []byte for { resetTimeout(ws, timeout) if err := websocket.Message.Receive(ws, &data); err != nil { return } } } // handshake ensures the provided user protocol matches one of the allowed protocols. It returns // no error if no protocol is specified. func handshake(config *websocket.Config, req *http.Request, allowed []string) error { protocols := config.Protocol if len(protocols) == 0 { protocols = []string{""} } for _, protocol := range protocols { for _, allow := range allowed { if allow == protocol { config.Protocol = []string{protocol} return nil } } } return fmt.Errorf("requested protocol(s) are not supported: %v; supports %v", config.Protocol, allowed) } // ChannelProtocolConfig describes a websocket subprotocol with channels. type ChannelProtocolConfig struct { Binary bool Channels []ChannelType } // NewDefaultChannelProtocols returns a channel protocol map with the // subprotocols "", "channel.k8s.io", "base64.channel.k8s.io" and the given // channels. func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig { return map[string]ChannelProtocolConfig{ "": {Binary: true, Channels: channels}, ChannelWebSocketProtocol: {Binary: true, Channels: channels}, Base64ChannelWebSocketProtocol: {Binary: false, Channels: channels}, } } // Conn supports sending multiple binary channels over a websocket connection. type Conn struct { protocols map[string]ChannelProtocolConfig selectedProtocol string channels []*websocketChannel codec codecType ready chan struct{} ws *websocket.Conn timeout time.Duration } // NewConn creates a WebSocket connection that supports a set of channels. Channels begin each // web socket message with a single byte indicating the channel number (0-N). 255 is reserved for // future use. The channel types for each channel are passed as an array, supporting the different // duplex modes. Read and Write refer to whether the channel can be used as a Reader or Writer. // // The protocols parameter maps subprotocol names to ChannelProtocols. The empty string subprotocol // name is used if websocket.Config.Protocol is empty. func NewConn(protocols map[string]ChannelProtocolConfig) *Conn { return &Conn{ ready: make(chan struct{}), protocols: protocols, } } // SetIdleTimeout sets the interval for both reads and writes before timeout. If not specified, // there is no timeout on the connection. func (conn *Conn) SetIdleTimeout(duration time.Duration) { conn.timeout = duration } // SetWriteDeadline sets a timeout on writing to the websocket connection. The // passed "duration" identifies how far into the future the write must complete // by before the timeout fires. func (conn *Conn) SetWriteDeadline(duration time.Duration) { conn.ws.SetWriteDeadline(time.Now().Add(duration)) //nolint:errcheck } // Open the connection and create channels for reading and writing. It returns // the selected subprotocol, a slice of channels and an error. func (conn *Conn) Open(w http.ResponseWriter, req *http.Request) (string, []io.ReadWriteCloser, error) { // serveHTTPComplete is channel that is closed/selected when "websocket#ServeHTTP" finishes. serveHTTPComplete := make(chan struct{}) // Ensure panic in spawned goroutine is propagated into the parent goroutine. panicChan := make(chan any, 1) go func() { // If websocket server returns, propagate panic if necessary. Otherwise, // signal HTTPServe finished by closing "serveHTTPComplete". defer func() { if p := recover(); p != nil { panicChan <- p } else { close(serveHTTPComplete) } }() websocket.Server{Handshake: conn.handshake, Handler: conn.handle}.ServeHTTP(w, req) }() // In normal circumstances, "websocket.Server#ServeHTTP" calls "initialize" which closes // "conn.ready" and then blocks until serving is complete. select { case <-conn.ready: klog.V(8).Infof("websocket server initialized--serving") case <-serveHTTPComplete: // websocket server returned before completing initialization; cleanup and return error. conn.closeNonThreadSafe() //nolint:errcheck return "", nil, fmt.Errorf("websocket server finished before becoming ready") case p := <-panicChan: panic(p) } rwc := make([]io.ReadWriteCloser, len(conn.channels)) for i := range conn.channels { rwc[i] = conn.channels[i] } return conn.selectedProtocol, rwc, nil } func (conn *Conn) initialize(ws *websocket.Conn) { negotiated := ws.Config().Protocol conn.selectedProtocol = negotiated[0] p := conn.protocols[conn.selectedProtocol] if p.Binary { conn.codec = rawCodec } else { conn.codec = base64Codec } conn.ws = ws conn.channels = make([]*websocketChannel, len(p.Channels)) for i, t := range p.Channels { switch t { case ReadChannel: conn.channels[i] = newWebsocketChannel(conn, byte(i), true, false) case WriteChannel: conn.channels[i] = newWebsocketChannel(conn, byte(i), false, true) case ReadWriteChannel: conn.channels[i] = newWebsocketChannel(conn, byte(i), true, true) case IgnoreChannel: conn.channels[i] = newWebsocketChannel(conn, byte(i), false, false) } } close(conn.ready) } func (conn *Conn) handshake(config *websocket.Config, req *http.Request) error { supportedProtocols := make([]string, 0, len(conn.protocols)) for p := range conn.protocols { supportedProtocols = append(supportedProtocols, p) } return handshake(config, req, supportedProtocols) } func (conn *Conn) resetTimeout() { if conn.timeout > 0 { conn.ws.SetDeadline(time.Now().Add(conn.timeout)) } } // closeNonThreadSafe cleans up by closing streams and the websocket // connection *without* waiting for the "ready" channel. func (conn *Conn) closeNonThreadSafe() error { for _, s := range conn.channels { s.Close() } var err error if conn.ws != nil { err = conn.ws.Close() } return err } // Close is only valid after Open has been called func (conn *Conn) Close() error { <-conn.ready return conn.closeNonThreadSafe() } // protocolSupportsStreamClose returns true if the passed protocol // supports the stream close signal (currently only V5 remotecommand); // false otherwise. func protocolSupportsStreamClose(protocol string) bool { return protocol == remotecommand.StreamProtocolV5Name } // handle implements a websocket handler. func (conn *Conn) handle(ws *websocket.Conn) { conn.initialize(ws) defer conn.Close() supportsStreamClose := protocolSupportsStreamClose(conn.selectedProtocol) for { conn.resetTimeout() var data []byte if err := websocket.Message.Receive(ws, &data); err != nil { if err != io.EOF { klog.Errorf("Error on socket receive: %v", err) } break } if len(data) == 0 { continue } if supportsStreamClose && data[0] == remotecommand.StreamClose { if len(data) != 2 { klog.Errorf("Single channel byte should follow stream close signal. Got %d bytes", len(data)-1) break } else { channel := data[1] if int(channel) >= len(conn.channels) { klog.Errorf("Close is targeted for a channel %d that is not valid, possible protocol error", channel) break } klog.V(4).Infof("Received half-close signal from client; close %d stream", channel) conn.channels[channel].Close() // After first Close, other closes are noop. } continue } channel := data[0] if conn.codec == base64Codec { channel = channel - '0' } data = data[1:] if int(channel) >= len(conn.channels) { klog.V(6).Infof("Frame is targeted for a reader %d that is not valid, possible protocol error", channel) continue } if _, err := conn.channels[channel].DataFromSocket(data); err != nil { klog.Errorf("Unable to write frame to %d: %v\n%s", channel, err, string(data)) continue } } } // write multiplexes the specified channel onto the websocket func (conn *Conn) write(num byte, data []byte) (int, error) { conn.resetTimeout() switch conn.codec { case rawCodec: frame := make([]byte, len(data)+1) frame[0] = num copy(frame[1:], data) if err := websocket.Message.Send(conn.ws, frame); err != nil { return 0, err } case base64Codec: frame := string('0'+num) + base64.StdEncoding.EncodeToString(data) if err := websocket.Message.Send(conn.ws, frame); err != nil { return 0, err } } return len(data), nil } // websocketChannel represents a channel in a connection type websocketChannel struct { conn *Conn num byte r io.Reader w io.WriteCloser read, write bool } // newWebsocketChannel creates a pipe for writing to a websocket. Do not write to this pipe // prior to the connection being opened. It may be no, half, or full duplex depending on // read and write. func newWebsocketChannel(conn *Conn, num byte, read, write bool) *websocketChannel { r, w := io.Pipe() return &websocketChannel{conn, num, r, w, read, write} } func (p *websocketChannel) Write(data []byte) (int, error) { if !p.write { return len(data), nil } return p.conn.write(p.num, data) } // DataFromSocket is invoked by the connection receiver to move data from the connection // into a specific channel. func (p *websocketChannel) DataFromSocket(data []byte) (int, error) { if !p.read { return len(data), nil } switch p.conn.codec { case rawCodec: return p.w.Write(data) case base64Codec: dst := make([]byte, len(data)) n, err := base64.StdEncoding.Decode(dst, data) if err != nil { return 0, err } return p.w.Write(dst[:n]) } return 0, nil } func (p *websocketChannel) Read(data []byte) (int, error) { if !p.read { return 0, io.EOF } return p.r.Read(data) } func (p *websocketChannel) Close() error { return p.w.Close() } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/wsstream/conn_test.go000066400000000000000000000266001453143165200263540ustar00rootroot00000000000000/* Copyright 2015 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 wsstream import ( "encoding/base64" "io" "net/http" "net/http/httptest" "reflect" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/websocket" ) func newServer(handler http.Handler) (*httptest.Server, string) { server := httptest.NewServer(handler) serverAddr := server.Listener.Addr().String() return server, serverAddr } func TestRawConn(t *testing.T) { channels := []ChannelType{ReadWriteChannel, ReadWriteChannel, IgnoreChannel, ReadChannel, WriteChannel} conn := NewConn(NewDefaultChannelProtocols(channels)) s, addr := newServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { conn.Open(w, req) })) defer s.Close() client, err := websocket.Dial("ws://"+addr, "", "http://localhost/") if err != nil { t.Fatal(err) } defer client.Close() <-conn.ready wg := sync.WaitGroup{} // verify we can read a client write wg.Add(1) go func() { defer wg.Done() data, err := io.ReadAll(conn.channels[0]) if err != nil { t.Error(err) return } if !reflect.DeepEqual(data, []byte("client")) { t.Errorf("unexpected server read: %v", data) } }() if n, err := client.Write(append([]byte{0}, []byte("client")...)); err != nil || n != 7 { t.Fatalf("%d: %v", n, err) } // verify we can read a server write wg.Add(1) go func() { defer wg.Done() if n, err := conn.channels[1].Write([]byte("server")); err != nil && n != 6 { t.Errorf("%d: %v", n, err) } }() data := make([]byte, 1024) if n, err := io.ReadAtLeast(client, data, 6); n != 7 || err != nil { t.Fatalf("%d: %v", n, err) } if !reflect.DeepEqual(data[:7], append([]byte{1}, []byte("server")...)) { t.Errorf("unexpected client read: %v", data[:7]) } // verify that an ignore channel is empty in both directions. if n, err := conn.channels[2].Write([]byte("test")); n != 4 || err != nil { t.Errorf("writes should be ignored") } data = make([]byte, 1024) if n, err := conn.channels[2].Read(data); n != 0 || err != io.EOF { t.Errorf("reads should be ignored") } // verify that a write to a Read channel doesn't block if n, err := conn.channels[3].Write([]byte("test")); n != 4 || err != nil { t.Errorf("writes should be ignored") } // verify that a read from a Write channel doesn't block data = make([]byte, 1024) if n, err := conn.channels[4].Read(data); n != 0 || err != io.EOF { t.Errorf("reads should be ignored") } // verify that a client write to a Write channel doesn't block (is dropped) if n, err := client.Write(append([]byte{4}, []byte("ignored")...)); err != nil || n != 8 { t.Fatalf("%d: %v", n, err) } client.Close() wg.Wait() } func TestBase64Conn(t *testing.T) { conn := NewConn(NewDefaultChannelProtocols([]ChannelType{ReadWriteChannel, ReadWriteChannel})) s, addr := newServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { conn.Open(w, req) })) defer s.Close() config, err := websocket.NewConfig("ws://"+addr, "http://localhost/") if err != nil { t.Fatal(err) } config.Protocol = []string{"base64.channel.k8s.io"} client, err := websocket.DialConfig(config) if err != nil { t.Fatal(err) } defer client.Close() <-conn.ready wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() data, err := io.ReadAll(conn.channels[0]) if err != nil { t.Error(err) return } if !reflect.DeepEqual(data, []byte("client")) { t.Errorf("unexpected server read: %s", string(data)) } }() clientData := base64.StdEncoding.EncodeToString([]byte("client")) if n, err := client.Write(append([]byte{'0'}, clientData...)); err != nil || n != len(clientData)+1 { t.Fatalf("%d: %v", n, err) } wg.Add(1) go func() { defer wg.Done() if n, err := conn.channels[1].Write([]byte("server")); err != nil && n != 6 { t.Errorf("%d: %v", n, err) } }() data := make([]byte, 1024) if n, err := io.ReadAtLeast(client, data, 9); n != 9 || err != nil { t.Fatalf("%d: %v", n, err) } expect := []byte(base64.StdEncoding.EncodeToString([]byte("server"))) if !reflect.DeepEqual(data[:9], append([]byte{'1'}, expect...)) { t.Errorf("unexpected client read: %v", data[:9]) } client.Close() wg.Wait() } type versionTest struct { supported map[string]bool // protocol -> binary requested []string error bool expected string } func versionTests() []versionTest { const ( binary = true base64 = false ) return []versionTest{ { supported: nil, requested: []string{"raw"}, error: true, }, { supported: map[string]bool{"": binary, "raw": binary, "base64": base64}, requested: nil, expected: "", }, { supported: map[string]bool{"": binary, "raw": binary, "base64": base64}, requested: []string{"v1.raw"}, error: true, }, { supported: map[string]bool{"": binary, "raw": binary, "base64": base64}, requested: []string{"v1.raw", "v1.base64"}, error: true, }, { supported: map[string]bool{"": binary, "raw": binary, "base64": base64}, requested: []string{"v1.raw", "raw"}, expected: "raw", }, { supported: map[string]bool{"": binary, "v1.raw": binary, "v1.base64": base64, "v2.raw": binary, "v2.base64": base64}, requested: []string{"v1.raw"}, expected: "v1.raw", }, { supported: map[string]bool{"": binary, "v1.raw": binary, "v1.base64": base64, "v2.raw": binary, "v2.base64": base64}, requested: []string{"v2.base64"}, expected: "v2.base64", }, } } func TestVersionedConn(t *testing.T) { for i, test := range versionTests() { func() { supportedProtocols := map[string]ChannelProtocolConfig{} for p, binary := range test.supported { supportedProtocols[p] = ChannelProtocolConfig{ Binary: binary, Channels: []ChannelType{ReadWriteChannel}, } } conn := NewConn(supportedProtocols) // note that it's not enough to wait for conn.ready to avoid a race here. Hence, // we use a channel. selectedProtocol := make(chan string) s, addr := newServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { p, _, _ := conn.Open(w, req) selectedProtocol <- p })) defer s.Close() config, err := websocket.NewConfig("ws://"+addr, "http://localhost/") if err != nil { t.Fatal(err) } config.Protocol = test.requested client, err := websocket.DialConfig(config) if err != nil { if !test.error { t.Fatalf("test %d: didn't expect error: %v", i, err) } else { return } } defer client.Close() if test.error && err == nil { t.Fatalf("test %d: expected an error", i) } <-conn.ready if got, expected := <-selectedProtocol, test.expected; got != expected { t.Fatalf("test %d: unexpected protocol version: got=%s expected=%s", i, got, expected) } }() } } func TestIsWebSocketRequestWithStreamCloseProtocol(t *testing.T) { tests := map[string]struct { headers map[string]string expected bool }{ "No headers returns false": { headers: map[string]string{}, expected: false, }, "Only connection upgrade header is false": { headers: map[string]string{ "Connection": "upgrade", }, expected: false, }, "Only websocket upgrade header is false": { headers: map[string]string{ "Upgrade": "websocket", }, expected: false, }, "Only websocket and connection upgrade headers is false": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", }, expected: false, }, "Missing connection/upgrade header is false": { headers: map[string]string{ "Upgrade": "websocket", WebSocketProtocolHeader: "v5.channel.k8s.io", }, expected: false, }, "Websocket connection upgrade headers with v5 protocol is true": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", WebSocketProtocolHeader: "v5.channel.k8s.io", }, expected: true, }, "Websocket connection upgrade headers with wrong case v5 protocol is false": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", WebSocketProtocolHeader: "v5.CHANNEL.k8s.io", // header value is case-sensitive }, expected: false, }, "Websocket connection upgrade headers with v4 protocol is false": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", WebSocketProtocolHeader: "v4.channel.k8s.io", }, expected: false, }, "Websocket connection upgrade headers with multiple protocols but missing v5 is false": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", WebSocketProtocolHeader: "v4.channel.k8s.io,v3.channel.k8s.io,v2.channel.k8s.io", }, expected: false, }, "Websocket connection upgrade headers with multiple protocols including v5 and spaces is true": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", WebSocketProtocolHeader: "v5.channel.k8s.io, v4.channel.k8s.io", }, expected: true, }, "Websocket connection upgrade headers with multiple protocols out of order including v5 and spaces is true": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", WebSocketProtocolHeader: "v4.channel.k8s.io, v5.channel.k8s.io, v3.channel.k8s.io", }, expected: true, }, "Websocket connection upgrade headers key is case-insensitive": { headers: map[string]string{ "Connection": "upgrade", "Upgrade": "websocket", "sec-websocket-protocol": "v4.channel.k8s.io, v5.channel.k8s.io, v3.channel.k8s.io", }, expected: true, }, } for name, test := range tests { req, err := http.NewRequest("GET", "http://www.example.com/", nil) require.NoError(t, err) for key, value := range test.headers { req.Header.Add(key, value) } actual := IsWebSocketRequestWithStreamCloseProtocol(req) assert.Equal(t, test.expected, actual, "%s: expected (%t), got (%t)", name, test.expected, actual) } } func TestProtocolSupportsStreamClose(t *testing.T) { tests := map[string]struct { protocol string expected bool }{ "empty protocol returns false": { protocol: "", expected: false, }, "not binary protocol returns false": { protocol: "base64.channel.k8s.io", expected: false, }, "V1 protocol returns false": { protocol: "channel.k8s.io", expected: false, }, "V4 protocol returns false": { protocol: "v4.channel.k8s.io", expected: false, }, "V5 protocol returns true": { protocol: "v5.channel.k8s.io", expected: true, }, "V5 protocol wrong case returns false": { protocol: "V5.channel.K8S.io", expected: false, }, } for name, test := range tests { actual := protocolSupportsStreamClose(test.protocol) assert.Equal(t, test.expected, actual, "%s: expected (%t), got (%t)", name, test.expected, actual) } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/wsstream/doc.go000066400000000000000000000056601453143165200251300ustar00rootroot00000000000000/* Copyright 2015 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 wsstream contains utilities for streaming content over WebSockets. // The Conn type allows callers to multiplex multiple read/write channels over // a single websocket. // // "channel.k8s.io" // // The Websocket RemoteCommand subprotocol "channel.k8s.io" prepends each binary message with a // byte indicating the channel number (zero indexed) the message was sent on. Messages in both // directions should prefix their messages with this channel byte. Used for remote execution, // the channel numbers are by convention defined to match the POSIX file-descriptors assigned // to STDIN, STDOUT, and STDERR (0, 1, and 2). No other conversion is performed on the raw // subprotocol - writes are sent as they are received by the server. // // Example client session: // // CONNECT http://server.com with subprotocol "channel.k8s.io" // WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN) // READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT) // CLOSE // // "v2.channel.k8s.io" // // The second Websocket subprotocol version "v2.channel.k8s.io" is the same as version 1, // but it is the first "versioned" subprotocol. // // "v3.channel.k8s.io" // // The third version of the Websocket RemoteCommand subprotocol adds another channel // for terminal resizing events. This channel is prepended with the byte '3', and it // transmits two window sizes (encoding TerminalSize struct) with integers in the range // (0,65536]. // // "v4.channel.k8s.io" // // The fourth version of the Websocket RemoteCommand subprotocol adds a channel for // errors. This channel returns structured errors containing process exit codes. The // error is "apierrors.StatusError{}". // // "v5.channel.k8s.io" // // The fifth version of the Websocket RemoteCommand subprotocol adds a CLOSE signal, // which is sent as the first byte of the message. The second byte is the channel // id. This CLOSE signal is handled by the websocket server by closing the stream, // allowing the other streams to complete transmission if necessary, and gracefully // shutdown the connection. // // Example client session: // // CONNECT http://server.com with subprotocol "v5.channel.k8s.io" // WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN) // WRITE []byte{255, 0} # send CLOSE signal (STDIN) // CLOSE package wsstream // import "k8s.io/apimachinery/pkg/util/httpstream/wsstream" golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/wsstream/stream.go000066400000000000000000000122431453143165200256510ustar00rootroot00000000000000/* Copyright 2015 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 wsstream import ( "encoding/base64" "io" "net/http" "sync" "time" "golang.org/x/net/websocket" "k8s.io/apimachinery/pkg/util/runtime" ) // The WebSocket subprotocol "binary.k8s.io" will only send messages to the // client and ignore messages sent to the server. The received messages are // the exact bytes written to the stream. Zero byte messages are possible. const binaryWebSocketProtocol = "binary.k8s.io" // The WebSocket subprotocol "base64.binary.k8s.io" will only send messages to the // client and ignore messages sent to the server. The received messages are // a base64 version of the bytes written to the stream. Zero byte messages are // possible. const base64BinaryWebSocketProtocol = "base64.binary.k8s.io" // ReaderProtocolConfig describes a websocket subprotocol with one stream. type ReaderProtocolConfig struct { Binary bool } // NewDefaultReaderProtocols returns a stream protocol map with the // subprotocols "", "channel.k8s.io", "base64.channel.k8s.io". func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig { return map[string]ReaderProtocolConfig{ "": {Binary: true}, binaryWebSocketProtocol: {Binary: true}, base64BinaryWebSocketProtocol: {Binary: false}, } } // Reader supports returning an arbitrary byte stream over a websocket channel. type Reader struct { err chan error r io.Reader ping bool timeout time.Duration protocols map[string]ReaderProtocolConfig selectedProtocol string handleCrash func(additionalHandlers ...func(interface{})) // overridable for testing } // NewReader creates a WebSocket pipe that will copy the contents of r to a provided // WebSocket connection. If ping is true, a zero length message will be sent to the client // before the stream begins reading. // // The protocols parameter maps subprotocol names to StreamProtocols. The empty string // subprotocol name is used if websocket.Config.Protocol is empty. func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader { return &Reader{ r: r, err: make(chan error), ping: ping, protocols: protocols, handleCrash: runtime.HandleCrash, } } // SetIdleTimeout sets the interval for both reads and writes before timeout. If not specified, // there is no timeout on the reader. func (r *Reader) SetIdleTimeout(duration time.Duration) { r.timeout = duration } func (r *Reader) handshake(config *websocket.Config, req *http.Request) error { supportedProtocols := make([]string, 0, len(r.protocols)) for p := range r.protocols { supportedProtocols = append(supportedProtocols, p) } return handshake(config, req, supportedProtocols) } // Copy the reader to the response. The created WebSocket is closed after this // method completes. func (r *Reader) Copy(w http.ResponseWriter, req *http.Request) error { go func() { defer r.handleCrash() websocket.Server{Handshake: r.handshake, Handler: r.handle}.ServeHTTP(w, req) }() return <-r.err } // handle implements a WebSocket handler. func (r *Reader) handle(ws *websocket.Conn) { // Close the connection when the client requests it, or when we finish streaming, whichever happens first closeConnOnce := &sync.Once{} closeConn := func() { closeConnOnce.Do(func() { ws.Close() }) } negotiated := ws.Config().Protocol r.selectedProtocol = negotiated[0] defer close(r.err) defer closeConn() go func() { defer runtime.HandleCrash() // This blocks until the connection is closed. // Client should not send anything. IgnoreReceives(ws, r.timeout) // Once the client closes, we should also close closeConn() }() r.err <- messageCopy(ws, r.r, !r.protocols[r.selectedProtocol].Binary, r.ping, r.timeout) } func resetTimeout(ws *websocket.Conn, timeout time.Duration) { if timeout > 0 { ws.SetDeadline(time.Now().Add(timeout)) } } func messageCopy(ws *websocket.Conn, r io.Reader, base64Encode, ping bool, timeout time.Duration) error { buf := make([]byte, 2048) if ping { resetTimeout(ws, timeout) if base64Encode { if err := websocket.Message.Send(ws, ""); err != nil { return err } } else { if err := websocket.Message.Send(ws, []byte{}); err != nil { return err } } } for { resetTimeout(ws, timeout) n, err := r.Read(buf) if err != nil { if err == io.EOF { return nil } return err } if n > 0 { if base64Encode { if err := websocket.Message.Send(ws, base64.StdEncoding.EncodeToString(buf[:n])); err != nil { return err } } else { if err := websocket.Message.Send(ws, buf[:n]); err != nil { return err } } } } } golang-k8s-apimachinery-0.29.0/pkg/util/httpstream/wsstream/stream_test.go000066400000000000000000000166601453143165200267170ustar00rootroot00000000000000/* Copyright 2015 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 wsstream import ( "bytes" "encoding/base64" "fmt" "io" "net/http" "reflect" "strings" "testing" "time" "golang.org/x/net/websocket" ) func TestStream(t *testing.T) { input := "some random text" r := NewReader(bytes.NewBuffer([]byte(input)), true, NewDefaultReaderProtocols()) r.SetIdleTimeout(time.Second) data, err := readWebSocket(r, t, nil) if !reflect.DeepEqual(data, []byte(input)) { t.Errorf("unexpected server read: %v", data) } if err != nil { t.Fatal(err) } } func TestStreamPing(t *testing.T) { input := "some random text" r := NewReader(bytes.NewBuffer([]byte(input)), true, NewDefaultReaderProtocols()) r.SetIdleTimeout(time.Second) err := expectWebSocketFrames(r, t, nil, [][]byte{ {}, []byte(input), }) if err != nil { t.Fatal(err) } } func TestStreamBase64(t *testing.T) { input := "some random text" encoded := base64.StdEncoding.EncodeToString([]byte(input)) r := NewReader(bytes.NewBuffer([]byte(input)), true, NewDefaultReaderProtocols()) data, err := readWebSocket(r, t, nil, "base64.binary.k8s.io") if !reflect.DeepEqual(data, []byte(encoded)) { t.Errorf("unexpected server read: %v\n%v", data, []byte(encoded)) } if err != nil { t.Fatal(err) } } func TestStreamVersionedBase64(t *testing.T) { input := "some random text" encoded := base64.StdEncoding.EncodeToString([]byte(input)) r := NewReader(bytes.NewBuffer([]byte(input)), true, map[string]ReaderProtocolConfig{ "": {Binary: true}, "binary.k8s.io": {Binary: true}, "base64.binary.k8s.io": {Binary: false}, "v1.binary.k8s.io": {Binary: true}, "v1.base64.binary.k8s.io": {Binary: false}, "v2.binary.k8s.io": {Binary: true}, "v2.base64.binary.k8s.io": {Binary: false}, }) data, err := readWebSocket(r, t, nil, "v2.base64.binary.k8s.io") if !reflect.DeepEqual(data, []byte(encoded)) { t.Errorf("unexpected server read: %v\n%v", data, []byte(encoded)) } if err != nil { t.Fatal(err) } } func TestStreamVersionedCopy(t *testing.T) { for i, test := range versionTests() { func() { supportedProtocols := map[string]ReaderProtocolConfig{} for p, binary := range test.supported { supportedProtocols[p] = ReaderProtocolConfig{ Binary: binary, } } input := "some random text" r := NewReader(bytes.NewBuffer([]byte(input)), true, supportedProtocols) s, addr := newServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { err := r.Copy(w, req) if err != nil { w.WriteHeader(503) } })) defer s.Close() config, err := websocket.NewConfig("ws://"+addr, "http://localhost/") if err != nil { t.Error(err) return } config.Protocol = test.requested client, err := websocket.DialConfig(config) if err != nil { if !test.error { t.Errorf("test %d: didn't expect error: %v", i, err) } return } defer client.Close() if test.error && err == nil { t.Errorf("test %d: expected an error", i) return } <-r.err if got, expected := r.selectedProtocol, test.expected; got != expected { t.Errorf("test %d: unexpected protocol version: got=%s expected=%s", i, got, expected) } }() } } func TestStreamError(t *testing.T) { input := "some random text" errs := &errorReader{ reads: [][]byte{ []byte("some random"), []byte(" text"), }, err: fmt.Errorf("bad read"), } r := NewReader(errs, false, NewDefaultReaderProtocols()) data, err := readWebSocket(r, t, nil) if !reflect.DeepEqual(data, []byte(input)) { t.Errorf("unexpected server read: %v", data) } if err == nil || err.Error() != "bad read" { t.Fatal(err) } } func TestStreamSurvivesPanic(t *testing.T) { input := "some random text" errs := &errorReader{ reads: [][]byte{ []byte("some random"), []byte(" text"), }, panicMessage: "bad read", } r := NewReader(errs, false, NewDefaultReaderProtocols()) // do not call runtime.HandleCrash() in handler. Otherwise, the tests are interrupted. r.handleCrash = func(additionalHandlers ...func(interface{})) { recover() } data, err := readWebSocket(r, t, nil) if !reflect.DeepEqual(data, []byte(input)) { t.Errorf("unexpected server read: %v", data) } if err != nil { t.Fatal(err) } } func TestStreamClosedDuringRead(t *testing.T) { for i := 0; i < 25; i++ { ch := make(chan struct{}) input := "some random text" errs := &errorReader{ reads: [][]byte{ []byte("some random"), []byte(" text"), }, err: fmt.Errorf("stuff"), pause: ch, } r := NewReader(errs, false, NewDefaultReaderProtocols()) data, err := readWebSocket(r, t, func(c *websocket.Conn) { c.Close() close(ch) }) // verify that the data returned by the server on an early close always has a specific error if err == nil || !strings.Contains(err.Error(), "use of closed network connection") { t.Fatal(err) } // verify that the data returned is a strict subset of the input if !bytes.HasPrefix([]byte(input), data) && len(data) != 0 { t.Fatalf("unexpected server read: %q", string(data)) } } } type errorReader struct { reads [][]byte err error panicMessage string pause chan struct{} } func (r *errorReader) Read(p []byte) (int, error) { if len(r.reads) == 0 { if r.pause != nil { <-r.pause } if len(r.panicMessage) != 0 { panic(r.panicMessage) } return 0, r.err } next := r.reads[0] r.reads = r.reads[1:] copy(p, next) return len(next), nil } func readWebSocket(r *Reader, t *testing.T, fn func(*websocket.Conn), protocols ...string) ([]byte, error) { errCh := make(chan error, 1) s, addr := newServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { errCh <- r.Copy(w, req) })) defer s.Close() config, _ := websocket.NewConfig("ws://"+addr, "http://"+addr) config.Protocol = protocols client, err := websocket.DialConfig(config) if err != nil { return nil, err } defer client.Close() if fn != nil { fn(client) } data, err := io.ReadAll(client) if err != nil { return data, err } return data, <-errCh } func expectWebSocketFrames(r *Reader, t *testing.T, fn func(*websocket.Conn), frames [][]byte, protocols ...string) error { errCh := make(chan error, 1) s, addr := newServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { errCh <- r.Copy(w, req) })) defer s.Close() config, _ := websocket.NewConfig("ws://"+addr, "http://"+addr) config.Protocol = protocols ws, err := websocket.DialConfig(config) if err != nil { return err } defer ws.Close() if fn != nil { fn(ws) } for i := range frames { var data []byte if err := websocket.Message.Receive(ws, &data); err != nil { return err } if !reflect.DeepEqual(frames[i], data) { return fmt.Errorf("frame %d did not match expected: %v", data, err) } } var data []byte if err := websocket.Message.Receive(ws, &data); err != io.EOF { return fmt.Errorf("expected no more frames: %v (%v)", err, data) } return <-errCh } golang-k8s-apimachinery-0.29.0/pkg/util/intstr/000077500000000000000000000000001453143165200213105ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/intstr/generated.pb.go000066400000000000000000000232621453143165200242020ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/intstr/generated.proto package intstr import ( fmt "fmt" io "io" math "math" math_bits "math/bits" proto "github.com/gogo/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package func (m *IntOrString) Reset() { *m = IntOrString{} } func (*IntOrString) ProtoMessage() {} func (*IntOrString) Descriptor() ([]byte, []int) { return fileDescriptor_94e046ae3ce6121c, []int{0} } func (m *IntOrString) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *IntOrString) 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 *IntOrString) XXX_Merge(src proto.Message) { xxx_messageInfo_IntOrString.Merge(m, src) } func (m *IntOrString) XXX_Size() int { return m.Size() } func (m *IntOrString) XXX_DiscardUnknown() { xxx_messageInfo_IntOrString.DiscardUnknown(m) } var xxx_messageInfo_IntOrString proto.InternalMessageInfo func init() { proto.RegisterType((*IntOrString)(nil), "k8s.io.apimachinery.pkg.util.intstr.IntOrString") } func init() { proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/intstr/generated.proto", fileDescriptor_94e046ae3ce6121c) } var fileDescriptor_94e046ae3ce6121c = []byte{ // 292 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0xb1, 0x4a, 0x03, 0x31, 0x1c, 0xc6, 0x13, 0x5b, 0x8b, 0x9e, 0xe0, 0x50, 0x1c, 0x8a, 0x43, 0x7a, 0x58, 0x90, 0x5b, 0x4c, 0x56, 0x71, 0xec, 0x56, 0x10, 0x84, 0x56, 0x1c, 0xdc, 0xee, 0xda, 0x98, 0x86, 0x6b, 0x93, 0x90, 0xfb, 0x9f, 0x70, 0x5b, 0x1f, 0x41, 0x37, 0x47, 0x1f, 0xe7, 0xc6, 0x8e, 0x1d, 0xa4, 0x78, 0xf1, 0x2d, 0x9c, 0xe4, 0x72, 0x07, 0x3a, 0x3a, 0x25, 0xdf, 0xf7, 0xfd, 0x7e, 0x19, 0x12, 0xdc, 0xa6, 0xd7, 0x19, 0x95, 0x9a, 0xa5, 0x79, 0xc2, 0xad, 0xe2, 0xc0, 0x33, 0xf6, 0xcc, 0xd5, 0x42, 0x5b, 0xd6, 0x0e, 0xb1, 0x91, 0xeb, 0x78, 0xbe, 0x94, 0x8a, 0xdb, 0x82, 0x99, 0x54, 0xb0, 0x1c, 0xe4, 0x8a, 0x49, 0x05, 0x19, 0x58, 0x26, 0xb8, 0xe2, 0x36, 0x06, 0xbe, 0xa0, 0xc6, 0x6a, 0xd0, 0xfd, 0x51, 0x23, 0xd1, 0xbf, 0x12, 0x35, 0xa9, 0xa0, 0xb5, 0x44, 0x1b, 0xe9, 0xfc, 0x4a, 0x48, 0x58, 0xe6, 0x09, 0x9d, 0xeb, 0x35, 0x13, 0x5a, 0x68, 0xe6, 0xdd, 0x24, 0x7f, 0xf2, 0xc9, 0x07, 0x7f, 0x6b, 0xde, 0xbc, 0x78, 0xc5, 0xc1, 0xc9, 0x44, 0xc1, 0x9d, 0x9d, 0x81, 0x95, 0x4a, 0xf4, 0xa3, 0xa0, 0x0b, 0x85, 0xe1, 0x03, 0x1c, 0xe2, 0xa8, 0x33, 0x3e, 0x2b, 0xf7, 0x43, 0xe4, 0xf6, 0xc3, 0xee, 0x7d, 0x61, 0xf8, 0x77, 0x7b, 0x4e, 0x3d, 0xd1, 0xbf, 0x0c, 0x7a, 0x52, 0xc1, 0x43, 0xbc, 0x1a, 0x1c, 0x84, 0x38, 0x3a, 0x1c, 0x9f, 0xb6, 0x6c, 0x6f, 0xe2, 0xdb, 0x69, 0xbb, 0xd6, 0x5c, 0x06, 0xb6, 0xe6, 0x3a, 0x21, 0x8e, 0x8e, 0x7f, 0xb9, 0x99, 0x6f, 0xa7, 0xed, 0x7a, 0x73, 0xf4, 0xf6, 0x3e, 0x44, 0x9b, 0x8f, 0x10, 0x8d, 0x27, 0x65, 0x45, 0xd0, 0xb6, 0x22, 0x68, 0x57, 0x11, 0xb4, 0x71, 0x04, 0x97, 0x8e, 0xe0, 0xad, 0x23, 0x78, 0xe7, 0x08, 0xfe, 0x74, 0x04, 0xbf, 0x7c, 0x11, 0xf4, 0x38, 0xfa, 0xc7, 0x17, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xdc, 0xc4, 0xf0, 0xa0, 0x81, 0x01, 0x00, 0x00, } func (m *IntOrString) 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 *IntOrString) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *IntOrString) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l i -= len(m.StrVal) copy(dAtA[i:], m.StrVal) i = encodeVarintGenerated(dAtA, i, uint64(len(m.StrVal))) i-- dAtA[i] = 0x1a i = encodeVarintGenerated(dAtA, i, uint64(m.IntVal)) i-- dAtA[i] = 0x10 i = encodeVarintGenerated(dAtA, i, uint64(m.Type)) i-- dAtA[i] = 0x8 return len(dAtA) - i, nil } func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { offset -= sovGenerated(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *IntOrString) Size() (n int) { if m == nil { return 0 } var l int _ = l n += 1 + sovGenerated(uint64(m.Type)) n += 1 + sovGenerated(uint64(m.IntVal)) l = len(m.StrVal) n += 1 + l + sovGenerated(uint64(l)) return n } func sovGenerated(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *IntOrString) 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: IntOrString: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: IntOrString: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) } m.Type = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.Type |= Type(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field IntVal", wireType) } m.IntVal = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.IntVal |= int32(b&0x7F) << shift if b < 0x80 { break } } case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StrVal", 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.StrVal = string(dAtA[iNdEx:postIndex]) 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 skipGenerated(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowGenerated } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthGenerated } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupGenerated } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthGenerated } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") ) golang-k8s-apimachinery-0.29.0/pkg/util/intstr/generated.proto000066400000000000000000000024551453143165200243410ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This file was autogenerated by go-to-protobuf. Do not edit it manually! syntax = "proto2"; package k8s.io.apimachinery.pkg.util.intstr; // Package-wide variables from generator "generated". option go_package = "k8s.io/apimachinery/pkg/util/intstr"; // IntOrString is a type that can hold an int32 or a string. When used in // JSON or YAML marshalling and unmarshalling, it produces or consumes the // inner type. This allows you to have, for example, a JSON field that can // accept a name or number. // TODO: Rename to Int32OrString // // +protobuf=true // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:openapi-gen=true message IntOrString { optional int64 type = 1; optional int32 intVal = 2; optional string strVal = 3; } golang-k8s-apimachinery-0.29.0/pkg/util/intstr/instr_fuzz.go000066400000000000000000000020211453143165200240470ustar00rootroot00000000000000//go:build !notest // +build !notest /* Copyright 2020 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 intstr import ( fuzz "github.com/google/gofuzz" ) // Fuzz satisfies fuzz.Interface func (intstr *IntOrString) Fuzz(c fuzz.Continue) { if intstr == nil { return } if c.RandBool() { intstr.Type = Int c.Fuzz(&intstr.IntVal) intstr.StrVal = "" } else { intstr.Type = String intstr.IntVal = 0 c.Fuzz(&intstr.StrVal) } } // ensure IntOrString implements fuzz.Interface var _ fuzz.Interface = &IntOrString{} golang-k8s-apimachinery-0.29.0/pkg/util/intstr/intstr.go000066400000000000000000000164011453143165200231640ustar00rootroot00000000000000/* 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 intstr import ( "encoding/json" "errors" "fmt" "math" "runtime/debug" "strconv" "strings" "k8s.io/klog/v2" ) // IntOrString is a type that can hold an int32 or a string. When used in // JSON or YAML marshalling and unmarshalling, it produces or consumes the // inner type. This allows you to have, for example, a JSON field that can // accept a name or number. // TODO: Rename to Int32OrString // // +protobuf=true // +protobuf.options.(gogoproto.goproto_stringer)=false // +k8s:openapi-gen=true type IntOrString struct { Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"` IntVal int32 `protobuf:"varint,2,opt,name=intVal"` StrVal string `protobuf:"bytes,3,opt,name=strVal"` } // Type represents the stored type of IntOrString. type Type int64 const ( Int Type = iota // The IntOrString holds an int. String // The IntOrString holds a string. ) // FromInt creates an IntOrString object with an int32 value. It is // your responsibility not to call this method with a value greater // than int32. // Deprecated: use FromInt32 instead. func FromInt(val int) IntOrString { if val > math.MaxInt32 || val < math.MinInt32 { klog.Errorf("value: %d overflows int32\n%s\n", val, debug.Stack()) } return IntOrString{Type: Int, IntVal: int32(val)} } // FromInt32 creates an IntOrString object with an int32 value. func FromInt32(val int32) IntOrString { return IntOrString{Type: Int, IntVal: val} } // FromString creates an IntOrString object with a string value. func FromString(val string) IntOrString { return IntOrString{Type: String, StrVal: val} } // Parse the given string and try to convert it to an int32 integer before // setting it as a string value. func Parse(val string) IntOrString { i, err := strconv.ParseInt(val, 10, 32) if err != nil { return FromString(val) } return FromInt32(int32(i)) } // UnmarshalJSON implements the json.Unmarshaller interface. func (intstr *IntOrString) UnmarshalJSON(value []byte) error { if value[0] == '"' { intstr.Type = String return json.Unmarshal(value, &intstr.StrVal) } intstr.Type = Int return json.Unmarshal(value, &intstr.IntVal) } // String returns the string value, or the Itoa of the int value. func (intstr *IntOrString) String() string { if intstr == nil { return "" } if intstr.Type == String { return intstr.StrVal } return strconv.Itoa(intstr.IntValue()) } // IntValue returns the IntVal if type Int, or if // it is a String, will attempt a conversion to int, // returning 0 if a parsing error occurs. func (intstr *IntOrString) IntValue() int { if intstr.Type == String { i, _ := strconv.Atoi(intstr.StrVal) return i } return int(intstr.IntVal) } // MarshalJSON implements the json.Marshaller interface. func (intstr IntOrString) MarshalJSON() ([]byte, error) { switch intstr.Type { case Int: return json.Marshal(intstr.IntVal) case String: return json.Marshal(intstr.StrVal) default: return []byte{}, fmt.Errorf("impossible IntOrString.Type") } } // OpenAPISchemaType is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. // // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators func (IntOrString) OpenAPISchemaType() []string { return []string{"string"} } // OpenAPISchemaFormat is used by the kube-openapi generator when constructing // the OpenAPI spec of this type. func (IntOrString) OpenAPISchemaFormat() string { return "int-or-string" } // OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing // the OpenAPI v3 spec of this type. func (IntOrString) OpenAPIV3OneOfTypes() []string { return []string{"integer", "string"} } func ValueOrDefault(intOrPercent *IntOrString, defaultValue IntOrString) *IntOrString { if intOrPercent == nil { return &defaultValue } return intOrPercent } // GetScaledValueFromIntOrPercent is meant to replace GetValueFromIntOrPercent. // This method returns a scaled value from an IntOrString type. If the IntOrString // is a percentage string value it's treated as a percentage and scaled appropriately // in accordance to the total, if it's an int value it's treated as a simple value and // if it is a string value which is either non-numeric or numeric but lacking a trailing '%' it returns an error. func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) { if intOrPercent == nil { return 0, errors.New("nil value for IntOrString") } value, isPercent, err := getIntOrPercentValueSafely(intOrPercent) if err != nil { return 0, fmt.Errorf("invalid value for IntOrString: %v", err) } if isPercent { if roundUp { value = int(math.Ceil(float64(value) * (float64(total)) / 100)) } else { value = int(math.Floor(float64(value) * (float64(total)) / 100)) } } return value, nil } // GetValueFromIntOrPercent was deprecated in favor of // GetScaledValueFromIntOrPercent. This method was treating all int as a numeric value and all // strings with or without a percent symbol as a percentage value. // Deprecated func GetValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) { if intOrPercent == nil { return 0, errors.New("nil value for IntOrString") } value, isPercent, err := getIntOrPercentValue(intOrPercent) if err != nil { return 0, fmt.Errorf("invalid value for IntOrString: %v", err) } if isPercent { if roundUp { value = int(math.Ceil(float64(value) * (float64(total)) / 100)) } else { value = int(math.Floor(float64(value) * (float64(total)) / 100)) } } return value, nil } // getIntOrPercentValue is a legacy function and only meant to be called by GetValueFromIntOrPercent // For a more correct implementation call getIntOrPercentSafely func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) { switch intOrStr.Type { case Int: return intOrStr.IntValue(), false, nil case String: s := strings.Replace(intOrStr.StrVal, "%", "", -1) v, err := strconv.Atoi(s) if err != nil { return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err) } return int(v), true, nil } return 0, false, fmt.Errorf("invalid type: neither int nor percentage") } func getIntOrPercentValueSafely(intOrStr *IntOrString) (int, bool, error) { switch intOrStr.Type { case Int: return intOrStr.IntValue(), false, nil case String: isPercent := false s := intOrStr.StrVal if strings.HasSuffix(s, "%") { isPercent = true s = strings.TrimSuffix(intOrStr.StrVal, "%") } else { return 0, false, fmt.Errorf("invalid type: string is not a percentage") } v, err := strconv.Atoi(s) if err != nil { return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err) } return int(v), isPercent, nil } return 0, false, fmt.Errorf("invalid type: neither int nor percentage") } golang-k8s-apimachinery-0.29.0/pkg/util/intstr/intstr_test.go000066400000000000000000000166631453143165200242350ustar00rootroot00000000000000/* 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 intstr import ( "encoding/json" "reflect" "testing" "sigs.k8s.io/yaml" ) func TestFromInt(t *testing.T) { i := FromInt(93) if i.Type != Int || i.IntVal != 93 { t.Errorf("Expected IntVal=93, got %+v", i) } } func TestFromInt32(t *testing.T) { i := FromInt32(93) if i.Type != Int || i.IntVal != 93 { t.Errorf("Expected IntVal=93, got %+v", i) } } func TestFromString(t *testing.T) { i := FromString("76") if i.Type != String || i.StrVal != "76" { t.Errorf("Expected StrVal=\"76\", got %+v", i) } } type IntOrStringHolder struct { IOrS IntOrString `json:"val"` } func TestIntOrStringUnmarshalJSON(t *testing.T) { cases := []struct { input string result IntOrString }{ {"{\"val\": 123}", FromInt32(123)}, {"{\"val\": \"123\"}", FromString("123")}, } for _, c := range cases { var result IntOrStringHolder if err := json.Unmarshal([]byte(c.input), &result); err != nil { t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) } if result.IOrS != c.result { t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) } } } func TestIntOrStringMarshalJSON(t *testing.T) { cases := []struct { input IntOrString result string }{ {FromInt32(123), "{\"val\":123}"}, {FromString("123"), "{\"val\":\"123\"}"}, } for _, c := range cases { input := IntOrStringHolder{c.input} result, err := json.Marshal(&input) if err != nil { t.Errorf("Failed to marshal input '%v': %v", input, err) } if string(result) != c.result { t.Errorf("Failed to marshal input '%v': expected: %+v, got %q", input, c.result, string(result)) } } } func TestIntOrStringMarshalJSONUnmarshalYAML(t *testing.T) { cases := []struct { input IntOrString }{ {FromInt32(123)}, {FromString("123")}, } for _, c := range cases { input := IntOrStringHolder{c.input} jsonMarshalled, err := json.Marshal(&input) if err != nil { t.Errorf("1: Failed to marshal input: '%v': %v", input, err) } var result IntOrStringHolder err = yaml.Unmarshal(jsonMarshalled, &result) if err != nil { t.Errorf("2: Failed to unmarshal '%+v': %v", string(jsonMarshalled), err) } if !reflect.DeepEqual(input, result) { t.Errorf("3: Failed to marshal input '%+v': got %+v", input, result) } } } func TestGetIntFromIntOrString(t *testing.T) { tests := []struct { input IntOrString expectErr bool expectVal int expectPerc bool }{ { input: FromInt32(200), expectErr: false, expectVal: 200, expectPerc: false, }, { input: FromString("200"), expectErr: true, expectPerc: false, }, { input: FromString("30%0"), expectErr: true, expectPerc: false, }, { input: FromString("40%"), expectErr: false, expectVal: 40, expectPerc: true, }, { input: FromString("%"), expectErr: true, expectPerc: false, }, { input: FromString("a%"), expectErr: true, expectPerc: false, }, { input: FromString("a"), expectErr: true, expectPerc: false, }, { input: FromString("40#"), expectErr: true, expectPerc: false, }, { input: FromString("40%%"), expectErr: true, expectPerc: false, }, } for _, test := range tests { t.Run("", func(t *testing.T) { value, isPercent, err := getIntOrPercentValueSafely(&test.input) if test.expectVal != value { t.Fatalf("expected value does not match, expected: %d, got: %d", test.expectVal, value) } if test.expectPerc != isPercent { t.Fatalf("expected percent does not match, expected: %t, got: %t", test.expectPerc, isPercent) } if test.expectErr != (err != nil) { t.Fatalf("expected error does not match, expected error: %v, got: %v", test.expectErr, err) } }) } } func TestGetIntFromIntOrPercent(t *testing.T) { tests := []struct { input IntOrString total int roundUp bool expectErr bool expectVal int }{ { input: FromInt32(123), expectErr: false, expectVal: 123, }, { input: FromString("90%"), total: 100, roundUp: true, expectErr: false, expectVal: 90, }, { input: FromString("90%"), total: 95, roundUp: true, expectErr: false, expectVal: 86, }, { input: FromString("90%"), total: 95, roundUp: false, expectErr: false, expectVal: 85, }, { input: FromString("%"), expectErr: true, }, { input: FromString("90#"), expectErr: true, }, { input: FromString("#%"), expectErr: true, }, { input: FromString("90"), expectErr: true, }, } for i, test := range tests { t.Logf("test case %d", i) value, err := GetScaledValueFromIntOrPercent(&test.input, test.total, test.roundUp) if test.expectErr && err == nil { t.Errorf("expected error, but got none") continue } if !test.expectErr && err != nil { t.Errorf("unexpected err: %v", err) continue } if test.expectVal != value { t.Errorf("expected %v, but got %v", test.expectVal, value) } } } func TestGetValueFromIntOrPercentNil(t *testing.T) { _, err := GetScaledValueFromIntOrPercent(nil, 0, false) if err == nil { t.Errorf("expected error got none") } } func TestParse(t *testing.T) { tests := []struct { input string output IntOrString }{ { input: "0", output: IntOrString{Type: Int, IntVal: 0}, }, { input: "2147483647", // math.MaxInt32 output: IntOrString{Type: Int, IntVal: 2147483647}, }, { input: "-2147483648", // math.MinInt32 output: IntOrString{Type: Int, IntVal: -2147483648}, }, { input: "2147483648", // math.MaxInt32+1 output: IntOrString{Type: String, StrVal: "2147483648"}, }, { input: "-2147483649", // math.MinInt32-1 output: IntOrString{Type: String, StrVal: "-2147483649"}, }, { input: "9223372036854775807", // math.MaxInt64 output: IntOrString{Type: String, StrVal: "9223372036854775807"}, }, { input: "-9223372036854775808", // math.MinInt64 output: IntOrString{Type: String, StrVal: "-9223372036854775808"}, }, { input: "9223372036854775808", // math.MaxInt64+1 output: IntOrString{Type: String, StrVal: "9223372036854775808"}, }, { input: "-9223372036854775809", // math.MinInt64-1 output: IntOrString{Type: String, StrVal: "-9223372036854775809"}, }, } for i, test := range tests { t.Logf("test case %d", i) value := Parse(test.input) if test.output.Type != value.Type { t.Errorf("expected type %d (%v), but got %d (%v)", test.output.Type, test.output, value.Type, value) continue } if value.Type == Int && test.output.IntVal != value.IntVal { t.Errorf("expected int value %d (%v), but got %d (%v)", test.output.IntVal, test.output, value.IntVal, value) continue } if value.Type == String && test.output.StrVal != value.StrVal { t.Errorf("expected string value %q (%v), but got %q (%v)", test.output.StrVal, test.output, value.StrVal, value) } } } golang-k8s-apimachinery-0.29.0/pkg/util/json/000077500000000000000000000000001453143165200207365ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/json/json.go000066400000000000000000000066371453143165200222520ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package json import ( "encoding/json" "fmt" "io" kjson "sigs.k8s.io/json" ) // NewEncoder delegates to json.NewEncoder // It is only here so this package can be a drop-in for common encoding/json uses func NewEncoder(w io.Writer) *json.Encoder { return json.NewEncoder(w) } // Marshal delegates to json.Marshal // It is only here so this package can be a drop-in for common encoding/json uses func Marshal(v interface{}) ([]byte, error) { return json.Marshal(v) } // limit recursive depth to prevent stack overflow errors const maxDepth = 10000 // Unmarshal unmarshals the given data. // Object keys are case-sensitive. // Numbers decoded into interface{} fields are converted to int64 or float64. func Unmarshal(data []byte, v interface{}) error { return kjson.UnmarshalCaseSensitivePreserveInts(data, v) } // ConvertInterfaceNumbers converts any json.Number values to int64 or float64. // Values which are map[string]interface{} or []interface{} are recursively visited func ConvertInterfaceNumbers(v *interface{}, depth int) error { var err error switch v2 := (*v).(type) { case json.Number: *v, err = convertNumber(v2) case map[string]interface{}: err = ConvertMapNumbers(v2, depth+1) case []interface{}: err = ConvertSliceNumbers(v2, depth+1) } return err } // ConvertMapNumbers traverses the map, converting any json.Number values to int64 or float64. // values which are map[string]interface{} or []interface{} are recursively visited func ConvertMapNumbers(m map[string]interface{}, depth int) error { if depth > maxDepth { return fmt.Errorf("exceeded max depth of %d", maxDepth) } var err error for k, v := range m { switch v := v.(type) { case json.Number: m[k], err = convertNumber(v) case map[string]interface{}: err = ConvertMapNumbers(v, depth+1) case []interface{}: err = ConvertSliceNumbers(v, depth+1) } if err != nil { return err } } return nil } // ConvertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64. // values which are map[string]interface{} or []interface{} are recursively visited func ConvertSliceNumbers(s []interface{}, depth int) error { if depth > maxDepth { return fmt.Errorf("exceeded max depth of %d", maxDepth) } var err error for i, v := range s { switch v := v.(type) { case json.Number: s[i], err = convertNumber(v) case map[string]interface{}: err = ConvertMapNumbers(v, depth+1) case []interface{}: err = ConvertSliceNumbers(v, depth+1) } if err != nil { return err } } return nil } // convertNumber converts a json.Number to an int64 or float64, or returns an error func convertNumber(n json.Number) (interface{}, error) { // Attempt to convert to an int64 first if i, err := n.Int64(); err == nil { return i, nil } // Return a float64 (default json.Decode() behavior) // An overflow will return an error return n.Float64() } golang-k8s-apimachinery-0.29.0/pkg/util/json/json_test.go000066400000000000000000000224601453143165200233010ustar00rootroot00000000000000//go:build go1.8 // +build go1.8 /* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package json import ( gojson "encoding/json" "fmt" "math" "reflect" "strconv" "strings" "testing" ) func TestEvaluateTypes(t *testing.T) { testCases := []struct { In string Data interface{} Out string Err bool }{ // Invalid syntaxes { In: `x`, Err: true, }, { In: ``, Err: true, }, // Null { In: `null`, Data: nil, Out: `null`, }, // Booleans { In: `true`, Data: true, Out: `true`, }, { In: `false`, Data: false, Out: `false`, }, // Integers { In: `0`, Data: int64(0), Out: `0`, }, { In: `-0`, Data: int64(-0), Out: `0`, }, { In: `1`, Data: int64(1), Out: `1`, }, { In: `2147483647`, Data: int64(math.MaxInt32), Out: `2147483647`, }, { In: `-2147483648`, Data: int64(math.MinInt32), Out: `-2147483648`, }, { In: `9223372036854775807`, Data: int64(math.MaxInt64), Out: `9223372036854775807`, }, { In: `-9223372036854775808`, Data: int64(math.MinInt64), Out: `-9223372036854775808`, }, // Int overflow { In: `9223372036854775808`, // MaxInt64 + 1 Data: float64(9223372036854775808), Out: `9223372036854776000`, }, { In: `-9223372036854775809`, // MinInt64 - 1 Data: float64(math.MinInt64), Out: `-9223372036854776000`, }, // Floats { In: `0.0`, Data: float64(0), Out: `0`, }, { In: `-0.0`, Data: float64(-0.0), //nolint:staticcheck // SA4026: in Go, the floating-point literal '-0.0' is the same as '0.0' Out: `-0`, }, { In: `0.5`, Data: float64(0.5), Out: `0.5`, }, { In: `1e3`, Data: float64(1e3), Out: `1000`, }, { In: `1.5`, Data: float64(1.5), Out: `1.5`, }, { In: `-0.3`, Data: float64(-.3), Out: `-0.3`, }, { // Largest representable float32 In: `3.40282346638528859811704183484516925440e+38`, Data: float64(math.MaxFloat32), Out: strconv.FormatFloat(math.MaxFloat32, 'g', -1, 64), }, { // Smallest float32 without losing precision In: `1.175494351e-38`, Data: float64(1.175494351e-38), Out: `1.175494351e-38`, }, { // float32 closest to zero In: `1.401298464324817070923729583289916131280e-45`, Data: float64(math.SmallestNonzeroFloat32), Out: strconv.FormatFloat(math.SmallestNonzeroFloat32, 'g', -1, 64), }, { // Largest representable float64 In: `1.797693134862315708145274237317043567981e+308`, Data: float64(math.MaxFloat64), Out: strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64), }, { // Closest to zero without losing precision In: `2.2250738585072014e-308`, Data: float64(2.2250738585072014e-308), Out: `2.2250738585072014e-308`, }, { // float64 closest to zero In: `4.940656458412465441765687928682213723651e-324`, Data: float64(math.SmallestNonzeroFloat64), Out: strconv.FormatFloat(math.SmallestNonzeroFloat64, 'g', -1, 64), }, { // math.MaxFloat64 + 2 overflow In: `1.7976931348623159e+308`, Err: true, }, // Strings { In: `""`, Data: string(""), Out: `""`, }, { In: `"0"`, Data: string("0"), Out: `"0"`, }, { In: `"A"`, Data: string("A"), Out: `"A"`, }, { In: `"Iñtërnâtiônàlizætiøn"`, Data: string("Iñtërnâtiônàlizætiøn"), Out: `"Iñtërnâtiônàlizætiøn"`, }, // Arrays { In: `[]`, Data: []interface{}{}, Out: `[]`, }, { In: `[` + strings.Join([]string{ `null`, `true`, `false`, `0`, `9223372036854775807`, `0.0`, `0.5`, `1.0`, `1.797693134862315708145274237317043567981e+308`, `"0"`, `"A"`, `"Iñtërnâtiônàlizætiøn"`, `[null,true,1,1.0,1.5]`, `{"boolkey":true,"floatkey":1.0,"intkey":1,"nullkey":null}`, }, ",") + `]`, Data: []interface{}{ nil, true, false, int64(0), int64(math.MaxInt64), float64(0.0), float64(0.5), float64(1.0), float64(math.MaxFloat64), string("0"), string("A"), string("Iñtërnâtiônàlizætiøn"), []interface{}{nil, true, int64(1), float64(1.0), float64(1.5)}, map[string]interface{}{"nullkey": nil, "boolkey": true, "intkey": int64(1), "floatkey": float64(1.0)}, }, Out: `[` + strings.Join([]string{ `null`, `true`, `false`, `0`, `9223372036854775807`, `0`, `0.5`, `1`, strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64), `"0"`, `"A"`, `"Iñtërnâtiônàlizætiøn"`, `[null,true,1,1,1.5]`, `{"boolkey":true,"floatkey":1,"intkey":1,"nullkey":null}`, // gets alphabetized by Marshal }, ",") + `]`, }, // Maps { In: `{}`, Data: map[string]interface{}{}, Out: `{}`, }, { In: `{"boolkey":true,"floatkey":1.0,"intkey":1,"nullkey":null}`, Data: map[string]interface{}{"nullkey": nil, "boolkey": true, "intkey": int64(1), "floatkey": float64(1.0)}, Out: `{"boolkey":true,"floatkey":1,"intkey":1,"nullkey":null}`, // gets alphabetized by Marshal }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d_map", i), func(t *testing.T) { // decode the input as a map item inputJSON := fmt.Sprintf(`{"data":%s}`, tc.In) expectedJSON := fmt.Sprintf(`{"data":%s}`, tc.Out) m := map[string]interface{}{} err := Unmarshal([]byte(inputJSON), &m) if tc.Err && err != nil { // Expected error return } if err != nil { t.Fatalf("%s: error decoding: %v", tc.In, err) } if tc.Err { t.Fatalf("%s: expected error, got none", tc.In) } data, ok := m["data"] if !ok { t.Fatalf("%s: decoded object missing data key: %#v", tc.In, m) } if !reflect.DeepEqual(tc.Data, data) { t.Fatalf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data)) } outputJSON, err := Marshal(m) if err != nil { t.Fatalf("%s: error encoding: %v", tc.In, err) } if expectedJSON != string(outputJSON) { t.Fatalf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON)) } }) t.Run(fmt.Sprintf("%d_slice", i), func(t *testing.T) { // decode the input as an array item inputJSON := fmt.Sprintf(`[0,%s]`, tc.In) expectedJSON := fmt.Sprintf(`[0,%s]`, tc.Out) m := []interface{}{} err := Unmarshal([]byte(inputJSON), &m) if tc.Err && err != nil { // Expected error return } if err != nil { t.Fatalf("%s: error decoding: %v", tc.In, err) } if tc.Err { t.Fatalf("%s: expected error, got none", tc.In) } if len(m) != 2 { t.Fatalf("%s: decoded object wasn't the right length: %#v", tc.In, m) } data := m[1] if !reflect.DeepEqual(tc.Data, data) { t.Fatalf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data)) } outputJSON, err := Marshal(m) if err != nil { t.Fatalf("%s: error encoding: %v", tc.In, err) } if expectedJSON != string(outputJSON) { t.Fatalf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON)) } }) t.Run(fmt.Sprintf("%d_raw", i), func(t *testing.T) { // decode the input as a standalone object inputJSON := tc.In expectedJSON := tc.Out var m interface{} err := Unmarshal([]byte(inputJSON), &m) if tc.Err && err != nil { // Expected error return } if err != nil { t.Fatalf("%s: error decoding: %v", tc.In, err) } if tc.Err { t.Fatalf("%s: expected error, got none", tc.In) } data := m if !reflect.DeepEqual(tc.Data, data) { t.Fatalf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data)) } outputJSON, err := Marshal(m) if err != nil { t.Fatalf("%s: error encoding: %v", tc.In, err) } if expectedJSON != string(outputJSON) { t.Fatalf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON)) } }) } } func TestUnmarshalNil(t *testing.T) { { var v *interface{} err := Unmarshal([]byte(`0`), v) goerr := gojson.Unmarshal([]byte(`0`), v) if err == nil || goerr == nil || err.Error() != goerr.Error() { t.Fatalf("expected error matching stdlib, got %v, %v", err, goerr) } else { t.Log(err) } } { var v *[]interface{} err := Unmarshal([]byte(`[]`), v) goerr := gojson.Unmarshal([]byte(`[]`), v) if err == nil || goerr == nil || err.Error() != goerr.Error() { t.Fatalf("expected error matching stdlib, got %v, %v", err, goerr) } else { t.Log(err) } } { var v *map[string]interface{} err := Unmarshal([]byte(`{}`), v) goerr := gojson.Unmarshal([]byte(`{}`), v) if err == nil || goerr == nil || err.Error() != goerr.Error() { t.Fatalf("expected error matching stdlib, got %v, %v", err, goerr) } else { t.Log(err) } } } golang-k8s-apimachinery-0.29.0/pkg/util/jsonmergepatch/000077500000000000000000000000001453143165200227765ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/jsonmergepatch/patch.go000066400000000000000000000114011453143165200244210ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package jsonmergepatch import ( "fmt" "reflect" "github.com/evanphx/json-patch" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/mergepatch" ) // Create a 3-way merge patch based-on JSON merge patch. // Calculate addition-and-change patch between current and modified. // Calculate deletion patch between original and modified. func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) { if len(original) == 0 { original = []byte(`{}`) } if len(modified) == 0 { modified = []byte(`{}`) } if len(current) == 0 { current = []byte(`{}`) } addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified) if err != nil { return nil, err } // Only keep addition and changes addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false) if err != nil { return nil, err } deletePatch, err := jsonpatch.CreateMergePatch(original, modified) if err != nil { return nil, err } // Only keep deletion deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true) if err != nil { return nil, err } hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj) if err != nil { return nil, err } if hasConflicts { return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj)) } patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch) if err != nil { return nil, err } var patchMap map[string]interface{} err = json.Unmarshal(patch, &patchMap) if err != nil { return nil, fmt.Errorf("failed to unmarshal patch for precondition check: %s", patch) } meetPreconditions, err := meetPreconditions(patchMap, fns...) if err != nil { return nil, err } if !meetPreconditions { return nil, mergepatch.NewErrPreconditionFailed(patchMap) } return patch, nil } // keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean. // It returns a filtered object and its corresponding json-encoded byte array. // It is a wrapper of func keepOrDeleteNullInObj func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) { var patchMap map[string]interface{} err := json.Unmarshal(patch, &patchMap) if err != nil { return nil, nil, err } filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull) if err != nil { return nil, nil, err } o, err := json.Marshal(filteredMap) return o, filteredMap, err } // keepOrDeleteNullInObj will keep only the null value and delete all the others, // if keepNull is true. Otherwise, it will delete all the null value and keep the others. func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) { filteredMap := make(map[string]interface{}) var err error for key, val := range m { switch { case keepNull && val == nil: filteredMap[key] = nil case val != nil: switch typedVal := val.(type) { case map[string]interface{}: // Explicitly-set empty maps are treated as values instead of empty patches if len(typedVal) == 0 { if !keepNull { filteredMap[key] = typedVal } continue } var filteredSubMap map[string]interface{} filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull) if err != nil { return nil, err } // If the returned filtered submap was empty, this is an empty patch for the entire subdict, so the key // should not be set if len(filteredSubMap) != 0 { filteredMap[key] = filteredSubMap } case []interface{}, string, float64, bool, int64, nil: // Lists are always replaced in Json, no need to check each entry in the list. if !keepNull { filteredMap[key] = val } default: return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal)) } } } return filteredMap, nil } func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) { // Apply the preconditions to the patch, and return an error if any of them fail. for _, fn := range fns { if !fn(patchObj) { return false, fmt.Errorf("precondition failed for: %v", patchObj) } } return true, nil } golang-k8s-apimachinery-0.29.0/pkg/util/jsonmergepatch/patch_test.go000066400000000000000000000354361453143165200254760ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package jsonmergepatch import ( "fmt" "reflect" "testing" jsonpatch "github.com/evanphx/json-patch" "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/json" "sigs.k8s.io/yaml" ) type FilterNullTestCases struct { TestCases []FilterNullTestCase } type FilterNullTestCase struct { Description string OriginalObj map[string]interface{} ExpectedWithNull map[string]interface{} ExpectedWithoutNull map[string]interface{} } var filterNullTestCaseData = []byte(` testCases: - description: nil original originalObj: {} expectedWithNull: {} expectedWithoutNull: {} - description: simple map originalObj: nilKey: null nonNilKey: foo expectedWithNull: nilKey: null expectedWithoutNull: nonNilKey: foo - description: simple map with all nil values originalObj: nilKey1: null nilKey2: null expectedWithNull: nilKey1: null nilKey2: null expectedWithoutNull: {} - description: simple map with all non-nil values originalObj: nonNilKey1: foo nonNilKey2: bar expectedWithNull: {} expectedWithoutNull: nonNilKey1: foo nonNilKey2: bar - description: nested map originalObj: mapKey: nilKey: null nonNilKey: foo expectedWithNull: mapKey: nilKey: null expectedWithoutNull: mapKey: nonNilKey: foo - description: nested map that all subkeys are nil originalObj: mapKey: nilKey1: null nilKey2: null expectedWithNull: mapKey: nilKey1: null nilKey2: null expectedWithoutNull: {} - description: nested map that all subkeys are non-nil originalObj: mapKey: nonNilKey1: foo nonNilKey2: bar expectedWithNull: {} expectedWithoutNull: mapKey: nonNilKey1: foo nonNilKey2: bar - description: explicitly empty map as value originalObj: mapKey: {} expectedWithNull: {} expectedWithoutNull: mapKey: {} - description: explicitly empty nested map originalObj: mapKey: nonNilKey: {} expectedWithNull: {} expectedWithoutNull: mapKey: nonNilKey: {} - description: multiple expliclty empty nested maps originalObj: mapKey: nonNilKey1: {} nonNilKey2: {} expectedWithNull: {} expectedWithoutNull: mapKey: nonNilKey1: {} nonNilKey2: {} - description: nested map with non-null value as empty map originalObj: mapKey: nonNilKey: {} nilKey: null expectedWithNull: mapKey: nilKey: null expectedWithoutNull: mapKey: nonNilKey: {} - description: empty list originalObj: listKey: [] expectedWithNull: {} expectedWithoutNull: listKey: [] - description: list of primitives originalObj: listKey: - 1 - 2 expectedWithNull: {} expectedWithoutNull: listKey: - 1 - 2 - description: list of maps originalObj: listKey: - k1: v1 - k2: null - k3: v3 k4: null expectedWithNull: {} expectedWithoutNull: listKey: - k1: v1 - k2: null - k3: v3 k4: null - description: list of different types originalObj: listKey: - k1: v1 - k2: null - v3 expectedWithNull: {} expectedWithoutNull: listKey: - k1: v1 - k2: null - v3 `) func TestKeepOrDeleteNullInObj(t *testing.T) { tc := FilterNullTestCases{} err := yaml.Unmarshal(filterNullTestCaseData, &tc) if err != nil { t.Fatalf("can't unmarshal test cases: %s\n", err) } for _, test := range tc.TestCases { resultWithNull, err := keepOrDeleteNullInObj(test.OriginalObj, true) if err != nil { t.Errorf("Failed in test case %q when trying to keep null values: %s", test.Description, err) } if !reflect.DeepEqual(test.ExpectedWithNull, resultWithNull) { t.Errorf("Failed in test case %q when trying to keep null values:\nexpected expectedWithNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithNull, resultWithNull) } resultWithoutNull, err := keepOrDeleteNullInObj(test.OriginalObj, false) if err != nil { t.Errorf("Failed in test case %q when trying to keep non-null values: %s", test.Description, err) } if !reflect.DeepEqual(test.ExpectedWithoutNull, resultWithoutNull) { t.Errorf("Failed in test case %q when trying to keep non-null values:\n expected expectedWithoutNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithoutNull, resultWithoutNull) } } } type JSONMergePatchTestCases struct { TestCases []JSONMergePatchTestCase } type JSONMergePatchTestCase struct { Description string JSONMergePatchTestCaseData } type JSONMergePatchTestCaseData struct { // Original is the original object (last-applied config in annotation) Original map[string]interface{} // Modified is the modified object (new config we want) Modified map[string]interface{} // Current is the current object (live config in the server) Current map[string]interface{} // ThreeWay is the expected three-way merge patch ThreeWay map[string]interface{} // Result is the expected object after applying the three-way patch on current object. Result map[string]interface{} } var createJSONMergePatchTestCaseData = []byte(` testCases: - description: nil original modified: name: 1 value: 1 current: name: 1 other: a threeWay: value: 1 result: name: 1 value: 1 other: a - description: nil patch original: name: 1 modified: name: 1 current: name: 1 threeWay: {} result: name: 1 - description: add field to map original: name: 1 modified: name: 1 value: 1 current: name: 1 other: a threeWay: value: 1 result: name: 1 value: 1 other: a - description: add field to map with conflict original: name: 1 modified: name: 1 value: 1 current: name: a other: a threeWay: name: 1 value: 1 result: name: 1 value: 1 other: a - description: add field and delete field from map original: name: 1 modified: value: 1 current: name: 1 other: a threeWay: name: null value: 1 result: value: 1 other: a - description: add field and delete field from map with conflict original: name: 1 modified: value: 1 current: name: a other: a threeWay: name: null value: 1 result: value: 1 other: a - description: delete field from nested map original: simpleMap: key1: 1 key2: 1 modified: simpleMap: key1: 1 current: simpleMap: key1: 1 key2: 1 other: a threeWay: simpleMap: key2: null result: simpleMap: key1: 1 other: a - description: delete field from nested map with conflict original: simpleMap: key1: 1 key2: 1 modified: simpleMap: key1: 1 current: simpleMap: key1: a key2: 1 other: a threeWay: simpleMap: key1: 1 key2: null result: simpleMap: key1: 1 other: a - description: delete all fields from map original: name: 1 value: 1 modified: {} current: name: 1 value: 1 other: a threeWay: name: null value: null result: other: a - description: delete all fields from map with conflict original: name: 1 value: 1 modified: {} current: name: 1 value: a other: a threeWay: name: null value: null result: other: a - description: add field and delete all fields from map original: name: 1 value: 1 modified: other: a current: name: 1 value: 1 other: a threeWay: name: null value: null result: other: a - description: add field and delete all fields from map with conflict original: name: 1 value: 1 modified: other: a current: name: 1 value: 1 other: b threeWay: name: null value: null other: a result: other: a - description: replace list of scalars original: intList: - 1 - 2 modified: intList: - 2 - 3 current: intList: - 1 - 2 threeWay: intList: - 2 - 3 result: intList: - 2 - 3 - description: replace list of scalars with conflict original: intList: - 1 - 2 modified: intList: - 2 - 3 current: intList: - 1 - 4 threeWay: intList: - 2 - 3 result: intList: - 2 - 3 - description: patch with different scalar type original: foo: 1 modified: foo: true current: foo: 1 bar: 2 threeWay: foo: true result: foo: true bar: 2 - description: patch from scalar to list original: foo: 0 modified: foo: - 1 - 2 current: foo: 0 bar: 2 threeWay: foo: - 1 - 2 result: foo: - 1 - 2 bar: 2 - description: patch from list to scalar original: foo: - 1 - 2 modified: foo: 0 current: foo: - 1 - 2 bar: 2 threeWay: foo: 0 result: foo: 0 bar: 2 - description: patch from scalar to map original: foo: 0 modified: foo: baz: 1 current: foo: 0 bar: 2 threeWay: foo: baz: 1 result: foo: baz: 1 bar: 2 - description: patch from map to scalar original: foo: baz: 1 modified: foo: 0 current: foo: baz: 1 bar: 2 threeWay: foo: 0 result: foo: 0 bar: 2 - description: patch from map to list original: foo: baz: 1 modified: foo: - 1 - 2 current: foo: baz: 1 bar: 2 threeWay: foo: - 1 - 2 result: foo: - 1 - 2 bar: 2 - description: patch from list to map original: foo: - 1 - 2 modified: foo: baz: 0 current: foo: - 1 - 2 bar: 2 threeWay: foo: baz: 0 result: foo: baz: 0 bar: 2 - description: patch with different nested types original: foo: - a: true - 2 - false modified: foo: - 1 - false - b: 1 current: foo: - a: true - 2 - false bar: 0 threeWay: foo: - 1 - false - b: 1 result: foo: - 1 - false - b: 1 bar: 0 - description: patch array with nil original: foo: - a: true - null - false bar: [] drop: - 1 modified: foo: - 1 - false - b: 1 bar: - c - null - null - a drop: - null current: foo: - a: true - 2 - false bar: - c - null - null - a drop: threeWay: foo: - 1 - false - b: 1 drop: - null result: foo: - 1 - false - b: 1 drop: - null bar: - c - null - null - a `) func TestCreateThreeWayJSONMergePatch(t *testing.T) { tc := JSONMergePatchTestCases{} err := yaml.Unmarshal(createJSONMergePatchTestCaseData, &tc) if err != nil { t.Errorf("can't unmarshal test cases: %s\n", err) return } for _, c := range tc.TestCases { testThreeWayPatch(t, c) } } func testThreeWayPatch(t *testing.T, c JSONMergePatchTestCase) { original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c) actual, err := CreateThreeWayJSONMergePatch(original, modified, current) if err != nil { t.Fatalf("error: %s", err) } testPatchCreation(t, expected, actual, c.Description) testPatchApplication(t, current, actual, result, c.Description) } func testPatchCreation(t *testing.T, expected, actual []byte, description string) { if !reflect.DeepEqual(actual, expected) { t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) return } } func testPatchApplication(t *testing.T, original, patch, expected []byte, description string) { result, err := jsonpatch.MergePatch(original, patch) if err != nil { t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) return } if !reflect.DeepEqual(result, expected) { format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" t.Errorf(format, description, jsonToYAMLOrError(original), jsonToYAMLOrError(patch), jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) return } } func threeWayTestCaseToJSONOrFail(t *testing.T, c JSONMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) { return testObjectToJSONOrFail(t, c.Original), testObjectToJSONOrFail(t, c.Modified), testObjectToJSONOrFail(t, c.Current), testObjectToJSONOrFail(t, c.ThreeWay), testObjectToJSONOrFail(t, c.Result) } func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte { if o == nil { return nil } j, err := toJSON(o) if err != nil { t.Error(err) } return j } func jsonToYAMLOrError(j []byte) string { y, err := jsonToYAML(j) if err != nil { return err.Error() } return string(y) } func toJSON(v interface{}) ([]byte, error) { j, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, dump.Pretty(v)) } return j, nil } func jsonToYAML(j []byte) ([]byte, error) { y, err := yaml.JSONToYAML(j) if err != nil { return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j) } return y, nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/000077500000000000000000000000001453143165200225505ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/endpoints.yaml000066400000000000000000005576561453143165200254670ustar00rootroot00000000000000apiVersion: v1 kind: Endpoints metadata: creationTimestamp: '2016-10-04T17:45:58Z' labels: app: my-app name: app-server namespace: default resourceVersion: '184597135' selfLink: /self/link uid: 6826f086-8a5a-11e6-8d09-42010a800005 subsets: - addresses: - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0000 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0001 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0002 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0003 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0004 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0005 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0006 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0007 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0008 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0009 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0010 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0011 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0012 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0013 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0014 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0015 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0016 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0017 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0018 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0019 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0020 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0021 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0022 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0023 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0024 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0025 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0026 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0027 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0028 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0029 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0030 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0031 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0032 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0033 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0034 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0035 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0036 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0037 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0038 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0039 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0040 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0041 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0042 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0043 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0044 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0045 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0046 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0047 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0048 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0049 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0050 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0051 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0052 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0053 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0054 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0055 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0056 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0057 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0058 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0059 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0060 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0061 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0062 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0063 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0064 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0065 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0066 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0067 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0068 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0069 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0070 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0071 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0072 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0073 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0074 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0075 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0076 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0077 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0078 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0079 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0080 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0081 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0082 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0083 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0084 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0085 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0086 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0087 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0088 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0089 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0090 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0091 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0092 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0093 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0094 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0095 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0096 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0097 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0098 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0099 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0100 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0101 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0102 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0103 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0104 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0105 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0106 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0107 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0108 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0109 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0110 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0111 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0112 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0113 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0114 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0115 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0116 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0117 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0118 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0119 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0120 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0121 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0122 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0123 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0124 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0125 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0126 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0127 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0128 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0129 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0130 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0131 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0132 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0133 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0134 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0135 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0136 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0137 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0138 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0139 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0140 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0141 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0142 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0143 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0144 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0145 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0146 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0147 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0148 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0149 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0150 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0151 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0152 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0153 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0154 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0155 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0156 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0157 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0158 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0159 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0160 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0161 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0162 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0163 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0164 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0165 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0166 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0167 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0168 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0169 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0170 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0171 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0172 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0173 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0174 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0175 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0176 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0177 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0178 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0179 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0180 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0181 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0182 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0183 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0184 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0185 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0186 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0187 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0188 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0189 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0190 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0191 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0192 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0193 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0194 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0195 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0196 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0197 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0198 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0199 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0200 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0201 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0202 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0203 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0204 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0205 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0206 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0207 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0208 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0209 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0210 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0211 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0212 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0213 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0214 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0215 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0216 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0217 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0218 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0219 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0220 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0221 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0222 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0223 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0224 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0225 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0226 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0227 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0228 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0229 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0230 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0231 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0232 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0233 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0234 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0235 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0236 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0237 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0238 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0239 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0240 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0241 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0242 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0243 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0244 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0245 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0246 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0247 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0248 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0249 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0250 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0251 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0252 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0253 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0254 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0255 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0256 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0257 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0258 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0259 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0260 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0261 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0262 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0263 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0264 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0265 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0266 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0267 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0268 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0269 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0270 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0271 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0272 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0273 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0274 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0275 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0276 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0277 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0278 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0279 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0280 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0281 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0282 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0283 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0284 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0285 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0286 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0287 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0288 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0289 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0290 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0291 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0292 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0293 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0294 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0295 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0296 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0297 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0298 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0299 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0300 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0301 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0302 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0303 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0304 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0305 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0306 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0307 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0308 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0309 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0310 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0311 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0312 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0313 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0314 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0315 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0316 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0317 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0318 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0319 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0320 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0321 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0322 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0323 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0324 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0325 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0326 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0327 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0328 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0329 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0330 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0331 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0332 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0333 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0334 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0335 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0336 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0337 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0338 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0339 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0340 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0341 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0342 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0343 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0344 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0345 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0346 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0347 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0348 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0349 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0350 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0351 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0352 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0353 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0354 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0355 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0356 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0357 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0358 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0359 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0360 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0361 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0362 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0363 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0364 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0365 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0366 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0367 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0368 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0369 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0370 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0371 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0372 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0373 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0374 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0375 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0376 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0377 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0378 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0379 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0380 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0381 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0382 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0383 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0384 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0385 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0386 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0387 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0388 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0389 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0390 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0391 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0392 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0393 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0394 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0395 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0396 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0397 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0398 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0399 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0400 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0401 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0402 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0403 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0404 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0405 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0406 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0407 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0408 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0409 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0410 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0411 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0412 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0413 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0414 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0415 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0416 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0417 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0418 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0419 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0420 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0421 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0422 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0423 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0424 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0425 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0426 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0427 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0428 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0429 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0430 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0431 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0432 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0433 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0434 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0435 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0436 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0437 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0438 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0439 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0440 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0441 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0442 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0443 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0444 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0445 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0446 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0447 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0448 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0449 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0450 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0451 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0452 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0453 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0454 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0455 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0456 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0457 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0458 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0459 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0460 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0461 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0462 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0463 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0464 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0465 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0466 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0467 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0468 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0469 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0470 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0471 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0472 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0473 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0474 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0475 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0476 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0477 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0478 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0479 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0480 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0481 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0482 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0483 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0484 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0485 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0486 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0487 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0488 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0489 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0490 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0491 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0492 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0493 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0494 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0495 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0496 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0497 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0498 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0499 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0500 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0501 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0502 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0503 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0504 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0505 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0506 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0507 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0508 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0509 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0510 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0511 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0512 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0513 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0514 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0515 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0516 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0517 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0518 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0519 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0520 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0521 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0522 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0523 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0524 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0525 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0526 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0527 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0528 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0529 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0530 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0531 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0532 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0533 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0534 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0535 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0536 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0537 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0538 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0539 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0540 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0541 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0542 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0543 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0544 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0545 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0546 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0547 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0548 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0549 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0550 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0551 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0552 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0553 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0554 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0555 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0556 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0557 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0558 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0559 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0560 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0561 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0562 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0563 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0564 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0565 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0566 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0567 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0568 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0569 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0570 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0571 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0572 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0573 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0574 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0575 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0576 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0577 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0578 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0579 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0580 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0581 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0582 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0583 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0584 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0585 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0586 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0587 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0588 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0589 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0590 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0591 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0592 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0593 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0594 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0595 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0596 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0597 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0598 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0599 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0600 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0601 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0602 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0603 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0604 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0605 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0606 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0607 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0608 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0609 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0610 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0611 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0612 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0613 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0614 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0615 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0616 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0617 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0618 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0619 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0620 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0621 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0622 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0623 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0624 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0625 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0626 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0627 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0628 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0629 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0630 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0631 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0632 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0633 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0634 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0635 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0636 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0637 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0638 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0639 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0640 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0641 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0642 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0643 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0644 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0645 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0646 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0647 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0648 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0649 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0650 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0651 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0652 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0653 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0654 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0655 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0656 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0657 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0658 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0659 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0660 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0661 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0662 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0663 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0664 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0665 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0666 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0667 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0668 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0669 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0670 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0671 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0672 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0673 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0674 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0675 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0676 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0677 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0678 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0679 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0680 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0681 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0682 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0683 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0684 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0685 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0686 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0687 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0688 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0689 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0690 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0691 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0692 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0693 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0694 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0695 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0696 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0697 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0698 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0699 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0700 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0701 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0702 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0703 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0704 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0705 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0706 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0707 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0708 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0709 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0710 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0711 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0712 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0713 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0714 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0715 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0716 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0717 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0718 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0719 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0720 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0721 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0722 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0723 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0724 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0725 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0726 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0727 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0728 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0729 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0730 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0731 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0732 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0733 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0734 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0735 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0736 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0737 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0738 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0739 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0740 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0741 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0742 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0743 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0744 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0745 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0746 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0747 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0748 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0749 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0750 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0751 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0752 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0753 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0754 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0755 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0756 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0757 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0758 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0759 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0760 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0761 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0762 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0763 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0764 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0765 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0766 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0767 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0768 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0769 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0770 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0771 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0772 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0773 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0774 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0775 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0776 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0777 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0778 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0779 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0780 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0781 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0782 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0783 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0784 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0785 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0786 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0787 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0788 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0789 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0790 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0791 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0792 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0793 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0794 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0795 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0796 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0797 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0798 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0799 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0800 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0801 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0802 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0803 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0804 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0805 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0806 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0807 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0808 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0809 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0810 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0811 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0812 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0813 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0814 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0815 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0816 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0817 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0818 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0819 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0820 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0821 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0822 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0823 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0824 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0825 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0826 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0827 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0828 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0829 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0830 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0831 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0832 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0833 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0834 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0835 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0836 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0837 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0838 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0839 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0840 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0841 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0842 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0843 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0844 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0845 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0846 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0847 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0848 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0849 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0850 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0851 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0852 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0853 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0854 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0855 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0856 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0857 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0858 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0859 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0860 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0861 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0862 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0863 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0864 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0865 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0866 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0867 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0868 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0869 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0870 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0871 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0872 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0873 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0874 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0875 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0876 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0877 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0878 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0879 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0880 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0881 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0882 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0883 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0884 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0885 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0886 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0887 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0888 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0889 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0890 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0891 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0892 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0893 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0894 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0895 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0896 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0897 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0898 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0899 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0900 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0901 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0902 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0903 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0904 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0905 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0906 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0907 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0908 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0909 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0910 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0911 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0912 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0913 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0914 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0915 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0916 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0917 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0918 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0919 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0920 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0921 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0922 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0923 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0924 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0925 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0926 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0927 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0928 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0929 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0930 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0931 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0932 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0933 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0934 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0935 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0936 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0937 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0938 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0939 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0940 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0941 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0942 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0943 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0944 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0945 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0946 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0947 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0948 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0949 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0950 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0951 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0952 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0953 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0954 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0955 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0956 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0957 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0958 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0959 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0960 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0961 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0962 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0963 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0964 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0965 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0966 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0967 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0968 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0969 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0970 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0971 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0972 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0973 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0974 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0975 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0976 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0977 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0978 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0979 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0980 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0981 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0982 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0983 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0984 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0985 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0986 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0987 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0988 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0989 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0990 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0991 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0992 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0993 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0994 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0995 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0996 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0997 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0998 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 - ip: 10.0.0.1 targetRef: kind: Pod name: pod-name-1234-0999 namespace: default resourceVersion: '1234567890' uid: 11111111-2222-3333-4444-555555555555 ports: - name: port-name port: 8080 protocol: TCP golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/extract.go000066400000000000000000000106041453143165200245520ustar00rootroot00000000000000/* Copyright 2021 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 managedfields import ( "bytes" "fmt" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/typed" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) // ExtractInto extracts the applied configuration state from object for fieldManager // into applyConfiguration. If no managed fields are found for the given fieldManager, // no error is returned, but applyConfiguration is left unpopulated. It is possible // that no managed fields were found for the fieldManager because other field managers // have taken ownership of all the fields previously owned by the fieldManager. It is // also possible the fieldManager never owned fields. // // The provided object MUST bo a root resource object since subresource objects // do not contain their own managed fields. For example, an autoscaling.Scale // object read from a "scale" subresource does not have any managed fields and so // cannot be used as the object. // // If the fields of a subresource are a subset of the fields of the root object, // and their field paths and types are exactly the same, then ExtractInto can be // called with the root resource as the object and the subresource as the // applyConfiguration. This works for "status", obviously, because status is // represented by the exact same object as the root resource. This does NOT // work, for example, with the "scale" subresources of Deployment, ReplicaSet and // StatefulSet. While the spec.replicas, status.replicas fields are in the same // exact field path locations as they are in autoscaling.Scale, the selector // fields are in different locations, and are a different type. func ExtractInto(object runtime.Object, objectType typed.ParseableType, fieldManager string, applyConfiguration interface{}, subresource string) error { typedObj, err := toTyped(object, objectType) if err != nil { return fmt.Errorf("error converting obj to typed: %w", err) } accessor, err := meta.Accessor(object) if err != nil { return fmt.Errorf("error accessing metadata: %w", err) } fieldsEntry, ok := findManagedFields(accessor, fieldManager, subresource) if !ok { return nil } fieldset := &fieldpath.Set{} err = fieldset.FromJSON(bytes.NewReader(fieldsEntry.FieldsV1.Raw)) if err != nil { return fmt.Errorf("error marshalling FieldsV1 to JSON: %w", err) } u := typedObj.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() m, ok := u.(map[string]interface{}) if !ok { return fmt.Errorf("unable to convert managed fields for %s to unstructured, expected map, got %T", fieldManager, u) } // set the type meta manually if it doesn't exist to avoid missing kind errors // when decoding from unstructured JSON if _, ok := m["kind"]; !ok && object.GetObjectKind().GroupVersionKind().Kind != "" { m["kind"] = object.GetObjectKind().GroupVersionKind().Kind m["apiVersion"] = object.GetObjectKind().GroupVersionKind().GroupVersion().String() } if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, applyConfiguration); err != nil { return fmt.Errorf("error extracting into obj from unstructured: %w", err) } return nil } func findManagedFields(accessor metav1.Object, fieldManager string, subresource string) (metav1.ManagedFieldsEntry, bool) { objManagedFields := accessor.GetManagedFields() for _, mf := range objManagedFields { if mf.Manager == fieldManager && mf.Operation == metav1.ManagedFieldsOperationApply && mf.Subresource == subresource { return mf, true } } return metav1.ManagedFieldsEntry{}, false } func toTyped(obj runtime.Object, objectType typed.ParseableType) (*typed.TypedValue, error) { switch o := obj.(type) { case *unstructured.Unstructured: return objectType.FromUnstructured(o.Object) default: return objectType.FromStructured(o) } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/extract_test.go000066400000000000000000000205131453143165200256110ustar00rootroot00000000000000/* Copyright 2021 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 managedfields import ( "testing" "github.com/google/go-cmp/cmp" "sigs.k8s.io/structured-merge-diff/v4/typed" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" runtimeschema "k8s.io/apimachinery/pkg/runtime/schema" ) func TestExtractInto(t *testing.T) { one := int32(1) parser, err := typed.NewParser(schemaYAML) if err != nil { t.Fatalf("Failed to parse schema: %v", err) } cases := []struct { name string obj runtime.Object objType typed.ParseableType managedFields []metav1.ManagedFieldsEntry // written to object before test is run fieldManager string expectedOut interface{} subresource string }{ { name: "unstructured, no matching manager", obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`, ""), }, fieldManager: "mgr1", expectedOut: map[string]interface{}{}, }, { name: "unstructured, one manager", obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), }, fieldManager: "mgr1", expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}, }, { name: "unstructured, multiple manager", obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`, ""), }, fieldManager: "mgr2", expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}, }, { name: "structured, no matching manager", obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`, ""), }, fieldManager: "mgr1", expectedOut: map[string]interface{}{}, }, { name: "structured, one manager", obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), }, fieldManager: "mgr1", expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": int64(1)}}, }, { name: "structured, multiple manager", obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one, Paused: true}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`, ""), }, fieldManager: "mgr2", expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}, }, { name: "subresource", obj: &fakeDeployment{Status: fakeDeploymentStatus{Replicas: &one}}, objType: parser.Type("io.k8s.api.apps.v1.Deployment"), managedFields: []metav1.ManagedFieldsEntry{ applyFieldsEntry("mgr1", `{ "f:status": { "f:replicas": {}}}`, "status"), }, fieldManager: "mgr1", expectedOut: map[string]interface{}{"status": map[string]interface{}{"replicas": int64(1)}}, subresource: "status", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { out := map[string]interface{}{} accessor, err := meta.Accessor(tc.obj) if err != nil { t.Fatalf("Error accessing object: %v", err) } accessor.SetManagedFields(tc.managedFields) err = ExtractInto(tc.obj, tc.objType, tc.fieldManager, &out, tc.subresource) if err != nil { t.Fatalf("Unexpected extract error: %v", err) } if !equality.Semantic.DeepEqual(out, tc.expectedOut) { t.Fatalf("Expected output did not match actual output: %s", cmp.Diff(out, tc.expectedOut)) } }) } } func applyFieldsEntry(fieldManager string, fieldsJSON string, subresource string) metav1.ManagedFieldsEntry { return metav1.ManagedFieldsEntry{ Manager: fieldManager, Operation: metav1.ManagedFieldsOperationApply, APIVersion: "v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(fieldsJSON)}, Subresource: subresource, } } type fakeDeployment struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec fakeDeploymentSpec `json:"spec"` Status fakeDeploymentStatus `json:"status"` } type fakeDeploymentSpec struct { Replicas *int32 `json:"replicas"` Paused bool `json:"paused,omitempty"` } type fakeDeploymentStatus struct { Replicas *int32 `json:"replicas"` } func (o *fakeDeployment) GetObjectMeta() metav1.ObjectMeta { return o.ObjectMeta } func (o *fakeDeployment) GetObjectKind() runtimeschema.ObjectKind { return runtimeschema.EmptyObjectKind } func (o *fakeDeployment) DeepCopyObject() runtime.Object { return o } // trimmed up schema for test purposes const schemaYAML = typed.YAMLObject(`types: - name: io.k8s.api.apps.v1.Deployment map: fields: - name: apiVersion type: scalar: string - name: kind type: scalar: string - name: metadata type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta - name: spec type: namedType: io.k8s.api.apps.v1.DeploymentSpec - name: status type: namedType: io.k8s.api.apps.v1.DeploymentStatus - name: io.k8s.api.apps.v1.DeploymentSpec map: fields: - name: paused type: scalar: boolean - name: replicas type: scalar: numeric - name: io.k8s.api.apps.v1.DeploymentStatus map: fields: - name: replicas type: scalar: numeric - name: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta map: fields: - name: creationTimestamp type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: managedFields type: list: elementType: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry elementRelationship: atomic - name: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry map: fields: - name: apiVersion type: scalar: string - name: fieldsType type: scalar: string - name: fieldsV1 type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 - name: manager type: scalar: string - name: operation type: scalar: string - name: time type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: subresource type: scalar: string - name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 map: elementType: scalar: untyped list: elementType: namedType: __untyped_atomic_ elementRelationship: atomic map: elementType: namedType: __untyped_atomic_ elementRelationship: atomic - name: io.k8s.apimachinery.pkg.apis.meta.v1.Time scalar: untyped - name: __untyped_atomic_ scalar: untyped list: elementType: namedType: __untyped_atomic_ elementRelationship: atomic map: elementType: namedType: __untyped_atomic_ elementRelationship: atomic `) golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/fieldmanager.go000066400000000000000000000054021453143165200255160ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package managedfields import ( "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) // FieldManager updates the managed fields and merges applied // configurations. type FieldManager = internal.FieldManager // NewDefaultFieldManager creates a new FieldManager that merges apply requests // and update managed fields for other types of requests. func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (*FieldManager, error) { f, err := internal.NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields) if err != nil { return nil, fmt.Errorf("failed to create field manager: %v", err) } return internal.NewDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil } // NewDefaultCRDFieldManager creates a new FieldManager specifically for // CRDs. This allows for the possibility of fields which are not defined // in models, as well as having no models defined at all. func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ *FieldManager, err error) { f, err := internal.NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields) if err != nil { return nil, fmt.Errorf("failed to create field manager: %v", err) } return internal.NewDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil } func ValidateManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) error { _, err := internal.DecodeManagedFields(encodedManagedFields) return err } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/fieldmanager_test.go000066400000000000000000001007651453143165200265650ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package managedfields_test import ( "encoding/json" "fmt" "net/http" "os" "path/filepath" "reflect" "strings" "testing" "time" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields" "k8s.io/apimachinery/pkg/util/managedfields/internal" "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" yamlutil "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/kube-openapi/pkg/validation/spec" "sigs.k8s.io/yaml" ) var fakeTypeConverter = func() managedfields.TypeConverter { data, err := os.ReadFile(filepath.Join(strings.Repeat(".."+string(filepath.Separator), 7), "api", "openapi-spec", "swagger.json")) if err != nil { panic(err) } swag := spec.Swagger{} if err := json.Unmarshal(data, &swag); err != nil { panic(err) } convertedDefs := map[string]*spec.Schema{} for k, v := range swag.Definitions { vCopy := v convertedDefs[k] = &vCopy } typeConverter, err := managedfields.NewTypeConverter(convertedDefs, false) if err != nil { panic(err) } return typeConverter }() // TestUpdateApplyConflict tests that applying to an object, which // wasn't created by apply, will give conflicts func TestUpdateApplyConflict(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) patch := []byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", "labels": {"app": "nginx"} }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [{ "name": "nginx", "image": "nginx:latest" }] } } } }`) newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(patch, &newObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Update(newObj, "fieldmanager_test"); err != nil { t.Fatalf("failed to apply object: %v", err) } appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", }, "spec": { "replicas": 101, } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } err := f.Apply(appliedObj, "fieldmanager_conflict", false) if err == nil || !apierrors.IsConflict(err) { t.Fatalf("Expecting to get conflicts but got %v", err) } } func TestApplyStripsFields(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) newObj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "apps/v1", "kind": "Deployment", }, } newObj.SetName("b") newObj.SetNamespace("b") newObj.SetUID("b") newObj.SetGeneration(0) newObj.SetResourceVersion("b") newObj.SetCreationTimestamp(metav1.NewTime(time.Now())) newObj.SetManagedFields([]metav1.ManagedFieldsEntry{ { Manager: "update", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", }, }) if err := f.Update(newObj, "fieldmanager_test"); err != nil { t.Fatalf("failed to apply object: %v", err) } if m := f.ManagedFields(); len(m) != 0 { t.Fatalf("fields did not get stripped: %v", m) } } func TestVersionCheck(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors err := f.Apply(appliedObj, "fieldmanager_test", false) if err != nil { t.Fatalf("failed to apply object: %v", err) } appliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1beta1", "kind": "Deployment", }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // patch has 'apiVersion: apps/v1beta1' but live version is apps/v1 -> error err = f.Apply(appliedObj, "fieldmanager_test", false) if err == nil { t.Fatalf("expected an error from mismatched patch and live versions") } switch typ := err.(type) { default: t.Fatalf("expected error to be of type %T was %T (%v)", apierrors.StatusError{}, typ, err) case apierrors.APIStatus: if typ.Status().Code != http.StatusBadRequest { t.Fatalf("expected status code to be %d but was %d", http.StatusBadRequest, typ.Status().Code) } } } func TestVersionCheckDoesNotPanic(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors err := f.Apply(appliedObj, "fieldmanager_test", false) if err != nil { t.Fatalf("failed to apply object: %v", err) } appliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error err = f.Apply(appliedObj, "fieldmanager_test", false) if err == nil { t.Fatalf("expected an error from mismatched patch and live versions") } switch typ := err.(type) { default: t.Fatalf("expected error to be of type %T was %T (%v)", apierrors.StatusError{}, typ, err) case apierrors.APIStatus: if typ.Status().Code != http.StatusBadRequest { t.Fatalf("expected status code to be %d but was %d", http.StatusBadRequest, typ.Status().Code) } } } func TestApplyDoesNotStripLabels(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "labels": { "a": "b" }, } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } err := f.Apply(appliedObj, "fieldmanager_test", false) if err != nil { t.Fatalf("failed to apply object: %v", err) } if m := f.ManagedFields(); len(m) != 1 { t.Fatalf("labels shouldn't get stripped on apply: %v", m) } } func getObjectBytes(file string) []byte { s, err := os.ReadFile(file) if err != nil { panic(err) } return s } func TestApplyNewObject(t *testing.T) { tests := []struct { gvk schema.GroupVersionKind obj []byte }{ { gvk: schema.FromAPIVersionAndKind("v1", "Pod"), obj: getObjectBytes("pod.yaml"), }, { gvk: schema.FromAPIVersionAndKind("v1", "Node"), obj: getObjectBytes("node.yaml"), }, { gvk: schema.FromAPIVersionAndKind("v1", "Endpoints"), obj: getObjectBytes("endpoints.yaml"), }, } for _, test := range tests { t.Run(test.gvk.String(), func(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, test.gvk) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(test.obj, &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Apply(appliedObj, "fieldmanager_test", false); err != nil { t.Fatal(err) } }) } } func TestApplyFailsWithManagedFields(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "managedFields": [ { "manager": "test", } ] } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } err := f.Apply(appliedObj, "fieldmanager_test", false) if err == nil { t.Fatalf("successfully applied with set managed fields") } } func TestApplySuccessWithNoManagedFields(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "labels": { "a": "b" }, } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } err := f.Apply(appliedObj, "fieldmanager_test", false) if err != nil { t.Fatalf("failed to apply object: %v", err) } } // Run an update and apply, and make sure that nothing has changed. func TestNoOpChanges(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "labels": { "a": "b" }, "creationTimestamp": null, } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", false); err != nil { t.Fatalf("failed to apply object: %v", err) } before := f.Live() // Wait to make sure the timestamp is different time.Sleep(time.Second) // Applying with a different fieldmanager will create an entry.. if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply_other", false); err != nil { t.Fatalf("failed to update object: %v", err) } if reflect.DeepEqual(before, f.Live()) { t.Fatalf("Applying no-op apply with new manager didn't change object: \n%v", f.Live()) } before = f.Live() // Wait to make sure the timestamp is different time.Sleep(time.Second) if err := f.Update(obj.DeepCopyObject(), "fieldmanager_test_update"); err != nil { t.Fatalf("failed to update object: %v", err) } if !reflect.DeepEqual(before, f.Live()) { t.Fatalf("No-op update has changed the object:\n%v\n---\n%v", before, f.Live()) } before = f.Live() // Wait to make sure the timestamp is different time.Sleep(time.Second) if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", true); err != nil { t.Fatalf("failed to re-apply object: %v", err) } if !reflect.DeepEqual(before, f.Live()) { t.Fatalf("No-op apply has changed the object:\n%v\n---\n%v", before, f.Live()) } } // Tests that one can reset the managedFields by sending either an empty // list func TestResetManagedFieldsEmptyList(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "labels": { "a": "b" }, } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Apply(obj, "fieldmanager_test_apply", false); err != nil { t.Fatalf("failed to apply object: %v", err) } if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "managedFields": [], "labels": { "a": "b" }, } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Update(obj, "update_manager"); err != nil { t.Fatalf("failed to update with empty manager: %v", err) } if len(f.ManagedFields()) != 0 { t.Fatalf("failed to reset managedFields: %v", f.ManagedFields()) } } // Tests that one can reset the managedFields by sending either a list with one empty item. func TestResetManagedFieldsEmptyItem(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "labels": { "a": "b" }, } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Apply(obj, "fieldmanager_test_apply", false); err != nil { t.Fatalf("failed to apply object: %v", err) } if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "managedFields": [{}], "labels": { "a": "b" }, } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Update(obj, "update_manager"); err != nil { t.Fatalf("failed to update with empty manager: %v", err) } if len(f.ManagedFields()) != 0 { t.Fatalf("failed to reset managedFields: %v", f.ManagedFields()) } } func TestServerSideApplyWithInvalidLastApplied(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) // create object with client-side apply newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} deployment := []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app-v1 spec: replicas: 1 `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } invalidLastApplied := "invalid-object" if err := internal.SetLastApplied(newObj, invalidLastApplied); err != nil { t.Errorf("failed to set last applied: %v", err) } if err := f.Update(newObj, "kubectl-client-side-apply-test"); err != nil { t.Errorf("failed to update object: %v", err) } lastApplied, err := getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if lastApplied != invalidLastApplied { t.Errorf("expected last applied annotation to be set to %q, but got: %q", invalidLastApplied, lastApplied) } // upgrade management of the object from client-side apply to server-side apply appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedDeployment := []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app-v2 spec: replicas: 100 `) if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := f.Apply(appliedObj, "kubectl", false); err == nil || !apierrors.IsConflict(err) { t.Errorf("expected conflict when applying with invalid last-applied annotation, but got no error for object: \n%+v", appliedObj) } lastApplied, err = getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if lastApplied != invalidLastApplied { t.Errorf("expected last applied annotation to be NOT be updated, but got: %q", lastApplied) } // force server-side apply should work and fix the annotation if err := f.Apply(appliedObj, "kubectl", true); err != nil { t.Errorf("failed to force server-side apply with: %v", err) } lastApplied, err = getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if lastApplied == invalidLastApplied || !strings.Contains(lastApplied, "my-app-v2") { t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) } } func TestInteropForClientSideApplyAndServerSideApply(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) // create object with client-side apply newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} deployment := []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image-v1 `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := setLastAppliedFromEncoded(newObj, deployment); err != nil { t.Errorf("failed to set last applied: %v", err) } if err := f.Update(newObj, "kubectl-client-side-apply-test"); err != nil { t.Errorf("failed to update object: %v", err) } lastApplied, err := getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if !strings.Contains(lastApplied, "my-image-v1") { t.Errorf("expected last applied annotation to be set properly, but got: %q", lastApplied) } // upgrade management of the object from client-side apply to server-side apply appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedDeployment := []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app-v2 # change spec: replicas: 8 # change selector: matchLabels: app: my-app-v2 # change template: metadata: labels: app: my-app-v2 # change spec: containers: - name: my-c image: my-image-v2 # change `) if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := f.Apply(appliedObj, "kubectl", false); err != nil { t.Errorf("error applying object: %v", err) } lastApplied, err = getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if !strings.Contains(lastApplied, "my-image-v2") { t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) } } func TestNoTrackManagedFieldsForClientSideApply(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) // create object newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} deployment := []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := f.Update(newObj, "test_kubectl_create"); err != nil { t.Errorf("failed to update object: %v", err) } if m := f.ManagedFields(); len(m) == 0 { t.Errorf("expected to have managed fields, but got: %v", m) } // stop tracking managed fields newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} deployment = []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment managedFields: [] # stop tracking managed fields labels: app: my-app spec: replicas: 100 `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } newObj.SetUID("nonempty") if err := f.Update(newObj, "test_kubectl_replace"); err != nil { t.Errorf("failed to update object: %v", err) } if m := f.ManagedFields(); len(m) != 0 { t.Errorf("expected to have stop tracking managed fields, but got: %v", m) } // check that we still don't track managed fields newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} deployment = []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := setLastAppliedFromEncoded(newObj, deployment); err != nil { t.Errorf("failed to set last applied: %v", err) } if err := f.Update(newObj, "test_k_client_side_apply"); err != nil { t.Errorf("failed to update object: %v", err) } if m := f.ManagedFields(); len(m) != 0 { t.Errorf("expected to continue to not track managed fields, but got: %v", m) } lastApplied, err := getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if !strings.Contains(lastApplied, "my-app") { t.Errorf("expected last applied annotation to be set properly, but got: %q", lastApplied) } // start tracking managed fields newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} deployment = []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := f.Apply(newObj, "test_server_side_apply_without_upgrade", false); err != nil { t.Errorf("error applying object: %v", err) } if m := f.ManagedFields(); len(m) < 2 { t.Errorf("expected to start tracking managed fields with at least 2 field managers, but got: %v", m) } if e, a := "test_server_side_apply_without_upgrade", f.ManagedFields()[0].Manager; e != a { t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "before-first-apply", f.ManagedFields()[1].Manager; e != a { t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } // upgrade management of the object from client-side apply to server-side apply newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} deployment = []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app-v2 # change spec: replicas: 8 # change `) if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := f.Apply(newObj, "kubectl", false); err != nil { t.Errorf("error applying object: %v", err) } if m := f.ManagedFields(); len(m) == 0 { t.Errorf("expected to track managed fields, but got: %v", m) } lastApplied, err = getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if !strings.Contains(lastApplied, "my-app-v2") { t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) } } func yamlToJSON(y []byte) (string, error) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(y, &obj.Object); err != nil { return "", fmt.Errorf("error decoding YAML: %v", err) } serialization, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) if err != nil { return "", fmt.Errorf("error encoding object: %v", err) } json, err := yamlutil.ToJSON(serialization) if err != nil { return "", fmt.Errorf("error converting to json: %v", err) } return string(json), nil } func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error { lastAppliedJSON, err := yamlToJSON(lastApplied) if err != nil { return err } return internal.SetLastApplied(obj, lastAppliedJSON) } func getLastApplied(obj runtime.Object) (string, error) { accessor := meta.NewAccessor() annotations, err := accessor.Annotations(obj) if err != nil { return "", fmt.Errorf("failed to access annotations: %v", err) } if annotations == nil { return "", fmt.Errorf("no annotations on obj: %v", obj) } lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation] if !ok { return "", fmt.Errorf("expected last applied annotation, but got none for object: %v", obj) } return lastApplied, nil } func TestUpdateViaSubresources(t *testing.T) { f := managedfieldstest.NewTestFieldManagerSubresource(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "scale") obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "labels": { "a":"b" }, } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } obj.SetManagedFields([]metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{ Raw: []byte(`{"f:metadata":{"f:labels":{"f:another_field":{}}}}`), }, }, }) // Check that managed fields cannot be changed explicitly via subresources expectedManager := "fieldmanager_test_subresource" if err := f.Update(obj, expectedManager); err != nil { t.Fatalf("failed to apply object: %v", err) } managedFields := f.ManagedFields() if len(managedFields) != 1 { t.Fatalf("Expected new managed fields to have one entry. Got:\n%#v", managedFields) } if managedFields[0].Manager != expectedManager { t.Fatalf("Expected first item to have manager set to: %s. Got: %s", expectedManager, managedFields[0].Manager) } // Check that managed fields cannot be reset via subresources newObj := obj.DeepCopy() newObj.SetManagedFields([]metav1.ManagedFieldsEntry{}) if err := f.Update(newObj, expectedManager); err != nil { t.Fatalf("failed to apply object: %v", err) } newManagedFields := f.ManagedFields() if len(newManagedFields) != 1 { t.Fatalf("Expected new managed fields to have one entry. Got:\n%#v", newManagedFields) } } // Ensures that a no-op Apply does not mutate managed fields func TestApplyDoesNotChangeManagedFields(t *testing.T) { originalManagedFields := []metav1.ManagedFieldsEntry{} f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) newObj := &unstructured.Unstructured{ Object: map[string]interface{}{}, } appliedObj := &unstructured.Unstructured{ Object: map[string]interface{}{}, } // Convert YAML string inputs to unstructured instances if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", "labels": {"app": "nginx"} }, "spec": { "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [{ "name": "nginx", "image": "nginx:latest" }] } } } }`), &newObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", }, "spec": { "replicas": 101, } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // Agent A applies initial configuration if err := f.Apply(newObj.DeepCopyObject(), "fieldmanager_z", false); err != nil { t.Fatalf("failed to apply object: %v", err) } // Agent B applies additive configuration if err := f.Apply(appliedObj, "fieldmanager_b", false); err != nil { t.Fatalf("failed to apply object %v", err) } // Next, agent A applies the initial configuration again, but we expect // a no-op to managed fields. // // The following update is expected not to change the liveObj, save off // the fields for _, field := range f.ManagedFields() { originalManagedFields = append(originalManagedFields, *field.DeepCopy()) } // Make sure timestamp change would be caught time.Sleep(2 * time.Second) if err := f.Apply(newObj, "fieldmanager_z", false); err != nil { t.Fatalf("failed to apply object: %v", err) } // ensure that the live object is unchanged if !reflect.DeepEqual(originalManagedFields, f.ManagedFields()) { originalYAML, _ := yaml.Marshal(originalManagedFields) current, _ := yaml.Marshal(f.ManagedFields()) // should have been a no-op w.r.t. managed fields t.Fatalf("managed fields changed: ORIGINAL\n%v\nCURRENT\n%v", string(originalYAML), string(current)) } } // Ensures that a no-op Update does not mutate managed fields func TestUpdateDoesNotChangeManagedFields(t *testing.T) { originalManagedFields := []metav1.ManagedFieldsEntry{} f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) newObj := &unstructured.Unstructured{ Object: map[string]interface{}{}, } if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", "labels": {"app": "nginx"} }, "spec": { "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [{ "name": "nginx", "image": "nginx:latest" }] } } } }`), &newObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // Agent A updates with initial configuration if err := f.Update(newObj.DeepCopyObject(), "fieldmanager_z"); err != nil { t.Fatalf("failed to apply object: %v", err) } for _, field := range f.ManagedFields() { originalManagedFields = append(originalManagedFields, *field.DeepCopy()) } // Make sure timestamp change would be caught time.Sleep(2 * time.Second) // If the same exact configuration is updated once again, the // managed fields are not expected to change // // However, a change in field ownership WOULD be a semantic change which // should cause managed fields to change. if err := f.Update(newObj, "fieldmanager_z"); err != nil { t.Fatalf("failed to apply object: %v", err) } // ensure that the live object is unchanged if !reflect.DeepEqual(originalManagedFields, f.ManagedFields()) { originalYAML, _ := yaml.Marshal(originalManagedFields) current, _ := yaml.Marshal(f.ManagedFields()) // should have been a no-op w.r.t. managed fields t.Fatalf("managed fields changed: ORIGINAL\n%v\nCURRENT\n%v", string(originalYAML), string(current)) } } // This test makes sure that the liveObject during a patch does not mutate // its managed fields. func TestLiveObjectManagedFieldsNotRemoved(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) newObj := &unstructured.Unstructured{ Object: map[string]interface{}{}, } appliedObj := &unstructured.Unstructured{ Object: map[string]interface{}{}, } // Convert YAML string inputs to unstructured instances if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", "labels": {"app": "nginx"} }, "spec": { "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [{ "name": "nginx", "image": "nginx:latest" }] } } } }`), &newObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := yaml.Unmarshal([]byte(`{ "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "deployment", }, "spec": { "replicas": 101, } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } // Agent A applies initial configuration if err := f.Apply(newObj.DeepCopyObject(), "fieldmanager_z", false); err != nil { t.Fatalf("failed to apply object: %v", err) } originalLiveObj := f.Live() accessor, err := meta.Accessor(originalLiveObj) if err != nil { panic(fmt.Errorf("couldn't get accessor: %v", err)) } // Managed fields should not be stripped if len(accessor.GetManagedFields()) == 0 { t.Fatalf("empty managed fields of object which expected nonzero fields") } // Agent A applies the exact same configuration if err := f.Apply(appliedObj.DeepCopyObject(), "fieldmanager_z", false); err != nil { t.Fatalf("failed to apply object: %v", err) } accessor, err = meta.Accessor(originalLiveObj) if err != nil { panic(fmt.Errorf("couldn't get accessor: %v", err)) } // Managed fields should not be stripped if len(accessor.GetManagedFields()) == 0 { t.Fatalf("empty managed fields of object which expected nonzero fields") } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/gvkparser.go000066400000000000000000000071551453143165200251130ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package managedfields import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kube-openapi/pkg/schemaconv" "k8s.io/kube-openapi/pkg/util/proto" smdschema "sigs.k8s.io/structured-merge-diff/v4/schema" "sigs.k8s.io/structured-merge-diff/v4/typed" ) // groupVersionKindExtensionKey is the key used to lookup the // GroupVersionKind value for an object definition from the // definition's "extensions" map. const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" // GvkParser contains a Parser that allows introspecting the schema. type GvkParser struct { gvks map[schema.GroupVersionKind]string parser typed.Parser } // Type returns a helper which can produce objects of the given type. Any // errors are deferred until a further function is called. func (p *GvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType { typeName, ok := p.gvks[gvk] if !ok { return nil } t := p.parser.Type(typeName) return &t } // NewGVKParser builds a GVKParser from a proto.Models. This // will automatically find the proper version of the object, and the // corresponding schema information. func NewGVKParser(models proto.Models, preserveUnknownFields bool) (*GvkParser, error) { typeSchema, err := schemaconv.ToSchemaWithPreserveUnknownFields(models, preserveUnknownFields) if err != nil { return nil, fmt.Errorf("failed to convert models to schema: %v", err) } parser := GvkParser{ gvks: map[schema.GroupVersionKind]string{}, } parser.parser = typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}} for _, modelName := range models.ListModels() { model := models.LookupModel(modelName) if model == nil { panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName)) } gvkList := parseGroupVersionKind(model) for _, gvk := range gvkList { if len(gvk.Kind) > 0 { _, ok := parser.gvks[gvk] if ok { return nil, fmt.Errorf("duplicate entry for %v", gvk) } parser.gvks[gvk] = modelName } } } return &parser, nil } // Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind { extensions := s.GetExtensions() gvkListResult := []schema.GroupVersionKind{} // Get the extensions gvkExtension, ok := extensions[groupVersionKindExtensionKey] if !ok { return []schema.GroupVersionKind{} } // gvk extension must be a list of at least 1 element. gvkList, ok := gvkExtension.([]interface{}) if !ok { return []schema.GroupVersionKind{} } for _, gvk := range gvkList { // gvk extension list must be a map with group, version, and // kind fields gvkMap, ok := gvk.(map[interface{}]interface{}) if !ok { continue } group, ok := gvkMap["group"].(string) if !ok { continue } version, ok := gvkMap["version"].(string) if !ok { continue } kind, ok := gvkMap["kind"].(string) if !ok { continue } gvkListResult = append(gvkListResult, schema.GroupVersionKind{ Group: group, Version: version, Kind: kind, }) } return gvkListResult } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/000077500000000000000000000000001453143165200243645ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/atmostevery.go000066400000000000000000000027461453143165200273060ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "sync" "time" ) // AtMostEvery will never run the method more than once every specified // duration. type AtMostEvery struct { delay time.Duration lastCall time.Time mutex sync.Mutex } // NewAtMostEvery creates a new AtMostEvery, that will run the method at // most every given duration. func NewAtMostEvery(delay time.Duration) *AtMostEvery { return &AtMostEvery{ delay: delay, } } // updateLastCall returns true if the lastCall time has been updated, // false if it was too early. func (s *AtMostEvery) updateLastCall() bool { s.mutex.Lock() defer s.mutex.Unlock() if time.Since(s.lastCall) < s.delay { return false } s.lastCall = time.Now() return true } // Do will run the method if enough time has passed, and return true. // Otherwise, it does nothing and returns false. func (s *AtMostEvery) Do(fn func()) bool { if !s.updateLastCall() { return false } fn() return true } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/atmostevery_test.go000066400000000000000000000023701453143165200303360ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "testing" "time" "k8s.io/apimachinery/pkg/util/managedfields/internal" ) func TestAtMostEvery(t *testing.T) { duration := time.Second delay := 179 * time.Millisecond atMostEvery := internal.NewAtMostEvery(delay) count := 0 exit := time.NewTicker(duration) tick := time.NewTicker(2 * time.Millisecond) defer exit.Stop() defer tick.Stop() done := false for !done { select { case <-exit.C: done = true case <-tick.C: atMostEvery.Do(func() { count++ }) } } if expected := int(duration/delay) + 1; count > expected { t.Fatalf("Function called %d times, should have been called less than or equal to %d times", count, expected) } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/buildmanagerinfo.go000066400000000000000000000050141453143165200302210ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) type buildManagerInfoManager struct { fieldManager Manager groupVersion schema.GroupVersion subresource string } var _ Manager = &buildManagerInfoManager{} // NewBuildManagerInfoManager creates a new Manager that converts the manager name into a unique identifier // combining operation and version for update requests, and just operation for apply requests. func NewBuildManagerInfoManager(f Manager, gv schema.GroupVersion, subresource string) Manager { return &buildManagerInfoManager{ fieldManager: f, groupVersion: gv, subresource: subresource, } } // Update implements Manager. func (f *buildManagerInfoManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate) if err != nil { return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err) } return f.fieldManager.Update(liveObj, newObj, managed, manager) } // Apply implements Manager. func (f *buildManagerInfoManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationApply) if err != nil { return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err) } return f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force) } func (f *buildManagerInfoManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) { managerInfo := metav1.ManagedFieldsEntry{ Manager: prefix, Operation: operation, APIVersion: f.groupVersion.String(), Subresource: f.subresource, } if managerInfo.Manager == "" { managerInfo.Manager = "unknown" } return BuildManagerIdentifier(&managerInfo) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/capmanagers.go000066400000000000000000000111731453143165200271770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "sort" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) type capManagersManager struct { fieldManager Manager maxUpdateManagers int oldUpdatesManagerName string } var _ Manager = &capManagersManager{} // NewCapManagersManager creates a new wrapped FieldManager which ensures that the number of managers from updates // does not exceed maxUpdateManagers, by merging some of the oldest entries on each update. func NewCapManagersManager(fieldManager Manager, maxUpdateManagers int) Manager { return &capManagersManager{ fieldManager: fieldManager, maxUpdateManagers: maxUpdateManagers, oldUpdatesManagerName: "ancient-changes", } } // Update implements Manager. func (f *capManagersManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager) if err != nil { return object, managed, err } if managed, err = f.capUpdateManagers(managed); err != nil { return nil, nil, fmt.Errorf("failed to cap update managers: %v", err) } return object, managed, nil } // Apply implements Manager. func (f *capManagersManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) } // capUpdateManagers merges a number of the oldest update entries into versioned buckets, // such that the number of entries from updates does not exceed f.maxUpdateManagers. func (f *capManagersManager) capUpdateManagers(managed Managed) (newManaged Managed, err error) { // Gather all entries from updates updaters := []string{} for manager, fields := range managed.Fields() { if !fields.Applied() { updaters = append(updaters, manager) } } if len(updaters) <= f.maxUpdateManagers { return managed, nil } // If we have more than the maximum, sort the update entries by time, oldest first. sort.Slice(updaters, func(i, j int) bool { iTime, jTime, iSeconds, jSeconds := managed.Times()[updaters[i]], managed.Times()[updaters[j]], int64(0), int64(0) if iTime != nil { iSeconds = iTime.Unix() } if jTime != nil { jSeconds = jTime.Unix() } if iSeconds != jSeconds { return iSeconds < jSeconds } return updaters[i] < updaters[j] }) // Merge the oldest updaters with versioned bucket managers until the number of updaters is under the cap versionToFirstManager := map[string]string{} for i, length := 0, len(updaters); i < len(updaters) && length > f.maxUpdateManagers; i++ { manager := updaters[i] vs := managed.Fields()[manager] time := managed.Times()[manager] version := string(vs.APIVersion()) // Create a new manager identifier for the versioned bucket entry. // The version for this manager comes from the version of the update being merged into the bucket. bucket, err := BuildManagerIdentifier(&metav1.ManagedFieldsEntry{ Manager: f.oldUpdatesManagerName, Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: version, }) if err != nil { return managed, fmt.Errorf("failed to create bucket manager for version %v: %v", version, err) } // Merge the fieldets if this is not the first time the version was seen. // Otherwise just record the manager name in versionToFirstManager if first, ok := versionToFirstManager[version]; ok { // If the bucket doesn't exists yet, create one. if _, ok := managed.Fields()[bucket]; !ok { s := managed.Fields()[first] delete(managed.Fields(), first) managed.Fields()[bucket] = s } managed.Fields()[bucket] = fieldpath.NewVersionedSet(vs.Set().Union(managed.Fields()[bucket].Set()), vs.APIVersion(), vs.Applied()) delete(managed.Fields(), manager) length-- // Use the time from the update being merged into the bucket, since it is more recent. managed.Times()[bucket] = time } else { versionToFirstManager[version] = manager } } return managed, nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/capmanagers_test.go000066400000000000000000000242111453143165200302330ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "bytes" "encoding/json" "fmt" "testing" "time" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) type fakeManager struct{} var _ internal.Manager = &fakeManager{} func (*fakeManager) Update(_, newObj runtime.Object, managed internal.Managed, _ string) (runtime.Object, internal.Managed, error) { return newObj, managed, nil } func (*fakeManager) Apply(_, _ runtime.Object, _ internal.Managed, _ string, _ bool) (runtime.Object, internal.Managed, error) { panic("not implemented") } func TestCapManagersManagerMergesEntries(t *testing.T) { f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager { return internal.NewCapManagersManager(m, 3) }) podWithLabels := func(labels ...string) runtime.Object { labelMap := map[string]interface{}{} for _, key := range labels { labelMap[key] = "true" } obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "metadata": map[string]interface{}{ "labels": labelMap, }, }, } obj.SetKind("Pod") obj.SetAPIVersion("v1") return obj } if err := f.Update(podWithLabels("one"), "fieldmanager_test_update_1"); err != nil { t.Fatalf("failed to update object: %v", err) } expectIdempotence(t, f) if err := f.Update(podWithLabels("one", "two"), "fieldmanager_test_update_2"); err != nil { t.Fatalf("failed to update object: %v", err) } expectIdempotence(t, f) if err := f.Update(podWithLabels("one", "two", "three"), "fieldmanager_test_update_3"); err != nil { t.Fatalf("failed to update object: %v", err) } expectIdempotence(t, f) if err := f.Update(podWithLabels("one", "two", "three", "four"), "fieldmanager_test_update_4"); err != nil { t.Fatalf("failed to update object: %v", err) } expectIdempotence(t, f) if e, a := 3, len(f.ManagedFields()); e != a { t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "ancient-changes", f.ManagedFields()[0].Manager; e != a { t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "fieldmanager_test_update_3", f.ManagedFields()[1].Manager; e != a { t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "fieldmanager_test_update_4", f.ManagedFields()[2].Manager; e != a { t.Fatalf("exected third manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } expectManagesField(t, f, "ancient-changes", fieldpath.MakePathOrDie("metadata", "labels", "one")) expectManagesField(t, f, "ancient-changes", fieldpath.MakePathOrDie("metadata", "labels", "two")) expectManagesField(t, f, "fieldmanager_test_update_3", fieldpath.MakePathOrDie("metadata", "labels", "three")) expectManagesField(t, f, "fieldmanager_test_update_4", fieldpath.MakePathOrDie("metadata", "labels", "four")) } func TestCapUpdateManagers(t *testing.T) { f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager { return internal.NewCapManagersManager(m, 3) }) set := func(fields ...string) *metav1.FieldsV1 { s := fieldpath.NewSet() for _, f := range fields { s.Insert(fieldpath.MakePathOrDie(f)) } b, err := s.ToJSON() if err != nil { panic(fmt.Sprintf("error building ManagedFieldsEntry for test: %v", err)) } return &metav1.FieldsV1{Raw: b} } entry := func(name string, version string, order int, fields *metav1.FieldsV1) metav1.ManagedFieldsEntry { return metav1.ManagedFieldsEntry{ Manager: name, APIVersion: version, Operation: "Update", FieldsType: "FieldsV1", FieldsV1: fields, Time: &metav1.Time{Time: time.Time{}.Add(time.Hour * time.Duration(order))}, } } testCases := []struct { name string input []metav1.ManagedFieldsEntry expected []metav1.ManagedFieldsEntry }{ { name: "one version, no ancient changes", input: []metav1.ManagedFieldsEntry{ entry("update-manager1", "v1", 1, set("a")), entry("update-manager2", "v1", 2, set("b")), entry("update-manager3", "v1", 3, set("c")), entry("update-manager4", "v1", 4, set("d")), }, expected: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 2, set("a", "b")), entry("update-manager3", "v1", 3, set("c")), entry("update-manager4", "v1", 4, set("d")), }, }, { name: "one version, one ancient changes", input: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 2, set("a", "b")), entry("update-manager3", "v1", 3, set("c")), entry("update-manager4", "v1", 4, set("d")), entry("update-manager5", "v1", 5, set("e")), }, expected: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 3, set("a", "b", "c")), entry("update-manager4", "v1", 4, set("d")), entry("update-manager5", "v1", 5, set("e")), }, }, { name: "two versions, no ancient changes", input: []metav1.ManagedFieldsEntry{ entry("update-manager1", "v1", 1, set("a")), entry("update-manager2", "v2", 2, set("b")), entry("update-manager3", "v1", 3, set("c")), entry("update-manager4", "v1", 4, set("d")), entry("update-manager5", "v1", 5, set("e")), }, expected: []metav1.ManagedFieldsEntry{ entry("update-manager2", "v2", 2, set("b")), entry("ancient-changes", "v1", 4, set("a", "c", "d")), entry("update-manager5", "v1", 5, set("e")), }, }, { name: "three versions, one ancient changes", input: []metav1.ManagedFieldsEntry{ entry("update-manager2", "v2", 2, set("b")), entry("ancient-changes", "v1", 4, set("a", "c", "d")), entry("update-manager5", "v1", 5, set("e")), entry("update-manager6", "v3", 6, set("f")), entry("update-manager7", "v2", 7, set("g")), }, expected: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), entry("update-manager6", "v3", 6, set("f")), entry("ancient-changes", "v2", 7, set("b", "g")), }, }, { name: "three versions, two ancient changes", input: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), entry("update-manager6", "v3", 6, set("f")), entry("ancient-changes", "v2", 7, set("b", "g")), entry("update-manager8", "v3", 8, set("h")), }, expected: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), entry("ancient-changes", "v2", 7, set("b", "g")), entry("ancient-changes", "v3", 8, set("f", "h")), }, }, { name: "four versions, two ancient changes", input: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), entry("update-manager6", "v3", 6, set("f")), entry("ancient-changes", "v2", 7, set("b", "g")), entry("update-manager8", "v4", 8, set("h")), }, expected: []metav1.ManagedFieldsEntry{ entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), entry("update-manager6", "v3", 6, set("f")), entry("ancient-changes", "v2", 7, set("b", "g")), entry("update-manager8", "v4", 8, set("h")), }, }, } for _, tc := range testCases { f.Reset() live := f.Live() accessor, err := meta.Accessor(live) if err != nil { t.Fatalf("%v: couldn't get accessor: %v", tc.name, err) } accessor.SetManagedFields(tc.input) if err := f.Update(live, "no-op-update"); err != nil { t.Fatalf("%v: failed to do no-op update to object: %v", tc.name, err) } if e, a := tc.expected, f.ManagedFields(); !apiequality.Semantic.DeepEqual(e, a) { t.Errorf("%v: unexpected value for managedFields:\nexpected: %v\n but got: %v", tc.name, mustMarshal(e), mustMarshal(a)) } expectIdempotence(t, f) } } // expectIdempotence does a no-op update and ensures that managedFields doesn't change by calling capUpdateManagers. func expectIdempotence(t *testing.T, f managedfieldstest.TestFieldManager) { before := []metav1.ManagedFieldsEntry{} for _, m := range f.ManagedFields() { before = append(before, *m.DeepCopy()) } if err := f.Update(f.Live(), "no-op-update"); err != nil { t.Fatalf("failed to do no-op update to object: %v", err) } if after := f.ManagedFields(); !apiequality.Semantic.DeepEqual(before, after) { t.Fatalf("exected idempotence, but managedFields changed:\nbefore: %v\n after: %v", mustMarshal(before), mustMarshal(after)) } } // expectManagesField ensures that manager m currently manages field path p. func expectManagesField(t *testing.T, f managedfieldstest.TestFieldManager, m string, p fieldpath.Path) { for _, e := range f.ManagedFields() { if e.Manager == m { var s fieldpath.Set err := s.FromJSON(bytes.NewReader(e.FieldsV1.Raw)) if err != nil { t.Fatalf("error parsing managedFields for %v: %v: %#v", m, err, f.ManagedFields()) } if !s.Has(p) { t.Fatalf("expected managedFields for %v to contain %v, but got:\n%v", m, p.String(), s.String()) } return } } t.Fatalf("exected to find manager name %v, but got: %#v", m, f.ManagedFields()) } func mustMarshal(i interface{}) string { b, err := json.MarshalIndent(i, "", " ") if err != nil { panic(fmt.Sprintf("error marshalling %v to json: %v", i, err)) } return string(b) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/conflict.go000066400000000000000000000056641453143165200265270ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "encoding/json" "fmt" "sort" "strings" "time" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/merge" ) // NewConflictError returns an error including details on the requests apply conflicts func NewConflictError(conflicts merge.Conflicts) *errors.StatusError { causes := []metav1.StatusCause{} for _, conflict := range conflicts { causes = append(causes, metav1.StatusCause{ Type: metav1.CauseTypeFieldManagerConflict, Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)), Field: conflict.Path.String(), }) } return errors.NewApplyConflict(causes, getConflictMessage(conflicts)) } func getConflictMessage(conflicts merge.Conflicts) string { if len(conflicts) == 1 { return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path) } m := map[string][]fieldpath.Path{} for _, conflict := range conflicts { m[conflict.Manager] = append(m[conflict.Manager], conflict.Path) } uniqueManagers := []string{} for manager := range m { uniqueManagers = append(uniqueManagers, manager) } // Print conflicts by sorted managers. sort.Strings(uniqueManagers) messages := []string{} for _, manager := range uniqueManagers { messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager))) for _, path := range m[manager] { messages = append(messages, fmt.Sprintf("- %v", path)) } } return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n")) } func printManager(manager string) string { encodedManager := &metav1.ManagedFieldsEntry{} if err := json.Unmarshal([]byte(manager), encodedManager); err != nil { return fmt.Sprintf("%q", manager) } managerStr := fmt.Sprintf("%q", encodedManager.Manager) if encodedManager.Subresource != "" { managerStr = fmt.Sprintf("%s with subresource %q", managerStr, encodedManager.Subresource) } if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate { if encodedManager.Time == nil { return fmt.Sprintf("%s using %v", managerStr, encodedManager.APIVersion) } return fmt.Sprintf("%s using %v at %v", managerStr, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339)) } return managerStr } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/conflict_test.go000066400000000000000000000101601453143165200275510ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "net/http" "reflect" "testing" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/managedfields/internal" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/merge" ) // TestNewConflictError tests that NewConflictError creates the correct StatusError for a given smd Conflicts func TestNewConflictError(t *testing.T) { testCases := []struct { conflict merge.Conflicts expected *errors.StatusError }{ { conflict: merge.Conflicts{ merge.Conflict{ Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`, Path: fieldpath.MakePathOrDie("spec", "replicas"), }, }, expected: &errors.StatusError{ ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{ { Type: metav1.CauseTypeFieldManagerConflict, Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`, Field: ".spec.replicas", }, }, }, Message: `Apply failed with 1 conflict: conflict with "foo" using v1 at 2001-02-03T04:05:06Z: .spec.replicas`, }, }, }, { conflict: merge.Conflicts{ merge.Conflict{ Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`, Path: fieldpath.MakePathOrDie("spec", "replicas"), }, merge.Conflict{ Manager: `{"manager":"bar","operation":"Apply"}`, Path: fieldpath.MakePathOrDie("metadata", "labels", "app"), }, }, expected: &errors.StatusError{ ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{ { Type: metav1.CauseTypeFieldManagerConflict, Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`, Field: ".spec.replicas", }, { Type: metav1.CauseTypeFieldManagerConflict, Message: `conflict with "bar"`, Field: ".metadata.labels.app", }, }, }, Message: `Apply failed with 2 conflicts: conflicts with "bar": - .metadata.labels.app conflicts with "foo" using v1 at 2001-02-03T04:05:06Z: - .spec.replicas`, }, }, }, { conflict: merge.Conflicts{ merge.Conflict{ Manager: `{"manager":"foo","operation":"Update","subresource":"scale","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`, Path: fieldpath.MakePathOrDie("spec", "replicas"), }, }, expected: &errors.StatusError{ ErrStatus: metav1.Status{ Status: metav1.StatusFailure, Code: http.StatusConflict, Reason: metav1.StatusReasonConflict, Details: &metav1.StatusDetails{ Causes: []metav1.StatusCause{ { Type: metav1.CauseTypeFieldManagerConflict, Message: `conflict with "foo" with subresource "scale" using v1 at 2001-02-03T04:05:06Z`, Field: ".spec.replicas", }, }, }, Message: `Apply failed with 1 conflict: conflict with "foo" with subresource "scale" using v1 at 2001-02-03T04:05:06Z: .spec.replicas`, }, }, }, } for _, tc := range testCases { actual := internal.NewConflictError(tc.conflict) if !reflect.DeepEqual(tc.expected, actual) { t.Errorf("Expected to get\n%+v\nbut got\n%+v", tc.expected.ErrStatus, actual.ErrStatus) } } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/fieldmanager.go000066400000000000000000000163421453143165200273370ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "reflect" "time" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" "sigs.k8s.io/structured-merge-diff/v4/merge" ) // DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates // if the number of update managers exceeds this, the oldest entries will be merged until the number is below the maximum. // TODO(jennybuckley): Determine if this is really the best value. Ideally we wouldn't unnecessarily merge too many entries. const DefaultMaxUpdateManagers int = 10 // DefaultTrackOnCreateProbability defines the default probability that the field management of an object // starts being tracked from the object's creation, instead of from the first time the object is applied to. const DefaultTrackOnCreateProbability float32 = 1 var atMostEverySecond = NewAtMostEvery(time.Second) // FieldManager updates the managed fields and merges applied // configurations. type FieldManager struct { fieldManager Manager subresource string } // NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields // on update and apply requests. func NewFieldManager(f Manager, subresource string) *FieldManager { return &FieldManager{fieldManager: f, subresource: subresource} } // newDefaultFieldManager is a helper function which wraps a Manager with certain default logic. func NewDefaultFieldManager(f Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, subresource string) *FieldManager { return NewFieldManager( NewVersionCheckManager( NewLastAppliedUpdater( NewLastAppliedManager( NewProbabilisticSkipNonAppliedManager( NewCapManagersManager( NewBuildManagerInfoManager( NewManagedFieldsUpdater( NewStripMetaManager(f), ), kind.GroupVersion(), subresource, ), DefaultMaxUpdateManagers, ), objectCreater, DefaultTrackOnCreateProbability, ), typeConverter, objectConverter, kind.GroupVersion(), ), ), kind, ), subresource, ) } func decodeLiveOrNew(liveObj, newObj runtime.Object, ignoreManagedFieldsFromRequestObject bool) (Managed, error) { liveAccessor, err := meta.Accessor(liveObj) if err != nil { return nil, err } // We take the managedFields of the live object in case the request tries to // manually set managedFields via a subresource. if ignoreManagedFieldsFromRequestObject { return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields())) } // If the object doesn't have metadata, we should just return without trying to // set the managedFields at all, so creates/updates/patches will work normally. newAccessor, err := meta.Accessor(newObj) if err != nil { return nil, err } if isResetManagedFields(newAccessor.GetManagedFields()) { return NewEmptyManaged(), nil } // If the managed field is empty or we failed to decode it, // let's try the live object. This is to prevent clients who // don't understand managedFields from deleting it accidentally. managed, err := DecodeManagedFields(newAccessor.GetManagedFields()) if err != nil || len(managed.Fields()) == 0 { return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields())) } return managed, nil } func emptyManagedFieldsOnErr(managed Managed, err error) (Managed, error) { if err != nil { return NewEmptyManaged(), nil } return managed, nil } // Update is used when the object has already been merged (non-apply // use-case), and simply updates the managed fields in the output // object. func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) { // First try to decode the managed fields provided in the update, // This is necessary to allow directly updating managed fields. isSubresource := f.subresource != "" managed, err := decodeLiveOrNew(liveObj, newObj, isSubresource) if err != nil { return newObj, nil } RemoveObjectManagedFields(newObj) if object, managed, err = f.fieldManager.Update(liveObj, newObj, managed, manager); err != nil { return nil, err } if err = EncodeObjectManagedFields(object, managed); err != nil { return nil, fmt.Errorf("failed to encode managed fields: %v", err) } return object, nil } // UpdateNoErrors is the same as Update, but it will not return // errors. If an error happens, the object is returned with // managedFields cleared. func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager string) runtime.Object { obj, err := f.Update(liveObj, newObj, manager) if err != nil { atMostEverySecond.Do(func() { ns, name := "unknown", "unknown" if accessor, err := meta.Accessor(newObj); err == nil { ns = accessor.GetNamespace() name = accessor.GetName() } klog.ErrorS(err, "[SHOULD NOT HAPPEN] failed to update managedFields", "versionKind", newObj.GetObjectKind().GroupVersionKind(), "namespace", ns, "name", name) }) // Explicitly remove managedFields on failure, so that // we can't have garbage in it. RemoveObjectManagedFields(newObj) return newObj } return obj } // Returns true if the managedFields indicate that the user is trying to // reset the managedFields, i.e. if the list is non-nil but empty, or if // the list has one empty item. func isResetManagedFields(managedFields []metav1.ManagedFieldsEntry) bool { if len(managedFields) == 0 { return managedFields != nil } if len(managedFields) == 1 { return reflect.DeepEqual(managedFields[0], metav1.ManagedFieldsEntry{}) } return false } // Apply is used when server-side apply is called, as it merges the // object and updates the managed fields. func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string, force bool) (object runtime.Object, err error) { // If the object doesn't have metadata, apply isn't allowed. accessor, err := meta.Accessor(liveObj) if err != nil { return nil, fmt.Errorf("couldn't get accessor: %v", err) } // Decode the managed fields in the live object, since it isn't allowed in the patch. managed, err := DecodeManagedFields(accessor.GetManagedFields()) if err != nil { return nil, fmt.Errorf("failed to decode managed fields: %v", err) } object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force) if err != nil { if conflicts, ok := err.(merge.Conflicts); ok { return nil, NewConflictError(conflicts) } return nil, err } if err = EncodeObjectManagedFields(object, managed); err != nil { return nil, fmt.Errorf("failed to encode managed fields: %v", err) } return object, nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/fieldmanager_test.go000066400000000000000000000024651453143165200303770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "encoding/json" "os" "path/filepath" "strings" "k8s.io/apimachinery/pkg/util/managedfields/internal" "k8s.io/kube-openapi/pkg/validation/spec" ) var fakeTypeConverter = func() internal.TypeConverter { data, err := os.ReadFile(filepath.Join( strings.Repeat(".."+string(filepath.Separator), 8), "api", "openapi-spec", "swagger.json")) if err != nil { panic(err) } convertedDefs := map[string]*spec.Schema{} spec := spec.Swagger{} if err := json.Unmarshal(data, &spec); err != nil { panic(err) } for k, v := range spec.Definitions { vCopy := v convertedDefs[k] = &vCopy } typeConverter, err := internal.NewTypeConverter(convertedDefs, false) if err != nil { panic(err) } return typeConverter }() golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/fields.go000066400000000000000000000024551453143165200261670ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "bytes" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) // EmptyFields represents a set with no paths // It looks like metav1.Fields{Raw: []byte("{}")} var EmptyFields = func() metav1.FieldsV1 { f, err := SetToFields(*fieldpath.NewSet()) if err != nil { panic("should never happen") } return f }() // FieldsToSet creates a set paths from an input trie of fields func FieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) { err = s.FromJSON(bytes.NewReader(f.Raw)) return s, err } // SetToFields creates a trie of fields from an input set of paths func SetToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) { f.Raw, err = s.ToJSON() return f, err } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/fields_test.go000066400000000000000000000076551453143165200272350ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "reflect" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) // TestFieldsRoundTrip tests that a fields trie can be round tripped as a path set func TestFieldsRoundTrip(t *testing.T) { tests := []metav1.FieldsV1{ { Raw: []byte(`{"f:metadata":{".":{},"f:name":{}}}`), }, EmptyFields, } for _, test := range tests { set, err := FieldsToSet(test) if err != nil { t.Fatalf("Failed to create path set: %v", err) } output, err := SetToFields(set) if err != nil { t.Fatalf("Failed to create fields trie from path set: %v", err) } if !reflect.DeepEqual(test, output) { t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output) } } } // TestFieldsToSetError tests that errors are picked up by FieldsToSet func TestFieldsToSetError(t *testing.T) { tests := []struct { fields metav1.FieldsV1 errString string }{ { fields: metav1.FieldsV1{ Raw: []byte(`{"k:{invalid json}":{"f:name":{},".":{}}}`), }, errString: "ReadObjectCB", }, } for _, test := range tests { _, err := FieldsToSet(test.fields) if err == nil || !strings.Contains(err.Error(), test.errString) { t.Fatalf("Expected error to contain %q but got: %v", test.errString, err) } } } // TestSetToFieldsError tests that errors are picked up by SetToFields func TestSetToFieldsError(t *testing.T) { validName := "ok" invalidPath := fieldpath.Path([]fieldpath.PathElement{{}, {FieldName: &validName}}) tests := []struct { set fieldpath.Set errString string }{ { set: *fieldpath.NewSet(invalidPath), errString: "invalid PathElement", }, } for _, test := range tests { _, err := SetToFields(test.set) if err == nil || !strings.Contains(err.Error(), test.errString) { t.Fatalf("Expected error to contain %q but got: %v", test.errString, err) } } } func BenchmarkSetToFields(b *testing.B) { set := fieldpath.NewSet( fieldpath.MakePathOrDie("foo", 0, "bar", "baz"), fieldpath.MakePathOrDie("foo", 0, "bar", "zot"), fieldpath.MakePathOrDie("foo", 0, "bar"), fieldpath.MakePathOrDie("foo", 0), fieldpath.MakePathOrDie("foo", 1, "bar", "baz"), fieldpath.MakePathOrDie("foo", 1, "bar"), fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first")), fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first"), "bar"), fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "second"), "bar"), ) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, err := SetToFields(*set) if err != nil { b.Fatal(err) } } } func BenchmarkFieldsToSet(b *testing.B) { set := fieldpath.NewSet( fieldpath.MakePathOrDie("foo", 0, "bar", "baz"), fieldpath.MakePathOrDie("foo", 0, "bar", "zot"), fieldpath.MakePathOrDie("foo", 0, "bar"), fieldpath.MakePathOrDie("foo", 0), fieldpath.MakePathOrDie("foo", 1, "bar", "baz"), fieldpath.MakePathOrDie("foo", 1, "bar"), fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first")), fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first"), "bar"), fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "second"), "bar"), ) fields, err := SetToFields(*set) if err != nil { b.Fatal(err) } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _, err := FieldsToSet(fields) if err != nil { b.Fatal(err) } } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/lastapplied.go000066400000000000000000000032451453143165200272210ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/api/meta" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" "k8s.io/apimachinery/pkg/runtime" ) // LastAppliedConfigAnnotation is the annotation used to store the previous // configuration of a resource for use in a three way diff by UpdateApplyAnnotation. // // This is a copy of the corev1 annotation since we don't want to depend on the whole package. const LastAppliedConfigAnnotation = "kubectl.kubernetes.io/last-applied-configuration" // SetLastApplied sets the last-applied annotation the given value in // the object. func SetLastApplied(obj runtime.Object, value string) error { accessor, err := meta.Accessor(obj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } var annotations = accessor.GetAnnotations() if annotations == nil { annotations = map[string]string{} } annotations[LastAppliedConfigAnnotation] = value if err := apimachineryvalidation.ValidateAnnotationsSize(annotations); err != nil { delete(annotations, LastAppliedConfigAnnotation) } accessor.SetAnnotations(annotations) return nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/lastappliedmanager.go000066400000000000000000000135511453143165200305550ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "encoding/json" "fmt" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/merge" ) type lastAppliedManager struct { fieldManager Manager typeConverter TypeConverter objectConverter runtime.ObjectConvertor groupVersion schema.GroupVersion } var _ Manager = &lastAppliedManager{} // NewLastAppliedManager converts the client-side apply annotation to // server-side apply managed fields func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager { return &lastAppliedManager{ fieldManager: fieldManager, typeConverter: typeConverter, objectConverter: objectConverter, groupVersion: groupVersion, } } // Update implements Manager. func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { return f.fieldManager.Update(liveObj, newObj, managed, manager) } // Apply will consider the last-applied annotation // for upgrading an object managed by client-side apply to server-side apply // without conflicts. func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force) // Upgrade the client-side apply annotation only from kubectl server-side-apply. // To opt-out of this behavior, users may specify a different field manager. if manager != "kubectl" { return newLiveObj, newManaged, newErr } // Check if we have conflicts if newErr == nil { return newLiveObj, newManaged, newErr } conflicts, ok := newErr.(merge.Conflicts) if !ok { return newLiveObj, newManaged, newErr } conflictSet := conflictsToSet(conflicts) // Check if conflicts are allowed due to client-side apply, // and if so, then force apply allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj) if err != nil { return newLiveObj, newManaged, newErr } if !conflictSet.Difference(allowedConflictSet).Empty() { newConflicts := conflictsDifference(conflicts, allowedConflictSet) return newLiveObj, newManaged, newConflicts } return f.fieldManager.Apply(liveObj, newObj, managed, manager, true) } func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) { var accessor, err = meta.Accessor(liveObj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } // If there is no client-side apply annotation, then there is nothing to do var annotations = accessor.GetAnnotations() if annotations == nil { return nil, fmt.Errorf("no last applied annotation") } var lastApplied, ok = annotations[LastAppliedConfigAnnotation] if !ok || lastApplied == "" { return nil, fmt.Errorf("no last applied annotation") } liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion) if err != nil { return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err) } liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned) if err != nil { return nil, fmt.Errorf("failed to convert live obj to typed: %v", err) } var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}} err = json.Unmarshal([]byte(lastApplied), lastAppliedObj) if err != nil { return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied) } if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() { return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err) } lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj) if err != nil { return nil, fmt.Errorf("failed to convert last applied to typed: %v", err) } lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet() if err != nil { return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err) } comparison, err := lastAppliedObjTyped.Compare(liveObjTyped) if err != nil { return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err) } // Remove fields in last applied that are different, added, or missing in // the live object. // Because last-applied fields don't match the live object fields, // then we don't own these fields. lastAppliedObjFieldSet = lastAppliedObjFieldSet. Difference(comparison.Modified). Difference(comparison.Added). Difference(comparison.Removed) return lastAppliedObjFieldSet, nil } // TODO: replace with merge.Conflicts.ToSet() func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set { conflictSet := fieldpath.NewSet() for _, conflict := range []merge.Conflict(conflicts) { conflictSet.Insert(conflict.Path) } return conflictSet } func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts { newConflicts := []merge.Conflict{} for _, conflict := range []merge.Conflict(conflicts) { if !s.Has(conflict.Path) { newConflicts = append(newConflicts, conflict) } } return newConflicts } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/lastappliedmanager_test.go000066400000000000000000000477431453143165200316260ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "fmt" "reflect" "testing" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" yamlutil "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/merge" "sigs.k8s.io/yaml" ) type testArgs struct { lastApplied []byte original []byte applied []byte fieldManager string expectConflictSet *fieldpath.Set } // TestApplyUsingLastAppliedAnnotation tests that applying to an object // created with the client-side apply last-applied annotation // will not give conflicts func TestApplyUsingLastAppliedAnnotation(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) tests := []testArgs{ { fieldManager: "kubectl", lastApplied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image-v1 - name: my-c2 image: my-image2 `), original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app # missing from last-applied spec: replicas: 100 # does not match last-applied selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image-v2 # does no match last-applied # note that second container in last-applied is missing `), applied: []byte(` # test conflicts due to fields not allowed by last-applied apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-new-label # NOT allowed: update label spec: replicas: 333 # NOT allowed: update replicas selector: matchLabels: app: my-new-label # allowed: update label template: metadata: labels: app: my-new-label # allowed: update-label spec: containers: - name: my-c image: my-image-new # NOT allowed: update image `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("metadata", "labels", "app"), fieldpath.MakePathOrDie("spec", "replicas"), fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"), ), }, { fieldManager: "kubectl", lastApplied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 # does not match last applied selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), applied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-new-label spec: replicas: 3 # expect conflict template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "replicas"), ), }, { fieldManager: "kubectl", original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), applied: []byte(` # applied object matches original apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), }, { fieldManager: "kubectl", original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), applied: []byte(` # test allowed update with no conflicts apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-new-label # update label spec: replicas: 333 # update replicas selector: matchLabels: app: my-new-label # update label template: metadata: labels: app: my-new-label # update-label spec: containers: - name: my-c image: my-image `), }, { fieldManager: "not_kubectl", lastApplied: []byte(` # expect conflicts because field manager is NOT kubectl apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image-v1 `), original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 # does not match last-applied selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image-v2 # does no match last-applied `), applied: []byte(` # test conflicts due to fields not allowed by last-applied apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-new-label # update label spec: replicas: 333 # update replicas selector: matchLabels: app: my-new-label # update label template: metadata: labels: app: my-new-label # update-label spec: containers: - name: my-c image: my-image-new # update image `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("metadata", "labels", "app"), fieldpath.MakePathOrDie("spec", "replicas"), fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic fieldpath.MakePathOrDie("spec", "template", "metadata", "labels", "app"), fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"), ), }, { fieldManager: "kubectl", original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), applied: []byte(` # test allowed update with no conflicts apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-new-label spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-new-image # update image `), }, { fieldManager: "not_kubectl", original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-app spec: replicas: 100 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `), applied: []byte(` # expect changes to fail because field manager is not kubectl apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment labels: app: my-new-label # update label spec: replicas: 3 # update replicas selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-new-image # update image `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("metadata", "labels", "app"), fieldpath.MakePathOrDie("spec", "replicas"), fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"), ), }, { fieldManager: "kubectl", original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 `), applied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 100 # update replicas `), }, { fieldManager: "kubectl", lastApplied: []byte(` apiVersion: extensions/v1beta1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 `), original: []byte(` apiVersion: apps/v1 # expect conflict due to apiVersion mismatch with last-applied kind: Deployment metadata: name: my-deployment spec: replicas: 3 `), applied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 100 # update replicas `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "replicas"), ), }, { fieldManager: "kubectl", lastApplied: []byte(` apiVerison: foo kind: bar spec: expect conflict due to invalid object `), original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 `), applied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 100 # update replicas `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "replicas"), ), }, { fieldManager: "kubectl", // last-applied is empty lastApplied: []byte{}, original: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 `), applied: []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 100 # update replicas `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "replicas"), ), }, } testConflicts(t, f, tests) } func TestServiceApply(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Service")) tests := []testArgs{ { fieldManager: "kubectl", original: []byte(` apiVersion: v1 kind: Service metadata: name: test spec: ports: - name: https port: 443 protocol: TCP targetPort: 8443 selector: old: test `), applied: []byte(` # All accepted while using the same field manager apiVersion: v1 kind: Service metadata: name: test spec: ports: - name: https port: 443 protocol: TCP targetPort: 8444 selector: new: test `), }, { fieldManager: "kubectl", original: []byte(` apiVersion: v1 kind: Service metadata: name: test spec: ports: - name: https port: 443 protocol: TCP targetPort: 8443 selector: old: test `), applied: []byte(` # Allowed to remove selectors while using the same field manager apiVersion: v1 kind: Service metadata: name: test spec: ports: - name: https port: 443 protocol: TCP targetPort: 8444 selector: {} `), }, { fieldManager: "not_kubectl", original: []byte(` apiVersion: v1 kind: Service metadata: name: test spec: ports: - name: https port: 443 protocol: TCP # TODO: issue - this is a defaulted field, should not be required in a new spec targetPort: 8443 selector: old: test `), applied: []byte(` # test selector update not allowed by last-applied apiVersion: v1 kind: Service metadata: name: test spec: ports: - name: https port: 443 protocol: TCP targetPort: 8444 selector: new: test `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic fieldpath.MakePathOrDie("spec", "ports", fieldpath.KeyByFields("port", 443, "protocol", "TCP"), "targetPort"), ), }, } testConflicts(t, f, tests) } func TestReplicationControllerApply(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ReplicationController")) tests := []testArgs{ { fieldManager: "kubectl", original: []byte(` apiVersion: v1 kind: ReplicationController metadata: name: test spec: replicas: 0 selector: old: test `), applied: []byte(` # All accepted while using the same field manager apiVersion: v1 kind: ReplicationController metadata: name: test spec: replicas: 3 selector: new: test `), }, { fieldManager: "not_kubectl", original: []byte(` apiVersion: v1 kind: ReplicationController metadata: name: test spec: replicas: 0 selector: old: test `), applied: []byte(` # test selector update not allowed by last-applied apiVersion: v1 kind: ReplicationController metadata: name: test spec: replicas: 3 selector: new: test `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic fieldpath.MakePathOrDie("spec", "replicas"), ), }, } testConflicts(t, f, tests) } func TestPodApply(t *testing.T) { f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) tests := []testArgs{ { fieldManager: "kubectl", original: []byte(` apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: definetlyControlPlane nodeSelector: node-role.kubernetes.io/master: "" `), applied: []byte(` # All accepted while using the same field manager apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeSelector: node-role.kubernetes.io/worker: "" `), }, { fieldManager: "not_kubectl", original: []byte(` apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: definetlyControlPlane nodeSelector: node-role.kubernetes.io/master: "" `), applied: []byte(` # test selector update not allowed by last-applied apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: definetlyControlPlane nodeSelector: node-role.kubernetes.io/master: "" otherNodeType: "" `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "nodeSelector"), // selector is atomic ), }, { fieldManager: "not_kubectl", original: []byte(` apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: definetlyControlPlane nodeSelector: node-role.kubernetes.io/master: "" `), applied: []byte(` # purging selector not allowed for different manager apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: another nodeSelector: {} `), expectConflictSet: fieldpath.NewSet( fieldpath.MakePathOrDie("spec", "nodeSelector"), // selector is atomic fieldpath.MakePathOrDie("spec", "nodeName"), ), }, { fieldManager: "kubectl", original: []byte(` apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: definetlyControlPlane nodeSelector: node-role.kubernetes.io/master: "" `), applied: []byte(` # same manager could purge nodeSelector apiVersion: v1 kind: Pod metadata: name: test namespace: test spec: containers: - args: - -v=2 command: - controller image: some.registry/app:latest name: doJob nodeName: another nodeSelector: {} `), }, } testConflicts(t, f, tests) } func testConflicts(t *testing.T, f managedfieldstest.TestFieldManager, tests []testArgs) { for i, test := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { f.Reset() originalObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(test.original, &originalObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if test.lastApplied == nil { test.lastApplied = test.original } if err := setLastAppliedFromEncoded(originalObj, test.lastApplied); err != nil { t.Errorf("failed to set last applied: %v", err) } if err := f.Update(originalObj, "test_client_side_apply"); err != nil { t.Errorf("failed to apply object: %v", err) } appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(test.applied, &appliedObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } err := f.Apply(appliedObj, test.fieldManager, false) if test.expectConflictSet == nil { if err != nil { t.Errorf("expected no error but got %v", err) } } else { if err == nil || !apierrors.IsConflict(err) { t.Errorf("expected to get conflicts but got %v", err) } expectedConflicts := merge.Conflicts{} test.expectConflictSet.Iterate(func(p fieldpath.Path) { expectedConflicts = append(expectedConflicts, merge.Conflict{ Manager: fmt.Sprintf(`{"manager":"test_client_side_apply","operation":"Update","apiVersion":"%s"}`, f.APIVersion()), Path: p, }) }) expectedConflictErr := internal.NewConflictError(expectedConflicts) if !reflect.DeepEqual(expectedConflictErr, err) { t.Errorf("expected to get\n%+v\nbut got\n%+v", expectedConflictErr, err) } // Yet force should resolve all conflicts err = f.Apply(appliedObj, test.fieldManager, true) if err != nil { t.Errorf("unexpected error during force ownership apply: %v", err) } } // Eventually resource should contain applied changes if !apiequality.Semantic.DeepDerivative(appliedObj, f.Live()) { t.Errorf("expected equal resource: \n%#v, got: \n%#v", appliedObj, f.Live()) } }) } } func yamlToJSON(y []byte) (string, error) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(y, &obj.Object); err != nil { return "", fmt.Errorf("error decoding YAML: %v", err) } serialization, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) if err != nil { return "", fmt.Errorf("error encoding object: %v", err) } json, err := yamlutil.ToJSON(serialization) if err != nil { return "", fmt.Errorf("error converting to json: %v", err) } return string(json), nil } func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error { lastAppliedJSON, err := yamlToJSON(lastApplied) if err != nil { return err } return internal.SetLastApplied(obj, lastAppliedJSON) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/lastappliedupdater.go000066400000000000000000000063171453143165200306110ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) type lastAppliedUpdater struct { fieldManager Manager } var _ Manager = &lastAppliedUpdater{} // NewLastAppliedUpdater sets the client-side apply annotation up to date with // server-side apply managed fields func NewLastAppliedUpdater(fieldManager Manager) Manager { return &lastAppliedUpdater{ fieldManager: fieldManager, } } // Update implements Manager. func (f *lastAppliedUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { return f.fieldManager.Update(liveObj, newObj, managed, manager) } // server-side apply managed fields func (f *lastAppliedUpdater) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { liveObj, managed, err := f.fieldManager.Apply(liveObj, newObj, managed, manager, force) if err != nil { return liveObj, managed, err } // Sync the client-side apply annotation only from kubectl server-side apply. // To opt-out of this behavior, users may specify a different field manager. // // If the client-side apply annotation doesn't exist, // then continue because we have no annotation to update if manager == "kubectl" && hasLastApplied(liveObj) { lastAppliedValue, err := buildLastApplied(newObj) if err != nil { return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err) } err = SetLastApplied(liveObj, lastAppliedValue) if err != nil { return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err) } } return liveObj, managed, err } func hasLastApplied(obj runtime.Object) bool { var accessor, err = meta.Accessor(obj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } var annotations = accessor.GetAnnotations() if annotations == nil { return false } lastApplied, ok := annotations[LastAppliedConfigAnnotation] return ok && len(lastApplied) > 0 } func buildLastApplied(obj runtime.Object) (string, error) { obj = obj.DeepCopyObject() var accessor, err = meta.Accessor(obj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } // Remove the annotation from the object before encoding the object var annotations = accessor.GetAnnotations() delete(annotations, LastAppliedConfigAnnotation) accessor.SetAnnotations(annotations) lastApplied, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) if err != nil { return "", fmt.Errorf("couldn't encode object into last applied annotation: %v", err) } return string(lastApplied), nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/lastappliedupdater_test.go000066400000000000000000000165521453143165200316520ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "encoding/json" "fmt" "strings" "testing" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" "sigs.k8s.io/yaml" ) func TestLastAppliedUpdater(t *testing.T) { f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment"), "", func(m internal.Manager) internal.Manager { return internal.NewLastAppliedUpdater(m) }) originalLastApplied := `nonempty` appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} appliedDeployment := []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment annotations: "kubectl.kubernetes.io/last-applied-configuration": "` + originalLastApplied + `" labels: app: my-app spec: replicas: 20 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-c image: my-image `) if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil { t.Errorf("error decoding YAML: %v", err) } if err := f.Apply(appliedObj, "NOT-KUBECTL", false); err != nil { t.Errorf("error applying object: %v", err) } lastApplied, err := getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if lastApplied != originalLastApplied { t.Errorf("expected last applied annotation to be %q and NOT be updated, but got: %q", originalLastApplied, lastApplied) } if err := f.Apply(appliedObj, "kubectl", false); err != nil { t.Errorf("error applying object: %v", err) } lastApplied, err = getLastApplied(f.Live()) if err != nil { t.Errorf("failed to get last applied: %v", err) } if lastApplied == originalLastApplied || !strings.Contains(lastApplied, "my-app") || !strings.Contains(lastApplied, "my-image") { t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) } } func TestLargeLastApplied(t *testing.T) { tests := []struct { name string oldObject *unstructured.Unstructured newObject *unstructured.Unstructured }{ { name: "old object + new object last-applied annotation is too big", oldObject: func() *unstructured.Unstructured { u := &unstructured.Unstructured{} err := json.Unmarshal([]byte(` { "metadata": { "name": "large-update-test-cm", "namespace": "default", "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "nonempty" } }, "apiVersion": "v1", "kind": "ConfigMap", "data": { "k": "v" } }`), &u) if err != nil { panic(err) } return u }(), newObject: func() *unstructured.Unstructured { u := &unstructured.Unstructured{} err := json.Unmarshal([]byte(` { "metadata": { "name": "large-update-test-cm", "namespace": "default", "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "nonempty" } }, "apiVersion": "v1", "kind": "ConfigMap", "data": { "k": "v" } }`), &u) if err != nil { panic(err) } for i := 0; i < 9999; i++ { unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) unstructured.SetNestedField(u.Object, "A", "data", unique) } return u }(), }, { name: "old object + new object annotations + new object last-applied annotation is too big", oldObject: func() *unstructured.Unstructured { u := &unstructured.Unstructured{} err := json.Unmarshal([]byte(` { "metadata": { "name": "large-update-test-cm", "namespace": "default", "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "nonempty" } }, "apiVersion": "v1", "kind": "ConfigMap", "data": { "k": "v" } }`), &u) if err != nil { panic(err) } for i := 0; i < 2000; i++ { unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) unstructured.SetNestedField(u.Object, "A", "data", unique) } return u }(), newObject: func() *unstructured.Unstructured { u := &unstructured.Unstructured{} err := json.Unmarshal([]byte(` { "metadata": { "name": "large-update-test-cm", "namespace": "default", "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "nonempty" } }, "apiVersion": "v1", "kind": "ConfigMap", "data": { "k": "v" } }`), &u) if err != nil { panic(err) } for i := 0; i < 2000; i++ { unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) unstructured.SetNestedField(u.Object, "A", "data", unique) unstructured.SetNestedField(u.Object, "A", "metadata", "annotations", unique) } return u }(), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap"), "", func(m internal.Manager) internal.Manager { return internal.NewLastAppliedUpdater(m) }) if err := f.Apply(test.oldObject, "kubectl", false); err != nil { t.Errorf("Error applying object: %v", err) } lastApplied, err := getLastApplied(f.Live()) if err != nil { t.Errorf("Failed to access last applied annotation: %v", err) } if len(lastApplied) == 0 || lastApplied == "nonempty" { t.Errorf("Expected an updated last-applied annotation, but got: %q", lastApplied) } if err := f.Apply(test.newObject, "kubectl", false); err != nil { t.Errorf("Error applying object: %v", err) } accessor := meta.NewAccessor() annotations, err := accessor.Annotations(f.Live()) if err != nil { t.Errorf("Failed to access annotations: %v", err) } if annotations == nil { t.Errorf("No annotations on obj: %v", f.Live()) } lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation] if ok || len(lastApplied) > 0 { t.Errorf("Expected no last applied annotation, but got last applied with length: %d", len(lastApplied)) } }) } } func getLastApplied(obj runtime.Object) (string, error) { accessor := meta.NewAccessor() annotations, err := accessor.Annotations(obj) if err != nil { return "", fmt.Errorf("failed to access annotations: %v", err) } if annotations == nil { return "", fmt.Errorf("no annotations on obj: %v", obj) } lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation] if !ok { return "", fmt.Errorf("expected last applied annotation, but got none for object: %v", obj) } return lastApplied, nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/managedfields.go000066400000000000000000000202111453143165200274720ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "encoding/json" "fmt" "sort" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) // ManagedInterface groups a fieldpath.ManagedFields together with the timestamps associated with each operation. type ManagedInterface interface { // Fields gets the fieldpath.ManagedFields. Fields() fieldpath.ManagedFields // Times gets the timestamps associated with each operation. Times() map[string]*metav1.Time } type managedStruct struct { fields fieldpath.ManagedFields times map[string]*metav1.Time } var _ ManagedInterface = &managedStruct{} // Fields implements ManagedInterface. func (m *managedStruct) Fields() fieldpath.ManagedFields { return m.fields } // Times implements ManagedInterface. func (m *managedStruct) Times() map[string]*metav1.Time { return m.times } // NewEmptyManaged creates an empty ManagedInterface. func NewEmptyManaged() ManagedInterface { return NewManaged(fieldpath.ManagedFields{}, map[string]*metav1.Time{}) } // NewManaged creates a ManagedInterface from a fieldpath.ManagedFields and the timestamps associated with each operation. func NewManaged(f fieldpath.ManagedFields, t map[string]*metav1.Time) ManagedInterface { return &managedStruct{ fields: f, times: t, } } // RemoveObjectManagedFields removes the ManagedFields from the object // before we merge so that it doesn't appear in the ManagedFields // recursively. func RemoveObjectManagedFields(obj runtime.Object) { accessor, err := meta.Accessor(obj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } accessor.SetManagedFields(nil) } // EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields func EncodeObjectManagedFields(obj runtime.Object, managed ManagedInterface) error { accessor, err := meta.Accessor(obj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } encodedManagedFields, err := encodeManagedFields(managed) if err != nil { return fmt.Errorf("failed to convert back managed fields to API: %v", err) } accessor.SetManagedFields(encodedManagedFields) return nil } // DecodeManagedFields converts ManagedFields from the wire format (api format) // to the format used by sigs.k8s.io/structured-merge-diff func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (ManagedInterface, error) { managed := managedStruct{} managed.fields = make(fieldpath.ManagedFields, len(encodedManagedFields)) managed.times = make(map[string]*metav1.Time, len(encodedManagedFields)) for i, encodedVersionedSet := range encodedManagedFields { switch encodedVersionedSet.Operation { case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate: default: return nil, fmt.Errorf("operation must be `Apply` or `Update`") } if len(encodedVersionedSet.APIVersion) < 1 { return nil, fmt.Errorf("apiVersion must not be empty") } switch encodedVersionedSet.FieldsType { case "FieldsV1": // Valid case. case "": return nil, fmt.Errorf("missing fieldsType in managed fields entry %d", i) default: return nil, fmt.Errorf("invalid fieldsType %q in managed fields entry %d", encodedVersionedSet.FieldsType, i) } manager, err := BuildManagerIdentifier(&encodedVersionedSet) if err != nil { return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err) } managed.fields[manager], err = decodeVersionedSet(&encodedVersionedSet) if err != nil { return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err) } managed.times[manager] = encodedVersionedSet.Time } return &managed, nil } // BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) { encodedManagerCopy := *encodedManager // Never include fields type in the manager identifier encodedManagerCopy.FieldsType = "" // Never include the fields in the manager identifier encodedManagerCopy.FieldsV1 = nil // Never include the time in the manager identifier encodedManagerCopy.Time = nil // For appliers, don't include the APIVersion in the manager identifier, // so it will always have the same manager identifier each time it applied. if encodedManager.Operation == metav1.ManagedFieldsOperationApply { encodedManagerCopy.APIVersion = "" } // Use the remaining fields to build the manager identifier b, err := json.Marshal(&encodedManagerCopy) if err != nil { return "", fmt.Errorf("error marshalling manager identifier: %v", err) } return string(b), nil } func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet fieldpath.VersionedSet, err error) { fields := EmptyFields if encodedVersionedSet.FieldsV1 != nil { fields = *encodedVersionedSet.FieldsV1 } set, err := FieldsToSet(fields) if err != nil { return nil, fmt.Errorf("error decoding set: %v", err) } return fieldpath.NewVersionedSet(&set, fieldpath.APIVersion(encodedVersionedSet.APIVersion), encodedVersionedSet.Operation == metav1.ManagedFieldsOperationApply), nil } // encodeManagedFields converts ManagedFields from the format used by // sigs.k8s.io/structured-merge-diff to the wire format (api format) func encodeManagedFields(managed ManagedInterface) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) { if len(managed.Fields()) == 0 { return nil, nil } encodedManagedFields = []metav1.ManagedFieldsEntry{} for manager := range managed.Fields() { versionedSet := managed.Fields()[manager] v, err := encodeManagerVersionedSet(manager, versionedSet) if err != nil { return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err) } if t, ok := managed.Times()[manager]; ok { v.Time = t } encodedManagedFields = append(encodedManagedFields, *v) } return sortEncodedManagedFields(encodedManagedFields) } func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (sortedManagedFields []metav1.ManagedFieldsEntry, err error) { sort.Slice(encodedManagedFields, func(i, j int) bool { p, q := encodedManagedFields[i], encodedManagedFields[j] if p.Operation != q.Operation { return p.Operation < q.Operation } pSeconds, qSeconds := int64(0), int64(0) if p.Time != nil { pSeconds = p.Time.Unix() } if q.Time != nil { qSeconds = q.Time.Unix() } if pSeconds != qSeconds { return pSeconds < qSeconds } if p.Manager != q.Manager { return p.Manager < q.Manager } if p.APIVersion != q.APIVersion { return p.APIVersion < q.APIVersion } return p.Subresource < q.Subresource }) return encodedManagedFields, nil } func encodeManagerVersionedSet(manager string, versionedSet fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) { encodedVersionedSet = &metav1.ManagedFieldsEntry{} // Get as many fields as we can from the manager identifier err = json.Unmarshal([]byte(manager), encodedVersionedSet) if err != nil { return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err) } // Get the APIVersion, Operation, and Fields from the VersionedSet encodedVersionedSet.APIVersion = string(versionedSet.APIVersion()) if versionedSet.Applied() { encodedVersionedSet.Operation = metav1.ManagedFieldsOperationApply } encodedVersionedSet.FieldsType = "FieldsV1" fields, err := SetToFields(*versionedSet.Set()) if err != nil { return nil, fmt.Errorf("error encoding set: %v", err) } encodedVersionedSet.FieldsV1 = &fields return encodedVersionedSet, nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/managedfields_test.go000066400000000000000000000416561453143165200305510ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "reflect" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" ) // TestHasFieldsType makes sure that we fail if we don't have a // FieldsType set properly. func TestHasFieldsType(t *testing.T) { var unmarshaled []metav1.ManagedFieldsEntry if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:field: {} manager: foo operation: Apply `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err != nil { t.Fatalf("did not expect decoding error but got: %v", err) } // Invalid fieldsType V2. if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsType: FieldsV2 fieldsV1: f:field: {} manager: foo operation: Apply `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err == nil { t.Fatal("Expect decoding error but got none") } // Missing fieldsType. if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsV1: f:field: {} manager: foo operation: Apply `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err == nil { t.Fatal("Expect decoding error but got none") } } // TestHasAPIVersion makes sure that we fail if we don't have an // APIVersion set. func TestHasAPIVersion(t *testing.T) { var unmarshaled []metav1.ManagedFieldsEntry if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:field: {} manager: foo operation: Apply `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err != nil { t.Fatalf("did not expect decoding error but got: %v", err) } // Missing apiVersion. unmarshaled = nil if err := yaml.Unmarshal([]byte(`- fieldsType: FieldsV1 fieldsV1: f:field: {} manager: foo operation: Apply `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err == nil { t.Fatal("Expect decoding error but got none") } } // TestHasOperation makes sure that we fail if we don't have an // Operation set properly. func TestHasOperation(t *testing.T) { var unmarshaled []metav1.ManagedFieldsEntry if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:field: {} manager: foo operation: Apply `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err != nil { t.Fatalf("did not expect decoding error but got: %v", err) } // Invalid operation. if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:field: {} manager: foo operation: Invalid `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err == nil { t.Fatal("Expect decoding error but got none") } // Missing operation. unmarshaled = nil if err := yaml.Unmarshal([]byte(`- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:field: {} manager: foo `), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } if _, err := DecodeManagedFields(unmarshaled); err == nil { t.Fatal("Expect decoding error but got none") } } // TestRoundTripManagedFields will roundtrip ManagedFields from the wire format // (api format) to the format used by sigs.k8s.io/structured-merge-diff and back func TestRoundTripManagedFields(t *testing.T) { tests := []string{ `null `, `- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: v:3: f:alsoPi: {} v:3.1415: f:pi: {} v:false: f:notTrue: {} manager: foo operation: Update time: "2001-02-03T04:05:06Z" - apiVersion: v1beta1 fieldsType: FieldsV1 fieldsV1: i:5: f:i: {} manager: foo operation: Update time: "2011-12-13T14:15:16Z" `, `- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:spec: f:containers: k:{"name":"c"}: f:image: {} f:name: {} manager: foo operation: Apply `, `- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:apiVersion: {} f:kind: {} f:metadata: f:labels: f:app: {} f:name: {} f:spec: f:replicas: {} f:selector: f:matchLabels: f:app: {} f:template: f:medatada: f:labels: f:app: {} f:spec: f:containers: k:{"name":"nginx"}: .: {} f:image: {} f:name: {} f:ports: i:0: f:containerPort: {} manager: foo operation: Update `, `- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:allowVolumeExpansion: {} f:apiVersion: {} f:kind: {} f:metadata: f:name: {} f:parameters: f:resturl: {} f:restuser: {} f:secretName: {} f:secretNamespace: {} f:provisioner: {} manager: foo operation: Apply `, `- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:apiVersion: {} f:kind: {} f:metadata: f:name: {} f:spec: f:group: {} f:names: f:kind: {} f:plural: {} f:shortNames: i:0: {} f:singular: {} f:scope: {} f:versions: k:{"name":"v1"}: f:name: {} f:served: {} f:storage: {} manager: foo operation: Update `, `- apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:spec: f:replicas: {} manager: foo operation: Update subresource: scale `, } for _, test := range tests { t.Run(test, func(t *testing.T) { var unmarshaled []metav1.ManagedFieldsEntry if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } decoded, err := DecodeManagedFields(unmarshaled) if err != nil { t.Fatalf("did not expect decoding error but got: %v", err) } encoded, err := encodeManagedFields(decoded) if err != nil { t.Fatalf("did not expect encoding error but got: %v", err) } marshaled, err := yaml.Marshal(&encoded) if err != nil { t.Fatalf("did not expect yaml marshalling error but got: %v", err) } if !reflect.DeepEqual(string(marshaled), test) { t.Fatalf("expected:\n%v\nbut got:\n%v", test, string(marshaled)) } }) } } func TestBuildManagerIdentifier(t *testing.T) { tests := []struct { managedFieldsEntry string expected string }{ { managedFieldsEntry: ` apiVersion: v1 fieldsV1: f:apiVersion: {} manager: foo operation: Update time: "2001-02-03T04:05:06Z" `, expected: "{\"manager\":\"foo\",\"operation\":\"Update\",\"apiVersion\":\"v1\"}", }, { managedFieldsEntry: ` apiVersion: v1 fieldsV1: f:apiVersion: {} manager: foo operation: Apply time: "2001-02-03T04:05:06Z" `, expected: "{\"manager\":\"foo\",\"operation\":\"Apply\"}", }, { managedFieldsEntry: ` apiVersion: v1 fieldsV1: f:apiVersion: {} manager: foo operation: Apply subresource: scale time: "2001-02-03T04:05:06Z" `, expected: "{\"manager\":\"foo\",\"operation\":\"Apply\",\"subresource\":\"scale\"}", }, } for _, test := range tests { t.Run(test.managedFieldsEntry, func(t *testing.T) { var unmarshaled metav1.ManagedFieldsEntry if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil { t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) } decoded, err := BuildManagerIdentifier(&unmarshaled) if err != nil { t.Fatalf("did not expect decoding error but got: %v", err) } if !reflect.DeepEqual(decoded, test.expected) { t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, decoded) } }) } } func TestSortEncodedManagedFields(t *testing.T) { tests := []struct { name string managedFields []metav1.ManagedFieldsEntry expected []metav1.ManagedFieldsEntry }{ { name: "empty", managedFields: []metav1.ManagedFieldsEntry{}, expected: []metav1.ManagedFieldsEntry{}, }, { name: "nil", managedFields: nil, expected: nil, }, { name: "remains untouched", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, }, { name: "manager without time first", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, }, { name: "manager without time first name last", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, }, { name: "apply first", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, }, { name: "newest last", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, }, }, { name: "manager last", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, }, }, { name: "manager sorted", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "g", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "f", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, {Manager: "i", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, {Manager: "h", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "e", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2003-01-01T01:00:00Z")}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "g", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "h", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "i", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, {Manager: "f", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, {Manager: "e", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2003-01-01T01:00:00Z")}, }, }, { name: "sort drops nanoseconds", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 1, time.UTC)}}, {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 2, time.UTC)}}, {Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 3, time.UTC)}}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 2, time.UTC)}}, {Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 3, time.UTC)}}, {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 1, time.UTC)}}, }, }, { name: "entries with subresource field", managedFields: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "status"}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "scale"}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply}, }, expected: []metav1.ManagedFieldsEntry{ {Manager: "a", Operation: metav1.ManagedFieldsOperationApply}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "scale"}, {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "status"}, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { sorted, err := sortEncodedManagedFields(test.managedFields) if err != nil { t.Fatalf("did not expect error when sorting but got: %v", err) } if !reflect.DeepEqual(sorted, test.expected) { t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, sorted) } }) } } func parseTimeOrPanic(s string) *metav1.Time { t, err := time.Parse(time.RFC3339, s) if err != nil { panic(fmt.Sprintf("failed to parse time %s, got: %v", s, err)) } return &metav1.Time{Time: t.UTC()} } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/managedfieldsupdater.go000066400000000000000000000052531453143165200310700ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) type managedFieldsUpdater struct { fieldManager Manager } var _ Manager = &managedFieldsUpdater{} // NewManagedFieldsUpdater is responsible for updating the managedfields // in the object, updating the time of the operation as necessary. For // updates, it uses a hard-coded manager to detect if things have // changed, and swaps back the correct manager after the operation is // done. func NewManagedFieldsUpdater(fieldManager Manager) Manager { return &managedFieldsUpdater{ fieldManager: fieldManager, } } // Update implements Manager. func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { self := "current-operation" object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, self) if err != nil { return object, managed, err } // If the current operation took any fields from anything, it means the object changed, // so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager if vs, ok := managed.Fields()[self]; ok { delete(managed.Fields(), self) if previous, ok := managed.Fields()[manager]; ok { managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied()) } else { managed.Fields()[manager] = vs } managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()} } return object, managed, nil } // Apply implements Manager. func (f *managedFieldsUpdater) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { object, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) if err != nil { return object, managed, err } if object != nil { managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()} } else { object = liveObj.DeepCopyObject() RemoveObjectManagedFields(object) } return object, managed, nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/managedfieldsupdater_test.go000066400000000000000000000323211453143165200321230ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "fmt" "reflect" "testing" "time" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" "sigs.k8s.io/yaml" ) func TestManagedFieldsUpdateDoesModifyTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = updateObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() time.Sleep(time.Second) err = updateObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "new-value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() if previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { t.Errorf("ManagedFields time has not been updated:\n%v", newManagedFields) } } func TestManagedFieldsApplyDoesModifyTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = applyObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() time.Sleep(time.Second) err = applyObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "new-value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() if previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { t.Errorf("ManagedFields time has not been updated:\n%v", newManagedFields) } } func TestManagedFieldsUpdateWithoutChangesDoesNotModifyTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = updateObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() time.Sleep(time.Second) err = updateObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() if !previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { t.Errorf("ManagedFields time has changed:\nBefore:\n%v\nAfter:\n%v", previousManagedFields, newManagedFields) } } func TestManagedFieldsApplyWithoutChangesDoesNotModifyTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = applyObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() time.Sleep(time.Second) err = applyObject(f, "fieldmanager_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key": "value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() if !previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { t.Errorf("ManagedFields time has changed:\nBefore:\n%v\nAfter:\n%v", previousManagedFields, newManagedFields) } } func TestNonManagedFieldsUpdateDoesNotModifyTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = updateObject(f, "fieldmanager_a_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_a": "value" } }`)) if err != nil { t.Fatal(err) } err = updateObject(f, "fieldmanager_b_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_b": "value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() previousEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range previousManagedFields { previousEntries[entry.Manager] = entry } time.Sleep(time.Second) err = updateObject(f, "fieldmanager_a_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_a": "value", "key_b": "new-value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() newEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range newManagedFields { newEntries[entry.Manager] = entry } if _, ok := newEntries["fieldmanager_b_test"]; ok { t.Errorf("FieldManager B ManagedFields has changed:\n%v", newEntries["fieldmanager_b_test"]) } } func TestNonManagedFieldsApplyDoesNotModifyTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = applyObject(f, "fieldmanager_a_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_a": "value" } }`)) if err != nil { t.Fatal(err) } err = applyObject(f, "fieldmanager_b_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_b": "value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() previousEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range previousManagedFields { previousEntries[entry.Manager] = entry } time.Sleep(time.Second) err = applyObject(f, "fieldmanager_a_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_a": "new-value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() newEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range newManagedFields { newEntries[entry.Manager] = entry } if !previousEntries["fieldmanager_b_test"].Time.Equal(newEntries["fieldmanager_b_test"].Time) { t.Errorf("FieldManager B ManagedFields time changed:\nBefore:\n%v\nAfter:\n%v", previousEntries["fieldmanager_b_test"], newEntries["fieldmanager_b_test"]) } } func TestTakingOverManagedFieldsDuringUpdateDoesNotModifyPreviousManagerTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = updateObject(f, "fieldmanager_a_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_a": "value", "key_b": value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() previousEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range previousManagedFields { previousEntries[entry.Manager] = entry } time.Sleep(time.Second) err = updateObject(f, "fieldmanager_b_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_b": "new-value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() newEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range newManagedFields { newEntries[entry.Manager] = entry } if !previousEntries["fieldmanager_a_test"].Time.Equal(newEntries["fieldmanager_a_test"].Time) { t.Errorf("FieldManager A ManagedFields time has been updated:\nBefore:\n%v\nAfter:\n%v", previousEntries["fieldmanager_a_test"], newEntries["fieldmanager_a_test"]) } } func TestTakingOverManagedFieldsDuringApplyDoesNotModifyPreviousManagerTime(t *testing.T) { var err error f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) err = applyObject(f, "fieldmanager_a_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_a": "value", "key_b": value" } }`)) if err != nil { t.Fatal(err) } previousManagedFields := f.ManagedFields() previousEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range previousManagedFields { previousEntries[entry.Manager] = entry } time.Sleep(time.Second) err = applyObject(f, "fieldmanager_b_test", []byte(`{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "configmap" }, "data": { "key_b": "new-value" } }`)) if err != nil { t.Fatal(err) } newManagedFields := f.ManagedFields() newEntries := map[string]v1.ManagedFieldsEntry{} for _, entry := range newManagedFields { newEntries[entry.Manager] = entry } if !previousEntries["fieldmanager_a_test"].Time.Equal(newEntries["fieldmanager_a_test"].Time) { t.Errorf("FieldManager A ManagedFields time has been updated:\nBefore:\n%v\nAfter:\n%v", previousEntries["fieldmanager_a_test"], newEntries["fieldmanager_a_test"]) } } type NoopManager struct{} func (NoopManager) Apply(liveObj, appliedObj runtime.Object, managed internal.Managed, fieldManager string, force bool) (runtime.Object, internal.Managed, error) { return nil, managed, nil } func (NoopManager) Update(liveObj, newObj runtime.Object, managed internal.Managed, manager string) (runtime.Object, internal.Managed, error) { return nil, nil, nil } func updateObject(f managedfieldstest.TestFieldManager, fieldManagerName string, object []byte) error { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(object, &obj.Object); err != nil { return fmt.Errorf("error decoding YAML: %v", err) } if err := f.Update(obj, fieldManagerName); err != nil { return fmt.Errorf("failed to update object: %v", err) } return nil } func applyObject(f managedfieldstest.TestFieldManager, fieldManagerName string, object []byte) error { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal(object, &obj.Object); err != nil { return fmt.Errorf("error decoding YAML: %v", err) } if err := f.Apply(obj, fieldManagerName, true); err != nil { return fmt.Errorf("failed to apply object: %v", err) } return nil } // Ensures that if ManagedFieldsUpdater gets a nil value from its nested manager // chain (meaning the operation was a no-op), then the ManagedFieldsUpdater // itself will return a copy of the input live object, with its managed fields // removed func TestNilNewObjectReplacedWithDeepCopyExcludingManagedFields(t *testing.T) { // Initialize our "live object" with some managed fields obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "pod", "labels": {"app": "nginx"}, "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:labels": { "f:app": {} } } }, "manager": "fieldmanager_test", "operation": "Apply", "time": "2021-11-11T18:41:17Z" } ] } }`), &obj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } accessor, err := meta.Accessor(obj) if err != nil { t.Fatalf("couldn't get accessor: %v", err) } // Decode the managed fields in the live object, since it isn't allowed in the patch. managed, err := internal.DecodeManagedFields(accessor.GetManagedFields()) if err != nil { t.Fatalf("failed to decode managed fields: %v", err) } updater := internal.NewManagedFieldsUpdater(NoopManager{}) newObject, _, err := updater.Apply(obj, obj.DeepCopyObject(), managed, "some_manager", false) if err != nil { t.Fatalf("failed to apply configuration %v", err) } if newObject == obj { t.Fatalf("returned newObject must not be the same instance as the passed in liveObj") } // Rip off managed fields of live, and check that it is deeply // equal to newObject liveWithoutManaged := obj.DeepCopyObject() internal.RemoveObjectManagedFields(liveWithoutManaged) if !reflect.DeepEqual(liveWithoutManaged, newObject) { t.Fatalf("returned newObject must be deeply equal to the input live object, without managed fields") } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/manager.go000066400000000000000000000040131453143165200263230ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) // Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation. type Managed interface { // Fields gets the fieldpath.ManagedFields. Fields() fieldpath.ManagedFields // Times gets the timestamps associated with each operation. Times() map[string]*metav1.Time } // Manager updates the managed fields and merges applied configurations. type Manager interface { // Update is used when the object has already been merged (non-apply // use-case), and simply updates the managed fields in the output // object. // * `liveObj` is not mutated by this function // * `newObj` may be mutated by this function // Returns the new object with managedFields removed, and the object's new // proposed managedFields separately. Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) // Apply is used when server-side apply is called, as it merges the // object and updates the managed fields. // * `liveObj` is not mutated by this function // * `newObj` may be mutated by this function // Returns the new object with managedFields removed, and the object's new // proposed managedFields separately. Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/pathelement.go000066400000000000000000000065131453143165200272260ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "encoding/json" "errors" "fmt" "strconv" "strings" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/value" ) const ( // Field indicates that the content of this path element is a field's name Field = "f" // Value indicates that the content of this path element is a field's value Value = "v" // Index indicates that the content of this path element is an index in an array Index = "i" // Key indicates that the content of this path element is a key value map Key = "k" // Separator separates the type of a path element from the contents Separator = ":" ) // NewPathElement parses a serialized path element func NewPathElement(s string) (fieldpath.PathElement, error) { split := strings.SplitN(s, Separator, 2) if len(split) < 2 { return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s) } switch split[0] { case Field: return fieldpath.PathElement{ FieldName: &split[1], }, nil case Value: val, err := value.FromJSON([]byte(split[1])) if err != nil { return fieldpath.PathElement{}, err } return fieldpath.PathElement{ Value: &val, }, nil case Index: i, err := strconv.Atoi(split[1]) if err != nil { return fieldpath.PathElement{}, err } return fieldpath.PathElement{ Index: &i, }, nil case Key: kv := map[string]json.RawMessage{} err := json.Unmarshal([]byte(split[1]), &kv) if err != nil { return fieldpath.PathElement{}, err } fields := value.FieldList{} for k, v := range kv { b, err := json.Marshal(v) if err != nil { return fieldpath.PathElement{}, err } val, err := value.FromJSON(b) if err != nil { return fieldpath.PathElement{}, err } fields = append(fields, value.Field{ Name: k, Value: val, }) } return fieldpath.PathElement{ Key: &fields, }, nil default: // Ignore unknown key types return fieldpath.PathElement{}, nil } } // PathElementString serializes a path element func PathElementString(pe fieldpath.PathElement) (string, error) { switch { case pe.FieldName != nil: return Field + Separator + *pe.FieldName, nil case pe.Key != nil: kv := map[string]json.RawMessage{} for _, k := range *pe.Key { b, err := value.ToJSON(k.Value) if err != nil { return "", err } m := json.RawMessage{} err = json.Unmarshal(b, &m) if err != nil { return "", err } kv[k.Name] = m } b, err := json.Marshal(kv) if err != nil { return "", err } return Key + ":" + string(b), nil case pe.Value != nil: b, err := value.ToJSON(*pe.Value) if err != nil { return "", err } return Value + ":" + string(b), nil case pe.Index != nil: return Index + ":" + strconv.Itoa(*pe.Index), nil default: return "", errors.New("Invalid type of path element") } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/pathelement_test.go000066400000000000000000000037031453143165200302630ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import "testing" func TestPathElementRoundTrip(t *testing.T) { tests := []string{ `i:0`, `i:1234`, `f:`, `f:spec`, `f:more-complicated-string`, `k:{"name":"my-container"}`, `k:{"port":"8080","protocol":"TCP"}`, `k:{"optionalField":null}`, `k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`, `k:{"listField":["1","2","3"]}`, `v:null`, `v:"some-string"`, `v:1234`, `v:{"some":"json"}`, } for _, test := range tests { t.Run(test, func(t *testing.T) { pe, err := NewPathElement(test) if err != nil { t.Fatalf("Failed to create path element: %v", err) } output, err := PathElementString(pe) if err != nil { t.Fatalf("Failed to create string from path element: %v", err) } if test != output { t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output) } }) } } func TestPathElementIgnoreUnknown(t *testing.T) { _, err := NewPathElement("r:Hello") if err != nil { t.Fatalf("Unknown qualifiers should be ignored") } } func TestNewPathElementError(t *testing.T) { tests := []string{ ``, `no-colon`, `i:index is not a number`, `i:1.23`, `i:`, `v:invalid json`, `v:`, `k:invalid json`, `k:{"name":invalid}`, } for _, test := range tests { t.Run(test, func(t *testing.T) { _, err := NewPathElement(test) if err == nil { t.Fatalf("Expected error, no error found") } }) } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/skipnonapplied.go000066400000000000000000000064061453143165200277410ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "math/rand" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" ) type skipNonAppliedManager struct { fieldManager Manager objectCreater runtime.ObjectCreater beforeApplyManagerName string probability float32 } var _ Manager = &skipNonAppliedManager{} // NewSkipNonAppliedManager creates a new wrapped FieldManager that only starts tracking managers after the first apply. func NewSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater) Manager { return NewProbabilisticSkipNonAppliedManager(fieldManager, objectCreater, 0.0) } // NewProbabilisticSkipNonAppliedManager creates a new wrapped FieldManager that starts tracking managers after the first apply, // or starts tracking on create with p probability. func NewProbabilisticSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater, p float32) Manager { return &skipNonAppliedManager{ fieldManager: fieldManager, objectCreater: objectCreater, beforeApplyManagerName: "before-first-apply", probability: p, } } // Update implements Manager. func (f *skipNonAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { accessor, err := meta.Accessor(liveObj) if err != nil { return newObj, managed, nil } // If managed fields is empty, we need to determine whether to skip tracking managed fields. if len(managed.Fields()) == 0 { // Check if the operation is a create, by checking whether lastObj's UID is empty. // If the operation is create, P(tracking managed fields) = f.probability // If the operation is update, skip tracking managed fields, since we already know managed fields is empty. if len(accessor.GetUID()) == 0 { if f.probability <= rand.Float32() { return newObj, managed, nil } } else { return newObj, managed, nil } } return f.fieldManager.Update(liveObj, newObj, managed, manager) } // Apply implements Manager. func (f *skipNonAppliedManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { if len(managed.Fields()) == 0 { gvk := appliedObj.GetObjectKind().GroupVersionKind() emptyObj, err := f.objectCreater.New(gvk) if err != nil { return nil, nil, fmt.Errorf("failed to create empty object of type %v: %v", gvk, err) } liveObj, managed, err = f.fieldManager.Update(emptyObj, liveObj, managed, f.beforeApplyManagerName) if err != nil { return nil, nil, fmt.Errorf("failed to create manager for existing fields: %v", err) } } return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/skipnonapplied_test.go000066400000000000000000000106441453143165200307770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal_test import ( "encoding/json" "strings" "testing" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" "sigs.k8s.io/yaml" ) func TestNoUpdateBeforeFirstApply(t *testing.T) { f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager { return internal.NewSkipNonAppliedManager(m, &internaltesting.FakeObjectCreater{}) }) appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "pod", "labels": {"app": "nginx"} }, "spec": { "containers": [{ "name": "nginx", "image": "nginx:latest" }] } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } if err := f.Apply(appliedObj, "fieldmanager_test_apply", false); err != nil { t.Fatalf("failed to update object: %v", err) } if e, a := 1, len(f.ManagedFields()); e != a { t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "fieldmanager_test_apply", f.ManagedFields()[0].Manager; e != a { t.Fatalf("exected manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } } func TestUpdateBeforeFirstApply(t *testing.T) { f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager { return internal.NewSkipNonAppliedManager(m, &internaltesting.FakeObjectCreater{}) }) updatedObj := &unstructured.Unstructured{} if err := json.Unmarshal([]byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": {"labels": {"app": "my-nginx"}}}`), updatedObj); err != nil { t.Fatalf("Failed to unmarshal object: %v", err) } if err := f.Update(updatedObj, "fieldmanager_test_update"); err != nil { t.Fatalf("failed to update object: %v", err) } if m := f.ManagedFields(); len(m) != 0 { t.Fatalf("managedFields were tracked on update only: %v", m) } appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(`{ "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "pod", "labels": {"app": "nginx"} }, "spec": { "containers": [{ "name": "nginx", "image": "nginx:latest" }] } }`), &appliedObj.Object); err != nil { t.Fatalf("error decoding YAML: %v", err) } err := f.Apply(appliedObj, "fieldmanager_test_apply", false) apiStatus, _ := err.(apierrors.APIStatus) if err == nil || !apierrors.IsConflict(err) || len(apiStatus.Status().Details.Causes) != 1 { t.Fatalf("Expecting to get one conflict but got %v", err) } if e, a := ".metadata.labels.app", apiStatus.Status().Details.Causes[0].Field; e != a { t.Fatalf("Expecting to conflict on field %q but conflicted on field %q: %v", e, a, err) } if e, a := "before-first-apply", apiStatus.Status().Details.Causes[0].Message; !strings.Contains(a, e) { t.Fatalf("Expecting conflict message to contain %q but got %q: %v", e, a, err) } if err := f.Apply(appliedObj, "fieldmanager_test_apply", true); err != nil { t.Fatalf("failed to update object: %v", err) } if e, a := 2, len(f.ManagedFields()); e != a { t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "fieldmanager_test_apply", f.ManagedFields()[0].Manager; e != a { t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } if e, a := "before-first-apply", f.ManagedFields()[1].Manager; e != a { t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/stripmeta.go000066400000000000000000000057341453143165200267340ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) type stripMetaManager struct { fieldManager Manager // stripSet is the list of fields that should never be part of a mangedFields. stripSet *fieldpath.Set } var _ Manager = &stripMetaManager{} // NewStripMetaManager creates a new Manager that strips metadata and typemeta fields from the manager's fieldset. func NewStripMetaManager(fieldManager Manager) Manager { return &stripMetaManager{ fieldManager: fieldManager, stripSet: fieldpath.NewSet( fieldpath.MakePathOrDie("apiVersion"), fieldpath.MakePathOrDie("kind"), fieldpath.MakePathOrDie("metadata"), fieldpath.MakePathOrDie("metadata", "name"), fieldpath.MakePathOrDie("metadata", "namespace"), fieldpath.MakePathOrDie("metadata", "creationTimestamp"), fieldpath.MakePathOrDie("metadata", "selfLink"), fieldpath.MakePathOrDie("metadata", "uid"), fieldpath.MakePathOrDie("metadata", "clusterName"), fieldpath.MakePathOrDie("metadata", "generation"), fieldpath.MakePathOrDie("metadata", "managedFields"), fieldpath.MakePathOrDie("metadata", "resourceVersion"), ), } } // Update implements Manager. func (f *stripMetaManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { newObj, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager) if err != nil { return nil, nil, err } f.stripFields(managed.Fields(), manager) return newObj, managed, nil } // Apply implements Manager. func (f *stripMetaManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { newObj, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force) if err != nil { return nil, nil, err } f.stripFields(managed.Fields(), manager) return newObj, managed, nil } // stripFields removes a predefined set of paths found in typed from managed func (f *stripMetaManager) stripFields(managed fieldpath.ManagedFields, manager string) { vs, ok := managed[manager] if ok { if vs == nil { panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager)) } newSet := vs.Set().Difference(f.stripSet) if newSet.Empty() { delete(managed, manager) } else { managed[manager] = fieldpath.NewVersionedSet(newSet, vs.APIVersion(), vs.Applied()) } } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/structuredmerge.go000066400000000000000000000164471453143165200301530ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/merge" "sigs.k8s.io/structured-merge-diff/v4/typed" ) type structuredMergeManager struct { typeConverter TypeConverter objectConverter runtime.ObjectConvertor objectDefaulter runtime.ObjectDefaulter groupVersion schema.GroupVersion hubVersion schema.GroupVersion updater merge.Updater } var _ Manager = &structuredMergeManager{} // NewStructuredMergeManager creates a new Manager that merges apply requests // and update managed fields for other types of requests. func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (Manager, error) { if typeConverter == nil { return nil, fmt.Errorf("typeconverter must not be nil") } return &structuredMergeManager{ typeConverter: typeConverter, objectConverter: objectConverter, objectDefaulter: objectDefaulter, groupVersion: gv, hubVersion: hub, updater: merge.Updater{ Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s IgnoredFields: resetFields, }, }, nil } // NewCRDStructuredMergeManager creates a new Manager specifically for // CRDs. This allows for the possibility of fields which are not defined // in models, as well as having no models defined at all. func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ Manager, err error) { return &structuredMergeManager{ typeConverter: typeConverter, objectConverter: objectConverter, objectDefaulter: objectDefaulter, groupVersion: gv, hubVersion: hub, updater: merge.Updater{ Converter: newCRDVersionConverter(typeConverter, objectConverter, hub), IgnoredFields: resetFields, }, }, nil } func objectGVKNN(obj runtime.Object) string { name := "" namespace := "" if accessor, err := meta.Accessor(obj); err == nil { name = accessor.GetName() namespace = accessor.GetNamespace() } return fmt.Sprintf("%v/%v; %v", namespace, name, obj.GetObjectKind().GroupVersionKind()) } // Update implements Manager. func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { newObjVersioned, err := f.toVersioned(newObj) if err != nil { return nil, nil, fmt.Errorf("failed to convert new object (%v) to proper version (%v): %v", objectGVKNN(newObj), f.groupVersion, err) } liveObjVersioned, err := f.toVersioned(liveObj) if err != nil { return nil, nil, fmt.Errorf("failed to convert live object (%v) to proper version: %v", objectGVKNN(liveObj), err) } newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned, typed.AllowDuplicates) if err != nil { return nil, nil, fmt.Errorf("failed to convert new object (%v) to smd typed: %v", objectGVKNN(newObjVersioned), err) } liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned, typed.AllowDuplicates) if err != nil { return nil, nil, fmt.Errorf("failed to convert live object (%v) to smd typed: %v", objectGVKNN(liveObjVersioned), err) } apiVersion := fieldpath.APIVersion(f.groupVersion.String()) // TODO(apelisse) use the first return value when unions are implemented _, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), manager) if err != nil { return nil, nil, fmt.Errorf("failed to update ManagedFields (%v): %v", objectGVKNN(newObjVersioned), err) } managed = NewManaged(managedFields, managed.Times()) return newObj, managed, nil } // Apply implements Manager. func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { // Check that the patch object has the same version as the live object if patchVersion := patchObj.GetObjectKind().GroupVersionKind().GroupVersion(); patchVersion != f.groupVersion { return nil, nil, errors.NewBadRequest( fmt.Sprintf("Incorrect version specified in apply patch. "+ "Specified patch version: %s, expected: %s", patchVersion, f.groupVersion)) } patchObjMeta, err := meta.Accessor(patchObj) if err != nil { return nil, nil, fmt.Errorf("couldn't get accessor: %v", err) } if patchObjMeta.GetManagedFields() != nil { return nil, nil, errors.NewBadRequest("metadata.managedFields must be nil") } liveObjVersioned, err := f.toVersioned(liveObj) if err != nil { return nil, nil, fmt.Errorf("failed to convert live object (%v) to proper version: %v", objectGVKNN(liveObj), err) } // Don't allow duplicates in the applied object. patchObjTyped, err := f.typeConverter.ObjectToTyped(patchObj) if err != nil { return nil, nil, fmt.Errorf("failed to create typed patch object (%v): %v", objectGVKNN(patchObj), err) } liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned, typed.AllowDuplicates) if err != nil { return nil, nil, fmt.Errorf("failed to create typed live object (%v): %v", objectGVKNN(liveObjVersioned), err) } apiVersion := fieldpath.APIVersion(f.groupVersion.String()) newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields(), manager, force) if err != nil { return nil, nil, err } managed = NewManaged(managedFields, managed.Times()) if newObjTyped == nil { return nil, managed, nil } newObj, err := f.typeConverter.TypedToObject(newObjTyped) if err != nil { return nil, nil, fmt.Errorf("failed to convert new typed object (%v) to object: %v", objectGVKNN(patchObj), err) } newObjVersioned, err := f.toVersioned(newObj) if err != nil { return nil, nil, fmt.Errorf("failed to convert new object (%v) to proper version: %v", objectGVKNN(patchObj), err) } f.objectDefaulter.Default(newObjVersioned) newObjUnversioned, err := f.toUnversioned(newObjVersioned) if err != nil { return nil, nil, fmt.Errorf("failed to convert to unversioned (%v): %v", objectGVKNN(patchObj), err) } return newObjUnversioned, managed, nil } func (f *structuredMergeManager) toVersioned(obj runtime.Object) (runtime.Object, error) { return f.objectConverter.ConvertToVersion(obj, f.groupVersion) } func (f *structuredMergeManager) toUnversioned(obj runtime.Object) (runtime.Object, error) { return f.objectConverter.ConvertToVersion(obj, f.hubVersion) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/testdata/000077500000000000000000000000001453143165200261755ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/testdata/swagger.json000066400000000000000000007125601453143165200305420ustar00rootroot00000000000000{ "definitions": { "io.k8s.api.apps.v1.Deployment": { "description": "Deployment enables declarative updates for Pods and ReplicaSets.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentSpec", "description": "Specification of the desired behavior of the Deployment." }, "status": { "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStatus", "description": "Most recently observed status of the Deployment." } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "apps", "kind": "Deployment", "version": "v1" } ] }, "io.k8s.api.apps.v1.DeploymentCondition": { "description": "DeploymentCondition describes the state of a deployment at a certain point.", "properties": { "lastTransitionTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Last time the condition transitioned from one status to another." }, "lastUpdateTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "The last time this condition was updated." }, "message": { "description": "A human readable message indicating details about the transition.", "type": "string" }, "reason": { "description": "The reason for the condition's last transition.", "type": "string" }, "status": { "description": "Status of the condition, one of True, False, Unknown.", "type": "string" }, "type": { "description": "Type of deployment condition.", "type": "string" } }, "required": [ "type", "status" ], "type": "object" }, "io.k8s.api.apps.v1.DeploymentSpec": { "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "properties": { "minReadySeconds": { "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "format": "int32", "type": "integer" }, "paused": { "description": "Indicates that the deployment is paused.", "type": "boolean" }, "progressDeadlineSeconds": { "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", "format": "int32", "type": "integer" }, "replicas": { "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", "format": "int32", "type": "integer" }, "revisionHistoryLimit": { "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", "format": "int32", "type": "integer" }, "selector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels." }, "strategy": { "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStrategy", "description": "The deployment strategy to use to replace existing pods with new ones.", "x-kubernetes-patch-strategy": "retainKeys" }, "template": { "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", "description": "Template describes the pods that will be created." } }, "required": [ "selector", "template" ], "type": "object" }, "io.k8s.api.apps.v1.DeploymentStatus": { "description": "DeploymentStatus is the most recently observed status of the Deployment.", "properties": { "availableReplicas": { "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", "format": "int32", "type": "integer" }, "collisionCount": { "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", "format": "int32", "type": "integer" }, "conditions": { "description": "Represents the latest available observations of a deployment's current state.", "items": { "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentCondition" }, "type": "array", "x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-strategy": "merge" }, "observedGeneration": { "description": "The generation observed by the deployment controller.", "format": "int64", "type": "integer" }, "readyReplicas": { "description": "Total number of ready pods targeted by this deployment.", "format": "int32", "type": "integer" }, "replicas": { "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", "format": "int32", "type": "integer" }, "unavailableReplicas": { "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", "format": "int32", "type": "integer" }, "updatedReplicas": { "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", "format": "int32", "type": "integer" } }, "type": "object" }, "io.k8s.api.apps.v1.DeploymentStrategy": { "description": "DeploymentStrategy describes how to replace existing pods with new ones.", "properties": { "rollingUpdate": { "$ref": "#/definitions/io.k8s.api.apps.v1.RollingUpdateDeployment", "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." }, "type": { "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", "type": "string" } }, "type": "object" }, "io.k8s.api.apps.v1.RollingUpdateDeployment": { "description": "Spec to control the desired behavior of rolling update.", "properties": { "maxSurge": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." }, "maxUnavailable": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." } }, "type": "object" }, "io.k8s.api.apps.v1beta1.Deployment": { "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentSpec", "description": "Specification of the desired behavior of the Deployment." }, "status": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentStatus", "description": "Most recently observed status of the Deployment." } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "apps", "kind": "Deployment", "version": "v1beta1" } ] }, "io.k8s.api.apps.v1beta1.DeploymentCondition": { "description": "DeploymentCondition describes the state of a deployment at a certain point.", "properties": { "lastTransitionTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Last time the condition transitioned from one status to another." }, "lastUpdateTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "The last time this condition was updated." }, "message": { "description": "A human readable message indicating details about the transition.", "type": "string" }, "reason": { "description": "The reason for the condition's last transition.", "type": "string" }, "status": { "description": "Status of the condition, one of True, False, Unknown.", "type": "string" }, "type": { "description": "Type of deployment condition.", "type": "string" } }, "required": [ "type", "status" ], "type": "object" }, "io.k8s.api.apps.v1beta1.DeploymentRollback": { "description": "DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "name": { "description": "Required: This must match the Name of a deployment.", "type": "string" }, "rollbackTo": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.RollbackConfig", "description": "The config of this deployment rollback." }, "updatedAnnotations": { "additionalProperties": { "type": "string" }, "description": "The annotations to be updated to a deployment", "type": "object" } }, "required": [ "name", "rollbackTo" ], "type": "object", "x-kubernetes-group-version-kind": [ { "group": "apps", "kind": "DeploymentRollback", "version": "v1beta1" } ] }, "io.k8s.api.apps.v1beta1.DeploymentSpec": { "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "properties": { "minReadySeconds": { "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "format": "int32", "type": "integer" }, "paused": { "description": "Indicates that the deployment is paused.", "type": "boolean" }, "progressDeadlineSeconds": { "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", "format": "int32", "type": "integer" }, "replicas": { "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", "format": "int32", "type": "integer" }, "revisionHistoryLimit": { "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 2.", "format": "int32", "type": "integer" }, "rollbackTo": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.RollbackConfig", "description": "DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done." }, "selector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment." }, "strategy": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentStrategy", "description": "The deployment strategy to use to replace existing pods with new ones.", "x-kubernetes-patch-strategy": "retainKeys" }, "template": { "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", "description": "Template describes the pods that will be created." } }, "required": [ "template" ], "type": "object" }, "io.k8s.api.apps.v1beta1.DeploymentStatus": { "description": "DeploymentStatus is the most recently observed status of the Deployment.", "properties": { "availableReplicas": { "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", "format": "int32", "type": "integer" }, "collisionCount": { "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", "format": "int32", "type": "integer" }, "conditions": { "description": "Represents the latest available observations of a deployment's current state.", "items": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentCondition" }, "type": "array", "x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-strategy": "merge" }, "observedGeneration": { "description": "The generation observed by the deployment controller.", "format": "int64", "type": "integer" }, "readyReplicas": { "description": "Total number of ready pods targeted by this deployment.", "format": "int32", "type": "integer" }, "replicas": { "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", "format": "int32", "type": "integer" }, "unavailableReplicas": { "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", "format": "int32", "type": "integer" }, "updatedReplicas": { "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", "format": "int32", "type": "integer" } }, "type": "object" }, "io.k8s.api.apps.v1beta1.DeploymentStrategy": { "description": "DeploymentStrategy describes how to replace existing pods with new ones.", "properties": { "rollingUpdate": { "$ref": "#/definitions/io.k8s.api.apps.v1beta1.RollingUpdateDeployment", "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." }, "type": { "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", "type": "string" } }, "type": "object" }, "io.k8s.api.apps.v1beta1.RollbackConfig": { "description": "DEPRECATED.", "properties": { "revision": { "description": "The revision to rollback to. If set to 0, rollback to the last revision.", "format": "int64", "type": "integer" } }, "type": "object" }, "io.k8s.api.apps.v1beta1.RollingUpdateDeployment": { "description": "Spec to control the desired behavior of rolling update.", "properties": { "maxSurge": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." }, "maxUnavailable": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." } }, "type": "object" }, "io.k8s.api.apps.v1beta2.Deployment": { "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentSpec", "description": "Specification of the desired behavior of the Deployment." }, "status": { "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentStatus", "description": "Most recently observed status of the Deployment." } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "apps", "kind": "Deployment", "version": "v1beta2" } ] }, "io.k8s.api.apps.v1beta2.DeploymentCondition": { "description": "DeploymentCondition describes the state of a deployment at a certain point.", "properties": { "lastTransitionTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Last time the condition transitioned from one status to another." }, "lastUpdateTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "The last time this condition was updated." }, "message": { "description": "A human readable message indicating details about the transition.", "type": "string" }, "reason": { "description": "The reason for the condition's last transition.", "type": "string" }, "status": { "description": "Status of the condition, one of True, False, Unknown.", "type": "string" }, "type": { "description": "Type of deployment condition.", "type": "string" } }, "required": [ "type", "status" ], "type": "object" }, "io.k8s.api.apps.v1beta2.DeploymentSpec": { "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "properties": { "minReadySeconds": { "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "format": "int32", "type": "integer" }, "paused": { "description": "Indicates that the deployment is paused.", "type": "boolean" }, "progressDeadlineSeconds": { "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", "format": "int32", "type": "integer" }, "replicas": { "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", "format": "int32", "type": "integer" }, "revisionHistoryLimit": { "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", "format": "int32", "type": "integer" }, "selector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels." }, "strategy": { "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentStrategy", "description": "The deployment strategy to use to replace existing pods with new ones.", "x-kubernetes-patch-strategy": "retainKeys" }, "template": { "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", "description": "Template describes the pods that will be created." } }, "required": [ "selector", "template" ], "type": "object" }, "io.k8s.api.apps.v1beta2.DeploymentStatus": { "description": "DeploymentStatus is the most recently observed status of the Deployment.", "properties": { "availableReplicas": { "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", "format": "int32", "type": "integer" }, "collisionCount": { "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", "format": "int32", "type": "integer" }, "conditions": { "description": "Represents the latest available observations of a deployment's current state.", "items": { "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentCondition" }, "type": "array", "x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-strategy": "merge" }, "observedGeneration": { "description": "The generation observed by the deployment controller.", "format": "int64", "type": "integer" }, "readyReplicas": { "description": "Total number of ready pods targeted by this deployment.", "format": "int32", "type": "integer" }, "replicas": { "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", "format": "int32", "type": "integer" }, "unavailableReplicas": { "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", "format": "int32", "type": "integer" }, "updatedReplicas": { "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", "format": "int32", "type": "integer" } }, "type": "object" }, "io.k8s.api.apps.v1beta2.DeploymentStrategy": { "description": "DeploymentStrategy describes how to replace existing pods with new ones.", "properties": { "rollingUpdate": { "$ref": "#/definitions/io.k8s.api.apps.v1beta2.RollingUpdateDeployment", "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." }, "type": { "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", "type": "string" } }, "type": "object" }, "io.k8s.api.apps.v1beta2.RollingUpdateDeployment": { "description": "Spec to control the desired behavior of rolling update.", "properties": { "maxSurge": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." }, "maxUnavailable": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." } }, "type": "object" }, "io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource": { "description": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", "properties": { "fsType": { "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", "type": "string" }, "partition": { "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).", "format": "int32", "type": "integer" }, "readOnly": { "description": "Specify \"true\" to force and set the ReadOnly property in VolumeMounts to \"true\". If omitted, the default is \"false\". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", "type": "boolean" }, "volumeID": { "description": "Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", "type": "string" } }, "required": [ "volumeID" ], "type": "object" }, "io.k8s.api.core.v1.Affinity": { "description": "Affinity is a group of affinity scheduling rules.", "properties": { "nodeAffinity": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeAffinity", "description": "Describes node affinity scheduling rules for the pod." }, "podAffinity": { "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinity", "description": "Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s))." }, "podAntiAffinity": { "$ref": "#/definitions/io.k8s.api.core.v1.PodAntiAffinity", "description": "Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s))." } }, "type": "object" }, "io.k8s.api.core.v1.AttachedVolume": { "description": "AttachedVolume describes a volume attached to a node", "properties": { "devicePath": { "description": "DevicePath represents the device path where the volume should be available", "type": "string" }, "name": { "description": "Name of the attached volume", "type": "string" } }, "required": [ "name", "devicePath" ], "type": "object" }, "io.k8s.api.core.v1.AzureDiskVolumeSource": { "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.", "properties": { "cachingMode": { "description": "Host Caching mode: None, Read Only, Read Write.", "type": "string" }, "diskName": { "description": "The Name of the data disk in the blob storage", "type": "string" }, "diskURI": { "description": "The URI the data disk in the blob storage", "type": "string" }, "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", "type": "string" }, "kind": { "description": "Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared", "type": "string" }, "readOnly": { "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" } }, "required": [ "diskName", "diskURI" ], "type": "object" }, "io.k8s.api.core.v1.AzureFileVolumeSource": { "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", "properties": { "readOnly": { "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" }, "secretName": { "description": "the name of secret that contains Azure Storage Account Name and Key", "type": "string" }, "shareName": { "description": "Share Name", "type": "string" } }, "required": [ "secretName", "shareName" ], "type": "object" }, "io.k8s.api.core.v1.CSIVolumeSource": { "description": "Represents a source location of a volume to mount, managed by an external CSI driver", "properties": { "driver": { "description": "Driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster.", "type": "string" }, "fsType": { "description": "Filesystem type to mount. Ex. \"ext4\", \"xfs\", \"ntfs\". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply.", "type": "string" }, "nodePublishSecretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "NodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed." }, "readOnly": { "description": "Specifies a read-only configuration for the volume. Defaults to false (read/write).", "type": "boolean" }, "volumeAttributes": { "additionalProperties": { "type": "string" }, "description": "VolumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values.", "type": "object" } }, "required": [ "driver" ], "type": "object" }, "io.k8s.api.core.v1.Capabilities": { "description": "Adds and removes POSIX capabilities from running containers.", "properties": { "add": { "description": "Added capabilities", "items": { "type": "string" }, "type": "array" }, "drop": { "description": "Removed capabilities", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.CephFSVolumeSource": { "description": "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", "properties": { "monitors": { "description": "Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", "items": { "type": "string" }, "type": "array" }, "path": { "description": "Optional: Used as the mounted root, rather than the full Ceph tree, default is /", "type": "string" }, "readOnly": { "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", "type": "boolean" }, "secretFile": { "description": "Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", "type": "string" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it" }, "user": { "description": "Optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", "type": "string" } }, "required": [ "monitors" ], "type": "object" }, "io.k8s.api.core.v1.CinderVolumeSource": { "description": "Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.", "properties": { "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", "type": "string" }, "readOnly": { "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", "type": "boolean" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "Optional: points to a secret object containing parameters used to connect to OpenStack." }, "volumeID": { "description": "volume id used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", "type": "string" } }, "required": [ "volumeID" ], "type": "object" }, "io.k8s.api.core.v1.ClientIPConfig": { "description": "ClientIPConfig represents the configurations of Client IP based session affinity.", "properties": { "timeoutSeconds": { "description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be >0 && <=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).", "format": "int32", "type": "integer" } }, "type": "object" }, "io.k8s.api.core.v1.ComponentCondition": { "description": "Information about the condition of a component.", "properties": { "error": { "description": "Condition error code for a component. For example, a health check error code.", "type": "string" }, "message": { "description": "Message about the condition for a component. For example, information about a health check.", "type": "string" }, "status": { "description": "Status of the condition for a component. Valid values for \"Healthy\": \"True\", \"False\", or \"Unknown\".", "type": "string" }, "type": { "description": "Type of condition for a component. Valid value: \"Healthy\"", "type": "string" } }, "required": [ "type", "status" ], "type": "object" }, "io.k8s.api.core.v1.ConfigMapEnvSource": { "description": "ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.\n\nThe contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables.", "properties": { "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the ConfigMap must be defined", "type": "boolean" } }, "type": "object" }, "io.k8s.api.core.v1.ConfigMapKeySelector": { "description": "Selects a key from a ConfigMap.", "properties": { "key": { "description": "The key to select.", "type": "string" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the ConfigMap or its key must be defined", "type": "boolean" } }, "required": [ "key" ], "type": "object" }, "io.k8s.api.core.v1.ConfigMapNodeConfigSource": { "description": "ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.", "properties": { "kubeletConfigKey": { "description": "KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", "type": "string" }, "name": { "description": "Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", "type": "string" }, "namespace": { "description": "Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", "type": "string" }, "resourceVersion": { "description": "ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", "type": "string" }, "uid": { "description": "UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", "type": "string" } }, "required": [ "namespace", "name", "kubeletConfigKey" ], "type": "object" }, "io.k8s.api.core.v1.ConfigMapProjection": { "description": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.", "properties": { "items": { "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" }, "type": "array" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the ConfigMap or its keys must be defined", "type": "boolean" } }, "type": "object" }, "io.k8s.api.core.v1.ConfigMapVolumeSource": { "description": "Adapts a ConfigMap into a volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.", "properties": { "defaultMode": { "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", "format": "int32", "type": "integer" }, "items": { "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" }, "type": "array" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the ConfigMap or its keys must be defined", "type": "boolean" } }, "type": "object" }, "io.k8s.api.core.v1.Container": { "description": "A single application container that you want to run within a pod.", "properties": { "args": { "description": "Arguments to the entrypoint. The container image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", "items": { "type": "string" }, "type": "array" }, "command": { "description": "Entrypoint array. Not executed within a shell. The container image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", "items": { "type": "string" }, "type": "array" }, "env": { "description": "List of environment variables to set in the container. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.EnvVar" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "envFrom": { "description": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.EnvFromSource" }, "type": "array" }, "image": { "description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.", "type": "string" }, "imagePullPolicy": { "description": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images", "type": "string" }, "lifecycle": { "$ref": "#/definitions/io.k8s.api.core.v1.Lifecycle", "description": "Actions that the management system should take in response to container lifecycle events. Cannot be updated." }, "livenessProbe": { "$ref": "#/definitions/io.k8s.api.core.v1.Probe", "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" }, "name": { "description": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.", "type": "string" }, "ports": { "description": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort" }, "type": "array", "x-kubernetes-list-map-keys": [ "containerPort", "protocol" ], "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "containerPort", "x-kubernetes-patch-strategy": "merge" }, "readinessProbe": { "$ref": "#/definitions/io.k8s.api.core.v1.Probe", "description": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" }, "resources": { "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements", "description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" }, "securityContext": { "$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext", "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" }, "startupProbe": { "$ref": "#/definitions/io.k8s.api.core.v1.Probe", "description": "StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. This is an alpha feature enabled by the StartupProbe feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" }, "stdin": { "description": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", "type": "boolean" }, "stdinOnce": { "description": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", "type": "boolean" }, "terminationMessagePath": { "description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", "type": "string" }, "terminationMessagePolicy": { "description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", "type": "string" }, "tty": { "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", "type": "boolean" }, "volumeDevices": { "description": "volumeDevices is the list of block devices to be used by the container. This is a beta feature.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.VolumeDevice" }, "type": "array", "x-kubernetes-patch-merge-key": "devicePath", "x-kubernetes-patch-strategy": "merge" }, "volumeMounts": { "description": "Pod volumes to mount into the container's filesystem. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.VolumeMount" }, "type": "array", "x-kubernetes-patch-merge-key": "mountPath", "x-kubernetes-patch-strategy": "merge" }, "workingDir": { "description": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", "type": "string" } }, "required": [ "name" ], "type": "object" }, "io.k8s.api.core.v1.ContainerImage": { "description": "Describe a container image", "properties": { "names": { "description": "Names by which this image is known. e.g. [\"registry.k8s.io/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]", "items": { "type": "string" }, "type": "array" }, "sizeBytes": { "description": "The size of the image in bytes.", "format": "int64", "type": "integer" } }, "required": [ "names" ], "type": "object" }, "io.k8s.api.core.v1.ContainerPort": { "description": "ContainerPort represents a network port in a single container.", "properties": { "containerPort": { "description": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.", "format": "int32", "type": "integer" }, "hostIP": { "description": "What host IP to bind the external port to.", "type": "string" }, "hostPort": { "description": "Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.", "format": "int32", "type": "integer" }, "name": { "description": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.", "type": "string" }, "protocol": { "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\".", "type": "string" } }, "required": [ "containerPort" ], "type": "object" }, "io.k8s.api.core.v1.ContainerState": { "description": "ContainerState holds a possible state of container. Only one of its members may be specified. If none of them is specified, the default one is ContainerStateWaiting.", "properties": { "running": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStateRunning", "description": "Details about a running container" }, "terminated": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStateTerminated", "description": "Details about a terminated container" }, "waiting": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStateWaiting", "description": "Details about a waiting container" } }, "type": "object" }, "io.k8s.api.core.v1.ContainerStateRunning": { "description": "ContainerStateRunning is a running state of a container.", "properties": { "startedAt": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Time at which the container was last (re-)started" } }, "type": "object" }, "io.k8s.api.core.v1.ContainerStateTerminated": { "description": "ContainerStateTerminated is a terminated state of a container.", "properties": { "containerID": { "description": "Container's ID in the format 'docker://'", "type": "string" }, "exitCode": { "description": "Exit status from the last termination of the container", "format": "int32", "type": "integer" }, "finishedAt": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Time at which the container last terminated" }, "message": { "description": "Message regarding the last termination of the container", "type": "string" }, "reason": { "description": "(brief) reason from the last termination of the container", "type": "string" }, "signal": { "description": "Signal from the last termination of the container", "format": "int32", "type": "integer" }, "startedAt": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Time at which previous execution of the container started" } }, "required": [ "exitCode" ], "type": "object" }, "io.k8s.api.core.v1.ContainerStateWaiting": { "description": "ContainerStateWaiting is a waiting state of a container.", "properties": { "message": { "description": "Message regarding why the container is not yet running.", "type": "string" }, "reason": { "description": "(brief) reason the container is not yet running.", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.ContainerStatus": { "description": "ContainerStatus contains details for the current status of this container.", "properties": { "containerID": { "description": "Container's ID in the format 'docker://'.", "type": "string" }, "image": { "description": "The image the container is running. More info: https://kubernetes.io/docs/concepts/containers/images", "type": "string" }, "imageID": { "description": "ImageID of the container's image.", "type": "string" }, "lastState": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerState", "description": "Details about the container's last termination condition." }, "name": { "description": "This must be a DNS_LABEL. Each container in a pod must have a unique name. Cannot be updated.", "type": "string" }, "ready": { "description": "Specifies whether the container has passed its readiness probe.", "type": "boolean" }, "restartCount": { "description": "The number of times the container has been restarted, currently based on the number of dead containers that have not yet been removed. Note that this is calculated from dead containers. But those containers are subject to garbage collection. This value will get capped at 5 by GC.", "format": "int32", "type": "integer" }, "started": { "description": "Specifies whether the container has passed its startup probe. Initialized as false, becomes true after startupProbe is considered successful. Resets to false when the container is restarted, or if kubelet loses state temporarily. Is always true when no startupProbe is defined.", "type": "boolean" }, "state": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerState", "description": "Details about the container's current condition." } }, "required": [ "name", "ready", "restartCount", "image", "imageID" ], "type": "object" }, "io.k8s.api.core.v1.DownwardAPIProjection": { "description": "Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.", "properties": { "items": { "description": "Items is a list of DownwardAPIVolume file", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeFile" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.DownwardAPIVolumeFile": { "description": "DownwardAPIVolumeFile represents information to create the file containing the pod field", "properties": { "fieldRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ObjectFieldSelector", "description": "Required: Selects a field of the pod: only annotations, labels, name and namespace are supported." }, "mode": { "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", "format": "int32", "type": "integer" }, "path": { "description": "Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..'", "type": "string" }, "resourceFieldRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ResourceFieldSelector", "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported." } }, "required": [ "path" ], "type": "object" }, "io.k8s.api.core.v1.DownwardAPIVolumeSource": { "description": "DownwardAPIVolumeSource represents a volume containing downward API info. Downward API volumes support ownership management and SELinux relabeling.", "properties": { "defaultMode": { "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", "format": "int32", "type": "integer" }, "items": { "description": "Items is a list of downward API volume file", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeFile" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.EmptyDirVolumeSource": { "description": "Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.", "properties": { "medium": { "description": "What type of storage medium should back this directory. The default is \"\" which means to use the node's default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", "type": "string" }, "sizeLimit": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", "description": "Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" } }, "type": "object" }, "io.k8s.api.core.v1.EnvFromSource": { "description": "EnvFromSource represents the source of a set of ConfigMaps", "properties": { "configMapRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapEnvSource", "description": "The ConfigMap to select from" }, "prefix": { "description": "An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.", "type": "string" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.SecretEnvSource", "description": "The Secret to select from" } }, "type": "object" }, "io.k8s.api.core.v1.EnvVar": { "description": "EnvVar represents an environment variable present in a Container.", "properties": { "name": { "description": "Name of the environment variable. Must be a C_IDENTIFIER.", "type": "string" }, "value": { "description": "Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to \"\".", "type": "string" }, "valueFrom": { "$ref": "#/definitions/io.k8s.api.core.v1.EnvVarSource", "description": "Source for the environment variable's value. Cannot be used if value is not empty." } }, "required": [ "name" ], "type": "object" }, "io.k8s.api.core.v1.EnvVarSource": { "description": "EnvVarSource represents a source for the value of an EnvVar.", "properties": { "configMapKeyRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapKeySelector", "description": "Selects a key of a ConfigMap." }, "fieldRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ObjectFieldSelector", "description": "Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs." }, "resourceFieldRef": { "$ref": "#/definitions/io.k8s.api.core.v1.ResourceFieldSelector", "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported." }, "secretKeyRef": { "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector", "description": "Selects a key of a secret in the pod's namespace" } }, "type": "object" }, "io.k8s.api.core.v1.EphemeralContainer": { "description": "An EphemeralContainer is a container that may be added temporarily to an existing pod for user-initiated activities such as debugging. Ephemeral containers have no resource or scheduling guarantees, and they will not be restarted when they exit or when a pod is removed or restarted. If an ephemeral container causes a pod to exceed its resource allocation, the pod may be evicted. Ephemeral containers may not be added by directly updating the pod spec. They must be added via the pod's ephemeralcontainers subresource, and they will appear in the pod spec once added.", "properties": { "args": { "description": "Arguments to the entrypoint. The container image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", "items": { "type": "string" }, "type": "array" }, "command": { "description": "Entrypoint array. Not executed within a shell. The container image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", "items": { "type": "string" }, "type": "array" }, "env": { "description": "List of environment variables to set in the container. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.EnvVar" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "envFrom": { "description": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.EnvFromSource" }, "type": "array" }, "image": { "description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images", "type": "string" }, "imagePullPolicy": { "description": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images", "type": "string" }, "lifecycle": { "$ref": "#/definitions/io.k8s.api.core.v1.Lifecycle", "description": "Lifecycle is not allowed for ephemeral containers." }, "livenessProbe": { "$ref": "#/definitions/io.k8s.api.core.v1.Probe", "description": "Probes are not allowed for ephemeral containers." }, "name": { "description": "Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers.", "type": "string" }, "ports": { "description": "Ports are not allowed for ephemeral containers.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort" }, "type": "array" }, "readinessProbe": { "$ref": "#/definitions/io.k8s.api.core.v1.Probe", "description": "Probes are not allowed for ephemeral containers." }, "resources": { "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements", "description": "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod." }, "securityContext": { "$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext", "description": "SecurityContext is not allowed for ephemeral containers." }, "startupProbe": { "$ref": "#/definitions/io.k8s.api.core.v1.Probe", "description": "Probes are not allowed for ephemeral containers." }, "stdin": { "description": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", "type": "boolean" }, "stdinOnce": { "description": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", "type": "boolean" }, "targetContainerName": { "description": "If set, the name of the container from PodSpec that this ephemeral container targets. The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. If not set then the ephemeral container is run in whatever namespaces are shared for the pod. Note that the container runtime must support this feature.", "type": "string" }, "terminationMessagePath": { "description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", "type": "string" }, "terminationMessagePolicy": { "description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", "type": "string" }, "tty": { "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", "type": "boolean" }, "volumeDevices": { "description": "volumeDevices is the list of block devices to be used by the container. This is a beta feature.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.VolumeDevice" }, "type": "array", "x-kubernetes-patch-merge-key": "devicePath", "x-kubernetes-patch-strategy": "merge" }, "volumeMounts": { "description": "Pod volumes to mount into the container's filesystem. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.VolumeMount" }, "type": "array", "x-kubernetes-patch-merge-key": "mountPath", "x-kubernetes-patch-strategy": "merge" }, "workingDir": { "description": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", "type": "string" } }, "required": [ "name" ], "type": "object" }, "io.k8s.api.core.v1.ExecAction": { "description": "ExecAction describes a \"run in container\" action.", "properties": { "command": { "description": "Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.FCVolumeSource": { "description": "Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.", "properties": { "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", "type": "string" }, "lun": { "description": "Optional: FC target lun number", "format": "int32", "type": "integer" }, "readOnly": { "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" }, "targetWWNs": { "description": "Optional: FC target worldwide names (WWNs)", "items": { "type": "string" }, "type": "array" }, "wwids": { "description": "Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.FlexVolumeSource": { "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", "properties": { "driver": { "description": "Driver is the name of the driver to use for this volume.", "type": "string" }, "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", "type": "string" }, "options": { "additionalProperties": { "type": "string" }, "description": "Optional: Extra command options if any.", "type": "object" }, "readOnly": { "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts." } }, "required": [ "driver" ], "type": "object" }, "io.k8s.api.core.v1.FlockerVolumeSource": { "description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.", "properties": { "datasetName": { "description": "Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated", "type": "string" }, "datasetUUID": { "description": "UUID of the dataset. This is unique identifier of a Flocker dataset", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.GCEPersistentDiskVolumeSource": { "description": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.", "properties": { "fsType": { "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", "type": "string" }, "partition": { "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", "format": "int32", "type": "integer" }, "pdName": { "description": "Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", "type": "string" }, "readOnly": { "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", "type": "boolean" } }, "required": [ "pdName" ], "type": "object" }, "io.k8s.api.core.v1.GitRepoVolumeSource": { "description": "Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.\n\nDEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container.", "properties": { "directory": { "description": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.", "type": "string" }, "repository": { "description": "Repository URL", "type": "string" }, "revision": { "description": "Commit hash for the specified revision.", "type": "string" } }, "required": [ "repository" ], "type": "object" }, "io.k8s.api.core.v1.GlusterfsVolumeSource": { "description": "Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.", "properties": { "endpoints": { "description": "EndpointsName is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", "type": "string" }, "path": { "description": "Path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", "type": "string" }, "readOnly": { "description": "ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", "type": "boolean" } }, "required": [ "endpoints", "path" ], "type": "object" }, "io.k8s.api.core.v1.HTTPGetAction": { "description": "HTTPGetAction describes an action based on HTTP Get requests.", "properties": { "host": { "description": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.", "type": "string" }, "httpHeaders": { "description": "Custom headers to set in the request. HTTP allows repeated headers.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.HTTPHeader" }, "type": "array" }, "path": { "description": "Path to access on the HTTP server.", "type": "string" }, "port": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." }, "scheme": { "description": "Scheme to use for connecting to the host. Defaults to HTTP.", "type": "string" } }, "required": [ "port" ], "type": "object" }, "io.k8s.api.core.v1.HTTPHeader": { "description": "HTTPHeader describes a custom header to be used in HTTP probes", "properties": { "name": { "description": "The header field name", "type": "string" }, "value": { "description": "The header field value", "type": "string" } }, "required": [ "name", "value" ], "type": "object" }, "io.k8s.api.core.v1.Handler": { "description": "Handler defines a specific action that should be taken", "properties": { "exec": { "$ref": "#/definitions/io.k8s.api.core.v1.ExecAction", "description": "One and only one of the following should be specified. Exec specifies the action to take." }, "httpGet": { "$ref": "#/definitions/io.k8s.api.core.v1.HTTPGetAction", "description": "HTTPGet specifies the http request to perform." }, "tcpSocket": { "$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction", "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported" } }, "type": "object" }, "io.k8s.api.core.v1.HostAlias": { "description": "HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.", "properties": { "hostnames": { "description": "Hostnames for the above IP address.", "items": { "type": "string" }, "type": "array" }, "ip": { "description": "IP address of the host file entry.", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.HostPathVolumeSource": { "description": "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.", "properties": { "path": { "description": "Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", "type": "string" }, "type": { "description": "Type for HostPath Volume Defaults to \"\" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", "type": "string" } }, "required": [ "path" ], "type": "object" }, "io.k8s.api.core.v1.ISCSIVolumeSource": { "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", "properties": { "chapAuthDiscovery": { "description": "whether support iSCSI Discovery CHAP authentication", "type": "boolean" }, "chapAuthSession": { "description": "whether support iSCSI Session CHAP authentication", "type": "boolean" }, "fsType": { "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", "type": "string" }, "initiatorName": { "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", "type": "string" }, "iqn": { "description": "Target iSCSI Qualified Name.", "type": "string" }, "iscsiInterface": { "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", "type": "string" }, "lun": { "description": "iSCSI Target Lun number.", "format": "int32", "type": "integer" }, "portals": { "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", "items": { "type": "string" }, "type": "array" }, "readOnly": { "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", "type": "boolean" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "CHAP Secret for iSCSI target and initiator authentication" }, "targetPortal": { "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", "type": "string" } }, "required": [ "targetPortal", "iqn", "lun" ], "type": "object" }, "io.k8s.api.core.v1.KeyToPath": { "description": "Maps a string key to a path within a volume.", "properties": { "key": { "description": "The key to project.", "type": "string" }, "mode": { "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", "format": "int32", "type": "integer" }, "path": { "description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'.", "type": "string" } }, "required": [ "key", "path" ], "type": "object" }, "io.k8s.api.core.v1.Lifecycle": { "description": "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", "properties": { "postStart": { "$ref": "#/definitions/io.k8s.api.core.v1.Handler", "description": "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" }, "preStop": { "$ref": "#/definitions/io.k8s.api.core.v1.Handler", "description": "PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod's termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod's termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" } }, "type": "object" }, "io.k8s.api.core.v1.LocalObjectReference": { "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", "properties": { "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.LocalVolumeSource": { "description": "Local represents directly-attached storage with node affinity (Beta feature)", "properties": { "fsType": { "description": "Filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default value is to auto-select a fileystem if unspecified.", "type": "string" }, "path": { "description": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...).", "type": "string" } }, "required": [ "path" ], "type": "object" }, "io.k8s.api.core.v1.NFSVolumeSource": { "description": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", "properties": { "path": { "description": "Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "type": "string" }, "readOnly": { "description": "ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "type": "boolean" }, "server": { "description": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", "type": "string" } }, "required": [ "server", "path" ], "type": "object" }, "io.k8s.api.core.v1.NodeAffinity": { "description": "Node affinity is a group of node affinity scheduling rules.", "properties": { "preferredDuringSchedulingIgnoredDuringExecution": { "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PreferredSchedulingTerm" }, "type": "array" }, "requiredDuringSchedulingIgnoredDuringExecution": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector", "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node." } }, "type": "object" }, "io.k8s.api.core.v1.NodeSelector": { "description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", "properties": { "nodeSelectorTerms": { "description": "Required. A list of node selector terms. The terms are ORed.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorTerm" }, "type": "array" } }, "required": [ "nodeSelectorTerms" ], "type": "object" }, "io.k8s.api.core.v1.NodeSelectorRequirement": { "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", "properties": { "key": { "description": "The label key that the selector applies to.", "type": "string" }, "operator": { "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", "type": "string" }, "values": { "description": "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. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", "items": { "type": "string" }, "type": "array" } }, "required": [ "key", "operator" ], "type": "object" }, "io.k8s.api.core.v1.NodeSelectorTerm": { "description": "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.", "properties": { "matchExpressions": { "description": "A list of node selector requirements by node's labels.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorRequirement" }, "type": "array" }, "matchFields": { "description": "A list of node selector requirements by node's fields.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorRequirement" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.ObjectFieldSelector": { "description": "ObjectFieldSelector selects an APIVersioned field of an object.", "properties": { "apiVersion": { "description": "Version of the schema the FieldPath is written in terms of, defaults to \"v1\".", "type": "string" }, "fieldPath": { "description": "Path of the field to select in the specified API version.", "type": "string" } }, "required": [ "fieldPath" ], "type": "object" }, "io.k8s.api.core.v1.ObjectReference": { "description": "ObjectReference contains enough information to let you inspect or modify the referred object.", "properties": { "apiVersion": { "description": "API version of the referent.", "type": "string" }, "fieldPath": { "description": "If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered the event) or if no container name is specified \"spec.containers[2]\" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.", "type": "string" }, "kind": { "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "namespace": { "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", "type": "string" }, "resourceVersion": { "description": "Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", "type": "string" }, "uid": { "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource": { "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", "properties": { "claimName": { "description": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", "type": "string" }, "readOnly": { "description": "Will force the ReadOnly setting in VolumeMounts. Default false.", "type": "boolean" } }, "required": [ "claimName" ], "type": "object" }, "io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource": { "description": "Represents a Photon Controller persistent disk resource.", "properties": { "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", "type": "string" }, "pdID": { "description": "ID that identifies Photon Controller persistent disk", "type": "string" } }, "required": [ "pdID" ], "type": "object" }, "io.k8s.api.core.v1.Pod": { "description": "Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.core.v1.PodSpec", "description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" }, "status": { "$ref": "#/definitions/io.k8s.api.core.v1.PodStatus", "description": "Most recently observed status of the pod. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "", "kind": "Pod", "version": "v1" } ] }, "io.k8s.api.core.v1.PodAffinity": { "description": "Pod affinity is a group of inter pod affinity scheduling rules.", "properties": { "preferredDuringSchedulingIgnoredDuringExecution": { "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" }, "type": "array" }, "requiredDuringSchedulingIgnoredDuringExecution": { "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.PodAffinityTerm": { "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", "properties": { "labelSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "description": "A label query over a set of resources, in this case pods." }, "namespaces": { "description": "namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means \"this pod's namespace\"", "items": { "type": "string" }, "type": "array" }, "topologyKey": { "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", "type": "string" } }, "required": [ "topologyKey" ], "type": "object" }, "io.k8s.api.core.v1.PodAntiAffinity": { "description": "Pod anti affinity is a group of inter pod anti affinity scheduling rules.", "properties": { "preferredDuringSchedulingIgnoredDuringExecution": { "description": "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" }, "type": "array" }, "requiredDuringSchedulingIgnoredDuringExecution": { "description": "If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.PodCondition": { "description": "PodCondition contains details for the current condition of this pod.", "properties": { "lastProbeTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Last time we probed the condition." }, "lastTransitionTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Last time the condition transitioned from one status to another." }, "message": { "description": "Human-readable message indicating details about last transition.", "type": "string" }, "reason": { "description": "Unique, one-word, CamelCase reason for the condition's last transition.", "type": "string" }, "status": { "description": "Status is the status of the condition. Can be True, False, Unknown. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", "type": "string" }, "type": { "description": "Type is the type of the condition. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", "type": "string" } }, "required": [ "type", "status" ], "type": "object" }, "io.k8s.api.core.v1.PodDNSConfig": { "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", "properties": { "nameservers": { "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.", "items": { "type": "string" }, "type": "array" }, "options": { "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PodDNSConfigOption" }, "type": "array" }, "searches": { "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.", "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.PodDNSConfigOption": { "description": "PodDNSConfigOption defines DNS resolver options of a pod.", "properties": { "name": { "description": "Required.", "type": "string" }, "value": { "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.PodIP": { "description": "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n IP: An IP address allocated to the pod. Routable at least within the cluster.", "properties": { "ip": { "description": "ip is an IP address (IPv4 or IPv6) assigned to the pod", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.PodReadinessGate": { "description": "PodReadinessGate contains the reference to a pod condition", "properties": { "conditionType": { "description": "ConditionType refers to a condition in the pod's condition list with matching type.", "type": "string" } }, "required": [ "conditionType" ], "type": "object" }, "io.k8s.api.core.v1.PodSecurityContext": { "description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", "properties": { "fsGroup": { "description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume.", "format": "int64", "type": "integer" }, "runAsGroup": { "description": "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", "format": "int64", "type": "integer" }, "runAsNonRoot": { "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", "type": "boolean" }, "runAsUser": { "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", "format": "int64", "type": "integer" }, "seLinuxOptions": { "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions", "description": "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container." }, "supplementalGroups": { "description": "A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container.", "items": { "format": "int64", "type": "integer" }, "type": "array" }, "sysctls": { "description": "Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Sysctl" }, "type": "array" }, "windowsOptions": { "$ref": "#/definitions/io.k8s.api.core.v1.WindowsSecurityContextOptions", "description": "The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." } }, "type": "object" }, "io.k8s.api.core.v1.PodSpec": { "description": "PodSpec is a description of a pod.", "properties": { "activeDeadlineSeconds": { "description": "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", "format": "int64", "type": "integer" }, "affinity": { "$ref": "#/definitions/io.k8s.api.core.v1.Affinity", "description": "If specified, the pod's scheduling constraints" }, "automountServiceAccountToken": { "description": "AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.", "type": "boolean" }, "containers": { "description": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Container" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "dnsConfig": { "$ref": "#/definitions/io.k8s.api.core.v1.PodDNSConfig", "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy." }, "dnsPolicy": { "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.", "type": "string" }, "enableServiceLinks": { "description": "EnableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Defaults to true.", "type": "boolean" }, "ephemeralContainers": { "description": "List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.EphemeralContainer" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "hostAliases": { "description": "HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.HostAlias" }, "type": "array", "x-kubernetes-patch-merge-key": "ip", "x-kubernetes-patch-strategy": "merge" }, "hostIPC": { "description": "Use the host's ipc namespace. Optional: Default to false.", "type": "boolean" }, "hostNetwork": { "description": "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false.", "type": "boolean" }, "hostPID": { "description": "Use the host's pid namespace. Optional: Default to false.", "type": "boolean" }, "hostname": { "description": "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value.", "type": "string" }, "imagePullSecrets": { "description": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "initContainers": { "description": "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Container" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "nodeName": { "description": "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.", "type": "string" }, "nodeSelector": { "additionalProperties": { "type": "string" }, "description": "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/", "type": "object" }, "overhead": { "additionalProperties": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" }, "description": "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead This field is alpha-level as of Kubernetes v1.16, and is only honored by servers that enable the PodOverhead feature.", "type": "object" }, "preemptionPolicy": { "description": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is alpha-level and is only honored by servers that enable the NonPreemptingPriority feature.", "type": "string" }, "priority": { "description": "The priority value. Various system components use this field to find the priority of the pod. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority.", "format": "int32", "type": "integer" }, "priorityClassName": { "description": "If specified, indicates the pod's priority. \"system-node-critical\" and \"system-cluster-critical\" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.", "type": "string" }, "readinessGates": { "description": "If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to \"True\" More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PodReadinessGate" }, "type": "array" }, "restartPolicy": { "description": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy", "type": "string" }, "runtimeClassName": { "description": "RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. If unset or empty, the \"legacy\" RuntimeClass will be used, which is an implicit class with an empty definition that uses the default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class This is a beta feature as of Kubernetes v1.14.", "type": "string" }, "schedulerName": { "description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.", "type": "string" }, "securityContext": { "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext", "description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field." }, "serviceAccount": { "description": "DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.", "type": "string" }, "serviceAccountName": { "description": "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", "type": "string" }, "shareProcessNamespace": { "description": "Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.", "type": "boolean" }, "subdomain": { "description": "If specified, the fully qualified Pod hostname will be \"...svc.\". If not specified, the pod will not have a domainname at all.", "type": "string" }, "terminationGracePeriodSeconds": { "description": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.", "format": "int64", "type": "integer" }, "tolerations": { "description": "If specified, the pod's tolerations.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Toleration" }, "type": "array" }, "topologySpreadConstraints": { "description": "TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.TopologySpreadConstraint" }, "type": "array", "x-kubernetes-list-map-keys": [ "topologyKey", "whenUnsatisfiable" ], "x-kubernetes-list-type": "map", "x-kubernetes-patch-merge-key": "topologyKey", "x-kubernetes-patch-strategy": "merge" }, "volumes": { "description": "List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Volume" }, "type": "array", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" } }, "required": [ "containers" ], "type": "object" }, "io.k8s.api.core.v1.PodStatus": { "description": "PodStatus represents information about the status of a pod. Status may trail the actual state of a system, especially if the node that hosts the pod cannot contact the control plane.", "properties": { "conditions": { "description": "Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PodCondition" }, "type": "array", "x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-strategy": "merge" }, "containerStatuses": { "description": "The list has one entry per container in the manifest. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStatus" }, "type": "array" }, "ephemeralContainerStatuses": { "description": "Status for any ephemeral containers that have run in this pod.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStatus" }, "type": "array" }, "hostIP": { "description": "IP address of the host to which the pod is assigned. Empty if not yet scheduled.", "type": "string" }, "initContainerStatuses": { "description": "The list has one entry per init container in the manifest. The most recent successful init container will have ready = true, the most recently started container will have startTime set. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStatus" }, "type": "array" }, "message": { "description": "A human readable message indicating details about why the pod is in this condition.", "type": "string" }, "nominatedNodeName": { "description": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", "type": "string" }, "phase": { "description": "The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. The conditions array, the reason and message fields, and the individual container status arrays contain more detail about the pod's status. There are five possible phase values:\n\nPending: The pod has been accepted by the Kubernetes system, but one or more of the container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Running: The pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. Succeeded: All containers in the pod have terminated in success, and will not be restarted. Failed: All containers in the pod have terminated, and at least one container has terminated in failure. The container either exited with non-zero status or was terminated by the system. Unknown: For some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod.\n\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase", "type": "string" }, "podIP": { "description": "IP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.", "type": "string" }, "podIPs": { "description": "podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list is empty if no IPs have been allocated yet.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.PodIP" }, "type": "array", "x-kubernetes-patch-merge-key": "ip", "x-kubernetes-patch-strategy": "merge" }, "qosClass": { "description": "The Quality of Service (QOS) classification assigned to the pod based on resource requirements See PodQOSClass type for available QOS classes More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes", "type": "string" }, "reason": { "description": "A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted'", "type": "string" }, "startTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "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 pod." } }, "type": "object" }, "io.k8s.api.core.v1.PodTemplate": { "description": "PodTemplate describes a template for creating copies of a predefined pod.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "template": { "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", "description": "Template defines the pods that will be created from this pod template. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "", "kind": "PodTemplate", "version": "v1" } ] }, "io.k8s.api.core.v1.PodTemplateSpec": { "description": "PodTemplateSpec describes the data a pod should have when created from a template", "properties": { "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "spec": { "$ref": "#/definitions/io.k8s.api.core.v1.PodSpec", "description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" } }, "type": "object" }, "io.k8s.api.core.v1.PortworxVolumeSource": { "description": "PortworxVolumeSource represents a Portworx volume resource.", "properties": { "fsType": { "description": "FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified.", "type": "string" }, "readOnly": { "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" }, "volumeID": { "description": "VolumeID uniquely identifies a Portworx volume", "type": "string" } }, "required": [ "volumeID" ], "type": "object" }, "io.k8s.api.core.v1.PreferredSchedulingTerm": { "description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", "properties": { "preference": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorTerm", "description": "A node selector term, associated with the corresponding weight." }, "weight": { "description": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", "format": "int32", "type": "integer" } }, "required": [ "weight", "preference" ], "type": "object" }, "io.k8s.api.core.v1.Probe": { "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", "properties": { "exec": { "$ref": "#/definitions/io.k8s.api.core.v1.ExecAction", "description": "One and only one of the following should be specified. Exec specifies the action to take." }, "failureThreshold": { "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.", "format": "int32", "type": "integer" }, "httpGet": { "$ref": "#/definitions/io.k8s.api.core.v1.HTTPGetAction", "description": "HTTPGet specifies the http request to perform." }, "initialDelaySeconds": { "description": "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", "type": "integer" }, "periodSeconds": { "description": "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.", "format": "int32", "type": "integer" }, "successThreshold": { "description": "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", "format": "int32", "type": "integer" }, "tcpSocket": { "$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction", "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported" }, "timeoutSeconds": { "description": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", "type": "integer" } }, "type": "object" }, "io.k8s.api.core.v1.ProjectedVolumeSource": { "description": "Represents a projected volume source", "properties": { "defaultMode": { "description": "Mode bits to use on created files by default. Must be a value between 0 and 0777. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", "format": "int32", "type": "integer" }, "sources": { "description": "list of volume projections", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.VolumeProjection" }, "type": "array" } }, "required": [ "sources" ], "type": "object" }, "io.k8s.api.core.v1.QuobyteVolumeSource": { "description": "Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.", "properties": { "group": { "description": "Group to map volume access to Default is no group", "type": "string" }, "readOnly": { "description": "ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.", "type": "boolean" }, "registry": { "description": "Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes", "type": "string" }, "tenant": { "description": "Tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin", "type": "string" }, "user": { "description": "User to map volume access to Defaults to serivceaccount user", "type": "string" }, "volume": { "description": "Volume is a string that references an already created Quobyte volume by name.", "type": "string" } }, "required": [ "registry", "volume" ], "type": "object" }, "io.k8s.api.core.v1.RBDVolumeSource": { "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", "properties": { "fsType": { "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", "type": "string" }, "image": { "description": "The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", "type": "string" }, "keyring": { "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", "type": "string" }, "monitors": { "description": "A collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", "items": { "type": "string" }, "type": "array" }, "pool": { "description": "The rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", "type": "string" }, "readOnly": { "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", "type": "boolean" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it" }, "user": { "description": "The rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", "type": "string" } }, "required": [ "monitors", "image" ], "type": "object" }, "io.k8s.api.core.v1.ResourceFieldSelector": { "description": "ResourceFieldSelector represents container resources (cpu, memory) and their output format", "properties": { "containerName": { "description": "Container name: required for volumes, optional for env vars", "type": "string" }, "divisor": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", "description": "Specifies the output format of the exposed resources, defaults to \"1\"" }, "resource": { "description": "Required: resource to select", "type": "string" } }, "required": [ "resource" ], "type": "object" }, "io.k8s.api.core.v1.ResourceRequirements": { "description": "ResourceRequirements describes the compute resource requirements.", "properties": { "limits": { "additionalProperties": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" }, "description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", "type": "object" }, "requests": { "additionalProperties": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" }, "description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", "type": "object" } }, "type": "object" }, "io.k8s.api.core.v1.SELinuxOptions": { "description": "SELinuxOptions are the labels to be applied to the container", "properties": { "level": { "description": "Level is SELinux level label that applies to the container.", "type": "string" }, "role": { "description": "Role is a SELinux role label that applies to the container.", "type": "string" }, "type": { "description": "Type is a SELinux type label that applies to the container.", "type": "string" }, "user": { "description": "User is a SELinux user label that applies to the container.", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.ScaleIOVolumeSource": { "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", "properties": { "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\".", "type": "string" }, "gateway": { "description": "The host address of the ScaleIO API Gateway.", "type": "string" }, "protectionDomain": { "description": "The name of the ScaleIO Protection Domain for the configured storage.", "type": "string" }, "readOnly": { "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail." }, "sslEnabled": { "description": "Flag to enable/disable SSL communication with Gateway, default false", "type": "boolean" }, "storageMode": { "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned.", "type": "string" }, "storagePool": { "description": "The ScaleIO Storage Pool associated with the protection domain.", "type": "string" }, "system": { "description": "The name of the storage system as configured in ScaleIO.", "type": "string" }, "volumeName": { "description": "The name of a volume already created in the ScaleIO system that is associated with this volume source.", "type": "string" } }, "required": [ "gateway", "system", "secretRef" ], "type": "object" }, "io.k8s.api.core.v1.ScopeSelector": { "description": "A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.", "properties": { "matchExpressions": { "description": "A list of scope selector requirements by scope of the resources.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.ScopedResourceSelectorRequirement" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.ScopedResourceSelectorRequirement": { "description": "A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.", "properties": { "operator": { "description": "Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.", "type": "string" }, "scopeName": { "description": "The name of the scope that the selector applies to.", "type": "string" }, "values": { "description": "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. This array is replaced during a strategic merge patch.", "items": { "type": "string" }, "type": "array" } }, "required": [ "scopeName", "operator" ], "type": "object" }, "io.k8s.api.core.v1.Secret": { "description": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "data": { "additionalProperties": { "format": "byte", "type": "string" }, "description": "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4", "type": "object" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "stringData": { "additionalProperties": { "type": "string" }, "description": "stringData allows specifying non-binary secret data in string form. It is provided as a write-only input field for convenience. All keys and values are merged into the data field on write, overwriting any existing values. The stringData field is never output when reading from the API.", "type": "object" }, "type": { "description": "Used to facilitate programmatic handling of secret data.", "type": "string" } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "", "kind": "Secret", "version": "v1" } ] }, "io.k8s.api.core.v1.SecretEnvSource": { "description": "SecretEnvSource selects a Secret to populate the environment variables with.\n\nThe contents of the target Secret's Data field will represent the key-value pairs as environment variables.", "properties": { "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the Secret must be defined", "type": "boolean" } }, "type": "object" }, "io.k8s.api.core.v1.SecretKeySelector": { "description": "SecretKeySelector selects a key of a Secret.", "properties": { "key": { "description": "The key of the secret to select from. Must be a valid secret key.", "type": "string" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the Secret or its key must be defined", "type": "boolean" } }, "required": [ "key" ], "type": "object" }, "io.k8s.api.core.v1.SecretProjection": { "description": "Adapts a secret into a projected volume.\n\nThe contents of the target Secret's Data field will be presented in a projected volume as files using the keys in the Data field as the file names. Note that this is identical to a secret volume source without the default mode.", "properties": { "items": { "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" }, "type": "array" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "optional": { "description": "Specify whether the Secret or its key must be defined", "type": "boolean" } }, "type": "object" }, "io.k8s.api.core.v1.SecretReference": { "description": "SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace", "properties": { "name": { "description": "Name is unique within a namespace to reference a secret resource.", "type": "string" }, "namespace": { "description": "Namespace defines the space within which the secret name must be unique.", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.SecretVolumeSource": { "description": "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.", "properties": { "defaultMode": { "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", "format": "int32", "type": "integer" }, "items": { "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" }, "type": "array" }, "optional": { "description": "Specify whether the Secret or its keys must be defined", "type": "boolean" }, "secretName": { "description": "Name of the secret in the pod's namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.SecurityContext": { "description": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", "properties": { "allowPrivilegeEscalation": { "description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN", "type": "boolean" }, "capabilities": { "$ref": "#/definitions/io.k8s.api.core.v1.Capabilities", "description": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime." }, "privileged": { "description": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.", "type": "boolean" }, "procMount": { "description": "procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled.", "type": "string" }, "readOnlyRootFilesystem": { "description": "Whether this container has a read-only root filesystem. Default is false.", "type": "boolean" }, "runAsGroup": { "description": "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", "format": "int64", "type": "integer" }, "runAsNonRoot": { "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", "type": "boolean" }, "runAsUser": { "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", "format": "int64", "type": "integer" }, "seLinuxOptions": { "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions", "description": "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." }, "windowsOptions": { "$ref": "#/definitions/io.k8s.api.core.v1.WindowsSecurityContextOptions", "description": "The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." } }, "type": "object" }, "io.k8s.api.core.v1.ServiceAccountTokenProjection": { "description": "ServiceAccountTokenProjection represents a projected service account token volume. This projection can be used to insert a service account token into the pods runtime filesystem for use against APIs (Kubernetes API Server or otherwise).", "properties": { "audience": { "description": "Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver.", "type": "string" }, "expirationSeconds": { "description": "ExpirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes.", "format": "int64", "type": "integer" }, "path": { "description": "Path is the path relative to the mount point of the file to project the token into.", "type": "string" } }, "required": [ "path" ], "type": "object" }, "io.k8s.api.core.v1.SessionAffinityConfig": { "description": "SessionAffinityConfig represents the configurations of session affinity.", "properties": { "clientIP": { "$ref": "#/definitions/io.k8s.api.core.v1.ClientIPConfig", "description": "clientIP contains the configurations of Client IP based session affinity." } }, "type": "object" }, "io.k8s.api.core.v1.StorageOSVolumeSource": { "description": "Represents a StorageOS persistent volume resource.", "properties": { "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", "type": "string" }, "readOnly": { "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", "type": "boolean" }, "secretRef": { "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", "description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted." }, "volumeName": { "description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.", "type": "string" }, "volumeNamespace": { "description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.Sysctl": { "description": "Sysctl defines a kernel parameter to be set", "properties": { "name": { "description": "Name of a property to set", "type": "string" }, "value": { "description": "Value of a property to set", "type": "string" } }, "required": [ "name", "value" ], "type": "object" }, "io.k8s.api.core.v1.TCPSocketAction": { "description": "TCPSocketAction describes an action based on opening a socket", "properties": { "host": { "description": "Optional: Host name to connect to, defaults to the pod IP.", "type": "string" }, "port": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." } }, "required": [ "port" ], "type": "object" }, "io.k8s.api.core.v1.Toleration": { "description": "The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator .", "properties": { "effect": { "description": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.", "type": "string" }, "key": { "description": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.", "type": "string" }, "operator": { "description": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.", "type": "string" }, "tolerationSeconds": { "description": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.", "format": "int64", "type": "integer" }, "value": { "description": "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.", "type": "string" } }, "type": "object" }, "io.k8s.api.core.v1.TopologySelectorLabelRequirement": { "description": "A topology selector requirement is a selector that matches given label. This is an alpha feature and may change in the future.", "properties": { "key": { "description": "The label key that the selector applies to.", "type": "string" }, "values": { "description": "An array of string values. One value must match the label to be selected. Each entry in Values is ORed.", "items": { "type": "string" }, "type": "array" } }, "required": [ "key", "values" ], "type": "object" }, "io.k8s.api.core.v1.TopologySelectorTerm": { "description": "A topology selector term represents the result of label queries. A null or empty topology selector term matches no objects. The requirements of them are ANDed. It provides a subset of functionality as NodeSelectorTerm. This is an alpha feature and may change in the future.", "properties": { "matchLabelExpressions": { "description": "A list of topology selector requirements by labels.", "items": { "$ref": "#/definitions/io.k8s.api.core.v1.TopologySelectorLabelRequirement" }, "type": "array" } }, "type": "object" }, "io.k8s.api.core.v1.TopologySpreadConstraint": { "description": "TopologySpreadConstraint specifies how to spread matching pods among the given topology.", "properties": { "labelSelector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain." }, "maxSkew": { "description": "MaxSkew describes the degree to which pods may be unevenly distributed. It's the maximum permitted difference between the number of matching pods in any two topology domains of a given topology type. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | zone1 | zone2 | zone3 | | P | P | | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1; scheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. It's a required field. Default value is 1 and 0 is not allowed.", "format": "int32", "type": "integer" }, "topologyKey": { "description": "TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a \"bucket\", and try to put balanced number of pods into each bucket. It's a required field.", "type": "string" }, "whenUnsatisfiable": { "description": "WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it - ScheduleAnyway tells the scheduler to still schedule it It's considered as \"Unsatisfiable\" if and only if placing incoming pod on any topology violates \"MaxSkew\". For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won't make it *more* imbalanced. It's a required field.", "type": "string" } }, "required": [ "maxSkew", "topologyKey", "whenUnsatisfiable" ], "type": "object" }, "io.k8s.api.core.v1.Volume": { "description": "Volume represents a named volume in a pod that may be accessed by any container in the pod.", "properties": { "awsElasticBlockStore": { "$ref": "#/definitions/io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource", "description": "AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" }, "azureDisk": { "$ref": "#/definitions/io.k8s.api.core.v1.AzureDiskVolumeSource", "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod." }, "azureFile": { "$ref": "#/definitions/io.k8s.api.core.v1.AzureFileVolumeSource", "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod." }, "cephfs": { "$ref": "#/definitions/io.k8s.api.core.v1.CephFSVolumeSource", "description": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime" }, "cinder": { "$ref": "#/definitions/io.k8s.api.core.v1.CinderVolumeSource", "description": "Cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md" }, "configMap": { "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapVolumeSource", "description": "ConfigMap represents a configMap that should populate this volume" }, "csi": { "$ref": "#/definitions/io.k8s.api.core.v1.CSIVolumeSource", "description": "CSI (Container Storage Interface) represents storage that is handled by an external CSI driver (Alpha feature)." }, "downwardAPI": { "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeSource", "description": "DownwardAPI represents downward API about the pod that should populate this volume" }, "emptyDir": { "$ref": "#/definitions/io.k8s.api.core.v1.EmptyDirVolumeSource", "description": "EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" }, "fc": { "$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource", "description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod." }, "flexVolume": { "$ref": "#/definitions/io.k8s.api.core.v1.FlexVolumeSource", "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." }, "flocker": { "$ref": "#/definitions/io.k8s.api.core.v1.FlockerVolumeSource", "description": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running" }, "gcePersistentDisk": { "$ref": "#/definitions/io.k8s.api.core.v1.GCEPersistentDiskVolumeSource", "description": "GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" }, "gitRepo": { "$ref": "#/definitions/io.k8s.api.core.v1.GitRepoVolumeSource", "description": "GitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container." }, "glusterfs": { "$ref": "#/definitions/io.k8s.api.core.v1.GlusterfsVolumeSource", "description": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md" }, "hostPath": { "$ref": "#/definitions/io.k8s.api.core.v1.HostPathVolumeSource", "description": "HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" }, "iscsi": { "$ref": "#/definitions/io.k8s.api.core.v1.ISCSIVolumeSource", "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md" }, "name": { "description": "Volume's name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", "type": "string" }, "nfs": { "$ref": "#/definitions/io.k8s.api.core.v1.NFSVolumeSource", "description": "NFS represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" }, "persistentVolumeClaim": { "$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource", "description": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" }, "photonPersistentDisk": { "$ref": "#/definitions/io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, "portworxVolume": { "$ref": "#/definitions/io.k8s.api.core.v1.PortworxVolumeSource", "description": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine" }, "projected": { "$ref": "#/definitions/io.k8s.api.core.v1.ProjectedVolumeSource", "description": "Items for all in one resources secrets, configmaps, and downward API" }, "quobyte": { "$ref": "#/definitions/io.k8s.api.core.v1.QuobyteVolumeSource", "description": "Quobyte represents a Quobyte mount on the host that shares a pod's lifetime" }, "rbd": { "$ref": "#/definitions/io.k8s.api.core.v1.RBDVolumeSource", "description": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md" }, "scaleIO": { "$ref": "#/definitions/io.k8s.api.core.v1.ScaleIOVolumeSource", "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes." }, "secret": { "$ref": "#/definitions/io.k8s.api.core.v1.SecretVolumeSource", "description": "Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret" }, "storageos": { "$ref": "#/definitions/io.k8s.api.core.v1.StorageOSVolumeSource", "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." }, "vsphereVolume": { "$ref": "#/definitions/io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource", "description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine" } }, "required": [ "name" ], "type": "object" }, "io.k8s.api.core.v1.VolumeDevice": { "description": "volumeDevice describes a mapping of a raw block device within a container.", "properties": { "devicePath": { "description": "devicePath is the path inside of the container that the device will be mapped to.", "type": "string" }, "name": { "description": "name must match the name of a persistentVolumeClaim in the pod", "type": "string" } }, "required": [ "name", "devicePath" ], "type": "object" }, "io.k8s.api.core.v1.VolumeMount": { "description": "VolumeMount describes a mounting of a Volume within a container.", "properties": { "mountPath": { "description": "Path within the container at which the volume should be mounted. Must not contain ':'.", "type": "string" }, "mountPropagation": { "description": "mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.", "type": "string" }, "name": { "description": "This must match the Name of a Volume.", "type": "string" }, "readOnly": { "description": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", "type": "boolean" }, "subPath": { "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root).", "type": "string" }, "subPathExpr": { "description": "Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to \"\" (volume's root). SubPathExpr and SubPath are mutually exclusive.", "type": "string" } }, "required": [ "name", "mountPath" ], "type": "object" }, "io.k8s.api.core.v1.VolumeNodeAffinity": { "description": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.", "properties": { "required": { "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector", "description": "Required specifies hard node constraints that must be met." } }, "type": "object" }, "io.k8s.api.core.v1.VolumeProjection": { "description": "Projection that may be projected along with other supported volume types", "properties": { "configMap": { "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapProjection", "description": "information about the configMap data to project" }, "downwardAPI": { "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIProjection", "description": "information about the downwardAPI data to project" }, "secret": { "$ref": "#/definitions/io.k8s.api.core.v1.SecretProjection", "description": "information about the secret data to project" }, "serviceAccountToken": { "$ref": "#/definitions/io.k8s.api.core.v1.ServiceAccountTokenProjection", "description": "information about the serviceAccountToken data to project" } }, "type": "object" }, "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource": { "description": "Represents a vSphere volume resource.", "properties": { "fsType": { "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", "type": "string" }, "storagePolicyID": { "description": "Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.", "type": "string" }, "storagePolicyName": { "description": "Storage Policy Based Management (SPBM) profile name.", "type": "string" }, "volumePath": { "description": "Path that identifies vSphere volume vmdk", "type": "string" } }, "required": [ "volumePath" ], "type": "object" }, "io.k8s.api.core.v1.WeightedPodAffinityTerm": { "description": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", "properties": { "podAffinityTerm": { "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm", "description": "Required. A pod affinity term, associated with the corresponding weight." }, "weight": { "description": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", "format": "int32", "type": "integer" } }, "required": [ "weight", "podAffinityTerm" ], "type": "object" }, "io.k8s.api.core.v1.WindowsSecurityContextOptions": { "description": "WindowsSecurityContextOptions contain Windows-specific options and credentials.", "properties": { "gmsaCredentialSpec": { "description": "GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. This field is alpha-level and is only honored by servers that enable the WindowsGMSA feature flag.", "type": "string" }, "gmsaCredentialSpecName": { "description": "GMSACredentialSpecName is the name of the GMSA credential spec to use. This field is alpha-level and is only honored by servers that enable the WindowsGMSA feature flag.", "type": "string" }, "runAsUserName": { "description": "The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. This field is beta-level and may be disabled with the WindowsRunAsUserName feature flag.", "type": "string" } }, "type": "object" }, "io.k8s.api.extensions.v1beta1.Deployment": { "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "metadata": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object metadata." }, "spec": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentSpec", "description": "Specification of the desired behavior of the Deployment." }, "status": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentStatus", "description": "Most recently observed status of the Deployment." } }, "type": "object", "x-kubernetes-group-version-kind": [ { "group": "extensions", "kind": "Deployment", "version": "v1beta1" } ] }, "io.k8s.api.extensions.v1beta1.DeploymentCondition": { "description": "DeploymentCondition describes the state of a deployment at a certain point.", "properties": { "lastTransitionTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Last time the condition transitioned from one status to another." }, "lastUpdateTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "The last time this condition was updated." }, "message": { "description": "A human readable message indicating details about the transition.", "type": "string" }, "reason": { "description": "The reason for the condition's last transition.", "type": "string" }, "status": { "description": "Status of the condition, one of True, False, Unknown.", "type": "string" }, "type": { "description": "Type of deployment condition.", "type": "string" } }, "required": [ "type", "status" ], "type": "object" }, "io.k8s.api.extensions.v1beta1.DeploymentRollback": { "description": "DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.", "properties": { "apiVersion": { "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string" }, "kind": { "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "name": { "description": "Required: This must match the Name of a deployment.", "type": "string" }, "rollbackTo": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollbackConfig", "description": "The config of this deployment rollback." }, "updatedAnnotations": { "additionalProperties": { "type": "string" }, "description": "The annotations to be updated to a deployment", "type": "object" } }, "required": [ "name", "rollbackTo" ], "type": "object", "x-kubernetes-group-version-kind": [ { "group": "extensions", "kind": "DeploymentRollback", "version": "v1beta1" } ] }, "io.k8s.api.extensions.v1beta1.DeploymentSpec": { "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", "properties": { "minReadySeconds": { "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", "format": "int32", "type": "integer" }, "paused": { "description": "Indicates that the deployment is paused and will not be processed by the deployment controller.", "type": "boolean" }, "progressDeadlineSeconds": { "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. This is set to the max value of int32 (i.e. 2147483647) by default, which means \"no deadline\".", "format": "int32", "type": "integer" }, "replicas": { "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", "format": "int32", "type": "integer" }, "revisionHistoryLimit": { "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. This is set to the max value of int32 (i.e. 2147483647) by default, which means \"retaining all old ReplicaSets\".", "format": "int32", "type": "integer" }, "rollbackTo": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollbackConfig", "description": "DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done." }, "selector": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment." }, "strategy": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentStrategy", "description": "The deployment strategy to use to replace existing pods with new ones.", "x-kubernetes-patch-strategy": "retainKeys" }, "template": { "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", "description": "Template describes the pods that will be created." } }, "required": [ "template" ], "type": "object" }, "io.k8s.api.extensions.v1beta1.DeploymentStatus": { "description": "DeploymentStatus is the most recently observed status of the Deployment.", "properties": { "availableReplicas": { "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", "format": "int32", "type": "integer" }, "collisionCount": { "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", "format": "int32", "type": "integer" }, "conditions": { "description": "Represents the latest available observations of a deployment's current state.", "items": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentCondition" }, "type": "array", "x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-strategy": "merge" }, "observedGeneration": { "description": "The generation observed by the deployment controller.", "format": "int64", "type": "integer" }, "readyReplicas": { "description": "Total number of ready pods targeted by this deployment.", "format": "int32", "type": "integer" }, "replicas": { "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", "format": "int32", "type": "integer" }, "unavailableReplicas": { "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", "format": "int32", "type": "integer" }, "updatedReplicas": { "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", "format": "int32", "type": "integer" } }, "type": "object" }, "io.k8s.api.extensions.v1beta1.DeploymentStrategy": { "description": "DeploymentStrategy describes how to replace existing pods with new ones.", "properties": { "rollingUpdate": { "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollingUpdateDeployment", "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." }, "type": { "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", "type": "string" } }, "type": "object" }, "io.k8s.api.extensions.v1beta1.RollbackConfig": { "description": "DEPRECATED.", "properties": { "revision": { "description": "The revision to rollback to. If set to 0, rollback to the last revision.", "format": "int64", "type": "integer" } }, "type": "object" }, "io.k8s.api.extensions.v1beta1.RollingUpdateDeployment": { "description": "Spec to control the desired behavior of rolling update.", "properties": { "maxSurge": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. By default, a value of 1 is used. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." }, "maxUnavailable": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. By default, a fixed value of 1 is used. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." } }, "type": "object" }, "io.k8s.apimachinery.pkg.api.resource.Quantity": { "description": "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n ::= \n (Note that may be empty, from the \"\" case in .)\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n ::= m | \"\" | k | M | G | T | P | E\n (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n ::= \"e\" | \"E\" \n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n a. No precision is lost\n b. No fractional digits will be emitted\n c. The exponent (or suffix) is as large as possible.\nThe sign will be omitted unless the number is negative.\n\nExamples:\n 1.5 will be serialized as \"1500m\"\n 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", "type": "string" }, "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { "description": "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:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": { "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", "properties": { "matchExpressions": { "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", "items": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement" }, "type": "array" }, "matchLabels": { "additionalProperties": { "type": "string" }, "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", "type": "object" } }, "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": { "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", "properties": { "key": { "description": "key is the label key that the selector applies to.", "type": "string", "x-kubernetes-patch-merge-key": "key", "x-kubernetes-patch-strategy": "merge" }, "operator": { "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", "type": "string" }, "values": { "description": "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. This array is replaced during a strategic merge patch.", "items": { "type": "string" }, "type": "array" } }, "required": [ "key", "operator" ], "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", "properties": { "continue": { "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", "type": "string" }, "remainingItemCount": { "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", "format": "int64", "type": "integer" }, "resourceVersion": { "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", "type": "string" }, "selfLink": { "description": "selfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", "type": "string" } }, "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", "properties": { "apiVersion": { "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", "type": "string" }, "fieldsType": { "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", "type": "string" }, "fieldsV1": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1", "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." }, "manager": { "description": "Manager is an identifier of the workflow managing these fields.", "type": "string" }, "operation": { "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", "type": "string" }, "time": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'" } }, "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", "properties": { "annotations": { "additionalProperties": { "type": "string" }, "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", "type": "object" }, "clusterName": { "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.", "type": "string" }, "creationTimestamp": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "deletionGracePeriodSeconds": { "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", "format": "int64", "type": "integer" }, "deletionTimestamp": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "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 directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" }, "finalizers": { "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", "items": { "type": "string" }, "type": "array", "x-kubernetes-patch-strategy": "merge" }, "generateName": { "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", "type": "string" }, "generation": { "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", "format": "int64", "type": "integer" }, "labels": { "additionalProperties": { "type": "string" }, "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", "type": "object" }, "managedFields": { "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", "items": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" }, "type": "array" }, "name": { "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", "type": "string" }, "namespace": { "description": "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", "type": "string" }, "ownerReferences": { "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", "items": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" }, "type": "array", "x-kubernetes-patch-merge-key": "uid", "x-kubernetes-patch-strategy": "merge" }, "resourceVersion": { "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", "type": "string" }, "selfLink": { "description": "SelfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", "type": "string" }, "uid": { "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", "type": "string" } }, "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", "properties": { "apiVersion": { "description": "API version of the referent.", "type": "string" }, "blockOwnerDeletion": { "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", "type": "boolean" }, "controller": { "description": "If true, this reference points to the managing controller.", "type": "boolean" }, "kind": { "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string" }, "name": { "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", "type": "string" }, "uid": { "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", "type": "string" } }, "required": [ "apiVersion", "kind", "name", "uid" ], "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", "format": "date-time", "type": "string" }, "io.k8s.apimachinery.pkg.util.intstr.IntOrString": { "description": "IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number.", "format": "int-or-string", "type": "string" } }, "info": { "title": "Kubernetes", "version": "test" }, "paths": { }, "swagger": "2.0" } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/testing/000077500000000000000000000000001453143165200260415ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/testing/testfieldmanager.go000066400000000000000000000121121453143165200317030ustar00rootroot00000000000000/* Copyright 2022 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 testing import ( "errors" "fmt" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields" "k8s.io/apimachinery/pkg/util/managedfields/internal" ) // FakeObjectCreater implements ObjectCreater, it can create empty // objects (unstructured) of the given GVK. type FakeObjectCreater struct{} func (f *FakeObjectCreater) New(gvk schema.GroupVersionKind) (runtime.Object, error) { u := unstructured.Unstructured{Object: map[string]interface{}{}} u.SetAPIVersion(gvk.GroupVersion().String()) u.SetKind(gvk.Kind) return &u, nil } // FakeObjectConvertor implements runtime.ObjectConvertor but it // actually does nothing but return its input. type FakeObjectConvertor struct{} //nolint:staticcheck,ineffassign // SA4009 backwards compatibility func (c *FakeObjectConvertor) Convert(in, out, context interface{}) error { out = in return nil } func (c *FakeObjectConvertor) ConvertToVersion(in runtime.Object, _ runtime.GroupVersioner) (runtime.Object, error) { return in, nil } func (c *FakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) { return "", "", errors.New("not implemented") } // FakeObjectDefaulter implements runtime.Defaulter, but it actually // does nothing. type FakeObjectDefaulter struct{} func (d *FakeObjectDefaulter) Default(in runtime.Object) {} type TestFieldManagerImpl struct { fieldManager *internal.FieldManager apiVersion string emptyObj runtime.Object liveObj runtime.Object } // APIVersion of the object that we're tracking. func (f *TestFieldManagerImpl) APIVersion() string { return f.apiVersion } // Reset resets the state of the liveObject by resetting it to an empty object. func (f *TestFieldManagerImpl) Reset() { f.liveObj = f.emptyObj.DeepCopyObject() } // Live returns a copy of the current liveObject. func (f *TestFieldManagerImpl) Live() runtime.Object { return f.liveObj.DeepCopyObject() } // Apply applies the given object on top of the current liveObj, for the // given manager and force flag. func (f *TestFieldManagerImpl) Apply(obj runtime.Object, manager string, force bool) error { out, err := f.fieldManager.Apply(f.liveObj, obj, manager, force) if err == nil { f.liveObj = out } return err } // Update will updates the managed fields in the liveObj based on the // changes performed by the update. func (f *TestFieldManagerImpl) Update(obj runtime.Object, manager string) error { out, err := f.fieldManager.Update(f.liveObj, obj, manager) if err == nil { f.liveObj = out } return err } // ManagedFields returns the list of existing managed fields for the // liveObj. func (f *TestFieldManagerImpl) ManagedFields() []metav1.ManagedFieldsEntry { accessor, err := meta.Accessor(f.liveObj) if err != nil { panic(fmt.Errorf("couldn't get accessor: %v", err)) } return accessor.GetManagedFields() } // NewTestFieldManager creates a new manager for the given GVK. func NewTestFieldManagerImpl(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind, subresource string, chainFieldManager func(internal.Manager) internal.Manager) *TestFieldManagerImpl { f, err := internal.NewStructuredMergeManager( typeConverter, &FakeObjectConvertor{}, &FakeObjectDefaulter{}, gvk.GroupVersion(), gvk.GroupVersion(), nil, ) if err != nil { panic(err) } live := &unstructured.Unstructured{} live.SetKind(gvk.Kind) live.SetAPIVersion(gvk.GroupVersion().String()) // This is different from `internal.NewDefaultFieldManager` because: // 1. We don't want to create a `internal.FieldManager` // 2. We don't want to use the CapManager that is tested separately with // a smaller than the default cap. f = internal.NewVersionCheckManager( internal.NewLastAppliedUpdater( internal.NewLastAppliedManager( internal.NewProbabilisticSkipNonAppliedManager( internal.NewBuildManagerInfoManager( internal.NewManagedFieldsUpdater( internal.NewStripMetaManager(f), ), gvk.GroupVersion(), subresource, ), &FakeObjectCreater{}, internal.DefaultTrackOnCreateProbability, ), typeConverter, &FakeObjectConvertor{}, gvk.GroupVersion(), ), ), gvk, ) if chainFieldManager != nil { f = chainFieldManager(f) } return &TestFieldManagerImpl{ fieldManager: internal.NewFieldManager(f, subresource), apiVersion: gvk.GroupVersion().String(), emptyObj: live, liveObj: live.DeepCopyObject(), } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/typeconverter.go000066400000000000000000000130741453143165200276310ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kube-openapi/pkg/schemaconv" "k8s.io/kube-openapi/pkg/validation/spec" smdschema "sigs.k8s.io/structured-merge-diff/v4/schema" "sigs.k8s.io/structured-merge-diff/v4/typed" "sigs.k8s.io/structured-merge-diff/v4/value" ) // TypeConverter allows you to convert from runtime.Object to // typed.TypedValue and the other way around. type TypeConverter interface { ObjectToTyped(runtime.Object, ...typed.ValidationOptions) (*typed.TypedValue, error) TypedToObject(*typed.TypedValue) (runtime.Object, error) } type typeConverter struct { parser map[schema.GroupVersionKind]*typed.ParseableType } var _ TypeConverter = &typeConverter{} func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { typeSchema, err := schemaconv.ToSchemaFromOpenAPI(openapiSpec, preserveUnknownFields) if err != nil { return nil, fmt.Errorf("failed to convert models to schema: %v", err) } typeParser := typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}} tr := indexModels(&typeParser, openapiSpec) return &typeConverter{parser: tr}, nil } func (c *typeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) { gvk := obj.GetObjectKind().GroupVersionKind() t := c.parser[gvk] if t == nil { return nil, NewNoCorrespondingTypeError(gvk) } switch o := obj.(type) { case *unstructured.Unstructured: return t.FromUnstructured(o.UnstructuredContent(), opts...) default: return t.FromStructured(obj, opts...) } } func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { return valueToObject(value.AsValue()) } type deducedTypeConverter struct{} // DeducedTypeConverter is a TypeConverter for CRDs that don't have a // schema. It does implement the same interface though (and create the // same types of objects), so that everything can still work the same. // CRDs are merged with all their fields being "atomic" (lists // included). func NewDeducedTypeConverter() TypeConverter { return deducedTypeConverter{} } // ObjectToTyped converts an object into a TypedValue with a "deduced type". func (deducedTypeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) { switch o := obj.(type) { case *unstructured.Unstructured: return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent(), opts...) default: return typed.DeducedParseableType.FromStructured(obj, opts...) } } // TypedToObject transforms the typed value into a runtime.Object. That // is not specific to deduced type. func (deducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { return valueToObject(value.AsValue()) } func valueToObject(val value.Value) (runtime.Object, error) { vu := val.Unstructured() switch o := vu.(type) { case map[string]interface{}: return &unstructured.Unstructured{Object: o}, nil default: return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu) } } func indexModels( typeParser *typed.Parser, openAPISchemas map[string]*spec.Schema, ) map[schema.GroupVersionKind]*typed.ParseableType { tr := map[schema.GroupVersionKind]*typed.ParseableType{} for modelName, model := range openAPISchemas { gvkList := parseGroupVersionKind(model.Extensions) if len(gvkList) == 0 { continue } parsedType := typeParser.Type(modelName) for _, gvk := range gvkList { if len(gvk.Kind) > 0 { tr[schema.GroupVersionKind(gvk)] = &parsedType } } } return tr } // Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. func parseGroupVersionKind(extensions map[string]interface{}) []schema.GroupVersionKind { gvkListResult := []schema.GroupVersionKind{} // Get the extensions gvkExtension, ok := extensions["x-kubernetes-group-version-kind"] if !ok { return []schema.GroupVersionKind{} } // gvk extension must be a list of at least 1 element. gvkList, ok := gvkExtension.([]interface{}) if !ok { return []schema.GroupVersionKind{} } for _, gvk := range gvkList { var group, version, kind string // gvk extension list must be a map with group, version, and // kind fields if gvkMap, ok := gvk.(map[interface{}]interface{}); ok { group, ok = gvkMap["group"].(string) if !ok { continue } version, ok = gvkMap["version"].(string) if !ok { continue } kind, ok = gvkMap["kind"].(string) if !ok { continue } } else if gvkMap, ok := gvk.(map[string]interface{}); ok { group, ok = gvkMap["group"].(string) if !ok { continue } version, ok = gvkMap["version"].(string) if !ok { continue } kind, ok = gvkMap["kind"].(string) if !ok { continue } } else { continue } gvkListResult = append(gvkListResult, schema.GroupVersionKind{ Group: group, Version: version, Kind: kind, }) } return gvkListResult } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/typeconverter_test.go000066400000000000000000000151441453143165200306700ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "reflect" "testing" "github.com/stretchr/testify/require" smdschema "sigs.k8s.io/structured-merge-diff/v4/schema" "sigs.k8s.io/structured-merge-diff/v4/typed" "sigs.k8s.io/yaml" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kube-openapi/pkg/validation/spec" ) func TestTypeConverter(t *testing.T) { dtc := NewDeducedTypeConverter() testCases := []struct { name string yaml string }{ { name: "apps/v1.Deployment", yaml: ` apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.15.4 `, }, { name: "extensions/v1beta1.Deployment", yaml: ` apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.15.4 `, }, { name: "v1.Pod", yaml: ` apiVersion: v1 kind: Pod metadata: name: nginx-pod labels: app: nginx spec: containers: - name: nginx image: nginx:1.15.4 `, }, } for _, testCase := range testCases { t.Run(fmt.Sprintf("%v ObjectToTyped with TypeConverter", testCase.name), func(t *testing.T) { testObjectToTyped(t, testTypeConverter, testCase.yaml) }) t.Run(fmt.Sprintf("%v ObjectToTyped with DeducedTypeConverter", testCase.name), func(t *testing.T) { testObjectToTyped(t, dtc, testCase.yaml) }) } } func testObjectToTyped(t *testing.T, tc TypeConverter, y string) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { t.Fatalf("Failed to parse yaml object: %v", err) } typed, err := tc.ObjectToTyped(obj) if err != nil { t.Fatalf("Failed to convert object to typed: %v", err) } newObj, err := tc.TypedToObject(typed) if err != nil { t.Fatalf("Failed to convert typed to object: %v", err) } if !reflect.DeepEqual(obj, newObj) { t.Errorf(`Round-trip failed: Original object: %#v Final object: %#v`, obj, newObj) } } var result typed.TypedValue func BenchmarkObjectToTyped(b *testing.B) { y := ` apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.15.4 ` obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { b.Fatalf("Failed to parse yaml object: %v", err) } b.ResetTimer() b.ReportAllocs() var r *typed.TypedValue for i := 0; i < b.N; i++ { var err error r, err = testTypeConverter.ObjectToTyped(obj) if err != nil { b.Fatalf("Failed to convert object to typed: %v", err) } } result = *r } func TestIndexModels(t *testing.T) { myDefs := map[string]*spec.Schema{ // Show empty GVK extension is ignored "def0": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-group-version-kind": []interface{}{}, }, }, }, // Show nil GVK is ignored "def0.0": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-group-version-kind": nil, }, }, }, // Show this is ignored "def0.1": {}, // Show allows binding a single GVK "def1": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-group-version-kind": []interface{}{ map[string]interface{}{ "group": "mygroup", "version": "v1", "kind": "MyKind", }, }, }, }, }, // Show allows bindings with two versions "def2": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-group-version-kind": []interface{}{ map[string]interface{}{ "group": "mygroup", "version": "v1", "kind": "MyOtherKind", }, map[string]interface{}{ "group": "mygroup", "version": "v2", "kind": "MyOtherKind", }, }, }, }, }, // Show that we can mix and match GVKs from other definitions, and // that both map[interface{}]interface{} and map[string]interface{} // are allowed "def3": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-group-version-kind": []interface{}{ map[string]interface{}{ "group": "mygroup", "version": "v3", "kind": "MyKind", }, map[interface{}]interface{}{ "group": "mygroup", "version": "v3", "kind": "MyOtherKind", }, }, }, }, }, } myTypes := []smdschema.TypeDef{ { Name: "def0", Atom: smdschema.Atom{}, }, { Name: "def0.1", Atom: smdschema.Atom{}, }, { Name: "def0.2", Atom: smdschema.Atom{}, }, { Name: "def1", Atom: smdschema.Atom{}, }, { Name: "def2", Atom: smdschema.Atom{}, }, { Name: "def3", Atom: smdschema.Atom{}, }, } parser := typed.Parser{Schema: smdschema.Schema{Types: myTypes}} gvkIndex := indexModels(&parser, myDefs) require.Len(t, gvkIndex, 5) resultNames := map[schema.GroupVersionKind]string{} for k, v := range gvkIndex { require.NotNil(t, v.TypeRef.NamedType) resultNames[k] = *v.TypeRef.NamedType } require.Equal(t, resultNames, map[schema.GroupVersionKind]string{ { Group: "mygroup", Version: "v1", Kind: "MyKind", }: "def1", { Group: "mygroup", Version: "v1", Kind: "MyOtherKind", }: "def2", { Group: "mygroup", Version: "v2", Kind: "MyOtherKind", }: "def2", { Group: "mygroup", Version: "v3", Kind: "MyKind", }: "def3", { Group: "mygroup", Version: "v3", Kind: "MyOtherKind", }: "def3", }) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/versioncheck.go000066400000000000000000000035121453143165200273770ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) type versionCheckManager struct { fieldManager Manager gvk schema.GroupVersionKind } var _ Manager = &versionCheckManager{} // NewVersionCheckManager creates a manager that makes sure that the // applied object is in the proper version. func NewVersionCheckManager(fieldManager Manager, gvk schema.GroupVersionKind) Manager { return &versionCheckManager{fieldManager: fieldManager, gvk: gvk} } // Update implements Manager. func (f *versionCheckManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { // Nothing to do for updates, this is checked in many other places. return f.fieldManager.Update(liveObj, newObj, managed, manager) } // Apply implements Manager. func (f *versionCheckManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { if gvk := appliedObj.GetObjectKind().GroupVersionKind(); gvk != f.gvk { return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid object type: %v", gvk)) } return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/versionconverter.go000066400000000000000000000072531453143165200303370ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "fmt" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/merge" "sigs.k8s.io/structured-merge-diff/v4/typed" ) // versionConverter is an implementation of // sigs.k8s.io/structured-merge-diff/merge.Converter type versionConverter struct { typeConverter TypeConverter objectConvertor runtime.ObjectConvertor hubGetter func(from schema.GroupVersion) schema.GroupVersion } var _ merge.Converter = &versionConverter{} // NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor. func newVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter { return &versionConverter{ typeConverter: t, objectConvertor: o, hubGetter: func(from schema.GroupVersion) schema.GroupVersion { return schema.GroupVersion{ Group: from.Group, Version: h.Version, } }, } } // NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor. func newCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter { return &versionConverter{ typeConverter: t, objectConvertor: o, hubGetter: func(from schema.GroupVersion) schema.GroupVersion { return h }, } } // Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter func (v *versionConverter) Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) { // Convert the smd typed value to a kubernetes object. objectToConvert, err := v.typeConverter.TypedToObject(object) if err != nil { return object, err } // Parse the target groupVersion. groupVersion, err := schema.ParseGroupVersion(string(version)) if err != nil { return object, err } // If attempting to convert to the same version as we already have, just return it. fromVersion := objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion() if fromVersion == groupVersion { return object, nil } // Convert to internal internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubGetter(fromVersion)) if err != nil { return object, err } // Convert the object into the target version convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion) if err != nil { return object, err } // Convert the object back to a smd typed value and return it. return v.typeConverter.ObjectToTyped(convertedObject) } // IsMissingVersionError func (v *versionConverter) IsMissingVersionError(err error) bool { return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err) } type noCorrespondingTypeErr struct { gvk schema.GroupVersionKind } func NewNoCorrespondingTypeError(gvk schema.GroupVersionKind) error { return &noCorrespondingTypeErr{gvk: gvk} } func (k *noCorrespondingTypeErr) Error() string { return fmt.Sprintf("no corresponding type for %v", k.gvk) } func isNoCorrespondingTypeError(err error) bool { if err == nil { return false } _, ok := err.(*noCorrespondingTypeErr) return ok } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/internal/versionconverter_test.go000066400000000000000000000066551453143165200314030ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal import ( "encoding/json" "fmt" "os" "path/filepath" "reflect" "testing" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kube-openapi/pkg/validation/spec" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) var testTypeConverter = func() TypeConverter { data, err := os.ReadFile(filepath.Join("testdata", "swagger.json")) if err != nil { panic(err) } swag := spec.Swagger{} if err := json.Unmarshal(data, &swag); err != nil { panic(err) } convertedDefs := map[string]*spec.Schema{} for k, v := range swag.Definitions { vCopy := v convertedDefs[k] = &vCopy } typeConverter, err := NewTypeConverter(convertedDefs, false) if err != nil { panic(err) } return typeConverter }() // TestVersionConverter tests the version converter func TestVersionConverter(t *testing.T) { oc := fakeObjectConvertorForTestSchema{ gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"), gvkForVersion("v1"): objForGroupVersion("apps/v1"), } vc := newVersionConverter(testTypeConverter, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) input, err := testTypeConverter.ObjectToTyped(objForGroupVersion("apps/v1beta1")) if err != nil { t.Fatalf("error creating converting input object to a typed value: %v", err) } expected := objForGroupVersion("apps/v1") output, err := vc.Convert(input, fieldpath.APIVersion("apps/v1")) if err != nil { t.Fatalf("expected err to be nil but got %v", err) } actual, err := testTypeConverter.TypedToObject(output) if err != nil { t.Fatalf("error converting output typed value to an object %v", err) } if !reflect.DeepEqual(expected, actual) { t.Fatalf("expected to get %v but got %v", expected, actual) } } func gvkForVersion(v string) schema.GroupVersionKind { return schema.GroupVersionKind{ Group: "apps", Version: v, Kind: "Deployment", } } func objForGroupVersion(gv string) runtime.Object { return &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": gv, "kind": "Deployment", }, } } type fakeObjectConvertorForTestSchema map[schema.GroupVersionKind]runtime.Object var _ runtime.ObjectConvertor = fakeObjectConvertorForTestSchema{} func (c fakeObjectConvertorForTestSchema) ConvertToVersion(_ runtime.Object, gv runtime.GroupVersioner) (runtime.Object, error) { allKinds := make([]schema.GroupVersionKind, 0) for kind := range c { allKinds = append(allKinds, kind) } gvk, _ := gv.KindForGroupVersionKinds(allKinds) return c[gvk], nil } func (fakeObjectConvertorForTestSchema) Convert(_, _, _ interface{}) error { return fmt.Errorf("function not implemented") } func (fakeObjectConvertorForTestSchema) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) { return "", "", fmt.Errorf("function not implemented") } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/managedfieldstest/000077500000000000000000000000001453143165200262335ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/managedfieldstest/testfieldmanager.go000066400000000000000000000070641453143165200321070ustar00rootroot00000000000000/* Copyright 2022 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 managedfieldstest import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields" "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" ) // TestFieldManager is a FieldManager that can be used in test to // simulate the behavior of Server-Side Apply and field tracking. This // also has a few methods to get a sense of the state of the object. // // This TestFieldManager uses a series of "fake" objects to simulate // some behavior which come with the limitation that you can only use // one version since there is no version conversion logic. // // You can use this rather than NewDefaultTestFieldManager if you want // to specify either a sub-resource, or a set of modified Manager to // test them specifically. type TestFieldManager interface { // APIVersion of the object that we're tracking. APIVersion() string // Reset resets the state of the liveObject by resetting it to an empty object. Reset() // Live returns a copy of the current liveObject. Live() runtime.Object // Apply applies the given object on top of the current liveObj, for the // given manager and force flag. Apply(obj runtime.Object, manager string, force bool) error // Update will updates the managed fields in the liveObj based on the // changes performed by the update. Update(obj runtime.Object, manager string) error // ManagedFields returns the list of existing managed fields for the // liveObj. ManagedFields() []metav1.ManagedFieldsEntry } // NewTestFieldManager returns a new TestFieldManager built for the // given gvk, on the main resource. func NewTestFieldManager(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind) TestFieldManager { return testing.NewTestFieldManagerImpl(typeConverter, gvk, "", nil) } // NewTestFieldManagerSubresource returns a new TestFieldManager built // for the given gvk, on the given sub-resource. func NewTestFieldManagerSubresource(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind, subresource string) TestFieldManager { return testing.NewTestFieldManagerImpl(typeConverter, gvk, subresource, nil) } // NewFakeFieldManager creates an actual FieldManager but that doesn't // perform any conversion. This is just a convenience for tests to // create an actual manager that they can use but in very restricted // ways. // // This is different from the TestFieldManager because it's not meant to // assert values, or hold the state, this acts like a normal // FieldManager. // // Also, this only operates on the main-resource, and sub-resource can't // be configured. func NewFakeFieldManager(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind) *managedfields.FieldManager { ffm, err := managedfields.NewDefaultFieldManager( typeConverter, &testing.FakeObjectConvertor{}, &testing.FakeObjectDefaulter{}, &testing.FakeObjectCreater{}, gvk, gvk.GroupVersion(), "", nil) if err != nil { panic(err) } return ffm } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/node.yaml000066400000000000000000000254531453143165200243720ustar00rootroot00000000000000apiVersion: v1 kind: Node metadata: annotations: container.googleapis.com/instance_id: "123456789321654789" node.alpha.kubernetes.io/ttl: "0" volumes.kubernetes.io/controller-managed-attach-detach: "true" creationTimestamp: "2019-07-09T16:17:29Z" labels: kubernetes.io/arch: amd64 beta.kubernetes.io/fluentd-ds-ready: "true" beta.kubernetes.io/instance-type: n1-standard-4 kubernetes.io/os: linux cloud.google.com/gke-nodepool: default-pool cloud.google.com/gke-os-distribution: cos failure-domain.beta.kubernetes.io/region: us-central1 failure-domain.beta.kubernetes.io/zone: us-central1-b topology.kubernetes.io/region: us-central1 topology.kubernetes.io/zone: us-central1-b kubernetes.io/hostname: node-default-pool-something name: node-default-pool-something resourceVersion: "211582541" selfLink: /api/v1/nodes/node-default-pool-something uid: 0c24d0e1-a265-11e9-abe4-42010a80026b spec: podCIDR: 10.0.0.1/24 providerID: some-provider-id-of-some-sort status: addresses: - address: 10.0.0.1 type: InternalIP - address: 192.168.0.1 type: ExternalIP - address: node-default-pool-something type: Hostname allocatable: cpu: 3920m ephemeral-storage: "104638878617" hugepages-2Mi: "0" memory: 12700100Ki pods: "110" capacity: cpu: "4" ephemeral-storage: 202086868Ki hugepages-2Mi: "0" memory: 15399364Ki pods: "110" conditions: - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:22:08Z" message: containerd is functioning properly reason: FrequentContainerdRestart status: "False" type: FrequentContainerdRestart - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:22:06Z" message: docker overlay2 is functioning properly reason: CorruptDockerOverlay2 status: "False" type: CorruptDockerOverlay2 - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:22:06Z" message: node is functioning properly reason: UnregisterNetDevice status: "False" type: FrequentUnregisterNetDevice - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:17:04Z" message: kernel has no deadlock reason: KernelHasNoDeadlock status: "False" type: KernelDeadlock - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:17:04Z" message: Filesystem is not read-only reason: FilesystemIsNotReadOnly status: "False" type: ReadonlyFilesystem - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:22:05Z" message: kubelet is functioning properly reason: FrequentKubeletRestart status: "False" type: FrequentKubeletRestart - lastHeartbeatTime: "2019-09-20T19:32:08Z" lastTransitionTime: "2019-07-09T16:22:06Z" message: docker is functioning properly reason: FrequentDockerRestart status: "False" type: FrequentDockerRestart - lastHeartbeatTime: "2019-07-09T16:17:47Z" lastTransitionTime: "2019-07-09T16:17:47Z" message: RouteController created a route reason: RouteCreated status: "False" type: NetworkUnavailable - lastHeartbeatTime: "2019-09-20T19:32:50Z" lastTransitionTime: "2019-07-09T16:17:29Z" message: kubelet has sufficient disk space available reason: KubeletHasSufficientDisk status: "False" type: OutOfDisk - lastHeartbeatTime: "2019-09-20T19:32:50Z" lastTransitionTime: "2019-07-09T16:17:29Z" message: kubelet has sufficient memory available reason: KubeletHasSufficientMemory status: "False" type: MemoryPressure - lastHeartbeatTime: "2019-09-20T19:32:50Z" lastTransitionTime: "2019-07-09T16:17:29Z" message: kubelet has no disk pressure reason: KubeletHasNoDiskPressure status: "False" type: DiskPressure - lastHeartbeatTime: "2019-09-20T19:32:50Z" lastTransitionTime: "2019-07-09T16:17:29Z" message: kubelet has sufficient PID available reason: KubeletHasSufficientPID status: "False" type: PIDPressure - lastHeartbeatTime: "2019-09-20T19:32:50Z" lastTransitionTime: "2019-07-09T16:17:49Z" message: kubelet is posting ready status. AppArmor enabled reason: KubeletReady status: "True" type: Ready daemonEndpoints: kubeletEndpoint: Port: 10250 images: - names: - grafana/grafana@sha256:80e5e113a984d74836aa16f5b4524012099436b1a50df293f00ac6377fb512c8 - grafana/grafana:4.4.2 sizeBytes: 287008013 - names: - registry.k8s.io/node-problem-detector@sha256:f95cab985c26b2f46e9bd43283e0bfa88860c14e0fb0649266babe8b65e9eb2b - registry.k8s.io/node-problem-detector:v0.4.1 sizeBytes: 286572743 - names: - grafana/grafana@sha256:7ff7f9b2501a5d55b55ce3f58d21771b1c5af1f2a4ab7dbf11bef7142aae7033 - grafana/grafana:4.2.0 sizeBytes: 277940263 - names: - influxdb@sha256:7dddf03376348876ed4bdf33d6dfa3326f45a2bae0930dbd80781a374eb519bc - influxdb:1.2.2 sizeBytes: 223948571 - names: - gcr.io/stackdriver-agents/stackdriver-logging-agent@sha256:f8d5231b67b9c53f60068b535a11811d29d1b3efd53d2b79f2a2591ea338e4f2 - gcr.io/stackdriver-agents/stackdriver-logging-agent:0.6-1.6.0-1 sizeBytes: 223242132 - names: - nginx@sha256:35779791c05d119df4fe476db8f47c0bee5943c83eba5656a15fc046db48178b - nginx:1.10.1 sizeBytes: 180708613 - names: - registry.k8s.io/fluentd-elasticsearch@sha256:b8c94527b489fb61d3d81ce5ad7f3ddbb7be71e9620a3a36e2bede2f2e487d73 - registry.k8s.io/fluentd-elasticsearch:v2.0.4 sizeBytes: 135716379 - names: - nginx@sha256:00be67d6ba53d5318cd91c57771530f5251cfbe028b7be2c4b70526f988cfc9f - nginx:latest sizeBytes: 109357355 - names: - registry.k8s.io/kubernetes-dashboard-amd64@sha256:dc4026c1b595435ef5527ca598e1e9c4343076926d7d62b365c44831395adbd0 - registry.k8s.io/kubernetes-dashboard-amd64:v1.8.3 sizeBytes: 102319441 - names: - gcr.io/google_containers/kube-proxy:v1.11.10-gke.5 - registry.k8s.io/kube-proxy:v1.11.10-gke.5 sizeBytes: 102279340 - names: - registry.k8s.io/event-exporter@sha256:7f9cd7cb04d6959b0aa960727d04fa86759008048c785397b7b0d9dff0007516 - registry.k8s.io/event-exporter:v0.2.3 sizeBytes: 94171943 - names: - registry.k8s.io/prometheus-to-sd@sha256:6c0c742475363d537ff059136e5d5e4ab1f512ee0fd9b7ca42ea48bc309d1662 - registry.k8s.io/prometheus-to-sd:v0.3.1 sizeBytes: 88077694 - names: - registry.k8s.io/fluentd-gcp-scaler@sha256:a5ace7506d393c4ed65eb2cbb6312c64ab357fcea16dff76b9055bc6e498e5ff - registry.k8s.io/fluentd-gcp-scaler:0.5.1 sizeBytes: 86637208 - names: - registry.k8s.io/heapster-amd64@sha256:9fae0af136ce0cf4f88393b3670f7139ffc464692060c374d2ae748e13144521 - registry.k8s.io/heapster-amd64:v1.6.0-beta.1 sizeBytes: 76016169 - names: - registry.k8s.io/ingress-glbc-amd64@sha256:31d36bbd9c44caffa135fc78cf0737266fcf25e3cf0cd1c2fcbfbc4f7309cc52 - registry.k8s.io/ingress-glbc-amd64:v1.1.1 sizeBytes: 67801919 - names: - registry.k8s.io/kube-addon-manager@sha256:d53486c3a0b49ebee019932878dc44232735d5622a51dbbdcec7124199020d09 - registry.k8s.io/kube-addon-manager:v8.7 sizeBytes: 63322109 - names: - nginx@sha256:4aacdcf186934dcb02f642579314075910f1855590fd3039d8fa4c9f96e48315 - nginx:1.10-alpine sizeBytes: 54042627 - names: - registry.k8s.io/cpvpa-amd64@sha256:cfe7b0a11c9c8e18c87b1eb34fef9a7cbb8480a8da11fc2657f78dbf4739f869 - registry.k8s.io/cpvpa-amd64:v0.6.0 sizeBytes: 51785854 - names: - registry.k8s.io/cluster-proportional-autoscaler-amd64@sha256:003f98d9f411ddfa6ff6d539196355e03ddd69fa4ed38c7ffb8fec6f729afe2d - registry.k8s.io/cluster-proportional-autoscaler-amd64:1.1.2-r2 sizeBytes: 49648481 - names: - registry.k8s.io/ip-masq-agent-amd64@sha256:1ffda57d87901bc01324c82ceb2145fe6a0448d3f0dd9cb65aa76a867cd62103 - registry.k8s.io/ip-masq-agent-amd64:v2.1.1 sizeBytes: 49612505 - names: - registry.k8s.io/k8s-dns-kube-dns-amd64@sha256:b99fc3eee2a9f052f7eb4cc00f15eb12fc405fa41019baa2d6b79847ae7284a8 - registry.k8s.io/k8s-dns-kube-dns-amd64:1.14.10 sizeBytes: 49549457 - names: - registry.k8s.io/rescheduler@sha256:156cfbfd05a5a815206fd2eeb6cbdaf1596d71ea4b415d3a6c43071dd7b99450 - registry.k8s.io/rescheduler:v0.4.0 sizeBytes: 48973149 - names: - registry.k8s.io/event-exporter@sha256:16ca66e2b5dc7a1ce6a5aafcb21d0885828b75cdfc08135430480f7ad2364adc - registry.k8s.io/event-exporter:v0.2.4 sizeBytes: 47261019 - names: - registry.k8s.io/coredns@sha256:db2bf53126ed1c761d5a41f24a1b82a461c85f736ff6e90542e9522be4757848 - registry.k8s.io/coredns:1.1.3 sizeBytes: 45587362 - names: - prom/prometheus@sha256:483f4c9d7733699ba79facca9f8bcce1cef1af43dfc3e7c5a1882aa85f53cb74 - prom/prometheus:v1.1.3 sizeBytes: 45493941 nodeInfo: architecture: amd64 bootID: a32eca78-4ad4-4b76-9252-f143d6c2ae61 containerRuntimeVersion: docker://17.3.2 kernelVersion: 4.14.127+ kubeProxyVersion: v1.11.10-gke.5 kubeletVersion: v1.11.10-gke.5 machineID: 1739555e5b231057f0f9a0b5fa29511b operatingSystem: linux osImage: Container-Optimized OS from Google systemUUID: 1739555E-5B23-1057-F0F9-A0B5FA29511B volumesAttached: - devicePath: /dev/disk/by-id/b9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049 name: kubernetes.io/pd/some-random-clusterb9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049 - devicePath: /dev/disk/by-id/b9772-pvc-8895a852-fd42-11e6-94d4-42010a800049 name: kubernetes.io/pd/some-random-clusterb9772-pvc-8895a852-fd42-11e6-94d4-42010a800049 - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049 name: kubernetes.io/pd/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049 - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049 name: kubernetes.io/pd/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049 - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049 name: kubernetes.io/pd/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049 - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283 name: kubernetes.io/pd/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283 volumesInUse: - kubernetes.io/pd/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049 - kubernetes.io/pd/some-random-clusterb9772-pvc-8895a852-fd42-11e6-94d4-42010a800049 - kubernetes.io/pd/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049 - kubernetes.io/pd/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283 - kubernetes.io/pd/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049 - kubernetes.io/pd/some-random-clusterb9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049 golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/pod.yaml000066400000000000000000000055271453143165200242270ustar00rootroot00000000000000apiVersion: v1 kind: Pod metadata: labels: app: some-app plugin1: some-value plugin2: some-value plugin3: some-value plugin4: some-value name: some-name namespace: default ownerReferences: - apiVersion: apps/v1 blockOwnerDeletion: true controller: true kind: ReplicaSet name: some-name uid: 0a9d2b9e-779e-11e7-b422-42010a8001be spec: containers: - args: - one - two - three - four - five - six - seven - eight - nine env: - name: VAR_3 valueFrom: secretKeyRef: key: some-other-key name: some-oher-name - name: VAR_2 valueFrom: secretKeyRef: key: other-key name: other-name - name: VAR_1 valueFrom: secretKeyRef: key: some-key name: some-name image: some-image-name imagePullPolicy: IfNotPresent name: some-name resources: requests: cpu: '0' terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: default-token-hu5jz readOnly: true dnsPolicy: ClusterFirst nodeName: node-name priority: 0 restartPolicy: Always schedulerName: default-scheduler securityContext: {} serviceAccount: default serviceAccountName: default terminationGracePeriodSeconds: 30 tolerations: - effect: NoExecute key: node.kubernetes.io/not-ready operator: Exists tolerationSeconds: 300 - effect: NoExecute key: node.kubernetes.io/unreachable operator: Exists tolerationSeconds: 300 volumes: - name: default-token-hu5jz secret: defaultMode: 420 secretName: default-token-hu5jz status: conditions: - lastProbeTime: null lastTransitionTime: '2019-07-08T09:31:18Z' status: 'True' type: Initialized - lastProbeTime: null lastTransitionTime: '2019-07-08T09:41:59Z' status: 'True' type: Ready - lastProbeTime: null lastTransitionTime: null status: 'True' type: ContainersReady - lastProbeTime: null lastTransitionTime: '2019-07-08T09:31:18Z' status: 'True' type: PodScheduled containerStatuses: - containerID: docker://885e82a1ed0b7356541bb410a0126921ac42439607c09875cd8097dd5d7b5376 image: some-image-name imageID: docker-pullable://some-image-id lastState: terminated: containerID: docker://d57290f9e00fad626b20d2dd87a3cf69bbc22edae07985374f86a8b2b4e39565 exitCode: 255 finishedAt: '2019-07-08T09:39:09Z' reason: Error startedAt: '2019-07-08T09:38:54Z' name: name ready: true restartCount: 6 state: running: startedAt: '2019-07-08T09:41:59Z' hostIP: 10.0.0.1 phase: Running podIP: 10.0.0.1 qosClass: BestEffort startTime: '2019-07-08T09:31:18Z' golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/scalehandler.go000066400000000000000000000127741453143165200255370ustar00rootroot00000000000000/* Copyright 2021 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 managedfields import ( "fmt" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields/internal" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) var ( scaleGroupVersion = schema.GroupVersion{Group: "autoscaling", Version: "v1"} replicasPathInScale = fieldpath.MakePathOrDie("spec", "replicas") ) // ResourcePathMappings maps a group/version to its replicas path. The // assumption is that all the paths correspond to leaf fields. type ResourcePathMappings map[string]fieldpath.Path // ScaleHandler manages the conversion of managed fields between a main // resource and the scale subresource type ScaleHandler struct { parentEntries []metav1.ManagedFieldsEntry groupVersion schema.GroupVersion mappings ResourcePathMappings } // NewScaleHandler creates a new ScaleHandler func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, groupVersion schema.GroupVersion, mappings ResourcePathMappings) *ScaleHandler { return &ScaleHandler{ parentEntries: parentEntries, groupVersion: groupVersion, mappings: mappings, } } // ToSubresource filter the managed fields of the main resource and convert // them so that they can be handled by scale. // For the managed fields that have a replicas path it performs two changes: // 1. APIVersion is changed to the APIVersion of the scale subresource // 2. Replicas path of the main resource is transformed to the replicas path of // the scale subresource func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) { managed, err := internal.DecodeManagedFields(h.parentEntries) if err != nil { return nil, err } f := fieldpath.ManagedFields{} t := map[string]*metav1.Time{} for manager, versionedSet := range managed.Fields() { path, ok := h.mappings[string(versionedSet.APIVersion())] // Skip the entry if the APIVersion is unknown if !ok || path == nil { continue } if versionedSet.Set().Has(path) { newVersionedSet := fieldpath.NewVersionedSet( fieldpath.NewSet(replicasPathInScale), fieldpath.APIVersion(scaleGroupVersion.String()), versionedSet.Applied(), ) f[manager] = newVersionedSet t[manager] = managed.Times()[manager] } } return managedFieldsEntries(internal.NewManaged(f, t)) } // ToParent merges `scaleEntries` with the entries of the main resource and // transforms them accordingly func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) { decodedParentEntries, err := internal.DecodeManagedFields(h.parentEntries) if err != nil { return nil, err } parentFields := decodedParentEntries.Fields() decodedScaleEntries, err := internal.DecodeManagedFields(scaleEntries) if err != nil { return nil, err } scaleFields := decodedScaleEntries.Fields() f := fieldpath.ManagedFields{} t := map[string]*metav1.Time{} for manager, versionedSet := range parentFields { // Get the main resource "replicas" path path, ok := h.mappings[string(versionedSet.APIVersion())] // Drop the entry if the APIVersion is unknown. if !ok { continue } // If the parent entry does not have the replicas path or it is nil, just // keep it as it is. The path is nil for Custom Resources without scale // subresource. if path == nil || !versionedSet.Set().Has(path) { f[manager] = versionedSet t[manager] = decodedParentEntries.Times()[manager] continue } if _, ok := scaleFields[manager]; !ok { // "Steal" the replicas path from the main resource entry newSet := versionedSet.Set().Difference(fieldpath.NewSet(path)) if !newSet.Empty() { newVersionedSet := fieldpath.NewVersionedSet( newSet, versionedSet.APIVersion(), versionedSet.Applied(), ) f[manager] = newVersionedSet t[manager] = decodedParentEntries.Times()[manager] } } else { // Field wasn't stolen, let's keep the entry as it is. f[manager] = versionedSet t[manager] = decodedParentEntries.Times()[manager] delete(scaleFields, manager) } } for manager, versionedSet := range scaleFields { if !versionedSet.Set().Has(replicasPathInScale) { continue } newVersionedSet := fieldpath.NewVersionedSet( fieldpath.NewSet(h.mappings[h.groupVersion.String()]), fieldpath.APIVersion(h.groupVersion.String()), versionedSet.Applied(), ) f[manager] = newVersionedSet t[manager] = decodedParentEntries.Times()[manager] } return managedFieldsEntries(internal.NewManaged(f, t)) } func managedFieldsEntries(entries internal.ManagedInterface) ([]metav1.ManagedFieldsEntry, error) { obj := &unstructured.Unstructured{Object: map[string]interface{}{}} if err := internal.EncodeObjectManagedFields(obj, entries); err != nil { return nil, err } accessor, err := meta.Accessor(obj) if err != nil { panic(fmt.Sprintf("couldn't get accessor: %v", err)) } return accessor.GetManagedFields(), nil } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/scalehandler_test.go000066400000000000000000000572621453143165200265770ustar00rootroot00000000000000/* Copyright 2021 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 managedfields import ( "reflect" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" ) func TestTransformManagedFieldsToSubresource(t *testing.T) { testTime, _ := time.ParseInLocation("2006-Jan-02", "2013-Feb-03", time.Local) managedFieldTime := metav1.NewTime(testTime) tests := []struct { desc string input []metav1.ManagedFieldsEntry expected []metav1.ManagedFieldsEntry }{ { desc: "filter one entry and transform it into a subresource entry", input: []metav1.ManagedFieldsEntry{ { Manager: "manager-1", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:another-field":{}}}`)}, }, { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Time: &managedFieldTime, }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Time: &managedFieldTime, }, }, }, { desc: "transform all entries", input: []metav1.ManagedFieldsEntry{ { Manager: "manager-1", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, { Manager: "manager-3", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "manager-1", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, { Manager: "manager-2", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, { Manager: "manager-3", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "drops fields if the api version is unknown", input: []metav1.ManagedFieldsEntry{ { Manager: "manager-1", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v10", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, }, expected: nil, }, } for _, test := range tests { handler := NewScaleHandler( test.input, schema.GroupVersion{Group: "apps", Version: "v1"}, defaultMappings(), ) subresourceEntries, err := handler.ToSubresource() if err != nil { t.Fatalf("test %q - expected no error but got %v", test.desc, err) } if !reflect.DeepEqual(subresourceEntries, test.expected) { t.Fatalf("test %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, subresourceEntries) } } } func TestTransformingManagedFieldsToParent(t *testing.T) { tests := []struct { desc string parent []metav1.ManagedFieldsEntry subresource []metav1.ManagedFieldsEntry expected []metav1.ManagedFieldsEntry }{ { desc: "different-managers: apply -> update", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "different-managers: apply -> apply", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, }, { desc: "different-managers: update -> update", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, }, { desc: "different-managers: update -> apply", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, }, { desc: "same manager: apply -> apply", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "same manager: update -> update", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "same manager: update -> apply", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, }, { desc: "same manager: apply -> update", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "test", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "subresource doesn't own the path anymore", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:status":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, }, { desc: "Subresource steals all the fields of the parent resource", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "apply without stealing", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`)}, }, { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "drops the entry if the api version is unknown", parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, { Manager: "another-manager", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v10", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, }, }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { handler := NewScaleHandler( test.parent, schema.GroupVersion{Group: "apps", Version: "v1"}, defaultMappings(), ) parentEntries, err := handler.ToParent(test.subresource) if err != nil { t.Fatalf("test: %q - expected no error but got %v", test.desc, err) } if !reflect.DeepEqual(parentEntries, test.expected) { t.Fatalf("test: %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, parentEntries) } }) } } func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { tests := []struct { desc string groupVersion schema.GroupVersion mappings ResourcePathMappings parent []metav1.ManagedFieldsEntry subresource []metav1.ManagedFieldsEntry expected []metav1.ManagedFieldsEntry }{ { desc: "multi-version", groupVersion: schema.GroupVersion{Group: "apps", Version: "v1"}, mappings: ResourcePathMappings{ "apps/v1": fieldpath.MakePathOrDie("spec", "the-replicas"), "apps/v2": fieldpath.MakePathOrDie("spec", "not-the-replicas"), }, parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`)}, }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v2", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:not-the-replicas":{},"f:selector":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "apps/v2", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "apps/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{}}}`)}, Subresource: "scale", }, }, }, { desc: "Custom resource without scale subresource, scaling a version with `scale`", groupVersion: schema.GroupVersion{Group: "mygroup", Version: "v1"}, mappings: ResourcePathMappings{ "mygroup/v1": fieldpath.MakePathOrDie("spec", "the-replicas"), "mygroup/v2": nil, }, parent: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`)}, }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v2", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:test-other":{}}}`)}, }, }, subresource: []metav1.ManagedFieldsEntry{ { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "autoscaling/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:replicas":{}}}`)}, Subresource: "scale", }, }, expected: []metav1.ManagedFieldsEntry{ { Manager: "test", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:selector":{}}}`)}, }, { Manager: "test-other", Operation: metav1.ManagedFieldsOperationApply, APIVersion: "mygroup/v2", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:test-other":{}}}`)}, }, { Manager: "scale", Operation: metav1.ManagedFieldsOperationUpdate, APIVersion: "mygroup/v1", FieldsType: "FieldsV1", FieldsV1: &metav1.FieldsV1{Raw: []byte(`{"f:spec":{"f:the-replicas":{}}}`)}, Subresource: "scale", }, }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { handler := NewScaleHandler( test.parent, test.groupVersion, test.mappings, ) parentEntries, err := handler.ToParent(test.subresource) if err != nil { t.Fatalf("test: %q - expected no error but got %v", test.desc, err) } if !reflect.DeepEqual(parentEntries, test.expected) { t.Fatalf("test: %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, parentEntries) } }) } } func defaultMappings() ResourcePathMappings { return ResourcePathMappings{ "apps/v1": fieldpath.MakePathOrDie("spec", "replicas"), } } golang-k8s-apimachinery-0.29.0/pkg/util/managedfields/typeconverter.go000066400000000000000000000036331453143165200260150ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package managedfields import ( "k8s.io/apimachinery/pkg/util/managedfields/internal" "k8s.io/kube-openapi/pkg/validation/spec" ) // TypeConverter allows you to convert from runtime.Object to // typed.TypedValue and the other way around. type TypeConverter = internal.TypeConverter // NewDeducedTypeConverter creates a TypeConverter for CRDs that don't // have a schema. It does implement the same interface though (and // create the same types of objects), so that everything can still work // the same. CRDs are merged with all their fields being "atomic" (lists // included). func NewDeducedTypeConverter() TypeConverter { return internal.NewDeducedTypeConverter() } // NewTypeConverter builds a TypeConverter from a map of OpenAPIV3 schemas. // This will automatically find the proper version of the object, and the // corresponding schema information. // The keys to the map must be consistent with the names // used by Refs within the schemas. // The schemas should conform to the Kubernetes Structural Schema OpenAPI // restrictions found in docs: // https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { return internal.NewTypeConverter(openapiSpec, preserveUnknownFields) } golang-k8s-apimachinery-0.29.0/pkg/util/mergepatch/000077500000000000000000000000001453143165200221045ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/mergepatch/OWNERS000066400000000000000000000001441453143165200230430ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners approvers: - pwittrock reviewers: - apelisse golang-k8s-apimachinery-0.29.0/pkg/util/mergepatch/errors.go000066400000000000000000000061711453143165200237540ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mergepatch import ( "errors" "fmt" "reflect" ) var ( ErrBadJSONDoc = errors.New("invalid JSON document") ErrNoListOfLists = errors.New("lists of lists are not supported") ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list") ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys") ErrBadPatchFormatForSetElementOrderList = errors.New("invalid patch format of setElementOrder list") ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list") ErrUnsupportedStrategicMergePatchFormat = errors.New("strategic merge patch format is not supported") ) func ErrNoMergeKey(m map[string]interface{}, k string) error { return fmt.Errorf("map: %v does not contain declared merge key: %s", m, k) } func ErrBadArgType(expected, actual interface{}) error { return fmt.Errorf("expected a %s, but received a %s", reflect.TypeOf(expected), reflect.TypeOf(actual)) } func ErrBadArgKind(expected, actual interface{}) error { var expectedKindString, actualKindString string if expected == nil { expectedKindString = "nil" } else { expectedKindString = reflect.TypeOf(expected).Kind().String() } if actual == nil { actualKindString = "nil" } else { actualKindString = reflect.TypeOf(actual).Kind().String() } return fmt.Errorf("expected a %s, but received a %s", expectedKindString, actualKindString) } func ErrBadPatchType(t interface{}, m map[string]interface{}) error { return fmt.Errorf("unknown patch type: %s in map: %v", t, m) } // IsPreconditionFailed returns true if the provided error indicates // a precondition failed. func IsPreconditionFailed(err error) bool { _, ok := err.(ErrPreconditionFailed) return ok } type ErrPreconditionFailed struct { message string } func NewErrPreconditionFailed(target map[string]interface{}) ErrPreconditionFailed { s := fmt.Sprintf("precondition failed for: %v", target) return ErrPreconditionFailed{s} } func (err ErrPreconditionFailed) Error() string { return err.message } type ErrConflict struct { message string } func NewErrConflict(patch, current string) ErrConflict { s := fmt.Sprintf("patch:\n%s\nconflicts with changes made from original to current:\n%s\n", patch, current) return ErrConflict{s} } func (err ErrConflict) Error() string { return err.message } // IsConflict returns true if the provided error indicates // a conflict between the patch and the current configuration. func IsConflict(err error) bool { _, ok := err.(ErrConflict) return ok } golang-k8s-apimachinery-0.29.0/pkg/util/mergepatch/util.go000066400000000000000000000070661453143165200234210ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mergepatch import ( "fmt" "reflect" "k8s.io/apimachinery/pkg/util/dump" "sigs.k8s.io/yaml" ) // PreconditionFunc asserts that an incompatible change is not present within a patch. type PreconditionFunc func(interface{}) bool // RequireKeyUnchanged returns a precondition function that fails if the provided key // is present in the patch (indicating that its value has changed). func RequireKeyUnchanged(key string) PreconditionFunc { return func(patch interface{}) bool { patchMap, ok := patch.(map[string]interface{}) if !ok { return true } // The presence of key means that its value has been changed, so the test fails. _, ok = patchMap[key] return !ok } } // RequireMetadataKeyUnchanged creates a precondition function that fails // if the metadata.key is present in the patch (indicating its value // has changed). func RequireMetadataKeyUnchanged(key string) PreconditionFunc { return func(patch interface{}) bool { patchMap, ok := patch.(map[string]interface{}) if !ok { return true } patchMap1, ok := patchMap["metadata"] if !ok { return true } patchMap2, ok := patchMap1.(map[string]interface{}) if !ok { return true } _, ok = patchMap2[key] return !ok } } func ToYAMLOrError(v interface{}) string { y, err := toYAML(v) if err != nil { return err.Error() } return y } func toYAML(v interface{}) (string, error) { y, err := yaml.Marshal(v) if err != nil { return "", fmt.Errorf("yaml marshal failed:%v\n%v\n", err, dump.Pretty(v)) } return string(y), nil } // HasConflicts returns true if the left and right JSON interface objects overlap with // different values in any key. All keys are required to be strings. Since patches of the // same Type have congruent keys, this is valid for multiple patch types. This method // supports JSON merge patch semantics. // // NOTE: Numbers with different types (e.g. int(0) vs int64(0)) will be detected as conflicts. // Make sure the unmarshaling of left and right are consistent (e.g. use the same library). func HasConflicts(left, right interface{}) (bool, error) { switch typedLeft := left.(type) { case map[string]interface{}: switch typedRight := right.(type) { case map[string]interface{}: for key, leftValue := range typedLeft { rightValue, ok := typedRight[key] if !ok { continue } if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict { return conflict, err } } return false, nil default: return true, nil } case []interface{}: switch typedRight := right.(type) { case []interface{}: if len(typedLeft) != len(typedRight) { return true, nil } for i := range typedLeft { if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict { return conflict, err } } return false, nil default: return true, nil } case string, float64, bool, int64, nil: return !reflect.DeepEqual(left, right), nil default: return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left)) } } golang-k8s-apimachinery-0.29.0/pkg/util/mergepatch/util_test.go000066400000000000000000000105411453143165200244500ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mergepatch import ( "fmt" "testing" ) func TestHasConflicts(t *testing.T) { testCases := []struct { A interface{} B interface{} Ret bool }{ {A: "hello", B: "hello", Ret: false}, {A: "hello", B: "hell", Ret: true}, {A: "hello", B: nil, Ret: true}, {A: "hello", B: int64(1), Ret: true}, {A: "hello", B: float64(1.0), Ret: true}, {A: "hello", B: false, Ret: true}, {A: int64(1), B: int64(1), Ret: false}, {A: nil, B: nil, Ret: false}, {A: false, B: false, Ret: false}, {A: float64(3), B: float64(3), Ret: false}, {A: "hello", B: []interface{}{}, Ret: true}, {A: []interface{}{int64(1)}, B: []interface{}{}, Ret: true}, {A: []interface{}{}, B: []interface{}{}, Ret: false}, {A: []interface{}{int64(1)}, B: []interface{}{int64(1)}, Ret: false}, {A: map[string]interface{}{}, B: []interface{}{int64(1)}, Ret: true}, {A: map[string]interface{}{}, B: map[string]interface{}{"a": int64(1)}, Ret: false}, {A: map[string]interface{}{"a": int64(1)}, B: map[string]interface{}{"a": int64(1)}, Ret: false}, {A: map[string]interface{}{"a": int64(1)}, B: map[string]interface{}{"a": int64(2)}, Ret: true}, {A: map[string]interface{}{"a": int64(1)}, B: map[string]interface{}{"b": int64(2)}, Ret: false}, { A: map[string]interface{}{"a": []interface{}{int64(1)}}, B: map[string]interface{}{"a": []interface{}{int64(1)}}, Ret: false, }, { A: map[string]interface{}{"a": []interface{}{int64(1)}}, B: map[string]interface{}{"a": []interface{}{}}, Ret: true, }, { A: map[string]interface{}{"a": []interface{}{int64(1)}}, B: map[string]interface{}{"a": int64(1)}, Ret: true, }, // Maps and lists with multiple entries. { A: map[string]interface{}{"a": int64(1), "b": int64(2)}, B: map[string]interface{}{"a": int64(1), "b": int64(0)}, Ret: true, }, { A: map[string]interface{}{"a": int64(1), "b": int64(2)}, B: map[string]interface{}{"a": int64(1), "b": int64(2)}, Ret: false, }, { A: map[string]interface{}{"a": int64(1), "b": int64(2)}, B: map[string]interface{}{"a": int64(1), "b": int64(0), "c": int64(3)}, Ret: true, }, { A: map[string]interface{}{"a": int64(1), "b": int64(2)}, B: map[string]interface{}{"a": int64(1), "b": int64(2), "c": int64(3)}, Ret: false, }, { A: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}, B: map[string]interface{}{"a": []interface{}{int64(1), int64(0)}}, Ret: true, }, { A: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}, B: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}, Ret: false, }, // Numeric types are not interchangeable. // Callers are expected to ensure numeric types are consistent in 'left' and 'right'. {A: int64(0), B: float64(0), Ret: true}, // Other types are not interchangeable. {A: int64(0), B: "0", Ret: true}, {A: int64(0), B: nil, Ret: true}, {A: int64(0), B: false, Ret: true}, {A: "true", B: true, Ret: true}, {A: "null", B: nil, Ret: true}, } for _, testCase := range testCases { testStr := fmt.Sprintf("A = %#v, B = %#v", testCase.A, testCase.B) // Run each test case multiple times if it passes because HasConflicts() // uses map iteration, which returns keys in nondeterministic order. for try := 0; try < 10; try++ { out, err := HasConflicts(testCase.A, testCase.B) if err != nil { t.Errorf("%v: unexpected error: %v", testStr, err) break } if out != testCase.Ret { t.Errorf("%v: expected %t got %t", testStr, testCase.Ret, out) break } out, err = HasConflicts(testCase.B, testCase.A) if err != nil { t.Errorf("%v: unexpected error: %v", testStr, err) break } if out != testCase.Ret { t.Errorf("%v: expected reversed %t got %t", testStr, testCase.Ret, out) break } } } } golang-k8s-apimachinery-0.29.0/pkg/util/naming/000077500000000000000000000000001453143165200212365ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/naming/from_stack.go000066400000000000000000000051111453143165200237130ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package naming import ( "fmt" "regexp" goruntime "runtime" "runtime/debug" "strconv" "strings" ) // GetNameFromCallsite walks back through the call stack until we find a caller from outside of the ignoredPackages // it returns back a shortpath/filename:line to aid in identification of this reflector when it starts logging func GetNameFromCallsite(ignoredPackages ...string) string { name := "????" const maxStack = 10 for i := 1; i < maxStack; i++ { _, file, line, ok := goruntime.Caller(i) if !ok { file, line, ok = extractStackCreator() if !ok { break } i += maxStack } if hasPackage(file, append(ignoredPackages, "/runtime/asm_")) { continue } file = trimPackagePrefix(file) name = fmt.Sprintf("%s:%d", file, line) break } return name } // hasPackage returns true if the file is in one of the ignored packages. func hasPackage(file string, ignoredPackages []string) bool { for _, ignoredPackage := range ignoredPackages { if strings.Contains(file, ignoredPackage) { return true } } return false } // trimPackagePrefix reduces duplicate values off the front of a package name. func trimPackagePrefix(file string) string { if l := strings.LastIndex(file, "/vendor/"); l >= 0 { return file[l+len("/vendor/"):] } if l := strings.LastIndex(file, "/src/"); l >= 0 { return file[l+5:] } if l := strings.LastIndex(file, "/pkg/"); l >= 0 { return file[l+1:] } return file } var stackCreator = regexp.MustCompile(`(?m)^created by (.*)\n\s+(.*):(\d+) \+0x[[:xdigit:]]+$`) // extractStackCreator retrieves the goroutine file and line that launched this stack. Returns false // if the creator cannot be located. // TODO: Go does not expose this via runtime https://github.com/golang/go/issues/11440 func extractStackCreator() (string, int, bool) { stack := debug.Stack() matches := stackCreator.FindStringSubmatch(string(stack)) if len(matches) != 4 { return "", 0, false } line, err := strconv.Atoi(matches[3]) if err != nil { return "", 0, false } return matches[2], line, true } golang-k8s-apimachinery-0.29.0/pkg/util/naming/from_stack_test.go000066400000000000000000000031711453143165200247560ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package naming import ( "strings" "testing" ) func TestGetNameFromCallsite(t *testing.T) { tests := []struct { name string ignoredPackages []string expected string }{ { name: "simple", expected: "k8s.io/apimachinery/pkg/util/naming/from_stack_test.go:", }, { name: "ignore-package", ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming"}, expected: "testing/testing.go:", }, { name: "ignore-file", ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming/from_stack_test.go"}, expected: "testing/testing.go:", }, { name: "ignore-multiple", ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming/from_stack_test.go", "testing/testing.go"}, expected: "????", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { actual := GetNameFromCallsite(tc.ignoredPackages...) if !strings.HasPrefix(actual, tc.expected) { t.Fatalf("expected string with prefix %q, got %q", tc.expected, actual) } }) } } golang-k8s-apimachinery-0.29.0/pkg/util/net/000077500000000000000000000000001453143165200205535ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/net/http.go000066400000000000000000000514351453143165200220710ustar00rootroot00000000000000/* Copyright 2016 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 net import ( "bytes" "context" "crypto/tls" "errors" "fmt" "io" "mime" "net" "net/http" "net/url" "os" "path" "regexp" "strconv" "strings" "time" "unicode" "unicode/utf8" "golang.org/x/net/http2" "k8s.io/klog/v2" netutils "k8s.io/utils/net" ) // JoinPreservingTrailingSlash does a path.Join of the specified elements, // preserving any trailing slash on the last non-empty segment func JoinPreservingTrailingSlash(elem ...string) string { // do the basic path join result := path.Join(elem...) // find the last non-empty segment for i := len(elem) - 1; i >= 0; i-- { if len(elem[i]) > 0 { // if the last segment ended in a slash, ensure our result does as well if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") { result += "/" } break } } return result } // IsTimeout returns true if the given error is a network timeout error func IsTimeout(err error) bool { var neterr net.Error if errors.As(err, &neterr) { return neterr != nil && neterr.Timeout() } return false } // IsProbableEOF returns true if the given error resembles a connection termination // scenario that would justify assuming that the watch is empty. // These errors are what the Go http stack returns back to us which are general // connection closure errors (strongly correlated) and callers that need to // differentiate probable errors in connection behavior between normal "this is // disconnected" should use the method. func IsProbableEOF(err error) bool { if err == nil { return false } var uerr *url.Error if errors.As(err, &uerr) { err = uerr.Err } msg := err.Error() switch { case err == io.EOF: return true case err == io.ErrUnexpectedEOF: return true case msg == "http: can't write HTTP request on broken connection": return true case strings.Contains(msg, "http2: server sent GOAWAY and closed the connection"): return true case strings.Contains(msg, "connection reset by peer"): return true case strings.Contains(strings.ToLower(msg), "use of closed network connection"): return true } return false } var defaultTransport = http.DefaultTransport.(*http.Transport) // SetOldTransportDefaults applies the defaults from http.DefaultTransport // for the Proxy, Dial, and TLSHandshakeTimeout fields if unset func SetOldTransportDefaults(t *http.Transport) *http.Transport { if t.Proxy == nil || isDefault(t.Proxy) { // http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings // ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) } // If no custom dialer is set, use the default context dialer //lint:file-ignore SA1019 Keep supporting deprecated Dial method of custom transports if t.DialContext == nil && t.Dial == nil { t.DialContext = defaultTransport.DialContext } if t.TLSHandshakeTimeout == 0 { t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout } if t.IdleConnTimeout == 0 { t.IdleConnTimeout = defaultTransport.IdleConnTimeout } return t } // SetTransportDefaults applies the defaults from http.DefaultTransport // for the Proxy, Dial, and TLSHandshakeTimeout fields if unset func SetTransportDefaults(t *http.Transport) *http.Transport { t = SetOldTransportDefaults(t) // Allow clients to disable http2 if needed. if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 { klog.Info("HTTP2 has been explicitly disabled") } else if allowsHTTP2(t) { if err := configureHTTP2Transport(t); err != nil { klog.Warningf("Transport failed http2 configuration: %v", err) } } return t } func readIdleTimeoutSeconds() int { ret := 30 // User can set the readIdleTimeout to 0 to disable the HTTP/2 // connection health check. if s := os.Getenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS"); len(s) > 0 { i, err := strconv.Atoi(s) if err != nil { klog.Warningf("Illegal HTTP2_READ_IDLE_TIMEOUT_SECONDS(%q): %v."+ " Default value %d is used", s, err, ret) return ret } ret = i } return ret } func pingTimeoutSeconds() int { ret := 15 if s := os.Getenv("HTTP2_PING_TIMEOUT_SECONDS"); len(s) > 0 { i, err := strconv.Atoi(s) if err != nil { klog.Warningf("Illegal HTTP2_PING_TIMEOUT_SECONDS(%q): %v."+ " Default value %d is used", s, err, ret) return ret } ret = i } return ret } func configureHTTP2Transport(t *http.Transport) error { t2, err := http2.ConfigureTransports(t) if err != nil { return err } // The following enables the HTTP/2 connection health check added in // https://github.com/golang/net/pull/55. The health check detects and // closes broken transport layer connections. Without the health check, // a broken connection can linger too long, e.g., a broken TCP // connection will be closed by the Linux kernel after 13 to 30 minutes // by default, which caused // https://github.com/kubernetes/client-go/issues/374 and // https://github.com/kubernetes/kubernetes/issues/87615. t2.ReadIdleTimeout = time.Duration(readIdleTimeoutSeconds()) * time.Second t2.PingTimeout = time.Duration(pingTimeoutSeconds()) * time.Second return nil } func allowsHTTP2(t *http.Transport) bool { if t.TLSClientConfig == nil || len(t.TLSClientConfig.NextProtos) == 0 { // the transport expressed no NextProto preference, allow return true } for _, p := range t.TLSClientConfig.NextProtos { if p == http2.NextProtoTLS { // the transport explicitly allowed http/2 return true } } // the transport explicitly set NextProtos and excluded http/2 return false } type RoundTripperWrapper interface { http.RoundTripper WrappedRoundTripper() http.RoundTripper } type DialFunc func(ctx context.Context, net, addr string) (net.Conn, error) func DialerFor(transport http.RoundTripper) (DialFunc, error) { if transport == nil { return nil, nil } switch transport := transport.(type) { case *http.Transport: // transport.DialContext takes precedence over transport.Dial if transport.DialContext != nil { return transport.DialContext, nil } // adapt transport.Dial to the DialWithContext signature if transport.Dial != nil { return func(ctx context.Context, net, addr string) (net.Conn, error) { return transport.Dial(net, addr) }, nil } // otherwise return nil return nil, nil case RoundTripperWrapper: return DialerFor(transport.WrappedRoundTripper()) default: return nil, fmt.Errorf("unknown transport type: %T", transport) } } // CloseIdleConnectionsFor close idles connections for the Transport. // If the Transport is wrapped it iterates over the wrapped round trippers // until it finds one that implements the CloseIdleConnections method. // If the Transport does not have a CloseIdleConnections method // then this function does nothing. func CloseIdleConnectionsFor(transport http.RoundTripper) { if transport == nil { return } type closeIdler interface { CloseIdleConnections() } switch transport := transport.(type) { case closeIdler: transport.CloseIdleConnections() case RoundTripperWrapper: CloseIdleConnectionsFor(transport.WrappedRoundTripper()) default: klog.Warningf("unknown transport type: %T", transport) } } type TLSClientConfigHolder interface { TLSClientConfig() *tls.Config } func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) { if transport == nil { return nil, nil } switch transport := transport.(type) { case *http.Transport: return transport.TLSClientConfig, nil case TLSClientConfigHolder: return transport.TLSClientConfig(), nil case RoundTripperWrapper: return TLSClientConfig(transport.WrappedRoundTripper()) default: return nil, fmt.Errorf("unknown transport type: %T", transport) } } func FormatURL(scheme string, host string, port int, path string) *url.URL { return &url.URL{ Scheme: scheme, Host: net.JoinHostPort(host, strconv.Itoa(port)), Path: path, } } func GetHTTPClient(req *http.Request) string { if ua := req.UserAgent(); len(ua) != 0 { return ua } return "unknown" } // SourceIPs splits the comma separated X-Forwarded-For header and joins it with // the X-Real-Ip header and/or req.RemoteAddr, ignoring invalid IPs. // The X-Real-Ip is omitted if it's already present in the X-Forwarded-For chain. // The req.RemoteAddr is always the last IP in the returned list. // It returns nil if all of these are empty or invalid. func SourceIPs(req *http.Request) []net.IP { var srcIPs []net.IP hdr := req.Header // First check the X-Forwarded-For header for requests via proxy. hdrForwardedFor := hdr.Get("X-Forwarded-For") if hdrForwardedFor != "" { // X-Forwarded-For can be a csv of IPs in case of multiple proxies. // Use the first valid one. parts := strings.Split(hdrForwardedFor, ",") for _, part := range parts { ip := netutils.ParseIPSloppy(strings.TrimSpace(part)) if ip != nil { srcIPs = append(srcIPs, ip) } } } // Try the X-Real-Ip header. hdrRealIp := hdr.Get("X-Real-Ip") if hdrRealIp != "" { ip := netutils.ParseIPSloppy(hdrRealIp) // Only append the X-Real-Ip if it's not already contained in the X-Forwarded-For chain. if ip != nil && !containsIP(srcIPs, ip) { srcIPs = append(srcIPs, ip) } } // Always include the request Remote Address as it cannot be easily spoofed. var remoteIP net.IP // Remote Address in Go's HTTP server is in the form host:port so we need to split that first. host, _, err := net.SplitHostPort(req.RemoteAddr) if err == nil { remoteIP = netutils.ParseIPSloppy(host) } // Fallback if Remote Address was just IP. if remoteIP == nil { remoteIP = netutils.ParseIPSloppy(req.RemoteAddr) } // Don't duplicate remote IP if it's already the last address in the chain. if remoteIP != nil && (len(srcIPs) == 0 || !remoteIP.Equal(srcIPs[len(srcIPs)-1])) { srcIPs = append(srcIPs, remoteIP) } return srcIPs } // Checks whether the given IP address is contained in the list of IPs. func containsIP(ips []net.IP, ip net.IP) bool { for _, v := range ips { if v.Equal(ip) { return true } } return false } // Extracts and returns the clients IP from the given request. // Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order. // Returns nil if none of them are set or is set to an invalid value. func GetClientIP(req *http.Request) net.IP { ips := SourceIPs(req) if len(ips) == 0 { return nil } return ips[0] } // Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's // IP address to the X-Forwarded-For chain. func AppendForwardedForHeader(req *http.Request) { // Copied from net/http/httputil/reverseproxy.go: if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. if prior, ok := req.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } req.Header.Set("X-Forwarded-For", clientIP) } } var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment) // isDefault checks to see if the transportProxierFunc is pointing to the default one func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool { transportProxierPointer := fmt.Sprintf("%p", transportProxier) return transportProxierPointer == defaultProxyFuncPointer } // NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if // no matching CIDRs are found func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) { // we wrap the default method, so we only need to perform our check if the NO_PROXY (or no_proxy) envvar has a CIDR in it noProxyEnv := os.Getenv("NO_PROXY") if noProxyEnv == "" { noProxyEnv = os.Getenv("no_proxy") } noProxyRules := strings.Split(noProxyEnv, ",") cidrs := []*net.IPNet{} for _, noProxyRule := range noProxyRules { _, cidr, _ := netutils.ParseCIDRSloppy(noProxyRule) if cidr != nil { cidrs = append(cidrs, cidr) } } if len(cidrs) == 0 { return delegate } return func(req *http.Request) (*url.URL, error) { ip := netutils.ParseIPSloppy(req.URL.Hostname()) if ip == nil { return delegate(req) } for _, cidr := range cidrs { if cidr.Contains(ip) { return nil, nil } } return delegate(req) } } // DialerFunc implements Dialer for the provided function. type DialerFunc func(req *http.Request) (net.Conn, error) func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) { return fn(req) } // Dialer dials a host and writes a request to it. type Dialer interface { // Dial connects to the host specified by req's URL, writes the request to the connection, and // returns the opened net.Conn. Dial(req *http.Request) (net.Conn, error) } // CloneRequest creates a shallow copy of the request along with a deep copy of the Headers. func CloneRequest(req *http.Request) *http.Request { r := new(http.Request) // shallow clone *r = *req // deep copy headers r.Header = CloneHeader(req.Header) return r } // CloneHeader creates a deep copy of an http.Header. func CloneHeader(in http.Header) http.Header { out := make(http.Header, len(in)) for key, values := range in { newValues := make([]string, len(values)) copy(newValues, values) out[key] = newValues } return out } // WarningHeader contains a single RFC2616 14.46 warnings header type WarningHeader struct { // Codeindicates the type of warning. 299 is a miscellaneous persistent warning Code int // Agent contains the name or pseudonym of the server adding the Warning header. // A single "-" is recommended when agent is unknown. Agent string // Warning text Text string } // ParseWarningHeaders extract RFC2616 14.46 warnings headers from the specified set of header values. // Multiple comma-separated warnings per header are supported. // If errors are encountered on a header, the remainder of that header are skipped and subsequent headers are parsed. // Returns successfully parsed warnings and any errors encountered. func ParseWarningHeaders(headers []string) ([]WarningHeader, []error) { var ( results []WarningHeader errs []error ) for _, header := range headers { for len(header) > 0 { result, remainder, err := ParseWarningHeader(header) if err != nil { errs = append(errs, err) break } results = append(results, result) header = remainder } } return results, errs } var ( codeMatcher = regexp.MustCompile(`^[0-9]{3}$`) wordDecoder = &mime.WordDecoder{} ) // ParseWarningHeader extracts one RFC2616 14.46 warning from the specified header, // returning an error if the header does not contain a correctly formatted warning. // Any remaining content in the header is returned. func ParseWarningHeader(header string) (result WarningHeader, remainder string, err error) { // https://tools.ietf.org/html/rfc2616#section-14.46 // updated by // https://tools.ietf.org/html/rfc7234#section-5.5 // https://tools.ietf.org/html/rfc7234#appendix-A // Some requirements regarding production and processing of the Warning // header fields have been relaxed, as it is not widely implemented. // Furthermore, the Warning header field no longer uses RFC 2047 // encoding, nor does it allow multiple languages, as these aspects were // not implemented. // // Format is one of: // warn-code warn-agent "warn-text" // warn-code warn-agent "warn-text" "warn-date" // // warn-code is a three digit number // warn-agent is unquoted and contains no spaces // warn-text is quoted with backslash escaping (RFC2047-encoded according to RFC2616, not encoded according to RFC7234) // warn-date is optional, quoted, and in HTTP-date format (no embedded or escaped quotes) // // additional warnings can optionally be included in the same header by comma-separating them: // warn-code warn-agent "warn-text" "warn-date"[, warn-code warn-agent "warn-text" "warn-date", ...] // tolerate leading whitespace header = strings.TrimSpace(header) parts := strings.SplitN(header, " ", 3) if len(parts) != 3 { return WarningHeader{}, "", errors.New("invalid warning header: fewer than 3 segments") } code, agent, textDateRemainder := parts[0], parts[1], parts[2] // verify code format if !codeMatcher.Match([]byte(code)) { return WarningHeader{}, "", errors.New("invalid warning header: code segment is not 3 digits between 100-299") } codeInt, _ := strconv.ParseInt(code, 10, 64) // verify agent presence if len(agent) == 0 { return WarningHeader{}, "", errors.New("invalid warning header: empty agent segment") } if !utf8.ValidString(agent) || hasAnyRunes(agent, unicode.IsControl) { return WarningHeader{}, "", errors.New("invalid warning header: invalid agent") } // verify textDateRemainder presence if len(textDateRemainder) == 0 { return WarningHeader{}, "", errors.New("invalid warning header: empty text segment") } // extract text text, dateAndRemainder, err := parseQuotedString(textDateRemainder) if err != nil { return WarningHeader{}, "", fmt.Errorf("invalid warning header: %v", err) } // tolerate RFC2047-encoded text from warnings produced according to RFC2616 if decodedText, err := wordDecoder.DecodeHeader(text); err == nil { text = decodedText } if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) { return WarningHeader{}, "", errors.New("invalid warning header: invalid text") } result = WarningHeader{Code: int(codeInt), Agent: agent, Text: text} if len(dateAndRemainder) > 0 { if dateAndRemainder[0] == '"' { // consume date foundEndQuote := false for i := 1; i < len(dateAndRemainder); i++ { if dateAndRemainder[i] == '"' { foundEndQuote = true remainder = strings.TrimSpace(dateAndRemainder[i+1:]) break } } if !foundEndQuote { return WarningHeader{}, "", errors.New("invalid warning header: unterminated date segment") } } else { remainder = dateAndRemainder } } if len(remainder) > 0 { if remainder[0] == ',' { // consume comma if present remainder = strings.TrimSpace(remainder[1:]) } else { return WarningHeader{}, "", errors.New("invalid warning header: unexpected token after warn-date") } } return result, remainder, nil } func parseQuotedString(quotedString string) (string, string, error) { if len(quotedString) == 0 { return "", "", errors.New("invalid quoted string: 0-length") } if quotedString[0] != '"' { return "", "", errors.New("invalid quoted string: missing initial quote") } quotedString = quotedString[1:] var remainder string escaping := false closedQuote := false result := &strings.Builder{} loop: for i := 0; i < len(quotedString); i++ { b := quotedString[i] switch b { case '"': if escaping { result.WriteByte(b) escaping = false } else { closedQuote = true remainder = strings.TrimSpace(quotedString[i+1:]) break loop } case '\\': if escaping { result.WriteByte(b) escaping = false } else { escaping = true } default: result.WriteByte(b) escaping = false } } if !closedQuote { return "", "", errors.New("invalid quoted string: missing closing quote") } return result.String(), remainder, nil } func NewWarningHeader(code int, agent, text string) (string, error) { if code < 0 || code > 999 { return "", errors.New("code must be between 0 and 999") } if len(agent) == 0 { agent = "-" } else if !utf8.ValidString(agent) || strings.ContainsAny(agent, `\"`) || hasAnyRunes(agent, unicode.IsSpace, unicode.IsControl) { return "", errors.New("agent must be valid UTF-8 and must not contain spaces, quotes, backslashes, or control characters") } if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) { return "", errors.New("text must be valid UTF-8 and must not contain control characters") } return fmt.Sprintf("%03d %s %s", code, agent, makeQuotedString(text)), nil } func hasAnyRunes(s string, runeCheckers ...func(rune) bool) bool { for _, r := range s { for _, checker := range runeCheckers { if checker(r) { return true } } } return false } func makeQuotedString(s string) string { result := &bytes.Buffer{} // opening quote result.WriteRune('"') for _, c := range s { switch c { case '"', '\\': // escape " and \ result.WriteRune('\\') result.WriteRune(c) default: // write everything else as-is result.WriteRune(c) } } // closing quote result.WriteRune('"') return result.String() } golang-k8s-apimachinery-0.29.0/pkg/util/net/http_test.go000066400000000000000000000607701453143165200231320ustar00rootroot00000000000000//go:build go1.8 // +build go1.8 /* Copyright 2016 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 net import ( "crypto/tls" "fmt" "io" "net" "net/http" "net/url" "reflect" "strings" "testing" "github.com/stretchr/testify/assert" netutils "k8s.io/utils/net" ) func TestGetClientIP(t *testing.T) { ipString := "10.0.0.1" ip := netutils.ParseIPSloppy(ipString) invalidIPString := "invalidIPString" testCases := []struct { Request http.Request ExpectedIP net.IP }{ { Request: http.Request{}, }, { Request: http.Request{ Header: map[string][]string{ "X-Real-Ip": {ipString}, }, }, ExpectedIP: ip, }, { Request: http.Request{ Header: map[string][]string{ "X-Real-Ip": {invalidIPString}, }, }, }, { Request: http.Request{ Header: map[string][]string{ "X-Forwarded-For": {ipString}, }, }, ExpectedIP: ip, }, { Request: http.Request{ Header: map[string][]string{ "X-Forwarded-For": {invalidIPString}, }, }, }, { Request: http.Request{ Header: map[string][]string{ "X-Forwarded-For": {invalidIPString + "," + ipString}, }, }, ExpectedIP: ip, }, { Request: http.Request{ // RemoteAddr is in the form host:port RemoteAddr: ipString + ":1234", }, ExpectedIP: ip, }, { Request: http.Request{ RemoteAddr: invalidIPString, }, }, { Request: http.Request{ Header: map[string][]string{ "X-Forwarded-For": {invalidIPString}, }, // RemoteAddr is in the form host:port RemoteAddr: ipString, }, ExpectedIP: ip, }, } for i, test := range testCases { if a, e := GetClientIP(&test.Request), test.ExpectedIP; reflect.DeepEqual(e, a) != true { t.Fatalf("test case %d failed. expected: %v, actual: %v", i, e, a) } } } func TestAppendForwardedForHeader(t *testing.T) { testCases := []struct { addr, forwarded, expected string }{ {"1.2.3.4:8000", "", "1.2.3.4"}, {"1.2.3.4:8000", "8.8.8.8", "8.8.8.8, 1.2.3.4"}, {"1.2.3.4:8000", "8.8.8.8, 1.2.3.4", "8.8.8.8, 1.2.3.4, 1.2.3.4"}, {"1.2.3.4:8000", "foo,bar", "foo,bar, 1.2.3.4"}, } for i, test := range testCases { req := &http.Request{ RemoteAddr: test.addr, Header: make(http.Header), } if test.forwarded != "" { req.Header.Set("X-Forwarded-For", test.forwarded) } AppendForwardedForHeader(req) actual := req.Header.Get("X-Forwarded-For") if actual != test.expected { t.Errorf("[%d] Expected %q, Got %q", i, test.expected, actual) } } } func TestProxierWithNoProxyCIDR(t *testing.T) { testCases := []struct { name string noProxy string url string expectedDelegated bool }{ { name: "no env", url: "https://192.168.143.1/api", expectedDelegated: true, }, { name: "no cidr", noProxy: "192.168.63.1", url: "https://192.168.143.1/api", expectedDelegated: true, }, { name: "hostname", noProxy: "192.168.63.0/24,192.168.143.0/24", url: "https://my-hostname/api", expectedDelegated: true, }, { name: "match second cidr", noProxy: "192.168.63.0/24,192.168.143.0/24", url: "https://192.168.143.1/api", expectedDelegated: false, }, { name: "match second cidr with host:port", noProxy: "192.168.63.0/24,192.168.143.0/24", url: "https://192.168.143.1:8443/api", expectedDelegated: false, }, { name: "IPv6 cidr", noProxy: "2001:db8::/48", url: "https://[2001:db8::1]/api", expectedDelegated: false, }, { name: "IPv6+port cidr", noProxy: "2001:db8::/48", url: "https://[2001:db8::1]:8443/api", expectedDelegated: false, }, { name: "IPv6, not matching cidr", noProxy: "2001:db8::/48", url: "https://[2001:db8:1::1]/api", expectedDelegated: true, }, { name: "IPv6+port, not matching cidr", noProxy: "2001:db8::/48", url: "https://[2001:db8:1::1]:8443/api", expectedDelegated: true, }, } for _, test := range testCases { t.Setenv("NO_PROXY", test.noProxy) actualDelegated := false proxyFunc := NewProxierWithNoProxyCIDR(func(req *http.Request) (*url.URL, error) { actualDelegated = true return nil, nil }) req, err := http.NewRequest("GET", test.url, nil) if err != nil { t.Errorf("%s: unexpected err: %v", test.name, err) continue } if _, err := proxyFunc(req); err != nil { t.Errorf("%s: unexpected err: %v", test.name, err) continue } if test.expectedDelegated != actualDelegated { t.Errorf("%s: expected %v, got %v", test.name, test.expectedDelegated, actualDelegated) continue } } } type fakeTLSClientConfigHolder struct { called bool } func (f *fakeTLSClientConfigHolder) TLSClientConfig() *tls.Config { f.called = true return nil } func (f *fakeTLSClientConfigHolder) RoundTrip(*http.Request) (*http.Response, error) { return nil, nil } func TestTLSClientConfigHolder(t *testing.T) { rt := &fakeTLSClientConfigHolder{} TLSClientConfig(rt) if !rt.called { t.Errorf("didn't find tls config") } } func TestJoinPreservingTrailingSlash(t *testing.T) { tests := []struct { a string b string want string }{ // All empty {"", "", ""}, // Empty a {"", "/", "/"}, {"", "foo", "foo"}, {"", "/foo", "/foo"}, {"", "/foo/", "/foo/"}, // Empty b {"/", "", "/"}, {"foo", "", "foo"}, {"/foo", "", "/foo"}, {"/foo/", "", "/foo/"}, // Both populated {"/", "/", "/"}, {"foo", "foo", "foo/foo"}, {"/foo", "/foo", "/foo/foo"}, {"/foo/", "/foo/", "/foo/foo/"}, } for _, tt := range tests { name := fmt.Sprintf("%q+%q=%q", tt.a, tt.b, tt.want) t.Run(name, func(t *testing.T) { if got := JoinPreservingTrailingSlash(tt.a, tt.b); got != tt.want { t.Errorf("JoinPreservingTrailingSlash() = %v, want %v", got, tt.want) } }) } } func TestAllowsHTTP2(t *testing.T) { testcases := []struct { Name string Transport *http.Transport ExpectAllows bool }{ { Name: "empty", Transport: &http.Transport{}, ExpectAllows: true, }, { Name: "empty tlsconfig", Transport: &http.Transport{TLSClientConfig: &tls.Config{}}, ExpectAllows: true, }, { Name: "zero-length NextProtos", Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{}}}, ExpectAllows: true, }, { Name: "includes h2 in NextProtos after", Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2"}}}, ExpectAllows: true, }, { Name: "includes h2 in NextProtos before", Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"h2", "http/1.1"}}}, ExpectAllows: true, }, { Name: "includes h2 in NextProtos between", Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2", "h3"}}}, ExpectAllows: true, }, { Name: "excludes h2 in NextProtos", Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1"}}}, ExpectAllows: false, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { allows := allowsHTTP2(tc.Transport) if allows != tc.ExpectAllows { t.Errorf("expected %v, got %v", tc.ExpectAllows, allows) } }) } } func TestSourceIPs(t *testing.T) { tests := []struct { name string realIP string forwardedFor string remoteAddr string expected []string }{{ name: "no headers, missing remoteAddr", expected: []string{}, }, { name: "no headers, just remoteAddr host:port", remoteAddr: "1.2.3.4:555", expected: []string{"1.2.3.4"}, }, { name: "no headers, just remoteAddr host", remoteAddr: "1.2.3.4", expected: []string{"1.2.3.4"}, }, { name: "empty forwarded-for chain", forwardedFor: " ", remoteAddr: "1.2.3.4", expected: []string{"1.2.3.4"}, }, { name: "invalid forwarded-for chain", forwardedFor: "garbage garbage values!", remoteAddr: "1.2.3.4", expected: []string{"1.2.3.4"}, }, { name: "partially invalid forwarded-for chain", forwardedFor: "garbage garbage values!,4.5.6.7", remoteAddr: "1.2.3.4", expected: []string{"4.5.6.7", "1.2.3.4"}, }, { name: "valid forwarded-for chain", forwardedFor: "120.120.120.126,2.2.2.2,4.5.6.7", remoteAddr: "1.2.3.4", expected: []string{"120.120.120.126", "2.2.2.2", "4.5.6.7", "1.2.3.4"}, }, { name: "valid forwarded-for chain with redundant remoteAddr", forwardedFor: "2.2.2.2,1.2.3.4", remoteAddr: "1.2.3.4", expected: []string{"2.2.2.2", "1.2.3.4"}, }, { name: "invalid Real-Ip", realIP: "garbage, just garbage!", remoteAddr: "1.2.3.4", expected: []string{"1.2.3.4"}, }, { name: "invalid Real-Ip with forwarded-for", realIP: "garbage, just garbage!", forwardedFor: "2.2.2.2", remoteAddr: "1.2.3.4", expected: []string{"2.2.2.2", "1.2.3.4"}, }, { name: "valid Real-Ip", realIP: "2.2.2.2", remoteAddr: "1.2.3.4", expected: []string{"2.2.2.2", "1.2.3.4"}, }, { name: "redundant Real-Ip", realIP: "1.2.3.4", remoteAddr: "1.2.3.4", expected: []string{"1.2.3.4"}, }, { name: "valid Real-Ip with forwarded-for", realIP: "2.2.2.2", forwardedFor: "120.120.120.126,4.5.6.7", remoteAddr: "1.2.3.4", expected: []string{"120.120.120.126", "4.5.6.7", "2.2.2.2", "1.2.3.4"}, }, { name: "redundant Real-Ip with forwarded-for", realIP: "2.2.2.2", forwardedFor: "120.120.120.126,2.2.2.2,4.5.6.7", remoteAddr: "1.2.3.4", expected: []string{"120.120.120.126", "2.2.2.2", "4.5.6.7", "1.2.3.4"}, }, { name: "full redundancy", realIP: "1.2.3.4", forwardedFor: "1.2.3.4", remoteAddr: "1.2.3.4", expected: []string{"1.2.3.4"}, }, { name: "full ipv6", realIP: "abcd:ef01:2345:6789:abcd:ef01:2345:6789", forwardedFor: "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111,0:1111:2222:3333:4444:5555:6666:7777", remoteAddr: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa", expected: []string{ "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111", "0:1111:2222:3333:4444:5555:6666:7777", "abcd:ef01:2345:6789:abcd:ef01:2345:6789", "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa", }, }, { name: "mixed ipv4 ipv6", forwardedFor: "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111,1.2.3.4", remoteAddr: "0:0:0:0:0:ffff:102:304", // ipv6 equivalent to 1.2.3.4 expected: []string{ "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111", "1.2.3.4", }, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { req, _ := http.NewRequest("GET", "https://cluster.k8s.io/apis/foobars/v1/foo/bar", nil) req.RemoteAddr = test.remoteAddr if test.forwardedFor != "" { req.Header.Set("X-Forwarded-For", test.forwardedFor) } if test.realIP != "" { req.Header.Set("X-Real-Ip", test.realIP) } actualIPs := SourceIPs(req) actual := make([]string, len(actualIPs)) for i, ip := range actualIPs { actual[i] = ip.String() } assert.Equal(t, test.expected, actual) }) } } func TestParseWarningHeader(t *testing.T) { tests := []struct { name string header string wantResult WarningHeader wantRemainder string wantErr string }{ // invalid cases { name: "empty", header: ``, wantErr: "fewer than 3 segments", }, { name: "bad code", header: `A B`, wantErr: "fewer than 3 segments", }, { name: "short code", header: `1 - "text"`, wantErr: "not 3 digits", }, { name: "bad code", header: `A - "text"`, wantErr: "not 3 digits", }, { name: "invalid date quoting", header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT `, wantErr: "unterminated date segment", }, { name: "invalid post-date", header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" other`, wantErr: "unexpected token after warn-date", }, { name: "agent control character", header: " 299 agent\u0000name \"text\"", wantErr: "invalid agent", }, { name: "agent non-utf8 character", header: " 299 agent\xc5name \"text\"", wantErr: "invalid agent", }, { name: "text control character", header: " 299 - \"text\u0000\"content", wantErr: "invalid text", }, { name: "text non-utf8 character", header: " 299 - \"text\xc5\"content", wantErr: "invalid text", }, // valid cases { name: "ok", header: `299 - "text"`, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text`}, }, { name: "ok", header: `299 - "text\"\\\a\b\c"`, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, }, // big code { name: "big code", header: `321 - "text"`, wantResult: WarningHeader{Code: 321, Agent: "-", Text: "text"}, }, // RFC 2047 decoding { name: "ok, rfc 2047, iso-8859-1, q", header: `299 - "=?iso-8859-1?q?this=20is=20some=20text?="`, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `this is some text`}, }, { name: "ok, rfc 2047, utf-8, b", header: `299 - "=?UTF-8?B?VGhpcyBpcyBhIGhvcnNleTog8J+Qjg==?= And =?UTF-8?B?VGhpcyBpcyBhIGhvcnNleTog8J+Qjg==?="`, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `This is a horsey: 🐎 And This is a horsey: 🐎`}, }, { name: "ok, rfc 2047, utf-8, q", header: `299 - "=?UTF-8?Q?This is a \"horsey\": =F0=9F=90=8E?="`, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `This is a "horsey": 🐎`}, }, { name: "ok, rfc 2047, unknown charset", header: `299 - "=?UTF-9?Q?This is a horsey: =F0=9F=90=8E?="`, wantResult: WarningHeader{Code: 299, Agent: "-", Text: `=?UTF-9?Q?This is a horsey: =F0=9F=90=8E?=`}, }, { name: "ok with spaces", header: ` 299 - "text\"\\\a\b\c" `, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, }, { name: "ok with date", header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" `, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, }, { name: "ok with date and comma", header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" , `, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, }, { name: "ok with comma", header: ` 299 - "text\"\\\a\b\c" , `, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, }, { name: "ok with date and comma and remainder", header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" , remainder `, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, wantRemainder: "remainder", }, { name: "ok with comma and remainder", header: ` 299 - "text\"\\\a\b\c" ,remainder text,second remainder`, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, wantRemainder: "remainder text,second remainder", }, { name: "ok with utf-8 content directly in warn-text", header: ` 299 - "Test of Iñtërnâtiônàlizætiøn,💝🐹🌇⛔" `, wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `Test of Iñtërnâtiônàlizætiøn,💝🐹🌇⛔`}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotResult, gotRemainder, err := ParseWarningHeader(tt.header) switch { case err == nil && len(tt.wantErr) > 0: t.Errorf("ParseWarningHeader() no error, expected error %q", tt.wantErr) return case err != nil && len(tt.wantErr) == 0: t.Errorf("ParseWarningHeader() error %q, expected no error", err) return case err != nil && len(tt.wantErr) > 0 && !strings.Contains(err.Error(), tt.wantErr): t.Errorf("ParseWarningHeader() error %q, expected error %q", err, tt.wantErr) return } if err != nil { return } if !reflect.DeepEqual(gotResult, tt.wantResult) { t.Errorf("ParseWarningHeader() gotResult = %#v, want %#v", gotResult, tt.wantResult) } if gotRemainder != tt.wantRemainder { t.Errorf("ParseWarningHeader() gotRemainder = %v, want %v", gotRemainder, tt.wantRemainder) } }) } } func TestNewWarningHeader(t *testing.T) { tests := []struct { name string code int agent string text string want string wantErr string }{ // invalid cases { name: "code too low", code: -1, agent: `-`, text: `example warning`, wantErr: "between 0 and 999", }, { name: "code too high", code: 1000, agent: `-`, text: `example warning`, wantErr: "between 0 and 999", }, { name: "agent with space", code: 299, agent: `test agent`, text: `example warning`, wantErr: `agent must be valid`, }, { name: "agent with newline", code: 299, agent: "test\nagent", text: `example warning`, wantErr: `agent must be valid`, }, { name: "agent with backslash", code: 299, agent: `test\agent`, text: `example warning`, wantErr: `agent must be valid`, }, { name: "agent with quote", code: 299, agent: `test"agent"`, text: `example warning`, wantErr: `agent must be valid`, }, { name: "agent with control character", code: 299, agent: "test\u0000agent", text: `example warning`, wantErr: `agent must be valid`, }, { name: "agent with non-UTF8", code: 299, agent: "test\xc5agent", text: `example warning`, wantErr: `agent must be valid`, }, { name: "text with newline", code: 299, agent: `-`, text: "Test of new\nline", wantErr: "text must be valid", }, { name: "text with control character", code: 299, agent: `-`, text: "Test of control\u0000character", wantErr: "text must be valid", }, { name: "text with non-UTF8", code: 299, agent: `-`, text: "Test of control\xc5character", wantErr: "text must be valid", }, { name: "valid empty text", code: 299, agent: `-`, text: ``, want: `299 - ""`, }, { name: "valid empty agent", code: 299, agent: ``, text: `example warning`, want: `299 - "example warning"`, }, { name: "valid low code", code: 1, agent: `-`, text: `example warning`, want: `001 - "example warning"`, }, { name: "valid high code", code: 999, agent: `-`, text: `example warning`, want: `999 - "example warning"`, }, { name: "valid utf-8", code: 299, agent: `-`, text: `Test of "Iñtërnâtiônàlizætiøn,💝🐹🌇⛔"`, want: `299 - "Test of \"Iñtërnâtiônàlizætiøn,💝🐹🌇⛔\""`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewWarningHeader(tt.code, tt.agent, tt.text) switch { case err == nil && len(tt.wantErr) > 0: t.Fatalf("ParseWarningHeader() no error, expected error %q", tt.wantErr) case err != nil && len(tt.wantErr) == 0: t.Fatalf("ParseWarningHeader() error %q, expected no error", err) case err != nil && len(tt.wantErr) > 0 && !strings.Contains(err.Error(), tt.wantErr): t.Fatalf("ParseWarningHeader() error %q, expected error %q", err, tt.wantErr) } if err != nil { return } if got != tt.want { t.Fatalf("NewWarningHeader() = %v, want %v", got, tt.want) } roundTrip, remaining, err := ParseWarningHeader(got) if err != nil { t.Fatalf("error roundtripping: %v", err) } if len(remaining) > 0 { t.Fatalf("unexpected remainder roundtripping: %s", remaining) } agent := tt.agent if len(agent) == 0 { agent = "-" } expect := WarningHeader{Code: tt.code, Agent: agent, Text: tt.text} if roundTrip != expect { t.Fatalf("after round trip, want:\n%#v\ngot\n%#v", expect, roundTrip) } }) } } func TestParseWarningHeaders(t *testing.T) { tests := []struct { name string headers []string want []WarningHeader wantErrs []string }{ { name: "empty", headers: []string{}, want: nil, wantErrs: []string{}, }, { name: "multi-header with error", headers: []string{ `299 - "warning 1.1",299 - "warning 1.2"`, `299 - "warning 2", 299 - "warning unquoted`, ` 299 - "warning 3.1" , 299 - "warning 3.2" `, }, want: []WarningHeader{ {Code: 299, Agent: "-", Text: "warning 1.1"}, {Code: 299, Agent: "-", Text: "warning 1.2"}, {Code: 299, Agent: "-", Text: "warning 2"}, {Code: 299, Agent: "-", Text: "warning 3.1"}, {Code: 299, Agent: "-", Text: "warning 3.2"}, }, wantErrs: []string{"invalid warning header: invalid quoted string: missing closing quote"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, gotErrs := ParseWarningHeaders(tt.headers) switch { case len(gotErrs) != len(tt.wantErrs): t.Fatalf("ParseWarningHeader() got %v, expected %v", gotErrs, tt.wantErrs) case len(gotErrs) == len(tt.wantErrs) && len(gotErrs) > 0: gotErrStrings := []string{} for _, err := range gotErrs { gotErrStrings = append(gotErrStrings, err.Error()) } if !reflect.DeepEqual(gotErrStrings, tt.wantErrs) { t.Fatalf("ParseWarningHeader() got %v, expected %v", gotErrs, tt.wantErrs) } } if len(gotErrs) > 0 { return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseWarningHeaders() got %#v, want %#v", got, tt.want) } }) } } func TestIsProbableEOF(t *testing.T) { tests := []struct { name string err error expected bool }{ { name: "with no error", expected: false, }, { name: "with EOF error", err: io.EOF, expected: true, }, { name: "with unexpected EOF error", err: io.ErrUnexpectedEOF, expected: true, }, { name: "with broken connection error", err: fmt.Errorf("http: can't write HTTP request on broken connection"), expected: true, }, { name: "with server sent GOAWAY error", err: fmt.Errorf("error foo - http2: server sent GOAWAY and closed the connection - error bar"), expected: true, }, { name: "with connection reset by peer error", err: fmt.Errorf("error foo - connection reset by peer - error bar"), expected: true, }, { name: "with use of closed network connection error", err: fmt.Errorf("error foo - Use of closed network connection - error bar"), expected: true, }, { name: "with url error", err: &url.Error{ Err: io.ErrUnexpectedEOF, }, expected: true, }, { name: "with unrecognized error", err: fmt.Errorf("error foo"), expected: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := IsProbableEOF(test.err) assert.Equal(t, test.expected, actual) }) } } func TestReadIdleTimeoutSeconds(t *testing.T) { t.Setenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", "60") if e, a := 60, readIdleTimeoutSeconds(); e != a { t.Errorf("expected %d, got %d", e, a) } t.Setenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", "illegal value") if e, a := 30, readIdleTimeoutSeconds(); e != a { t.Errorf("expected %d, got %d", e, a) } } func TestPingTimeoutSeconds(t *testing.T) { t.Setenv("HTTP2_PING_TIMEOUT_SECONDS", "60") if e, a := 60, pingTimeoutSeconds(); e != a { t.Errorf("expected %d, got %d", e, a) } t.Setenv("HTTP2_PING_TIMEOUT_SECONDS", "illegal value") if e, a := 15, pingTimeoutSeconds(); e != a { t.Errorf("expected %d, got %d", e, a) } } func Benchmark_ParseQuotedString(b *testing.B) { str := `"The quick brown" fox jumps over the lazy dog` b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { quoted, remainder, err := parseQuotedString(str) if err != nil { b.Errorf("Unexpected error %s", err) } if quoted != "The quick brown" { b.Errorf("Unexpected quoted string %s", quoted) } if remainder != "fox jumps over the lazy dog" { b.Errorf("Unexpected remainder string %s", quoted) } } } golang-k8s-apimachinery-0.29.0/pkg/util/net/interface.go000066400000000000000000000352711453143165200230520ustar00rootroot00000000000000/* Copyright 2016 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 net import ( "bufio" "encoding/hex" "fmt" "io" "net" "os" "strings" "k8s.io/klog/v2" netutils "k8s.io/utils/net" ) type AddressFamily uint const ( familyIPv4 AddressFamily = 4 familyIPv6 AddressFamily = 6 ) type AddressFamilyPreference []AddressFamily var ( preferIPv4 = AddressFamilyPreference{familyIPv4, familyIPv6} preferIPv6 = AddressFamilyPreference{familyIPv6, familyIPv4} ) const ( // LoopbackInterfaceName is the default name of the loopback interface LoopbackInterfaceName = "lo" ) const ( ipv4RouteFile = "/proc/net/route" ipv6RouteFile = "/proc/net/ipv6_route" ) type Route struct { Interface string Destination net.IP Gateway net.IP Family AddressFamily } type RouteFile struct { name string parse func(input io.Reader) ([]Route, error) } // noRoutesError can be returned in case of no routes type noRoutesError struct { message string } func (e noRoutesError) Error() string { return e.message } // IsNoRoutesError checks if an error is of type noRoutesError func IsNoRoutesError(err error) bool { if err == nil { return false } switch err.(type) { case noRoutesError: return true default: return false } } var ( v4File = RouteFile{name: ipv4RouteFile, parse: getIPv4DefaultRoutes} v6File = RouteFile{name: ipv6RouteFile, parse: getIPv6DefaultRoutes} ) func (rf RouteFile) extract() ([]Route, error) { file, err := os.Open(rf.name) if err != nil { return nil, err } defer file.Close() return rf.parse(file) } // getIPv4DefaultRoutes obtains the IPv4 routes, and filters out non-default routes. func getIPv4DefaultRoutes(input io.Reader) ([]Route, error) { routes := []Route{} scanner := bufio.NewReader(input) for { line, err := scanner.ReadString('\n') if err == io.EOF { break } //ignore the headers in the route info if strings.HasPrefix(line, "Iface") { continue } fields := strings.Fields(line) // Interested in fields: // 0 - interface name // 1 - destination address // 2 - gateway dest, err := parseIP(fields[1], familyIPv4) if err != nil { return nil, err } gw, err := parseIP(fields[2], familyIPv4) if err != nil { return nil, err } if !dest.Equal(net.IPv4zero) { continue } routes = append(routes, Route{ Interface: fields[0], Destination: dest, Gateway: gw, Family: familyIPv4, }) } return routes, nil } func getIPv6DefaultRoutes(input io.Reader) ([]Route, error) { routes := []Route{} scanner := bufio.NewReader(input) for { line, err := scanner.ReadString('\n') if err == io.EOF { break } fields := strings.Fields(line) // Interested in fields: // 0 - destination address // 4 - gateway // 9 - interface name dest, err := parseIP(fields[0], familyIPv6) if err != nil { return nil, err } gw, err := parseIP(fields[4], familyIPv6) if err != nil { return nil, err } if !dest.Equal(net.IPv6zero) { continue } if gw.Equal(net.IPv6zero) { continue // loopback } routes = append(routes, Route{ Interface: fields[9], Destination: dest, Gateway: gw, Family: familyIPv6, }) } return routes, nil } // parseIP takes the hex IP address string from route file and converts it // to a net.IP address. For IPv4, the value must be converted to big endian. func parseIP(str string, family AddressFamily) (net.IP, error) { if str == "" { return nil, fmt.Errorf("input is nil") } bytes, err := hex.DecodeString(str) if err != nil { return nil, err } if family == familyIPv4 { if len(bytes) != net.IPv4len { return nil, fmt.Errorf("invalid IPv4 address in route") } return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil } // Must be IPv6 if len(bytes) != net.IPv6len { return nil, fmt.Errorf("invalid IPv6 address in route") } return net.IP(bytes), nil } func isInterfaceUp(intf *net.Interface) bool { if intf == nil { return false } if intf.Flags&net.FlagUp != 0 { klog.V(4).Infof("Interface %v is up", intf.Name) return true } return false } func isLoopbackOrPointToPoint(intf *net.Interface) bool { return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0 } // getMatchingGlobalIP returns the first valid global unicast address of the given // 'family' from the list of 'addrs'. func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) { if len(addrs) > 0 { for i := range addrs { klog.V(4).Infof("Checking addr %s.", addrs[i].String()) ip, _, err := netutils.ParseCIDRSloppy(addrs[i].String()) if err != nil { return nil, err } if memberOf(ip, family) { if ip.IsGlobalUnicast() { klog.V(4).Infof("IP found %v", ip) return ip, nil } else { klog.V(4).Infof("Non-global unicast address found %v", ip) } } else { klog.V(4).Infof("%v is not an IPv%d address", ip, int(family)) } } } return nil, nil } // getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The // interface must be up, the IP must in the family requested, and the IP must be a global unicast address. func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { intf, err := nw.InterfaceByName(intfName) if err != nil { return nil, err } if isInterfaceUp(intf) { addrs, err := nw.Addrs(intf) if err != nil { return nil, err } klog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs) matchingIP, err := getMatchingGlobalIP(addrs, forFamily) if err != nil { return nil, err } if matchingIP != nil { klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName) return matchingIP, nil } } return nil, nil } // getIPFromLoopbackInterface gets the IPs on a loopback interface and returns a global unicast address, if any. // The loopback interface must be up, the IP must in the family requested, and the IP must be a global unicast address. func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { intfs, err := nw.Interfaces() if err != nil { return nil, err } for _, intf := range intfs { if !isInterfaceUp(&intf) { continue } if intf.Flags&(net.FlagLoopback) != 0 { addrs, err := nw.Addrs(&intf) if err != nil { return nil, err } klog.V(4).Infof("Interface %q has %d addresses :%v.", intf.Name, len(addrs), addrs) matchingIP, err := getMatchingGlobalIP(addrs, forFamily) if err != nil { return nil, err } if matchingIP != nil { klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intf.Name) return matchingIP, nil } } } return nil, nil } // memberOf tells if the IP is of the desired family. Used for checking interface addresses. func memberOf(ip net.IP, family AddressFamily) bool { if ip.To4() != nil { return family == familyIPv4 } else { return family == familyIPv6 } } // chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that // has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP. // addressFamilies determines whether it prefers IPv4 or IPv6 func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { intfs, err := nw.Interfaces() if err != nil { return nil, err } if len(intfs) == 0 { return nil, fmt.Errorf("no interfaces found on host.") } for _, family := range addressFamilies { klog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family)) for _, intf := range intfs { if !isInterfaceUp(&intf) { klog.V(4).Infof("Skipping: down interface %q", intf.Name) continue } if isLoopbackOrPointToPoint(&intf) { klog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name) continue } addrs, err := nw.Addrs(&intf) if err != nil { return nil, err } if len(addrs) == 0 { klog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name) continue } for _, addr := range addrs { ip, _, err := netutils.ParseCIDRSloppy(addr.String()) if err != nil { return nil, fmt.Errorf("unable to parse CIDR for interface %q: %s", intf.Name, err) } if !memberOf(ip, family) { klog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name) continue } // TODO: Decide if should open up to allow IPv6 LLAs in future. if !ip.IsGlobalUnicast() { klog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name) continue } klog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name) return ip, nil } } } return nil, fmt.Errorf("no acceptable interface with global unicast address found on host") } // ChooseHostInterface is a method used fetch an IP for a daemon. // If there is no routing info file, it will choose a global IP from the system // interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the // IP of the interface with a gateway on it (with priority given to IPv4). For a node // with no internet connection, it returns error. func ChooseHostInterface() (net.IP, error) { return chooseHostInterface(preferIPv4) } func chooseHostInterface(addressFamilies AddressFamilyPreference) (net.IP, error) { var nw networkInterfacer = networkInterface{} if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) { return chooseIPFromHostInterfaces(nw, addressFamilies) } routes, err := getAllDefaultRoutes() if err != nil { return nil, err } return chooseHostInterfaceFromRoute(routes, nw, addressFamilies) } // networkInterfacer defines an interface for several net library functions. Production // code will forward to net library functions, and unit tests will override the methods // for testing purposes. type networkInterfacer interface { InterfaceByName(intfName string) (*net.Interface, error) Addrs(intf *net.Interface) ([]net.Addr, error) Interfaces() ([]net.Interface, error) } // networkInterface implements the networkInterfacer interface for production code, just // wrapping the underlying net library function calls. type networkInterface struct{} func (_ networkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return net.InterfaceByName(intfName) } func (_ networkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { return intf.Addrs() } func (_ networkInterface) Interfaces() ([]net.Interface, error) { return net.Interfaces() } // getAllDefaultRoutes obtains IPv4 and IPv6 default routes on the node. If unable // to read the IPv4 routing info file, we return an error. If unable to read the IPv6 // routing info file (which is optional), we'll just use the IPv4 route information. // Using all the routing info, if no default routes are found, an error is returned. func getAllDefaultRoutes() ([]Route, error) { routes, err := v4File.extract() if err != nil { return nil, err } v6Routes, _ := v6File.extract() routes = append(routes, v6Routes...) if len(routes) == 0 { return nil, noRoutesError{ message: fmt.Sprintf("no default routes found in %q or %q", v4File.name, v6File.name), } } return routes, nil } // chooseHostInterfaceFromRoute cycles through each default route provided, looking for a // global IP address from the interface for the route. If there are routes but no global // address is obtained from the interfaces, it checks if the loopback interface has a global address. // addressFamilies determines whether it prefers IPv4 or IPv6 func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { for _, family := range addressFamilies { klog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family)) for _, route := range routes { if route.Family != family { continue } klog.V(4).Infof("Default route transits interface %q", route.Interface) finalIP, err := getIPFromInterface(route.Interface, family, nw) if err != nil { return nil, err } if finalIP != nil { klog.V(4).Infof("Found active IP %v ", finalIP) return finalIP, nil } // In case of network setups where default routes are present, but network // interfaces use only link-local addresses (e.g. as described in RFC5549). // the global IP is assigned to the loopback interface, and we should use it loopbackIP, err := getIPFromLoopbackInterface(family, nw) if err != nil { return nil, err } if loopbackIP != nil { klog.V(4).Infof("Found active IP %v on Loopback interface", loopbackIP) return loopbackIP, nil } } } klog.V(4).Infof("No active IP found by looking at default routes") return nil, fmt.Errorf("unable to select an IP from default routes.") } // ResolveBindAddress returns the IP address of a daemon, based on the given bindAddress: // If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface(). // If bindAddress is unspecified or loopback, it returns the default IP of the same // address family as bindAddress. // Otherwise, it just returns bindAddress. func ResolveBindAddress(bindAddress net.IP) (net.IP, error) { addressFamilies := preferIPv4 if bindAddress != nil && memberOf(bindAddress, familyIPv6) { addressFamilies = preferIPv6 } if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() { hostIP, err := chooseHostInterface(addressFamilies) if err != nil { return nil, err } bindAddress = hostIP } return bindAddress, nil } // ChooseBindAddressForInterface choose a global IP for a specific interface, with priority given to IPv4. // This is required in case of network setups where default routes are present, but network // interfaces use only link-local addresses (e.g. as described in RFC5549). // e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface. func ChooseBindAddressForInterface(intfName string) (net.IP, error) { var nw networkInterfacer = networkInterface{} for _, family := range preferIPv4 { ip, err := getIPFromInterface(intfName, family, nw) if err != nil { return nil, err } if ip != nil { return ip, nil } } return nil, fmt.Errorf("unable to select an IP from %s network interface", intfName) } golang-k8s-apimachinery-0.29.0/pkg/util/net/interface_test.go000066400000000000000000001022501453143165200241010ustar00rootroot00000000000000/* 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 net import ( "fmt" "net" "os" "strings" "testing" netutils "k8s.io/utils/net" ) const gatewayfirst = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const gatewaylast = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 ` const gatewaymiddle = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const noInternetConnection = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const nothing = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT ` const badDestination = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0AA1 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const badGateway = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0AA1 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const route_Invalidhex = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0AA 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const v6gatewayfirst = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3 20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo ` const v6gatewaylast = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo 00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3 ` const v6gatewaymiddle = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3 00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo ` const v6noDefaultRoutes = `00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo 20010001000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00000001 docker0 20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 fe800000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 ` const v6nothing = `` const v6badDestination = `2001000200000000 7a 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo ` const v6badGateway = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 200100010000000000000000000000000012 00000064 00000000 00000000 00000003 eth3 ` const v6route_Invalidhex = `000000000000000000000000000000000 00 00000000000000000000000000000000 00 fe80000000000000021fcafffea0ec00 00000064 00000000 00000000 00000003 enp1s0f0 ` const ( flagUp = net.FlagUp | net.FlagBroadcast | net.FlagMulticast flagDown = net.FlagBroadcast | net.FlagMulticast flagLoopback = net.FlagUp | net.FlagLoopback flagP2P = net.FlagUp | net.FlagPointToPoint ) func makeIntf(index int, name string, flags net.Flags) net.Interface { mac := net.HardwareAddr{0, 0x32, 0x7d, 0x69, 0xf7, byte(0x30 + index)} return net.Interface{ Index: index, MTU: 1500, Name: name, HardwareAddr: mac, Flags: flags} } var ( downIntf = makeIntf(1, "eth3", flagDown) loopbackIntf = makeIntf(1, "lo", flagLoopback) p2pIntf = makeIntf(1, "lo", flagP2P) upIntf = makeIntf(1, "eth3", flagUp) ) var ( ipv4Route = Route{Interface: "eth3", Destination: netutils.ParseIPSloppy("0.0.0.0"), Gateway: netutils.ParseIPSloppy("10.254.0.1"), Family: familyIPv4} ipv6Route = Route{Interface: "eth3", Destination: netutils.ParseIPSloppy("::"), Gateway: netutils.ParseIPSloppy("2001:1::1"), Family: familyIPv6} ) var ( noRoutes = []Route{} routeV4 = []Route{ipv4Route} routeV6 = []Route{ipv6Route} bothRoutes = []Route{ipv4Route, ipv6Route} ) func TestGetIPv4Routes(t *testing.T) { testCases := []struct { tcase string route string count int expected *Route errStrFrag string }{ {"gatewayfirst", gatewayfirst, 1, &ipv4Route, ""}, {"gatewaymiddle", gatewaymiddle, 1, &ipv4Route, ""}, {"gatewaylast", gatewaylast, 1, &ipv4Route, ""}, {"no routes", nothing, 0, nil, ""}, {"badDestination", badDestination, 0, nil, "invalid IPv4"}, {"badGateway", badGateway, 0, nil, "invalid IPv4"}, {"route_Invalidhex", route_Invalidhex, 0, nil, "odd length hex string"}, {"no default routes", noInternetConnection, 0, nil, ""}, } for _, tc := range testCases { r := strings.NewReader(tc.route) routes, err := getIPv4DefaultRoutes(r) if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) } } else if tc.errStrFrag != "" { t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) } else { if tc.count != len(routes) { t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) } else if tc.count == 1 { if !tc.expected.Gateway.Equal(routes[0].Gateway) { t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err) } if !routes[0].Destination.Equal(net.IPv4zero) { t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase) } } } } } func TestGetIPv6Routes(t *testing.T) { testCases := []struct { tcase string route string count int expected *Route errStrFrag string }{ {"v6 gatewayfirst", v6gatewayfirst, 1, &ipv6Route, ""}, {"v6 gatewaymiddle", v6gatewaymiddle, 1, &ipv6Route, ""}, {"v6 gatewaylast", v6gatewaylast, 1, &ipv6Route, ""}, {"v6 no routes", v6nothing, 0, nil, ""}, {"v6 badDestination", v6badDestination, 0, nil, "invalid IPv6"}, {"v6 badGateway", v6badGateway, 0, nil, "invalid IPv6"}, {"v6 route_Invalidhex", v6route_Invalidhex, 0, nil, "odd length hex string"}, {"v6 no default routes", v6noDefaultRoutes, 0, nil, ""}, } for _, tc := range testCases { r := strings.NewReader(tc.route) routes, err := getIPv6DefaultRoutes(r) if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) } } else if tc.errStrFrag != "" { t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) } else { if tc.count != len(routes) { t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) } else if tc.count == 1 { if !tc.expected.Gateway.Equal(routes[0].Gateway) { t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err) } if !routes[0].Destination.Equal(net.IPv6zero) { t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase) } } } } } func TestParseIP(t *testing.T) { testCases := []struct { tcase string ip string family AddressFamily success bool expected net.IP }{ {"empty", "", familyIPv4, false, nil}, {"too short", "AA", familyIPv4, false, nil}, {"too long", "0011223344", familyIPv4, false, nil}, {"invalid", "invalid!", familyIPv4, false, nil}, {"zero", "00000000", familyIPv4, true, net.IP{0, 0, 0, 0}}, {"ffff", "FFFFFFFF", familyIPv4, true, net.IP{0xff, 0xff, 0xff, 0xff}}, {"valid v4", "12345678", familyIPv4, true, net.IP{120, 86, 52, 18}}, {"valid v6", "fe800000000000000000000000000000", familyIPv6, true, net.IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {"v6 too short", "fe80000000000000021fcafffea0ec0", familyIPv6, false, nil}, {"v6 too long", "fe80000000000000021fcafffea0ec002", familyIPv6, false, nil}, } for _, tc := range testCases { ip, err := parseIP(tc.ip, tc.family) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %q, got %q . err : %v", tc.tcase, tc.expected, ip, err) } } } func TestIsInterfaceUp(t *testing.T) { testCases := []struct { tcase string intf *net.Interface expected bool }{ {"up", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}, true}, {"down", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0}, false}, {"no interface", nil, false}, } for _, tc := range testCases { it := isInterfaceUp(tc.intf) if it != tc.expected { t.Errorf("case[%v]: expected %v, got %v .", tc.tcase, tc.expected, it) } } } type addrStruct struct{ val string } func (a addrStruct) Network() string { return a.val } func (a addrStruct) String() string { return a.val } func TestFinalIP(t *testing.T) { testCases := []struct { tcase string addr []net.Addr family AddressFamily expected net.IP }{ {"no ipv4", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv4, nil}, {"no ipv6", []net.Addr{addrStruct{val: "10.128.0.4/32"}}, familyIPv6, nil}, {"invalidV4CIDR", []net.Addr{addrStruct{val: "10.20.30.40.50/24"}}, familyIPv4, nil}, {"invalidV6CIDR", []net.Addr{addrStruct{val: "fe80::2f7:67fff:fe6e:2956/64"}}, familyIPv6, nil}, {"loopback", []net.Addr{addrStruct{val: "127.0.0.1/24"}}, familyIPv4, nil}, {"loopbackv6", []net.Addr{addrStruct{val: "::1/128"}}, familyIPv6, nil}, {"link local v4", []net.Addr{addrStruct{val: "169.254.1.10/16"}}, familyIPv4, nil}, {"link local v6", []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}, familyIPv6, nil}, {"ip4", []net.Addr{addrStruct{val: "10.254.12.132/17"}}, familyIPv4, netutils.ParseIPSloppy("10.254.12.132")}, {"ip6", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv6, netutils.ParseIPSloppy("2001::5")}, {"no addresses", []net.Addr{}, familyIPv4, nil}, } for _, tc := range testCases { ip, err := getMatchingGlobalIP(tc.addr, tc.family) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, ip, err) } } } func TestAddrs(t *testing.T) { var nw networkInterfacer = validNetworkInterface{} intf := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0} addrs, err := nw.Addrs(&intf) if err != nil { t.Errorf("expected no error got : %v", err) } if len(addrs) != 2 { t.Errorf("expected addrs: 2 got null") } } // Has a valid IPv4 address (IPv6 is LLA) type validNetworkInterface struct { } func (_ validNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ validNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{ addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}} return ifat, nil } func (_ validNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } // Both IPv4 and IPv6 addresses (expecting IPv4 to be used) type v4v6NetworkInterface struct { } func (_ v4v6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ v4v6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{ addrStruct{val: "2001::10/64"}, addrStruct{val: "10.254.71.145/17"}} return ifat, nil } func (_ v4v6NetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } // Interface with only IPv6 address type ipv6NetworkInterface struct { } func (_ ipv6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ ipv6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{addrStruct{val: "2001::200/64"}} return ifat, nil } func (_ ipv6NetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } // Only with link local addresses type networkInterfaceWithOnlyLinkLocals struct { } func (_ networkInterfaceWithOnlyLinkLocals) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ networkInterfaceWithOnlyLinkLocals) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}} return ifat, nil } func (_ networkInterfaceWithOnlyLinkLocals) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } // Unable to get interface(s) type failGettingNetworkInterface struct { } func (_ failGettingNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return nil, fmt.Errorf("unable get Interface") } func (_ failGettingNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { return nil, nil } func (_ failGettingNetworkInterface) Interfaces() ([]net.Interface, error) { return nil, fmt.Errorf("mock failed getting all interfaces") } // No interfaces type noNetworkInterface struct { } func (_ noNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return nil, fmt.Errorf("no such network interface") } func (_ noNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { return nil, nil } func (_ noNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{}, nil } // Interface is down type downNetworkInterface struct { } func (_ downNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return &downIntf, nil } func (_ downNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{ addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}} return ifat, nil } func (_ downNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{downIntf}, nil } // Loopback interface type loopbackNetworkInterface struct { } func (_ loopbackNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return &loopbackIntf, nil } func (_ loopbackNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{ addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}} return ifat, nil } func (_ loopbackNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{loopbackIntf}, nil } // Point to point interface type p2pNetworkInterface struct { } func (_ p2pNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { return &p2pIntf, nil } func (_ p2pNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{ addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}} return ifat, nil } func (_ p2pNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{p2pIntf}, nil } // Interface with link locals and loopback interface with global addresses type linkLocalLoopbackNetworkInterface struct { } func (_ linkLocalLoopbackNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { if intfName == LoopbackInterfaceName { return &loopbackIntf, nil } return &upIntf, nil } func (_ linkLocalLoopbackNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}} if intf.Name == LoopbackInterfaceName { ifat = []net.Addr{addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}, // global addresses on loopback interface addrStruct{val: "10.1.1.1/32"}, addrStruct{val: "fd00:1:1::1/128"}} } return ifat, nil } func (_ linkLocalLoopbackNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf, loopbackIntf}, nil } // Interface and loopback interface with global addresses type globalsNetworkInterface struct { } func (_ globalsNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { if intfName == LoopbackInterfaceName { return &loopbackIntf, nil } return &upIntf, nil } func (_ globalsNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}, addrStruct{val: "192.168.1.1/31"}, addrStruct{val: "fd00::200/127"}} if intf.Name == LoopbackInterfaceName { ifat = []net.Addr{addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}, // global addresses on loopback interface addrStruct{val: "10.1.1.1/32"}, addrStruct{val: "fd00:1:1::1/128"}} } return ifat, nil } func (_ globalsNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf, loopbackIntf}, nil } // Unable to get IP addresses for interface type networkInterfaceFailGetAddrs struct { } func (_ networkInterfaceFailGetAddrs) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ networkInterfaceFailGetAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) { return nil, fmt.Errorf("unable to get Addrs") } func (_ networkInterfaceFailGetAddrs) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } // No addresses for interface type networkInterfaceWithNoAddrs struct { } func (_ networkInterfaceWithNoAddrs) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ networkInterfaceWithNoAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) { ifat := []net.Addr{} return ifat, nil } func (_ networkInterfaceWithNoAddrs) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } // Invalid addresses for interface type networkInterfaceWithInvalidAddr struct { } func (_ networkInterfaceWithInvalidAddr) InterfaceByName(intfName string) (*net.Interface, error) { return &upIntf, nil } func (_ networkInterfaceWithInvalidAddr) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{addrStruct{val: "10.20.30.40.50/24"}} return ifat, nil } func (_ networkInterfaceWithInvalidAddr) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } func TestGetIPFromInterface(t *testing.T) { testCases := []struct { tcase string nwname string family AddressFamily nw networkInterfacer expected net.IP errStrFrag string }{ {"ipv4", "eth3", familyIPv4, validNetworkInterface{}, netutils.ParseIPSloppy("10.254.71.145"), ""}, {"ipv6", "eth3", familyIPv6, ipv6NetworkInterface{}, netutils.ParseIPSloppy("2001::200"), ""}, {"no ipv4", "eth3", familyIPv4, ipv6NetworkInterface{}, nil, ""}, {"no ipv6", "eth3", familyIPv6, validNetworkInterface{}, nil, ""}, {"I/F down", "eth3", familyIPv4, downNetworkInterface{}, nil, ""}, {"I/F get fail", "eth3", familyIPv4, noNetworkInterface{}, nil, "no such network interface"}, {"fail get addr", "eth3", familyIPv4, networkInterfaceFailGetAddrs{}, nil, "unable to get Addrs"}, {"bad addr", "eth3", familyIPv4, networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"}, } for _, tc := range testCases { ip, err := getIPFromInterface(tc.nwname, tc.family, tc.nw) if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) } } else if tc.errStrFrag != "" { t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) } else if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) } } } func TestGetIPFromLoopbackInterface(t *testing.T) { testCases := []struct { tcase string family AddressFamily nw networkInterfacer expected net.IP errStrFrag string }{ {"ipv4", familyIPv4, linkLocalLoopbackNetworkInterface{}, netutils.ParseIPSloppy("10.1.1.1"), ""}, {"ipv6", familyIPv6, linkLocalLoopbackNetworkInterface{}, netutils.ParseIPSloppy("fd00:1:1::1"), ""}, {"no global ipv4", familyIPv4, loopbackNetworkInterface{}, nil, ""}, {"no global ipv6", familyIPv6, loopbackNetworkInterface{}, nil, ""}, } for _, tc := range testCases { ip, err := getIPFromLoopbackInterface(tc.family, tc.nw) if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) } } else if tc.errStrFrag != "" { t.Errorf("case[%s]: Error %q expected, but seen %v", tc.tcase, tc.errStrFrag, err) } else if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) } } } func TestChooseHostInterfaceFromRoute(t *testing.T) { testCases := []struct { tcase string routes []Route nw networkInterfacer order AddressFamilyPreference expected net.IP }{ {"single-stack ipv4", routeV4, validNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145")}, {"single-stack ipv4, prefer v6", routeV4, validNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("10.254.71.145")}, {"single-stack ipv6", routeV6, ipv6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("2001::200")}, {"single-stack ipv6, prefer v6", routeV6, ipv6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::200")}, {"dual stack", bothRoutes, v4v6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145")}, {"dual stack, prefer v6", bothRoutes, v4v6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::10")}, {"LLA and loopback with global, IPv4", routeV4, linkLocalLoopbackNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.1.1.1")}, {"LLA and loopback with global, IPv6", routeV6, linkLocalLoopbackNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00:1:1::1")}, {"LLA and loopback with global, dual stack prefer IPv4", bothRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.1.1.1")}, {"LLA and loopback with global, dual stack prefer IPv6", bothRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00:1:1::1")}, {"LLA and loopback with global, no routes", noRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv6, nil}, {"interface and loopback with global, IPv4", routeV4, globalsNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("192.168.1.1")}, {"interface and loopback with global, IPv6", routeV6, globalsNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00::200")}, {"interface and loopback with global, dual stack prefer IPv4", bothRoutes, globalsNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("192.168.1.1")}, {"interface and loopback with global, dual stack prefer IPv6", bothRoutes, globalsNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00::200")}, {"interface and loopback with global, no routes", noRoutes, globalsNetworkInterface{}, preferIPv6, nil}, {"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil}, {"no routes", noRoutes, validNetworkInterface{}, preferIPv4, nil}, {"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil}, } for _, tc := range testCases { ip, err := chooseHostInterfaceFromRoute(tc.routes, tc.nw, tc.order) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) } } } func TestMemberOf(t *testing.T) { testCases := []struct { tcase string ip net.IP family AddressFamily expected bool }{ {"ipv4 is 4", netutils.ParseIPSloppy("10.20.30.40"), familyIPv4, true}, {"ipv4 is 6", netutils.ParseIPSloppy("10.10.10.10"), familyIPv6, false}, {"ipv6 is 4", netutils.ParseIPSloppy("2001::100"), familyIPv4, false}, {"ipv6 is 6", netutils.ParseIPSloppy("2001::100"), familyIPv6, true}, } for _, tc := range testCases { if memberOf(tc.ip, tc.family) != tc.expected { t.Errorf("case[%s]: expected %+v", tc.tcase, tc.expected) } } } func TestGetIPFromHostInterfaces(t *testing.T) { testCases := []struct { tcase string nw networkInterfacer order AddressFamilyPreference expected net.IP errStrFrag string }{ {"fail get I/Fs", failGettingNetworkInterface{}, preferIPv4, nil, "failed getting all interfaces"}, {"no interfaces", noNetworkInterface{}, preferIPv4, nil, "no interfaces"}, {"I/F not up", downNetworkInterface{}, preferIPv4, nil, "no acceptable"}, {"loopback only", loopbackNetworkInterface{}, preferIPv4, nil, "no acceptable"}, {"P2P I/F only", p2pNetworkInterface{}, preferIPv4, nil, "no acceptable"}, {"fail get addrs", networkInterfaceFailGetAddrs{}, preferIPv4, nil, "unable to get Addrs"}, {"no addresses", networkInterfaceWithNoAddrs{}, preferIPv4, nil, "no acceptable"}, {"invalid addr", networkInterfaceWithInvalidAddr{}, preferIPv4, nil, "invalid CIDR"}, {"no matches", networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil, "no acceptable"}, {"single-stack ipv4", validNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145"), ""}, {"single-stack ipv4, prefer ipv6", validNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("10.254.71.145"), ""}, {"single-stack ipv6", ipv6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("2001::200"), ""}, {"single-stack ipv6, prefer ipv6", ipv6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::200"), ""}, {"dual stack", v4v6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145"), ""}, {"dual stack, prefer ipv6", v4v6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::10"), ""}, } for _, tc := range testCases { ip, err := chooseIPFromHostInterfaces(tc.nw, tc.order) if !ip.Equal(tc.expected) { t.Errorf("case[%s]: expected %+v, got %+v with err : %v", tc.tcase, tc.expected, ip, err) } if err != nil && !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: unable to find %q in error string %q", tc.tcase, tc.errStrFrag, err.Error()) } } } func makeRouteFile(content string, t *testing.T) (*os.File, error) { routeFile, err := os.CreateTemp("", "route") if err != nil { return nil, err } if _, err := routeFile.Write([]byte(content)); err != nil { return routeFile, err } err = routeFile.Close() return routeFile, err } func TestFailGettingIPv4Routes(t *testing.T) { defer func() { v4File.name = ipv4RouteFile }() // Try failure to open file (should not occur, as caller ensures we have IPv4 route file, but being thorough) v4File.name = "no-such-file" errStrFrag := "no such file" _, err := v4File.extract() if err == nil { t.Errorf("Expected error trying to read non-existent v4 route file") } if !strings.Contains(err.Error(), errStrFrag) { t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error()) } } func TestFailGettingIPv6Routes(t *testing.T) { defer func() { v6File.name = ipv6RouteFile }() // Try failure to open file (this would be ignored by caller) v6File.name = "no-such-file" errStrFrag := "no such file" _, err := v6File.extract() if err == nil { t.Errorf("Expected error trying to read non-existent v6 route file") } if !strings.Contains(err.Error(), errStrFrag) { t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error()) } } func TestGetAllDefaultRoutesFailNoV4RouteFile(t *testing.T) { defer func() { v4File.name = ipv4RouteFile }() // Should not occur, as caller ensures we have IPv4 route file, but being thorough v4File.name = "no-such-file" errStrFrag := "no such file" _, err := getAllDefaultRoutes() if err == nil { t.Errorf("Expected error trying to read non-existent v4 route file") } if !strings.Contains(err.Error(), errStrFrag) { t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error()) } } func TestGetAllDefaultRoutes(t *testing.T) { testCases := []struct { tcase string v4Info string v6Info string count int expected []Route errStrFrag string }{ {"no routes", noInternetConnection, v6noDefaultRoutes, 0, nil, "no default routes"}, {"only v4 route", gatewayfirst, v6noDefaultRoutes, 1, routeV4, ""}, {"only v6 route", noInternetConnection, v6gatewayfirst, 1, routeV6, ""}, {"v4 and v6 routes", gatewayfirst, v6gatewayfirst, 2, bothRoutes, ""}, } defer func() { v4File.name = ipv4RouteFile v6File.name = ipv6RouteFile }() for _, tc := range testCases { routeFile, err := makeRouteFile(tc.v4Info, t) if routeFile != nil { defer os.Remove(routeFile.Name()) } if err != nil { t.Errorf("case[%s]: test setup failure for IPv4 route file: %v", tc.tcase, err) } v4File.name = routeFile.Name() v6routeFile, err := makeRouteFile(tc.v6Info, t) if v6routeFile != nil { defer os.Remove(v6routeFile.Name()) } if err != nil { t.Errorf("case[%s]: test setup failure for IPv6 route file: %v", tc.tcase, err) } v6File.name = v6routeFile.Name() routes, err := getAllDefaultRoutes() if err != nil { if !strings.Contains(err.Error(), tc.errStrFrag) { t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) } } else if tc.errStrFrag != "" { t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) } else { if tc.count != len(routes) { t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) } for i, expected := range tc.expected { if !expected.Gateway.Equal(routes[i].Gateway) { t.Errorf("case[%s]: at %d expected %v, got %v .err : %v", tc.tcase, i, tc.expected, routes, err) } zeroIP := net.IPv4zero if expected.Family == familyIPv6 { zeroIP = net.IPv6zero } if !routes[i].Destination.Equal(zeroIP) { t.Errorf("case[%s}: at %d destination is not for default route (not %v)", tc.tcase, i, zeroIP) } } } } } golang-k8s-apimachinery-0.29.0/pkg/util/net/port_range.go000066400000000000000000000066151453143165200232520ustar00rootroot00000000000000/* Copyright 2015 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 net import ( "fmt" "strconv" "strings" ) // PortRange represents a range of TCP/UDP ports. To represent a single port, // set Size to 1. type PortRange struct { Base int Size int } // Contains tests whether a given port falls within the PortRange. func (pr *PortRange) Contains(p int) bool { return (p >= pr.Base) && ((p - pr.Base) < pr.Size) } // String converts the PortRange to a string representation, which can be // parsed by PortRange.Set or ParsePortRange. func (pr PortRange) String() string { if pr.Size == 0 { return "" } return fmt.Sprintf("%d-%d", pr.Base, pr.Base+pr.Size-1) } // Set parses a string of the form "value", "min-max", or "min+offset", inclusive at both ends, and // sets the PortRange from it. This is part of the flag.Value and pflag.Value // interfaces. func (pr *PortRange) Set(value string) error { const ( SinglePortNotation = 1 << iota HyphenNotation PlusNotation ) value = strings.TrimSpace(value) hyphenIndex := strings.Index(value, "-") plusIndex := strings.Index(value, "+") if value == "" { pr.Base = 0 pr.Size = 0 return nil } var err error var low, high int var notation int if plusIndex == -1 && hyphenIndex == -1 { notation |= SinglePortNotation } if hyphenIndex != -1 { notation |= HyphenNotation } if plusIndex != -1 { notation |= PlusNotation } switch notation { case SinglePortNotation: var port int port, err = strconv.Atoi(value) if err != nil { return err } low = port high = port case HyphenNotation: low, err = strconv.Atoi(value[:hyphenIndex]) if err != nil { return err } high, err = strconv.Atoi(value[hyphenIndex+1:]) if err != nil { return err } case PlusNotation: var offset int low, err = strconv.Atoi(value[:plusIndex]) if err != nil { return err } offset, err = strconv.Atoi(value[plusIndex+1:]) if err != nil { return err } high = low + offset default: return fmt.Errorf("unable to parse port range: %s", value) } if low > 65535 || high > 65535 { return fmt.Errorf("the port range cannot be greater than 65535: %s", value) } if high < low { return fmt.Errorf("end port cannot be less than start port: %s", value) } pr.Base = low pr.Size = 1 + high - low return nil } // Type returns a descriptive string about this type. This is part of the // pflag.Value interface. func (*PortRange) Type() string { return "portRange" } // ParsePortRange parses a string of the form "min-max", inclusive at both // ends, and initializes a new PortRange from it. func ParsePortRange(value string) (*PortRange, error) { pr := &PortRange{} err := pr.Set(value) if err != nil { return nil, err } return pr, nil } func ParsePortRangeOrDie(value string) *PortRange { pr, err := ParsePortRange(value) if err != nil { panic(fmt.Sprintf("couldn't parse port range %q: %v", value, err)) } return pr } golang-k8s-apimachinery-0.29.0/pkg/util/net/port_range_test.go000066400000000000000000000041521453143165200243030ustar00rootroot00000000000000/* Copyright 2015 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 net import ( "testing" flag "github.com/spf13/pflag" ) func TestPortRange(t *testing.T) { testCases := []struct { input string success bool expected string included int excluded int }{ {"100-200", true, "100-200", 200, 201}, {" 100-200 ", true, "100-200", 200, 201}, {"0-0", true, "0-0", 0, 1}, {"", true, "", -1, 0}, {"100", true, "100-100", 100, 101}, {"100 - 200", false, "", -1, -1}, {"-100", false, "", -1, -1}, {"100-", false, "", -1, -1}, {"200-100", false, "", -1, -1}, {"60000-70000", false, "", -1, -1}, {"70000-80000", false, "", -1, -1}, {"70000+80000", false, "", -1, -1}, {"1+0", true, "1-1", 1, 2}, {"0+0", true, "0-0", 0, 1}, {"1+-1", false, "", -1, -1}, {"1-+1", false, "", -1, -1}, {"100+200", true, "100-300", 300, 301}, {"1+65535", false, "", -1, -1}, {"0+65535", true, "0-65535", 65535, 65536}, } for i := range testCases { tc := &testCases[i] pr := &PortRange{} var f flag.Value = pr err := f.Set(tc.input) if err != nil && tc.success { t.Errorf("expected success, got %q", err) continue } else if err == nil && !tc.success { t.Errorf("expected failure %#v", testCases[i]) continue } else if tc.success { if f.String() != tc.expected { t.Errorf("expected %q, got %q", tc.expected, f.String()) } if tc.included >= 0 && !pr.Contains(tc.included) { t.Errorf("expected %q to include %d", f.String(), tc.included) } if tc.excluded >= 0 && pr.Contains(tc.excluded) { t.Errorf("expected %q to exclude %d", f.String(), tc.excluded) } } } } golang-k8s-apimachinery-0.29.0/pkg/util/net/port_split.go000066400000000000000000000042751453143165200233110ustar00rootroot00000000000000/* Copyright 2015 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 net import ( "strings" "k8s.io/apimachinery/pkg/util/sets" ) var validSchemes = sets.NewString("http", "https", "") // SplitSchemeNamePort takes a string of the following forms: // - "", returns "", "","", true // - ":", returns "", "","",true // - "::", returns "","","",true // // Name must be non-empty or valid will be returned false. // Scheme must be "http" or "https" if specified // Port is returned as a string, and it is not required to be numeric (could be // used for a named port, for example). func SplitSchemeNamePort(id string) (scheme, name, port string, valid bool) { parts := strings.Split(id, ":") switch len(parts) { case 1: name = parts[0] case 2: name = parts[0] port = parts[1] case 3: scheme = parts[0] name = parts[1] port = parts[2] default: return "", "", "", false } if len(name) > 0 && validSchemes.Has(scheme) { return scheme, name, port, true } else { return "", "", "", false } } // JoinSchemeNamePort returns a string that specifies the scheme, name, and port: // - "" // - ":" // - "::" // // None of the parameters may contain a ':' character // Name is required // Scheme must be "", "http", or "https" func JoinSchemeNamePort(scheme, name, port string) string { if len(scheme) > 0 { // Must include three segments to specify scheme return scheme + ":" + name + ":" + port } if len(port) > 0 { // Must include two segments to specify port return name + ":" + port } // Return name alone return name } golang-k8s-apimachinery-0.29.0/pkg/util/net/port_split_test.go000066400000000000000000000056221453143165200243450ustar00rootroot00000000000000/* Copyright 2015 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 net import ( "testing" ) func TestSplitSchemeNamePort(t *testing.T) { table := []struct { in string name, port, scheme string valid bool normalized bool }{ { in: "aoeu:asdf", name: "aoeu", port: "asdf", valid: true, normalized: true, }, { in: "http:aoeu:asdf", scheme: "http", name: "aoeu", port: "asdf", valid: true, normalized: true, }, { in: "https:aoeu:", scheme: "https", name: "aoeu", port: "", valid: true, normalized: false, }, { in: "https:aoeu:asdf", scheme: "https", name: "aoeu", port: "asdf", valid: true, normalized: true, }, { in: "aoeu:", name: "aoeu", valid: true, normalized: false, }, { in: "aoeu", name: "aoeu", valid: true, normalized: true, }, { in: ":asdf", valid: false, }, { in: "aoeu:asdf:htns", valid: false, }, { in: "http::asdf", valid: false, }, { in: "http::", valid: false, }, { in: "", valid: false, }, } for _, item := range table { scheme, name, port, valid := SplitSchemeNamePort(item.in) if e, a := item.scheme, scheme; e != a { t.Errorf("%q: Wanted %q, got %q", item.in, e, a) } if e, a := item.name, name; e != a { t.Errorf("%q: Wanted %q, got %q", item.in, e, a) } if e, a := item.port, port; e != a { t.Errorf("%q: Wanted %q, got %q", item.in, e, a) } if e, a := item.valid, valid; e != a { t.Errorf("%q: Wanted %t, got %t", item.in, e, a) } // Make sure valid items round trip through JoinSchemeNamePort if item.valid { out := JoinSchemeNamePort(scheme, name, port) if item.normalized && out != item.in { t.Errorf("%q: Wanted %s, got %s", item.in, item.in, out) } scheme, name, port, valid := SplitSchemeNamePort(out) if e, a := item.scheme, scheme; e != a { t.Errorf("%q: Wanted %q, got %q", item.in, e, a) } if e, a := item.name, name; e != a { t.Errorf("%q: Wanted %q, got %q", item.in, e, a) } if e, a := item.port, port; e != a { t.Errorf("%q: Wanted %q, got %q", item.in, e, a) } if e, a := item.valid, valid; e != a { t.Errorf("%q: Wanted %t, got %t", item.in, e, a) } } } } golang-k8s-apimachinery-0.29.0/pkg/util/net/testing/000077500000000000000000000000001453143165200222305ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/net/testing/http.go000066400000000000000000000075021453143165200235420ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package nettesting contains utilities for testing networking functionality. // Don't use these utilities in production code. They have not been security // reviewed. package nettesting import ( "io" "net" "net/http" "net/http/httputil" "sync" "testing" "github.com/onsi/ginkgo/v2" ) type TB interface { Logf(format string, args ...any) } // NewHTTPProxyHandler returns a new HTTPProxyHandler. It accepts an optional // hook which is called early in the handler to export request state. If the // hook returns false, the handler returns immediately with a server error. func NewHTTPProxyHandler(t TB, hook func(req *http.Request) bool) *HTTPProxyHandler { // Ensure that this is only used in tests. This code has not been security // reviewed. switch t.(type) { case testing.TB, ginkgo.GinkgoTInterface: default: panic("t is not a known test interface") } h := &HTTPProxyHandler{ hook: hook, httpProxy: httputil.ReverseProxy{ Director: func(req *http.Request) { req.URL.Scheme = "http" req.URL.Host = req.Host }, }, t: t, } return h } // HTTPProxyHandler implements a simple handler for http_proxy and https_proxy // requests for use in testing. type HTTPProxyHandler struct { handlerDone sync.WaitGroup hook func(r *http.Request) bool // httpProxy is the reverse proxy we use for standard http proxy requests. httpProxy httputil.ReverseProxy t TB } // ServeHTTP handles an HTTP proxy request. func (h *HTTPProxyHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { h.handlerDone.Add(1) defer h.handlerDone.Done() if h.hook != nil { if ok := h.hook(req); !ok { rw.WriteHeader(http.StatusInternalServerError) return } } b, err := httputil.DumpRequest(req, false) if err != nil { h.t.Logf("Failed to dump request, host=%s: %v", req.Host, err) } else { h.t.Logf("Proxy Request: %s", string(b)) } if req.Method != http.MethodConnect { h.httpProxy.ServeHTTP(rw, req) return } // CONNECT proxy sconn, err := net.Dial("tcp", req.Host) if err != nil { h.t.Logf("Failed to dial proxy backend, host=%s: %v", req.Host, err) rw.WriteHeader(http.StatusInternalServerError) return } defer sconn.Close() hj, ok := rw.(http.Hijacker) if !ok { h.t.Logf("Can't switch protocols using non-Hijacker ResponseWriter: type=%T, host=%s", rw, req.Host) rw.WriteHeader(http.StatusInternalServerError) return } rw.WriteHeader(http.StatusOK) conn, brw, err := hj.Hijack() if err != nil { h.t.Logf("Failed to hijack client connection, host=%s: %v", req.Host, err) return } defer conn.Close() if err := brw.Flush(); err != nil { h.t.Logf("Failed to flush pending writes to client, host=%s: %v", req.Host, err) return } if _, err := io.Copy(sconn, io.LimitReader(brw, int64(brw.Reader.Buffered()))); err != nil { h.t.Logf("Failed to flush buffered reads to server, host=%s: %v", req.Host, err) return } var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() defer h.t.Logf("Server read close, host=%s", req.Host) io.Copy(conn, sconn) }() go func() { defer wg.Done() defer h.t.Logf("Server write close, host=%s", req.Host) io.Copy(sconn, conn) }() wg.Wait() h.t.Logf("Done handling CONNECT request, host=%s", req.Host) } func (h *HTTPProxyHandler) Wait() { h.handlerDone.Wait() } golang-k8s-apimachinery-0.29.0/pkg/util/net/util.go000066400000000000000000000033061453143165200220610ustar00rootroot00000000000000/* Copyright 2016 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 net import ( "errors" "net" "reflect" "strings" "syscall" ) // IPNetEqual checks if the two input IPNets are representing the same subnet. // For example, // // 10.0.0.1/24 and 10.0.0.0/24 are the same subnet. // 10.0.0.1/24 and 10.0.0.0/25 are not the same subnet. func IPNetEqual(ipnet1, ipnet2 *net.IPNet) bool { if ipnet1 == nil || ipnet2 == nil { return false } if reflect.DeepEqual(ipnet1.Mask, ipnet2.Mask) && ipnet1.Contains(ipnet2.IP) && ipnet2.Contains(ipnet1.IP) { return true } return false } // Returns if the given err is "connection reset by peer" error. func IsConnectionReset(err error) bool { var errno syscall.Errno if errors.As(err, &errno) { return errno == syscall.ECONNRESET } return false } // Returns if the given err is "http2: client connection lost" error. func IsHTTP2ConnectionLost(err error) bool { return err != nil && strings.Contains(err.Error(), "http2: client connection lost") } // Returns if the given err is "connection refused" error func IsConnectionRefused(err error) bool { var errno syscall.Errno if errors.As(err, &errno) { return errno == syscall.ECONNREFUSED } return false } golang-k8s-apimachinery-0.29.0/pkg/util/net/util_test.go000066400000000000000000000105341453143165200231210ustar00rootroot00000000000000/* Copyright 2016 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 net import ( "fmt" "io" "net" "net/http" "net/http/httptest" "net/url" "os" "sync/atomic" "syscall" "testing" "time" "golang.org/x/net/http2" netutils "k8s.io/utils/net" ) func getIPNet(cidr string) *net.IPNet { _, ipnet, _ := netutils.ParseCIDRSloppy(cidr) return ipnet } func TestIPNetEqual(t *testing.T) { testCases := []struct { ipnet1 *net.IPNet ipnet2 *net.IPNet expect bool }{ // null case { getIPNet("10.0.0.1/24"), getIPNet(""), false, }, { getIPNet("10.0.0.0/24"), getIPNet("10.0.0.0/24"), true, }, { getIPNet("10.0.0.0/24"), getIPNet("10.0.0.1/24"), true, }, { getIPNet("10.0.0.0/25"), getIPNet("10.0.0.0/24"), false, }, { getIPNet("10.0.1.0/24"), getIPNet("10.0.0.0/24"), false, }, } for _, tc := range testCases { if tc.expect != IPNetEqual(tc.ipnet1, tc.ipnet2) { t.Errorf("Expect equality of %s and %s be to %v", tc.ipnet1.String(), tc.ipnet2.String(), tc.expect) } } } func TestIsConnectionRefused(t *testing.T) { testCases := []struct { err error expect bool }{ { &url.Error{Err: &net.OpError{Err: syscall.ECONNRESET}}, false, }, { &url.Error{Err: &net.OpError{Err: syscall.ECONNREFUSED}}, true, }, {&url.Error{Err: &net.OpError{Err: &os.SyscallError{Err: syscall.ECONNREFUSED}}}, true, }, } for _, tc := range testCases { if result := IsConnectionRefused(tc.err); result != tc.expect { t.Errorf("Expect to be %v, but actual is %v", tc.expect, result) } } } type tcpLB struct { t *testing.T ln net.Listener serverURL string dials int32 } func (lb *tcpLB) handleConnection(in net.Conn, stopCh chan struct{}) { out, err := net.Dial("tcp", lb.serverURL) if err != nil { lb.t.Log(err) return } go io.Copy(out, in) go io.Copy(in, out) <-stopCh if err := out.Close(); err != nil { lb.t.Fatalf("failed to close connection: %v", err) } } func (lb *tcpLB) serve(stopCh chan struct{}) { conn, err := lb.ln.Accept() if err != nil { lb.t.Fatalf("failed to accept: %v", err) } atomic.AddInt32(&lb.dials, 1) go lb.handleConnection(conn, stopCh) } func newLB(t *testing.T, serverURL string) *tcpLB { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("failed to bind: %v", err) } lb := tcpLB{ serverURL: serverURL, ln: ln, t: t, } return &lb } func TestIsConnectionReset(t *testing.T) { ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s", r.Proto) })) ts.EnableHTTP2 = true ts.StartTLS() defer ts.Close() u, err := url.Parse(ts.URL) if err != nil { t.Fatalf("failed to parse URL from %q: %v", ts.URL, err) } lb := newLB(t, u.Host) defer lb.ln.Close() stopCh := make(chan struct{}) go lb.serve(stopCh) c := ts.Client() transport, ok := ts.Client().Transport.(*http.Transport) if !ok { t.Fatalf("failed to assert *http.Transport") } t2, err := http2.ConfigureTransports(transport) if err != nil { t.Fatalf("failed to configure *http.Transport: %+v", err) } t2.ReadIdleTimeout = time.Second t2.PingTimeout = time.Second // Create an HTTP2 connection to reuse later resp, err := c.Get("https://" + lb.ln.Addr().String()) if err != nil { t.Fatalf("unexpected error: %+v", err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("unexpected error: %+v", err) } if string(data) != "Hello, HTTP/2.0" { t.Fatalf("unexpected response: %s", data) } // Deliberately let the LB stop proxying traffic for the current // connection. This mimics a broken TCP connection that's not properly // closed. close(stopCh) _, err = c.Get("https://" + lb.ln.Addr().String()) if !IsHTTP2ConnectionLost(err) { t.Fatalf("expected HTTP2ConnectionLost error, got %v", err) } } golang-k8s-apimachinery-0.29.0/pkg/util/proxy/000077500000000000000000000000001453143165200211465ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/proxy/dial.go000066400000000000000000000075321453143165200224150ustar00rootroot00000000000000/* Copyright 2015 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 proxy import ( "context" "crypto/tls" "fmt" "net" "net/http" "net/url" utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/third_party/forked/golang/netutil" "k8s.io/klog/v2" ) // DialURL will dial the specified URL using the underlying dialer held by the passed // RoundTripper. The primary use of this method is to support proxying upgradable connections. // For this reason this method will prefer to negotiate http/1.1 if the URL scheme is https. // If you wish to ensure ALPN negotiates http2 then set NextProto=[]string{"http2"} in the // TLSConfig of the http.Transport func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (net.Conn, error) { dialAddr := netutil.CanonicalAddr(url) dialer, err := utilnet.DialerFor(transport) if err != nil { klog.V(5).Infof("Unable to unwrap transport %T to get dialer: %v", transport, err) } switch url.Scheme { case "http": if dialer != nil { return dialer(ctx, "tcp", dialAddr) } var d net.Dialer return d.DialContext(ctx, "tcp", dialAddr) case "https": // Get the tls config from the transport if we recognize it tlsConfig, err := utilnet.TLSClientConfig(transport) if err != nil { klog.V(5).Infof("Unable to unwrap transport %T to get at TLS config: %v", transport, err) } if dialer != nil { // We have a dialer; use it to open the connection, then // create a tls client using the connection. netConn, err := dialer(ctx, "tcp", dialAddr) if err != nil { return nil, err } if tlsConfig == nil { // tls.Client requires non-nil config klog.Warning("using custom dialer with no TLSClientConfig. Defaulting to InsecureSkipVerify") // tls.Handshake() requires ServerName or InsecureSkipVerify tlsConfig = &tls.Config{ InsecureSkipVerify: true, } } else if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify { // tls.HandshakeContext() requires ServerName or InsecureSkipVerify // infer the ServerName from the hostname we're connecting to. inferredHost := dialAddr if host, _, err := net.SplitHostPort(dialAddr); err == nil { inferredHost = host } // Make a copy to avoid polluting the provided config tlsConfigCopy := tlsConfig.Clone() tlsConfigCopy.ServerName = inferredHost tlsConfig = tlsConfigCopy } // Since this method is primarily used within a "Connection: Upgrade" call we assume the caller is // going to write HTTP/1.1 request to the wire. http2 should not be allowed in the TLSConfig.NextProtos, // so we explicitly set that here. We only do this check if the TLSConfig support http/1.1. if supportsHTTP11(tlsConfig.NextProtos) { tlsConfig = tlsConfig.Clone() tlsConfig.NextProtos = []string{"http/1.1"} } tlsConn := tls.Client(netConn, tlsConfig) if err := tlsConn.HandshakeContext(ctx); err != nil { netConn.Close() return nil, err } return tlsConn, nil } else { // Dial. tlsDialer := tls.Dialer{ Config: tlsConfig, } return tlsDialer.DialContext(ctx, "tcp", dialAddr) } default: return nil, fmt.Errorf("unknown scheme: %s", url.Scheme) } } func supportsHTTP11(nextProtos []string) bool { if len(nextProtos) == 0 { return true } for _, proto := range nextProtos { if proto == "http/1.1" { return true } } return false } golang-k8s-apimachinery-0.29.0/pkg/util/proxy/dial_test.go000066400000000000000000000206251453143165200234520ustar00rootroot00000000000000/* Copyright 2016 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 proxy import ( "context" "crypto/tls" "crypto/x509" "fmt" "net" "net/http" "net/http/httptest" "net/url" "reflect" "regexp" "testing" "github.com/google/go-cmp/cmp" utilnet "k8s.io/apimachinery/pkg/util/net" ) func TestDialURL(t *testing.T) { roots := x509.NewCertPool() if !roots.AppendCertsFromPEM(localhostCert) { t.Fatal("error setting up localhostCert pool") } cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { t.Fatal(err) } var d net.Dialer testcases := map[string]struct { TLSConfig *tls.Config Dial utilnet.DialFunc ExpectError string ExpectProto string }{ "insecure": { TLSConfig: &tls.Config{InsecureSkipVerify: true}, }, "secure, no roots": { TLSConfig: &tls.Config{InsecureSkipVerify: false}, ExpectError: "unknown authority|not trusted", }, "secure with roots": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots}, }, "secure with mismatched server": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"}, ExpectError: "not bogus.com", }, "secure with matched server": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"}, }, "insecure, custom dial": { TLSConfig: &tls.Config{InsecureSkipVerify: true}, Dial: d.DialContext, }, "secure, no roots, custom dial": { TLSConfig: &tls.Config{InsecureSkipVerify: false}, Dial: d.DialContext, ExpectError: "unknown authority|not trusted", }, "secure with roots, custom dial": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots}, Dial: d.DialContext, }, "secure with mismatched server, custom dial": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"}, Dial: d.DialContext, ExpectError: "not bogus.com", }, "secure with matched server, custom dial": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"}, Dial: d.DialContext, }, "ensure we use http2 if specified": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com", NextProtos: []string{"http2"}}, Dial: d.DialContext, ExpectProto: "http2", }, "ensure we use http/1.1 if unspecified": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"}, Dial: d.DialContext, ExpectProto: "http/1.1", }, "ensure we use http/1.1 if available": { TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com", NextProtos: []string{"http2", "http/1.1"}}, Dial: d.DialContext, ExpectProto: "http/1.1", }, } for k, tc := range testcases { func() { ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {})) defer ts.Close() ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}, NextProtos: []string{"http2", "http/1.1"}} ts.StartTLS() // Make a copy of the config tlsConfigCopy := tc.TLSConfig.Clone() // Clone() mutates the receiver (!), so also call it on the copy tlsConfigCopy.Clone() transport := &http.Transport{ DialContext: tc.Dial, TLSClientConfig: tlsConfigCopy, } extractedDial, err := utilnet.DialerFor(transport) if err != nil { t.Fatal(err) } if fmt.Sprintf("%p", extractedDial) != fmt.Sprintf("%p", tc.Dial) { t.Fatalf("%s: Unexpected dial", k) } extractedTLSConfig, err := utilnet.TLSClientConfig(transport) if err != nil { t.Fatal(err) } if extractedTLSConfig == nil { t.Fatalf("%s: Expected tlsConfig", k) } u, _ := url.Parse(ts.URL) _, p, _ := net.SplitHostPort(u.Host) u.Host = net.JoinHostPort("127.0.0.1", p) conn, err := DialURL(context.Background(), u, transport) // Make sure dialing doesn't mutate the transport's TLSConfig if !reflect.DeepEqual(tc.TLSConfig, tlsConfigCopy) { t.Errorf("%s: transport's copy of TLSConfig was mutated\n%s", k, cmp.Diff(tc.TLSConfig, tlsConfigCopy)) } if err != nil { if tc.ExpectError == "" { t.Errorf("%s: expected no error, got %q", k, err.Error()) } if tc.ExpectError != "" && !regexp.MustCompile(tc.ExpectError).MatchString(err.Error()) { t.Errorf("%s: expected error containing %q, got %q", k, tc.ExpectError, err.Error()) } return } tlsConn := conn.(*tls.Conn) if tc.ExpectProto != "" { if tlsConn.ConnectionState().NegotiatedProtocol != tc.ExpectProto { t.Errorf("%s: expected proto %s, got %s", k, tc.ExpectProto, tlsConn.ConnectionState().NegotiatedProtocol) } } conn.Close() if tc.ExpectError != "" { t.Errorf("%s: expected error %q, got none", k, tc.ExpectError) } }() } } // localhostCert was generated from crypto/tls/generate_cert.go with the following command: // // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var localhostCert = []byte(`-----BEGIN CERTIFICATE----- MIIDGTCCAgGgAwIBAgIRAKfNl1LEAt7nFPYvHBnpv2swDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAKww39FwmV5lDIbAUIAuSYYVtZke6bca1oyq19ZrRL0uavwPXSJm +Qxt4RKUQhzYhZ/alJp8iRfu/Z+Yv9Beez89dQB9V8YnHj/AX4Jph9lJ2aawWMI6 AqPLdIzKLQVVvPw+UVKH9x8yy08H/23AIFGyK4Dbht+KZJeUbJQFiGlRFJim8atx KA3C9NzCHw6hyhP46jguLl65rcxLMSzcTz97ToG0MP66YEUbsA/YzFTKDwht7ESH nRMBnQ4wZfWpvAiXMr3XJGOa3NYJy1A+WkWyrfZO7guwsZ4L6dGqnlPpzA5QkKYx H9Z5K1bUaYEi0Yi2ug7Jkvd1HE179nkF7t0CAwEAAaNoMGYwDgYDVR0PAQH/BAQD AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI hvcNAQELBQADggEBAAKSQToD1iLujFhQwaLnPVRV6r4nEFVXCxXYtQNEX1DVSKSj JYbBGJnL50oc0N4Ar+Spqofm+THkiTQJUzptPtnYIzNpKYdE6+bPwqURWzFEI2OF ks3fYZ4ZdbMbmJRo1qPJO34emm4KrOl9aoV0qwp2QyTvHgLroU3icKoe4e7+p4KK 02Rt3qczHvCKoUnw6m07Ql0n9e7Ncpujcs2A8PaQ1iPX+BVOmvjTVT8y5NSRDzwL a2wur8BSZ5E8SVzzvNZJlLSi6BbObQUjALHkjVYm11dWv/BY8jHdt+iFhbNBRASx ENuih3pX1Poki1qRYOtB/vAS99E1ORj9zJlUlzo= -----END CERTIFICATE-----`) // localhostKey is the private key for localhostCert. var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEArDDf0XCZXmUMhsBQgC5JhhW1mR7ptxrWjKrX1mtEvS5q/A9d Imb5DG3hEpRCHNiFn9qUmnyJF+79n5i/0F57Pz11AH1XxiceP8BfgmmH2UnZprBY wjoCo8t0jMotBVW8/D5RUof3HzLLTwf/bcAgUbIrgNuG34pkl5RslAWIaVEUmKbx q3EoDcL03MIfDqHKE/jqOC4uXrmtzEsxLNxPP3tOgbQw/rpgRRuwD9jMVMoPCG3s RIedEwGdDjBl9am8CJcyvdckY5rc1gnLUD5aRbKt9k7uC7Cxngvp0aqeU+nMDlCQ pjEf1nkrVtRpgSLRiLa6DsmS93UcTXv2eQXu3QIDAQABAoIBACAYnB+2FWB7BXK4 tkiuWBYeRdNc58OxxPxDfCgDprR8yoRheMLI3vNqJ+IGsKwf0AiT/c8uF3/WlIAD QP3eHqsTEZQdyRaug/zuJt9wPFpMYb2ocWMC3Ssa6Ya0yN+Ns8Rw+UehAHdYSH1a yEn03hFcXK+QO/u/GDEJAZQ108+NdznT4ql59tt791d97meNlMVJwkwVf/NqtDqi UNx6BvSj5+6MoWjU8hqrYv9pkzP386QRsl70tVH+0LZd5XUZsSyof/IdV1EmfGUR 5les8tsd+fuo3LaPObksJu+GBwvEStmQPjZjiBUzw0Sx8VYTJfZr7gl2h4mmk/AJ F5P+fSECgYEAzwDcJCuYPA8nzVB+ZOM+Wl+3uUKG/Xn8Yx8uWtWU7o9qsJmasLqO sLtz1zadPtYOBXsb4H5PisNPuosVEqnthjRwmPhIA3tK/X3UzhnriACCrKpg3Ix0 uJG2vqpdaPXYxmyTQfI8YSp5X0gTg3R4xQqmbGMyAQg+1NzcGAf+qQ8CgYEA1PKX vkxzJuSPsfQYr34fnZRuogANNGUaWCTYMhH6sK8qrJu5RXmEemaraqT/esUUu1fl cTAxRqUb8ysexA+RKR848hFkrvAR5M1t6xK2hPuSec1Lm9HNfHoFB7Pa5t7APoJ9 8NkjNzI0mL9YqYcfJpzfFrxtzfLwlm6B3irS8VMCgYBg3skmUBRcvsbkiO+tLL7I MhTbKGvdgNGAXV4m+d5JSWonHKrMW3Fc+Uv7gb5SYn+LRxJDmziD+mR8KowBAO57 qFys6TtiDbeJKvKERJL5QSvlu5G6hCw3F1GKplUyQiJgsPy0lrR00BieYy9mjAHc S+CXxk/nNcGZgYWp5UviNwKBgC7t46kpmfsJRe222LXcOsV0j8kd78sLOPoR7J9k PPYxNFtj2jnIZPzAoahYAoGg60e6QDNopoNmIbm+WAJnV9tTKS6XzLOM7rSY3U+A CT9XXdl/99i4LOvwzCj9ZxGYJ4/fHDg28j7YzqSXDsgVojTVP4j4L87CamkMo4w9 rc1HAoGARE2WActS2PF75jRXCjj4SjB/3vOJVGKxrJdoo2HPzY0psTmdJJULOGYZ MU1KC4EDzhSfM3juBbEhaZx9NFZOHVp2hxZpg77B5cQXGH6HIiZ20jCNjdcioHl9 HeVeFG/9rJG0NcQe3pIm9f0EY5JCbzr0fa2tTPV3N9jGHc0sFtI= -----END RSA PRIVATE KEY----- `) golang-k8s-apimachinery-0.29.0/pkg/util/proxy/doc.go000066400000000000000000000012751453143165200222470ustar00rootroot00000000000000/* 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 proxy provides transport and upgrade support for proxies. package proxy // import "k8s.io/apimachinery/pkg/util/proxy" golang-k8s-apimachinery-0.29.0/pkg/util/proxy/transport.go000066400000000000000000000215341453143165200235360ustar00rootroot00000000000000/* 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 proxy import ( "bytes" "compress/flate" "compress/gzip" "fmt" "io" "net/http" "net/url" "path" "strings" "golang.org/x/net/html" "golang.org/x/net/html/atom" "k8s.io/klog/v2" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/sets" ) // atomsToAttrs states which attributes of which tags require URL substitution. // Sources: http://www.w3.org/TR/REC-html40/index/attributes.html // // http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1 var atomsToAttrs = map[atom.Atom]sets.String{ atom.A: sets.NewString("href"), atom.Applet: sets.NewString("codebase"), atom.Area: sets.NewString("href"), atom.Audio: sets.NewString("src"), atom.Base: sets.NewString("href"), atom.Blockquote: sets.NewString("cite"), atom.Body: sets.NewString("background"), atom.Button: sets.NewString("formaction"), atom.Command: sets.NewString("icon"), atom.Del: sets.NewString("cite"), atom.Embed: sets.NewString("src"), atom.Form: sets.NewString("action"), atom.Frame: sets.NewString("longdesc", "src"), atom.Head: sets.NewString("profile"), atom.Html: sets.NewString("manifest"), atom.Iframe: sets.NewString("longdesc", "src"), atom.Img: sets.NewString("longdesc", "src", "usemap"), atom.Input: sets.NewString("src", "usemap", "formaction"), atom.Ins: sets.NewString("cite"), atom.Link: sets.NewString("href"), atom.Object: sets.NewString("classid", "codebase", "data", "usemap"), atom.Q: sets.NewString("cite"), atom.Script: sets.NewString("src"), atom.Source: sets.NewString("src"), atom.Video: sets.NewString("poster", "src"), // TODO: css URLs hidden in style elements. } // Transport is a transport for text/html content that replaces URLs in html // content with the prefix of the proxy server type Transport struct { Scheme string Host string PathPrepend string http.RoundTripper } // RoundTrip implements the http.RoundTripper interface func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { // Add reverse proxy headers. forwardedURI := path.Join(t.PathPrepend, req.URL.EscapedPath()) if strings.HasSuffix(req.URL.Path, "/") { forwardedURI = forwardedURI + "/" } req.Header.Set("X-Forwarded-Uri", forwardedURI) if len(t.Host) > 0 { req.Header.Set("X-Forwarded-Host", t.Host) } if len(t.Scheme) > 0 { req.Header.Set("X-Forwarded-Proto", t.Scheme) } rt := t.RoundTripper if rt == nil { rt = http.DefaultTransport } resp, err := rt.RoundTrip(req) if err != nil { return nil, errors.NewServiceUnavailable(fmt.Sprintf("error trying to reach service: %v", err)) } if redirect := resp.Header.Get("Location"); redirect != "" { targetURL, err := url.Parse(redirect) if err != nil { return nil, errors.NewInternalError(fmt.Errorf("error trying to parse Location header: %v", err)) } resp.Header.Set("Location", t.rewriteURL(targetURL, req.URL, req.Host)) return resp, nil } cType := resp.Header.Get("Content-Type") cType = strings.TrimSpace(strings.SplitN(cType, ";", 2)[0]) if cType != "text/html" { // Do nothing, simply pass through return resp, nil } return t.rewriteResponse(req, resp) } var _ = net.RoundTripperWrapper(&Transport{}) func (rt *Transport) WrappedRoundTripper() http.RoundTripper { return rt.RoundTripper } // rewriteURL rewrites a single URL to go through the proxy, if the URL refers // to the same host as sourceURL, which is the page on which the target URL // occurred, or if the URL matches the sourceRequestHost. func (t *Transport) rewriteURL(url *url.URL, sourceURL *url.URL, sourceRequestHost string) string { // Example: // When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/), // the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The // sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the // URL is sought, which can be different from sourceURL.Host. For example, if user sends the // request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/), // sourceRequestHost is "localhost:8001". // // If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host // or sourceRequestHost, we should not consider the returned URL to be a completely different host. // It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the // necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/). isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost isRelative := !strings.HasPrefix(url.Path, "/") if isDifferentHost || isRelative { return url.String() } // Do not rewrite scheme and host if the Transport has empty scheme and host // when targetURL already contains the sourceRequestHost if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") { url.Scheme = t.Scheme url.Host = t.Host } origPath := url.Path // Do not rewrite URL if the sourceURL already contains the necessary prefix. if strings.HasPrefix(url.Path, t.PathPrepend) { return url.String() } url.Path = path.Join(t.PathPrepend, url.Path) if strings.HasSuffix(origPath, "/") { // Add back the trailing slash, which was stripped by path.Join(). url.Path += "/" } return url.String() } // rewriteHTML scans the HTML for tags with url-valued attributes, and updates // those values with the urlRewriter function. The updated HTML is output to the // writer. func rewriteHTML(reader io.Reader, writer io.Writer, urlRewriter func(*url.URL) string) error { // Note: This assumes the content is UTF-8. tokenizer := html.NewTokenizer(reader) var err error for err == nil { tokenType := tokenizer.Next() switch tokenType { case html.ErrorToken: err = tokenizer.Err() case html.StartTagToken, html.SelfClosingTagToken: token := tokenizer.Token() if urlAttrs, ok := atomsToAttrs[token.DataAtom]; ok { for i, attr := range token.Attr { if urlAttrs.Has(attr.Key) { url, err := url.Parse(attr.Val) if err != nil { // Do not rewrite the URL if it isn't valid. It is intended not // to error here to prevent the inability to understand the // content of the body to cause a fatal error. continue } token.Attr[i].Val = urlRewriter(url) } } } _, err = writer.Write([]byte(token.String())) default: _, err = writer.Write(tokenizer.Raw()) } } if err != io.EOF { return err } return nil } // rewriteResponse modifies an HTML response by updating absolute links referring // to the original host to instead refer to the proxy transport. func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*http.Response, error) { origBody := resp.Body defer origBody.Close() newContent := &bytes.Buffer{} var reader io.Reader = origBody var writer io.Writer = newContent encoding := resp.Header.Get("Content-Encoding") switch encoding { case "gzip": var err error reader, err = gzip.NewReader(reader) if err != nil { return nil, fmt.Errorf("errorf making gzip reader: %v", err) } gzw := gzip.NewWriter(writer) defer gzw.Close() writer = gzw case "deflate": var err error reader = flate.NewReader(reader) flw, err := flate.NewWriter(writer, flate.BestCompression) if err != nil { return nil, fmt.Errorf("errorf making flate writer: %v", err) } defer func() { flw.Close() flw.Flush() }() writer = flw case "": // This is fine default: // Some encoding we don't understand-- don't try to parse this klog.Errorf("Proxy encountered encoding %v for text/html; can't understand this so not fixing links.", encoding) return resp, nil } urlRewriter := func(targetUrl *url.URL) string { return t.rewriteURL(targetUrl, req.URL, req.Host) } err := rewriteHTML(reader, writer, urlRewriter) if err != nil { klog.Errorf("Failed to rewrite URLs: %v", err) return resp, err } resp.Body = io.NopCloser(newContent) // Update header node with new content-length // TODO: Remove any hash/signature headers here? resp.Header.Del("Content-Length") resp.ContentLength = int64(newContent.Len()) return resp, err } golang-k8s-apimachinery-0.29.0/pkg/util/proxy/transport_test.go000066400000000000000000000334161453143165200245770ustar00rootroot00000000000000/* 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 proxy import ( "bytes" "compress/flate" "compress/gzip" "fmt" "io" "net/http" "net/http/httptest" "net/url" "strings" "testing" ) func parseURLOrDie(inURL string) *url.URL { parsed, err := url.Parse(inURL) if err != nil { panic(err) } return parsed } func TestProxyTransport(t *testing.T) { testTransport := &Transport{ Scheme: "http", Host: "foo.com", PathPrepend: "/proxy/node/node1:10250", } testTransport2 := &Transport{ Scheme: "https", Host: "foo.com", PathPrepend: "/proxy/node/node1:8080", } emptyHostTransport := &Transport{ Scheme: "https", PathPrepend: "/proxy/node/node1:10250", } emptySchemeTransport := &Transport{ Host: "foo.com", PathPrepend: "/proxy/node/node1:10250", } emptyHostAndSchemeTransport := &Transport{ PathPrepend: "/proxy/node/node1:10250", } type Item struct { input string sourceURL string transport *Transport output string contentType string forwardedURI string redirect string redirectWant string reqHost string } table := map[string]Item{ "normal": { input: `
kubelet.loggoogle.log
`, sourceURL: "http://mynode.com/logs/log.log", transport: testTransport, output: `
kubelet.loggoogle.log
`, contentType: "text/html", forwardedURI: "/proxy/node/node1:10250/logs/log.log", }, "full document": { input: `
kubelet.loggoogle.log
`, sourceURL: "http://mynode.com/logs/log.log", transport: testTransport, output: `
kubelet.loggoogle.log
`, contentType: "text/html", forwardedURI: "/proxy/node/node1:10250/logs/log.log", }, "trailing slash": { input: `
kubelet.loggoogle.log
`, sourceURL: "http://mynode.com/logs/log.log", transport: testTransport, output: `
kubelet.loggoogle.log
`, contentType: "text/html", forwardedURI: "/proxy/node/node1:10250/logs/log.log", }, "content-type charset": { input: `
kubelet.loggoogle.log
`, sourceURL: "http://mynode.com/logs/log.log", transport: testTransport, output: `
kubelet.loggoogle.log
`, contentType: "text/html; charset=utf-8", forwardedURI: "/proxy/node/node1:10250/logs/log.log", }, "content-type passthrough": { input: `
kubelet.loggoogle.log
`, sourceURL: "http://mynode.com/logs/log.log", transport: testTransport, output: `
kubelet.loggoogle.log
`, contentType: "text/plain", forwardedURI: "/proxy/node/node1:10250/logs/log.log", }, "subdir": { input: `kubelet.loggoogle.log`, sourceURL: "http://mynode.com/whatever/apt/somelog.log", transport: testTransport2, output: `kubelet.loggoogle.log`, contentType: "text/html", forwardedURI: "/proxy/node/node1:8080/whatever/apt/somelog.log", }, "image": { input: `
`, sourceURL: "http://mynode.com/", transport: testTransport, output: `
`, contentType: "text/html", forwardedURI: "/proxy/node/node1:10250/", }, "abs": { input: `", transport: testTransport, output: "", contentType: "text/html", forwardedURI: "/proxy/node/node1:10250/logs/log.log%00%3Cscript%3Ealert%281%29%3C/script%3E", }, "redirect rel must be escaped": { sourceURL: "http://mynode.com/redirect", transport: testTransport, redirect: "/redirected/target/%00/", redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", forwardedURI: "/proxy/node/node1:10250/redirect", }, "redirect abs same host must be escaped": { sourceURL: "http://mynode.com/redirect", transport: testTransport, redirect: "http://mynode.com/redirected/target/%00/", redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", forwardedURI: "/proxy/node/node1:10250/redirect", }, "redirect abs other host must be escaped": { sourceURL: "http://mynode.com/redirect", transport: testTransport, redirect: "http://example.com/redirected/target/%00/", redirectWant: "http://example.com/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", forwardedURI: "/proxy/node/node1:10250/redirect", }, "redirect abs use reqHost no host no scheme must be escaped": { sourceURL: "http://mynode.com/redirect", transport: emptyHostAndSchemeTransport, redirect: "http://10.0.0.1:8001/redirected/target/%00/", redirectWant: "http://10.0.0.1:8001/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", forwardedURI: "/proxy/node/node1:10250/redirect", reqHost: "10.0.0.1:8001", }, } testItem := func(name string, item *Item) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Check request headers. if got, want := r.Header.Get("X-Forwarded-Uri"), item.forwardedURI; got != want { t.Errorf("%v: X-Forwarded-Uri = %q, want %q", name, got, want) } if len(item.transport.Host) == 0 { _, present := r.Header["X-Forwarded-Host"] if present { t.Errorf("%v: X-Forwarded-Host header should not be present", name) } } else { if got, want := r.Header.Get("X-Forwarded-Host"), item.transport.Host; got != want { t.Errorf("%v: X-Forwarded-Host = %q, want %q", name, got, want) } } if len(item.transport.Scheme) == 0 { _, present := r.Header["X-Forwarded-Proto"] if present { t.Errorf("%v: X-Forwarded-Proto header should not be present", name) } } else { if got, want := r.Header.Get("X-Forwarded-Proto"), item.transport.Scheme; got != want { t.Errorf("%v: X-Forwarded-Proto = %q, want %q", name, got, want) } } // Send response. if item.redirect != "" { http.Redirect(w, r, item.redirect, http.StatusMovedPermanently) return } w.Header().Set("Content-Type", item.contentType) fmt.Fprint(w, item.input) })) defer server.Close() // Replace source URL with our test server address. sourceURL := parseURLOrDie(item.sourceURL) serverURL := parseURLOrDie(server.URL) item.input = strings.Replace(item.input, sourceURL.Host, serverURL.Host, -1) item.redirect = strings.Replace(item.redirect, sourceURL.Host, serverURL.Host, -1) sourceURL.Host = serverURL.Host req, err := http.NewRequest("GET", sourceURL.String(), nil) if err != nil { t.Errorf("%v: Unexpected error: %v", name, err) return } if item.reqHost != "" { req.Host = item.reqHost } resp, err := item.transport.RoundTrip(req) if err != nil { t.Errorf("%v: Unexpected error: %v", name, err) return } if item.redirect != "" { // Check that redirect URLs get rewritten properly. if got, want := resp.Header.Get("Location"), item.redirectWant; got != want { t.Errorf("%v: Location header = %q, want %q", name, got, want) } return } body, err := io.ReadAll(resp.Body) if err != nil { t.Errorf("%v: Unexpected error: %v", name, err) return } if e, a := item.output, string(body); e != a { t.Errorf("%v: expected %v, but got %v", name, e, a) } } for name, item := range table { testItem(name, &item) } } func TestRewriteResponse(t *testing.T) { gzipbuf := bytes.NewBuffer(nil) flatebuf := bytes.NewBuffer(nil) testTransport := &Transport{ Scheme: "http", Host: "foo.com", PathPrepend: "/proxy/node/node1:10250", } expected := []string{ "short body test", strings.Repeat("long body test", 4097), } test := []struct { encodeType string writer func(string) *http.Response reader func(*http.Response) string }{ { encodeType: "gzip", writer: func(ept string) *http.Response { gzw := gzip.NewWriter(gzipbuf) defer gzw.Close() gzw.Write([]byte(ept)) gzw.Flush() return &http.Response{ Body: io.NopCloser(gzipbuf), } }, reader: func(rep *http.Response) string { reader, _ := gzip.NewReader(rep.Body) s, _ := io.ReadAll(reader) return string(s) }, }, { encodeType: "deflate", writer: func(ept string) *http.Response { flw, _ := flate.NewWriter(flatebuf, flate.BestCompression) defer flw.Close() flw.Write([]byte(ept)) flw.Flush() return &http.Response{ Body: io.NopCloser(flatebuf), } }, reader: func(rep *http.Response) string { reader := flate.NewReader(rep.Body) s, _ := io.ReadAll(reader) return string(s) }, }, } errFn := func(encode string, err error) { t.Errorf("%s failed to read and write: %v", encode, err) } for _, v := range test { request, _ := http.NewRequest("GET", "http://mynode.com/", nil) request.Header.Set("Content-Encoding", v.encodeType) request.Header.Add("Accept-Encoding", v.encodeType) for _, exp := range expected { resp := v.writer(exp) gotResponse, err := testTransport.rewriteResponse(request, resp) if err != nil { errFn(v.encodeType, err) } result := v.reader(gotResponse) if result != exp { errFn(v.encodeType, fmt.Errorf("expected %s, get %s", exp, result)) } } } } golang-k8s-apimachinery-0.29.0/pkg/util/proxy/upgradeaware.go000066400000000000000000000467551453143165200241650ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package proxy import ( "bufio" "bytes" "fmt" "io" "log" "net" "net/http" "net/http/httputil" "net/url" "os" "strings" "time" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/httpstream" utilnet "k8s.io/apimachinery/pkg/util/net" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "github.com/mxk/go-flowrate/flowrate" "k8s.io/klog/v2" ) // UpgradeRequestRoundTripper provides an additional method to decorate a request // with any authentication or other protocol level information prior to performing // an upgrade on the server. Any response will be handled by the intercepting // proxy. type UpgradeRequestRoundTripper interface { http.RoundTripper // WrapRequest takes a valid HTTP request and returns a suitably altered version // of request with any HTTP level values required to complete the request half of // an upgrade on the server. It does not get a chance to see the response and // should bypass any request side logic that expects to see the response. WrapRequest(*http.Request) (*http.Request, error) } // UpgradeAwareHandler is a handler for proxy requests that may require an upgrade type UpgradeAwareHandler struct { // UpgradeRequired will reject non-upgrade connections if true. UpgradeRequired bool // Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server // for upgrade requests unless UseRequestLocationOnUpgrade is true. Location *url.URL // AppendLocationPath determines if the original path of the Location should be appended to the upstream proxy request path AppendLocationPath bool // Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used Transport http.RoundTripper // UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided. // This allows clients to disable HTTP/2. UpgradeTransport UpgradeRequestRoundTripper // WrapTransport indicates whether the provided Transport should be wrapped with default proxy transport behavior (URL rewriting, X-Forwarded-* header setting) WrapTransport bool // UseRequestLocation will use the incoming request URL when talking to the backend server. UseRequestLocation bool // UseLocationHost overrides the HTTP host header in requests to the backend server to use the Host from Location. // This will override the req.Host field of a request, while UseRequestLocation will override the req.URL field // of a request. The req.URL.Host specifies the server to connect to, while the req.Host field // specifies the Host header value to send in the HTTP request. If this is false, the incoming req.Host header will // just be forwarded to the backend server. UseLocationHost bool // FlushInterval controls how often the standard HTTP proxy will flush content from the upstream. FlushInterval time.Duration // MaxBytesPerSec controls the maximum rate for an upstream connection. No rate is imposed if the value is zero. MaxBytesPerSec int64 // Responder is passed errors that occur while setting up proxying. Responder ErrorResponder // Reject to forward redirect response RejectForwardingRedirects bool } const defaultFlushInterval = 200 * time.Millisecond // ErrorResponder abstracts error reporting to the proxy handler to remove the need to hardcode a particular // error format. type ErrorResponder interface { Error(w http.ResponseWriter, req *http.Request, err error) } // SimpleErrorResponder is the legacy implementation of ErrorResponder for callers that only // service a single request/response per proxy. type SimpleErrorResponder interface { Error(err error) } func NewErrorResponder(r SimpleErrorResponder) ErrorResponder { return simpleResponder{r} } type simpleResponder struct { responder SimpleErrorResponder } func (r simpleResponder) Error(w http.ResponseWriter, req *http.Request, err error) { r.responder.Error(err) } // upgradeRequestRoundTripper implements proxy.UpgradeRequestRoundTripper. type upgradeRequestRoundTripper struct { http.RoundTripper upgrader http.RoundTripper } var ( _ UpgradeRequestRoundTripper = &upgradeRequestRoundTripper{} _ utilnet.RoundTripperWrapper = &upgradeRequestRoundTripper{} ) // WrappedRoundTripper returns the round tripper that a caller would use. func (rt *upgradeRequestRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.RoundTripper } // WriteToRequest calls the nested upgrader and then copies the returned request // fields onto the passed request. func (rt *upgradeRequestRoundTripper) WrapRequest(req *http.Request) (*http.Request, error) { resp, err := rt.upgrader.RoundTrip(req) if err != nil { return nil, err } return resp.Request, nil } // onewayRoundTripper captures the provided request - which is assumed to have // been modified by other round trippers - and then returns a fake response. type onewayRoundTripper struct{} // RoundTrip returns a simple 200 OK response that captures the provided request. func (onewayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { return &http.Response{ Status: "200 OK", StatusCode: http.StatusOK, Body: io.NopCloser(&bytes.Buffer{}), Request: req, }, nil } // MirrorRequest is a round tripper that can be called to get back the calling request as // the core round tripper in a chain. var MirrorRequest http.RoundTripper = onewayRoundTripper{} // NewUpgradeRequestRoundTripper takes two round trippers - one for the underlying TCP connection, and // one that is able to write headers to an HTTP request. The request rt is used to set the request headers // and that is written to the underlying connection rt. func NewUpgradeRequestRoundTripper(connection, request http.RoundTripper) UpgradeRequestRoundTripper { return &upgradeRequestRoundTripper{ RoundTripper: connection, upgrader: request, } } // normalizeLocation returns the result of parsing the full URL, with scheme set to http if missing func normalizeLocation(location *url.URL) *url.URL { normalized, _ := url.Parse(location.String()) if len(normalized.Scheme) == 0 { normalized.Scheme = "http" } return normalized } // NewUpgradeAwareHandler creates a new proxy handler with a default flush interval. Responder is required for returning // errors to the caller. func NewUpgradeAwareHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder ErrorResponder) *UpgradeAwareHandler { return &UpgradeAwareHandler{ Location: normalizeLocation(location), Transport: transport, WrapTransport: wrapTransport, UpgradeRequired: upgradeRequired, FlushInterval: defaultFlushInterval, Responder: responder, } } func proxyRedirectsforRootPath(path string, w http.ResponseWriter, req *http.Request) bool { redirect := false method := req.Method // From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP: // Redirect requests with an empty path to a location that ends with a '/' // This is essentially a hack for https://issue.k8s.io/4958. // Note: Keep this code after tryUpgrade to not break that flow. if len(path) == 0 && (method == http.MethodGet || method == http.MethodHead) { var queryPart string if len(req.URL.RawQuery) > 0 { queryPart = "?" + req.URL.RawQuery } w.Header().Set("Location", req.URL.Path+"/"+queryPart) w.WriteHeader(http.StatusMovedPermanently) redirect = true } return redirect } // ServeHTTP handles the proxy request func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if h.tryUpgrade(w, req) { return } if h.UpgradeRequired { h.Responder.Error(w, req, errors.NewBadRequest("Upgrade request required")) return } loc := *h.Location loc.RawQuery = req.URL.RawQuery // If original request URL ended in '/', append a '/' at the end of the // of the proxy URL if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { loc.Path += "/" } proxyRedirect := proxyRedirectsforRootPath(loc.Path, w, req) if proxyRedirect { return } if h.Transport == nil || h.WrapTransport { h.Transport = h.defaultProxyTransport(req.URL, h.Transport) } // WithContext creates a shallow clone of the request with the same context. newReq := req.WithContext(req.Context()) newReq.Header = utilnet.CloneHeader(req.Header) if !h.UseRequestLocation { newReq.URL = &loc } if h.UseLocationHost { // exchanging req.Host with the backend location is necessary for backends that act on the HTTP host header (e.g. API gateways), // because req.Host has preference over req.URL.Host in filling this header field newReq.Host = h.Location.Host } // create the target location to use for the reverse proxy reverseProxyLocation := &url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host} if h.AppendLocationPath { reverseProxyLocation.Path = h.Location.Path } proxy := httputil.NewSingleHostReverseProxy(reverseProxyLocation) proxy.Transport = h.Transport proxy.FlushInterval = h.FlushInterval proxy.ErrorLog = log.New(noSuppressPanicError{}, "", log.LstdFlags) if h.RejectForwardingRedirects { oldModifyResponse := proxy.ModifyResponse proxy.ModifyResponse = func(response *http.Response) error { code := response.StatusCode if code >= 300 && code <= 399 && len(response.Header.Get("Location")) > 0 { // close the original response response.Body.Close() msg := "the backend attempted to redirect this request, which is not permitted" // replace the response *response = http.Response{ StatusCode: http.StatusBadGateway, Status: fmt.Sprintf("%d %s", response.StatusCode, http.StatusText(response.StatusCode)), Body: io.NopCloser(strings.NewReader(msg)), ContentLength: int64(len(msg)), } } else { if oldModifyResponse != nil { if err := oldModifyResponse(response); err != nil { return err } } } return nil } } if h.Responder != nil { // if an optional error interceptor/responder was provided wire it // the custom responder might be used for providing a unified error reporting // or supporting retry mechanisms by not sending non-fatal errors to the clients proxy.ErrorHandler = h.Responder.Error } proxy.ServeHTTP(w, newReq) } type noSuppressPanicError struct{} func (noSuppressPanicError) Write(p []byte) (n int, err error) { // skip "suppressing panic for copyResponse error in test; copy error" error message // that ends up in CI tests on each kube-apiserver termination as noise and // everybody thinks this is fatal. if strings.Contains(string(p), "suppressing panic") { return len(p), nil } return os.Stderr.Write(p) } // tryUpgrade returns true if the request was handled. func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool { if !httpstream.IsUpgradeRequest(req) { klog.V(6).Infof("Request was not an upgrade") return false } var ( backendConn net.Conn rawResponse []byte err error ) location := *h.Location if h.UseRequestLocation { location = *req.URL location.Scheme = h.Location.Scheme location.Host = h.Location.Host if h.AppendLocationPath { location.Path = singleJoiningSlash(h.Location.Path, location.Path) } } clone := utilnet.CloneRequest(req) // Only append X-Forwarded-For in the upgrade path, since httputil.NewSingleHostReverseProxy // handles this in the non-upgrade path. utilnet.AppendForwardedForHeader(clone) klog.V(6).Infof("Connecting to backend proxy (direct dial) %s\n Headers: %v", &location, clone.Header) if h.UseLocationHost { clone.Host = h.Location.Host } clone.URL = &location backendConn, err = h.DialForUpgrade(clone) if err != nil { klog.V(6).Infof("Proxy connection error: %v", err) h.Responder.Error(w, req, err) return true } defer backendConn.Close() // determine the http response code from the backend by reading from rawResponse+backendConn backendHTTPResponse, headerBytes, err := getResponse(io.MultiReader(bytes.NewReader(rawResponse), backendConn)) if err != nil { klog.V(6).Infof("Proxy connection error: %v", err) h.Responder.Error(w, req, err) return true } if len(headerBytes) > len(rawResponse) { // we read beyond the bytes stored in rawResponse, update rawResponse to the full set of bytes read from the backend rawResponse = headerBytes } // If the backend did not upgrade the request, return an error to the client. If the response was // an error, the error is forwarded directly after the connection is hijacked. Otherwise, just // return a generic error here. if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 { err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode) klog.Errorf("Proxy upgrade error: %v", err) h.Responder.Error(w, req, err) return true } // Once the connection is hijacked, the ErrorResponder will no longer work, so // hijacking should be the last step in the upgrade. requestHijacker, ok := w.(http.Hijacker) if !ok { klog.V(6).Infof("Unable to hijack response writer: %T", w) h.Responder.Error(w, req, fmt.Errorf("request connection cannot be hijacked: %T", w)) return true } requestHijackedConn, _, err := requestHijacker.Hijack() if err != nil { klog.V(6).Infof("Unable to hijack response: %v", err) h.Responder.Error(w, req, fmt.Errorf("error hijacking connection: %v", err)) return true } defer requestHijackedConn.Close() if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols { // If the backend did not upgrade the request, echo the response from the backend to the client and return, closing the connection. klog.V(6).Infof("Proxy upgrade error, status code %d", backendHTTPResponse.StatusCode) // set read/write deadlines deadline := time.Now().Add(10 * time.Second) backendConn.SetReadDeadline(deadline) requestHijackedConn.SetWriteDeadline(deadline) // write the response to the client err := backendHTTPResponse.Write(requestHijackedConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { klog.Errorf("Error proxying data from backend to client: %v", err) } // Indicate we handled the request return true } // Forward raw response bytes back to client. if len(rawResponse) > 0 { klog.V(6).Infof("Writing %d bytes to hijacked connection", len(rawResponse)) if _, err = requestHijackedConn.Write(rawResponse); err != nil { utilruntime.HandleError(fmt.Errorf("Error proxying response from backend to client: %v", err)) } } // Proxy the connection. This is bidirectional, so we need a goroutine // to copy in each direction. Once one side of the connection exits, we // exit the function which performs cleanup and in the process closes // the other half of the connection in the defer. writerComplete := make(chan struct{}) readerComplete := make(chan struct{}) go func() { var writer io.WriteCloser if h.MaxBytesPerSec > 0 { writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec) } else { writer = backendConn } _, err := io.Copy(writer, requestHijackedConn) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { klog.Errorf("Error proxying data from client to backend: %v", err) } close(writerComplete) }() go func() { var reader io.ReadCloser if h.MaxBytesPerSec > 0 { reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec) } else { reader = backendConn } _, err := io.Copy(requestHijackedConn, reader) if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { klog.Errorf("Error proxying data from backend to client: %v", err) } close(readerComplete) }() // Wait for one half the connection to exit. Once it does the defer will // clean up the other half of the connection. select { case <-writerComplete: case <-readerComplete: } klog.V(6).Infof("Disconnecting from backend proxy %s\n Headers: %v", &location, clone.Header) return true } // FIXME: Taken from net/http/httputil/reverseproxy.go as singleJoiningSlash is not exported to be re-used. // See-also: https://github.com/golang/go/issues/44290 func singleJoiningSlash(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") switch { case aslash && bslash: return a + b[1:] case !aslash && !bslash: return a + "/" + b } return a + b } func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) { if h.UpgradeTransport == nil { return dial(req, h.Transport) } updatedReq, err := h.UpgradeTransport.WrapRequest(req) if err != nil { return nil, err } return dial(updatedReq, h.UpgradeTransport) } // getResponseCode reads a http response from the given reader, returns the response, // the bytes read from the reader, and any error encountered func getResponse(r io.Reader) (*http.Response, []byte, error) { rawResponse := bytes.NewBuffer(make([]byte, 0, 256)) // Save the bytes read while reading the response headers into the rawResponse buffer resp, err := http.ReadResponse(bufio.NewReader(io.TeeReader(r, rawResponse)), nil) if err != nil { return nil, nil, err } // return the http response and the raw bytes consumed from the reader in the process return resp, rawResponse.Bytes(), nil } // dial dials the backend at req.URL and writes req to it. func dial(req *http.Request, transport http.RoundTripper) (net.Conn, error) { conn, err := DialURL(req.Context(), req.URL, transport) if err != nil { return nil, fmt.Errorf("error dialing backend: %v", err) } if err = req.Write(conn); err != nil { conn.Close() return nil, fmt.Errorf("error sending request: %v", err) } return conn, err } func (h *UpgradeAwareHandler) defaultProxyTransport(url *url.URL, internalTransport http.RoundTripper) http.RoundTripper { scheme := url.Scheme host := url.Host suffix := h.Location.Path if strings.HasSuffix(url.Path, "/") && !strings.HasSuffix(suffix, "/") { suffix += "/" } pathPrepend := strings.TrimSuffix(url.Path, suffix) rewritingTransport := &Transport{ Scheme: scheme, Host: host, PathPrepend: pathPrepend, RoundTripper: internalTransport, } return &corsRemovingTransport{ RoundTripper: rewritingTransport, } } // corsRemovingTransport is a wrapper for an internal transport. It removes CORS headers // from the internal response. // Implements pkg/util/net.RoundTripperWrapper type corsRemovingTransport struct { http.RoundTripper } var _ = utilnet.RoundTripperWrapper(&corsRemovingTransport{}) func (rt *corsRemovingTransport) RoundTrip(req *http.Request) (*http.Response, error) { resp, err := rt.RoundTripper.RoundTrip(req) if err != nil { return nil, err } removeCORSHeaders(resp) return resp, nil } func (rt *corsRemovingTransport) WrappedRoundTripper() http.RoundTripper { return rt.RoundTripper } // removeCORSHeaders strip CORS headers sent from the backend // This should be called on all responses before returning func removeCORSHeaders(resp *http.Response) { resp.Header.Del("Access-Control-Allow-Credentials") resp.Header.Del("Access-Control-Allow-Headers") resp.Header.Del("Access-Control-Allow-Methods") resp.Header.Del("Access-Control-Allow-Origin") } golang-k8s-apimachinery-0.29.0/pkg/util/proxy/upgradeaware_test.go000066400000000000000000001167131453143165200252140ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package proxy import ( "bufio" "bytes" "compress/gzip" "context" "crypto/tls" "crypto/x509" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "net/http/httputil" "net/url" "reflect" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/websocket" "k8s.io/apimachinery/pkg/util/httpstream" utilnet "k8s.io/apimachinery/pkg/util/net" ) const fakeStatusCode = 567 type fakeResponder struct { t *testing.T called bool err error // called chan error w http.ResponseWriter } func (r *fakeResponder) Error(w http.ResponseWriter, req *http.Request, err error) { if r.called { r.t.Errorf("Error responder called again!\nprevious error: %v\nnew error: %v", r.err, err) } w.WriteHeader(fakeStatusCode) _, writeErr := w.Write([]byte(err.Error())) assert.NoError(r.t, writeErr) r.called = true r.err = err } type fakeConn struct { err error // The error to return when io is performed over the connection. } func (f *fakeConn) Read([]byte) (int, error) { return 0, f.err } func (f *fakeConn) Write([]byte) (int, error) { return 0, f.err } func (f *fakeConn) Close() error { return nil } func (fakeConn) LocalAddr() net.Addr { return nil } func (fakeConn) RemoteAddr() net.Addr { return nil } func (fakeConn) SetDeadline(t time.Time) error { return nil } func (fakeConn) SetReadDeadline(t time.Time) error { return nil } func (fakeConn) SetWriteDeadline(t time.Time) error { return nil } type SimpleBackendHandler struct { requestURL url.URL requestHost string requestHeader http.Header requestBody []byte requestMethod string responseBody string responseHeader map[string]string t *testing.T } func (s *SimpleBackendHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { s.requestURL = *req.URL s.requestHost = req.Host s.requestHeader = req.Header s.requestMethod = req.Method var err error s.requestBody, err = io.ReadAll(req.Body) if err != nil { s.t.Errorf("Unexpected error: %v", err) return } if s.responseHeader != nil { for k, v := range s.responseHeader { w.Header().Add(k, v) } } w.Write([]byte(s.responseBody)) } func validateParameters(t *testing.T, name string, actual url.Values, expected map[string]string) { for k, v := range expected { actualValue, ok := actual[k] if !ok { t.Errorf("%s: Expected parameter %s not received", name, k) continue } if actualValue[0] != v { t.Errorf("%s: Parameter %s values don't match. Actual: %#v, Expected: %s", name, k, actualValue, v) } } } func validateHeaders(t *testing.T, name string, actual http.Header, expected map[string]string, notExpected []string) { for k, v := range expected { actualValue, ok := actual[k] if !ok { t.Errorf("%s: Expected header %s not received", name, k) continue } if actualValue[0] != v { t.Errorf("%s: Header %s values don't match. Actual: %s, Expected: %s", name, k, actualValue, v) } } if notExpected == nil { return } for _, h := range notExpected { if _, present := actual[h]; present { t.Errorf("%s: unexpected header: %s", name, h) } } } func TestServeHTTP(t *testing.T) { tests := []struct { name string method string requestPath string expectedPath string requestBody string requestParams map[string]string requestHeader map[string]string responseHeader map[string]string expectedRespHeader map[string]string notExpectedRespHeader []string upgradeRequired bool appendLocationPath bool expectError func(err error) bool useLocationHost bool }{ { name: "root path, simple get", method: "GET", requestPath: "/", expectedPath: "/", }, { name: "no upgrade header sent", method: "GET", requestPath: "/", upgradeRequired: true, expectError: func(err error) bool { return err != nil && strings.Contains(err.Error(), "Upgrade request required") }, }, { name: "simple path, get", method: "GET", requestPath: "/path/to/test", expectedPath: "/path/to/test", }, { name: "request params", method: "POST", requestPath: "/some/path/", expectedPath: "/some/path/", requestParams: map[string]string{"param1": "value/1", "param2": "value%2"}, requestBody: "test request body", }, { name: "request headers", method: "PUT", requestPath: "/some/path", expectedPath: "/some/path", requestHeader: map[string]string{"Header1": "value1", "Header2": "value2"}, }, { name: "empty path - slash should be added", method: "GET", requestPath: "", expectedPath: "/", }, { name: "remove CORS headers", method: "GET", requestPath: "/some/path", expectedPath: "/some/path", responseHeader: map[string]string{ "Header1": "value1", "Access-Control-Allow-Origin": "some.server", "Access-Control-Allow-Methods": "GET"}, expectedRespHeader: map[string]string{ "Header1": "value1", }, notExpectedRespHeader: []string{ "Access-Control-Allow-Origin", "Access-Control-Allow-Methods", }, }, { name: "use location host", method: "GET", requestPath: "/some/path", expectedPath: "/some/path", useLocationHost: true, }, { name: "use location host - invalid upgrade", method: "GET", upgradeRequired: true, requestHeader: map[string]string{ httpstream.HeaderConnection: httpstream.HeaderUpgrade, }, expectError: func(err error) bool { return err != nil && strings.Contains(err.Error(), "invalid upgrade response: status code 200") }, requestPath: "/some/path", expectedPath: "/some/path", useLocationHost: true, }, { name: "append server path to request path", method: "GET", requestPath: "/base", expectedPath: "/base/base", appendLocationPath: true, }, { name: "append server path to request path with ending slash", method: "GET", requestPath: "/base/", expectedPath: "/base/base/", appendLocationPath: true, }, { name: "don't append server path to request path", method: "GET", requestPath: "/base", expectedPath: "/base", appendLocationPath: false, }, } for i, test := range tests { func() { backendResponse := "Hello" backendResponseHeader := test.responseHeader // Test a simple header if not specified in the test if backendResponseHeader == nil && test.expectedRespHeader == nil { backendResponseHeader = map[string]string{"Content-Type": "text/html"} test.expectedRespHeader = map[string]string{"Content-Type": "text/html"} } backendHandler := &SimpleBackendHandler{ responseBody: backendResponse, responseHeader: backendResponseHeader, } backendServer := httptest.NewServer(backendHandler) defer backendServer.Close() responder := &fakeResponder{t: t} backendURL, _ := url.Parse(backendServer.URL) backendURL.Path = test.requestPath proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, test.upgradeRequired, responder) proxyHandler.UseLocationHost = test.useLocationHost proxyHandler.AppendLocationPath = test.appendLocationPath proxyServer := httptest.NewServer(proxyHandler) defer proxyServer.Close() proxyURL, _ := url.Parse(proxyServer.URL) proxyURL.Path = test.requestPath paramValues := url.Values{} for k, v := range test.requestParams { paramValues[k] = []string{v} } proxyURL.RawQuery = paramValues.Encode() var requestBody io.Reader if test.requestBody != "" { requestBody = bytes.NewBufferString(test.requestBody) } req, err := http.NewRequest(test.method, proxyURL.String(), requestBody) if test.requestHeader != nil { header := http.Header{} for k, v := range test.requestHeader { header.Add(k, v) } req.Header = header } if err != nil { t.Errorf("Error creating client request: %v", err) } client := &http.Client{} res, err := client.Do(req) if err != nil { t.Errorf("Error from proxy request: %v", err) } // Host if test.useLocationHost && backendHandler.requestHost != backendURL.Host { t.Errorf("Unexpected request host: %s", backendHandler.requestHost) } else if !test.useLocationHost && backendHandler.requestHost == backendURL.Host { t.Errorf("Unexpected request host: %s", backendHandler.requestHost) } if test.expectError != nil { if !responder.called { t.Errorf("%d: responder was not invoked", i) return } if !test.expectError(responder.err) { t.Errorf("%d: unexpected error: %v", i, responder.err) } return } // Validate backend request // Method if backendHandler.requestMethod != test.method { t.Errorf("Unexpected request method: %s. Expected: %s", backendHandler.requestMethod, test.method) } // Body if string(backendHandler.requestBody) != test.requestBody { t.Errorf("Unexpected request body: %s. Expected: %s", string(backendHandler.requestBody), test.requestBody) } // Path if backendHandler.requestURL.Path != test.expectedPath { t.Errorf("Unexpected request path: %s", backendHandler.requestURL.Path) } // Parameters validateParameters(t, test.name, backendHandler.requestURL.Query(), test.requestParams) // Headers validateHeaders(t, test.name+" backend request", backendHandler.requestHeader, test.requestHeader, nil) // Validate proxy response // Response Headers validateHeaders(t, test.name+" backend headers", res.Header, test.expectedRespHeader, test.notExpectedRespHeader) // Validate Body responseBody, err := io.ReadAll(res.Body) if err != nil { t.Errorf("Unexpected error reading response body: %v", err) } if rb := string(responseBody); rb != backendResponse { t.Errorf("Did not get expected response body: %s. Expected: %s", rb, backendResponse) } // Error if responder.called { t.Errorf("Unexpected proxy handler error: %v", responder.err) } }() } } type RoundTripperFunc func(req *http.Request) (*http.Response, error) func (fn RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { return fn(req) } func TestProxyUpgrade(t *testing.T) { localhostPool := x509.NewCertPool() if !localhostPool.AppendCertsFromPEM(localhostCert) { t.Errorf("error setting up localhostCert pool") } var d net.Dialer testcases := map[string]struct { ServerFunc func(http.Handler) *httptest.Server ProxyTransport http.RoundTripper UpgradeTransport UpgradeRequestRoundTripper ExpectedAuth string }{ "http": { ServerFunc: httptest.NewServer, ProxyTransport: nil, }, "both client and server support http2, but force to http/1.1 for upgrade": { ServerFunc: func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(exampleCert, exampleKey) if err != nil { t.Errorf("https (invalid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"http2", "http/1.1"}, } ts.StartTLS() return ts }, ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{ NextProtos: []string{"http2", "http/1.1"}, InsecureSkipVerify: true, }}), }, "https (invalid hostname + InsecureSkipVerify)": { ServerFunc: func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(exampleCert, exampleKey) if err != nil { t.Errorf("https (invalid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts }, ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}), }, "https (valid hostname + RootCAs)": { ServerFunc: func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { t.Errorf("https (valid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts }, ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), }, "https (valid hostname + RootCAs + custom dialer)": { ServerFunc: func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { t.Errorf("https (valid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts }, ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{DialContext: d.DialContext, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), }, "https (valid hostname + RootCAs + custom dialer + bearer token)": { ServerFunc: func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { t.Errorf("https (valid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts }, ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{DialContext: d.DialContext, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), UpgradeTransport: NewUpgradeRequestRoundTripper( utilnet.SetOldTransportDefaults(&http.Transport{DialContext: d.DialContext, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), RoundTripperFunc(func(req *http.Request) (*http.Response, error) { req = utilnet.CloneRequest(req) req.Header.Set("Authorization", "Bearer 1234") return MirrorRequest.RoundTrip(req) }), ), ExpectedAuth: "Bearer 1234", }, } for k, tc := range testcases { tcName := k backendPath := "/hello" func() { // Cleanup after each test case. backend := http.NewServeMux() backend.Handle("/hello", websocket.Handler(func(ws *websocket.Conn) { if ws.Request().Header.Get("Authorization") != tc.ExpectedAuth { t.Errorf("%s: unexpected headers on request: %v", k, ws.Request().Header) defer ws.Close() ws.Write([]byte("you failed")) return } defer ws.Close() body := make([]byte, 5) ws.Read(body) ws.Write([]byte("hello " + string(body))) })) backend.Handle("/redirect", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/hello", http.StatusFound) })) backendServer := tc.ServerFunc(backend) defer backendServer.Close() serverURL, _ := url.Parse(backendServer.URL) serverURL.Path = backendPath proxyHandler := NewUpgradeAwareHandler(serverURL, tc.ProxyTransport, false, false, &noErrorsAllowed{t: t}) proxyHandler.UpgradeTransport = tc.UpgradeTransport proxy := httptest.NewServer(proxyHandler) defer proxy.Close() ws, err := websocket.Dial("ws://"+proxy.Listener.Addr().String()+"/some/path", "", "http://127.0.0.1/") if err != nil { t.Fatalf("%s: websocket dial err: %s", tcName, err) } defer ws.Close() if _, err := ws.Write([]byte("world")); err != nil { t.Fatalf("%s: write err: %s", tcName, err) } response := make([]byte, 20) n, err := ws.Read(response) if err != nil { t.Fatalf("%s: read err: %s", tcName, err) } if e, a := "hello world", string(response[0:n]); e != a { t.Fatalf("%s: expected '%#v', got '%#v'", tcName, e, a) } }() } } type noErrorsAllowed struct { t *testing.T } func (r *noErrorsAllowed) Error(w http.ResponseWriter, req *http.Request, err error) { r.t.Error(err) } func TestProxyUpgradeConnectionErrorResponse(t *testing.T) { var ( responder *fakeResponder expectedErr = errors.New("EXPECTED") ) proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return &fakeConn{err: expectedErr}, nil }, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } responder = &fakeResponder{t: t, w: w} proxyHandler := NewUpgradeAwareHandler( &url.URL{ Host: "fake-backend", }, transport, false, true, responder, ) proxyHandler.ServeHTTP(w, r) })) defer proxy.Close() // Send request to proxy server. req, err := http.NewRequest("POST", "http://"+proxy.Listener.Addr().String()+"/some/path", nil) require.NoError(t, err) req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) resp, err := http.DefaultClient.Do(req) require.NoError(t, err) defer resp.Body.Close() // Expect error response. assert.True(t, responder.called) assert.Equal(t, fakeStatusCode, resp.StatusCode) msg, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Contains(t, string(msg), expectedErr.Error()) } func TestProxyUpgradeErrorResponseTerminates(t *testing.T) { for _, code := range []int{400, 500} { t.Run(fmt.Sprintf("code=%v", code), func(t *testing.T) { // Set up a backend server backend := http.NewServeMux() backend.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(code) w.Write([]byte(`some data`)) })) backend.Handle("/there", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Error("request to /there") })) backendServer := httptest.NewServer(backend) defer backendServer.Close() backendServerURL, _ := url.Parse(backendServer.URL) backendServerURL.Path = "/hello" // Set up a proxy pointing to a specific path on the backend proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &noErrorsAllowed{t: t}) proxy := httptest.NewServer(proxyHandler) defer proxy.Close() proxyURL, _ := url.Parse(proxy.URL) conn, err := net.Dial("tcp", proxyURL.Host) require.NoError(t, err) bufferedReader := bufio.NewReader(conn) // Send upgrade request resulting in a non-101 response from the backend req, _ := http.NewRequest("GET", "/", nil) req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) require.NoError(t, req.Write(conn)) // Verify we get the correct response and full message body content resp, err := http.ReadResponse(bufferedReader, nil) require.NoError(t, err) data, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, resp.StatusCode, code) require.Equal(t, data, []byte(`some data`)) resp.Body.Close() // try to read from the connection to verify it was closed b := make([]byte, 1) conn.SetReadDeadline(time.Now().Add(time.Second)) if _, err := conn.Read(b); err != io.EOF { t.Errorf("expected EOF, got %v", err) } // Send another request to another endpoint to verify it is not received req, _ = http.NewRequest("GET", "/there", nil) req.Write(conn) // wait to ensure the handler does not receive the request time.Sleep(time.Second) // clean up conn.Close() }) } } func TestProxyUpgradeErrorResponse(t *testing.T) { for _, code := range []int{200, 300, 302, 307} { t.Run(fmt.Sprintf("code=%v", code), func(t *testing.T) { // Set up a backend server backend := http.NewServeMux() backend.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://example.com/there", code) })) backendServer := httptest.NewServer(backend) defer backendServer.Close() backendServerURL, _ := url.Parse(backendServer.URL) backendServerURL.Path = "/hello" // Set up a proxy pointing to a specific path on the backend proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &fakeResponder{t: t}) proxy := httptest.NewServer(proxyHandler) defer proxy.Close() proxyURL, _ := url.Parse(proxy.URL) conn, err := net.Dial("tcp", proxyURL.Host) require.NoError(t, err) bufferedReader := bufio.NewReader(conn) // Send upgrade request resulting in a non-101 response from the backend req, _ := http.NewRequest("GET", "/", nil) req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) require.NoError(t, req.Write(conn)) // Verify we get the correct response and full message body content resp, err := http.ReadResponse(bufferedReader, nil) require.NoError(t, err) assert.Equal(t, fakeStatusCode, resp.StatusCode) resp.Body.Close() // clean up conn.Close() }) } } func TestRejectForwardingRedirectsOption(t *testing.T) { originalBody := []byte(`some data`) testCases := []struct { name string rejectForwardingRedirects bool serverStatusCode int redirect string expectStatusCode int expectBody []byte }{ { name: "reject redirection enabled in proxy, backend server sending 200 response", rejectForwardingRedirects: true, serverStatusCode: 200, expectStatusCode: 200, expectBody: originalBody, }, { name: "reject redirection enabled in proxy, backend server sending 301 response", rejectForwardingRedirects: true, serverStatusCode: 301, redirect: "/", expectStatusCode: 502, expectBody: []byte(`the backend attempted to redirect this request, which is not permitted`), }, { name: "reject redirection enabled in proxy, backend server sending 304 response with a location header", rejectForwardingRedirects: true, serverStatusCode: 304, redirect: "/", expectStatusCode: 502, expectBody: []byte(`the backend attempted to redirect this request, which is not permitted`), }, { name: "reject redirection enabled in proxy, backend server sending 304 response with no location header", rejectForwardingRedirects: true, serverStatusCode: 304, expectStatusCode: 304, expectBody: []byte{}, // client doesn't read the body for 304 responses }, { name: "reject redirection disabled in proxy, backend server sending 200 response", rejectForwardingRedirects: false, serverStatusCode: 200, expectStatusCode: 200, expectBody: originalBody, }, { name: "reject redirection disabled in proxy, backend server sending 301 response", rejectForwardingRedirects: false, serverStatusCode: 301, redirect: "/", expectStatusCode: 301, expectBody: originalBody, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Set up a backend server backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tc.redirect != "" { w.Header().Set("Location", tc.redirect) } w.WriteHeader(tc.serverStatusCode) w.Write(originalBody) })) defer backendServer.Close() backendServerURL, _ := url.Parse(backendServer.URL) // Set up a proxy pointing to the backend proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &fakeResponder{t: t}) proxyHandler.RejectForwardingRedirects = tc.rejectForwardingRedirects proxy := httptest.NewServer(proxyHandler) defer proxy.Close() proxyURL, _ := url.Parse(proxy.URL) conn, err := net.Dial("tcp", proxyURL.Host) require.NoError(t, err) bufferedReader := bufio.NewReader(conn) req, _ := http.NewRequest("GET", proxyURL.String(), nil) require.NoError(t, req.Write(conn)) // Verify we get the correct response and message body content resp, err := http.ReadResponse(bufferedReader, nil) require.NoError(t, err) assert.Equal(t, tc.expectStatusCode, resp.StatusCode) data, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Equal(t, tc.expectBody, data) assert.Equal(t, int64(len(tc.expectBody)), resp.ContentLength) resp.Body.Close() // clean up conn.Close() }) } } func TestDefaultProxyTransport(t *testing.T) { tests := []struct { name, url, location, expectedScheme, expectedHost, expectedPathPrepend string }{ { name: "simple path", url: "http://test.server:8080/a/test/location", location: "http://localhost/location", expectedScheme: "http", expectedHost: "test.server:8080", expectedPathPrepend: "/a/test", }, { name: "empty path", url: "http://test.server:8080/a/test/", location: "http://localhost", expectedScheme: "http", expectedHost: "test.server:8080", expectedPathPrepend: "/a/test", }, { name: "location ending in slash", url: "http://test.server:8080/a/test/", location: "http://localhost/", expectedScheme: "http", expectedHost: "test.server:8080", expectedPathPrepend: "/a/test", }, } for _, test := range tests { locURL, _ := url.Parse(test.location) URL, _ := url.Parse(test.url) h := NewUpgradeAwareHandler(locURL, nil, false, false, nil) result := h.defaultProxyTransport(URL, nil) transport := result.(*corsRemovingTransport).RoundTripper.(*Transport) if transport.Scheme != test.expectedScheme { t.Errorf("%s: unexpected scheme. Actual: %s, Expected: %s", test.name, transport.Scheme, test.expectedScheme) } if transport.Host != test.expectedHost { t.Errorf("%s: unexpected host. Actual: %s, Expected: %s", test.name, transport.Host, test.expectedHost) } if transport.PathPrepend != test.expectedPathPrepend { t.Errorf("%s: unexpected path prepend. Actual: %s, Expected: %s", test.name, transport.PathPrepend, test.expectedPathPrepend) } } } func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) { chunk := func(data []byte) []byte { out := &bytes.Buffer{} chunker := httputil.NewChunkedWriter(out) for _, b := range data { if _, err := chunker.Write([]byte{b}); err != nil { panic(err) } } chunker.Close() out.Write([]byte("\r\n")) return out.Bytes() } zip := func(data []byte) []byte { out := &bytes.Buffer{} zipper := gzip.NewWriter(out) if _, err := zipper.Write(data); err != nil { panic(err) } zipper.Close() return out.Bytes() } sampleData := []byte("abcde") table := map[string]struct { reqHeaders http.Header reqBody []byte expectedHeaders http.Header expectedBody []byte }{ "content-length": { reqHeaders: http.Header{ "Content-Length": []string{"5"}, }, reqBody: sampleData, expectedHeaders: http.Header{ "Content-Length": []string{"5"}, "Content-Encoding": nil, // none set "Transfer-Encoding": nil, // none set }, expectedBody: sampleData, }, "content-length + gzip content-encoding": { reqHeaders: http.Header{ "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, "Content-Encoding": []string{"gzip"}, }, reqBody: zip(sampleData), expectedHeaders: http.Header{ "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, "Content-Encoding": []string{"gzip"}, "Transfer-Encoding": nil, // none set }, expectedBody: zip(sampleData), }, "chunked transfer-encoding": { reqHeaders: http.Header{ "Transfer-Encoding": []string{"chunked"}, }, reqBody: chunk(sampleData), expectedHeaders: http.Header{ "Content-Length": nil, // none set "Content-Encoding": nil, // none set "Transfer-Encoding": nil, // Transfer-Encoding gets removed }, expectedBody: sampleData, // sample data is unchunked }, "chunked transfer-encoding + gzip content-encoding": { reqHeaders: http.Header{ "Content-Encoding": []string{"gzip"}, "Transfer-Encoding": []string{"chunked"}, }, reqBody: chunk(zip(sampleData)), expectedHeaders: http.Header{ "Content-Length": nil, // none set "Content-Encoding": []string{"gzip"}, "Transfer-Encoding": nil, // gets removed }, expectedBody: zip(sampleData), // sample data is unchunked, but content-encoding is preserved }, // "Transfer-Encoding: gzip" is not supported by go // See http/transfer.go#fixTransferEncoding (https://golang.org/src/net/http/transfer.go#L427) // Once it is supported, this test case should succeed // // "gzip+chunked transfer-encoding": { // reqHeaders: http.Header{ // "Transfer-Encoding": []string{"chunked,gzip"}, // }, // reqBody: chunk(zip(sampleData)), // // expectedHeaders: http.Header{ // "Content-Length": nil, // no content-length headers // "Transfer-Encoding": nil, // Transfer-Encoding gets removed // }, // expectedBody: sampleData, // }, } successfulResponse := "backend passed tests" for k, item := range table { // Start the downstream server downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { // Verify headers for header, v := range item.expectedHeaders { if !reflect.DeepEqual(v, req.Header[header]) { t.Errorf("%s: Expected headers for %s to be %v, got %v", k, header, v, req.Header[header]) } } // Read body body, err := io.ReadAll(req.Body) if err != nil { t.Errorf("%s: unexpected error %v", k, err) } req.Body.Close() // Verify length if req.ContentLength > 0 && req.ContentLength != int64(len(body)) { t.Errorf("%s: ContentLength was %d, len(data) was %d", k, req.ContentLength, len(body)) } // Verify content if !bytes.Equal(item.expectedBody, body) { t.Errorf("%s: Expected %q, got %q", k, string(item.expectedBody), string(body)) } // Write successful response w.Write([]byte(successfulResponse)) })) defer downstreamServer.Close() responder := &fakeResponder{t: t} backendURL, _ := url.Parse(downstreamServer.URL) proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, false, responder) proxyServer := httptest.NewServer(proxyHandler) defer proxyServer.Close() // Dial the proxy server conn, err := net.Dial(proxyServer.Listener.Addr().Network(), proxyServer.Listener.Addr().String()) if err != nil { t.Errorf("unexpected error %v", err) continue } defer conn.Close() // Add standard http 1.1 headers if item.reqHeaders == nil { item.reqHeaders = http.Header{} } item.reqHeaders.Add("Connection", "close") item.reqHeaders.Add("Host", proxyServer.Listener.Addr().String()) // Write the request headers if _, err := fmt.Fprint(conn, "POST / HTTP/1.1\r\n"); err != nil { t.Fatalf("%s unexpected error %v", k, err) } for header, values := range item.reqHeaders { for _, value := range values { if _, err := fmt.Fprintf(conn, "%s: %s\r\n", header, value); err != nil { t.Fatalf("%s: unexpected error %v", k, err) } } } // Header separator if _, err := fmt.Fprint(conn, "\r\n"); err != nil { t.Fatalf("%s: unexpected error %v", k, err) } // Body if _, err := conn.Write(item.reqBody); err != nil { t.Fatalf("%s: unexpected error %v", k, err) } // Read response response, err := io.ReadAll(conn) if err != nil { t.Errorf("%s: unexpected error %v", k, err) continue } if !strings.HasSuffix(string(response), successfulResponse) { t.Errorf("%s: Did not get successful response: %s", k, string(response)) continue } } } func TestFlushIntervalHeaders(t *testing.T) { const expected = "hi" stopCh := make(chan struct{}) backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("MyHeader", expected) w.WriteHeader(200) w.(http.Flusher).Flush() <-stopCh })) defer backend.Close() defer close(stopCh) backendURL, err := url.Parse(backend.URL) if err != nil { t.Fatal(err) } responder := &fakeResponder{t: t} proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, false, responder) frontend := httptest.NewServer(proxyHandler) defer frontend.Close() req, _ := http.NewRequest("GET", frontend.URL, nil) req.Close = true ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second) defer cancel() req = req.WithContext(ctx) res, err := frontend.Client().Do(req) if err != nil { t.Fatalf("Get: %v", err) } defer res.Body.Close() if res.Header.Get("MyHeader") != expected { t.Errorf("got header %q; expected %q", res.Header.Get("MyHeader"), expected) } } type fakeRT struct { err error } func (frt *fakeRT) RoundTrip(*http.Request) (*http.Response, error) { return nil, frt.err } // TestErrorPropagation checks if the default transport doesn't swallow the errors by providing a fakeResponder that intercepts and stores the error. func TestErrorPropagation(t *testing.T) { backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { panic("unreachable") })) defer backend.Close() backendURL, err := url.Parse(backend.URL) if err != nil { t.Fatal(err) } responder := &fakeResponder{t: t} expectedErr := errors.New("nasty error") proxyHandler := NewUpgradeAwareHandler(backendURL, &fakeRT{err: expectedErr}, true, false, responder) frontend := httptest.NewServer(proxyHandler) defer frontend.Close() req, _ := http.NewRequest("GET", frontend.URL, nil) req.Close = true ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second) defer cancel() req = req.WithContext(ctx) res, err := frontend.Client().Do(req) if err != nil { t.Fatalf("Get: %v", err) } defer res.Body.Close() if res.StatusCode != fakeStatusCode { t.Fatalf("unexpected HTTP status code returned: %v, expected: %v", res.StatusCode, fakeStatusCode) } if !strings.Contains(responder.err.Error(), expectedErr.Error()) { t.Fatalf("responder got unexpected error: %v, expected the error to contain %q", responder.err.Error(), expectedErr.Error()) } } func TestProxyRedirectsforRootPath(t *testing.T) { tests := []struct { name string method string requestPath string expectedHeader http.Header expectedStatusCode int redirect bool }{ { name: "root path, simple get", method: "GET", requestPath: "", redirect: true, expectedStatusCode: 301, expectedHeader: http.Header{ "Location": []string{"/"}, }, }, { name: "root path, simple put", method: "PUT", requestPath: "", redirect: false, expectedStatusCode: 200, }, { name: "root path, simple head", method: "HEAD", requestPath: "", redirect: true, expectedStatusCode: 301, expectedHeader: http.Header{ "Location": []string{"/"}, }, }, { name: "root path, simple delete with params", method: "DELETE", requestPath: "", redirect: false, expectedStatusCode: 200, }, } for _, test := range tests { func() { w := httptest.NewRecorder() req, err := http.NewRequest(test.method, test.requestPath, nil) if err != nil { t.Fatal(err) } redirect := proxyRedirectsforRootPath(test.requestPath, w, req) if got, want := redirect, test.redirect; got != want { t.Errorf("Expected redirect state %v; got %v", want, got) } res := w.Result() if got, want := res.StatusCode, test.expectedStatusCode; got != want { t.Errorf("Expected status code %d; got %d", want, got) } if res.StatusCode == 301 && !reflect.DeepEqual(res.Header, test.expectedHeader) { t.Errorf("Expected location header to be %v, got %v", test.expectedHeader, res.Header) } }() } } // exampleCert was generated from crypto/tls/generate_cert.go with the following command: // // go run generate_cert.go --rsa-bits 1024 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h var exampleCert = []byte(`-----BEGIN CERTIFICATE----- MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy +i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he 7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+ 2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL 1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP uvEQdw== -----END CERTIFICATE-----`) var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9 p+tnzm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/ JlNy+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj 7aMivXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJ OoUGf8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb +9he7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABAoIBAQClt4CiYaaF5ltx wVDjz6TNcJUBUs3CKE+uWAYFnF5Ii1nyU876Pxj8Aaz9fHZ6Kde0GkwiXY7gFOj1 YHo2tzcELSKS/SEDZcYbYFTGCjq13g1AH74R+SV6WZLn+5m8kPvVrM1ZWap188H5 bmuCkRDqVmIvShkbRW7EwhC35J9fiuW3majC/sjmsxtxyP6geWmu4f5/Ttqahcdb osPZIgIIPzqAkNtkLTi7+meHYI9wlrGhL7XZTwnJ1Oc/Y67zzmbthLYB5YFSLUew rXT58jtSjX4gbiQyheBSrWxW08QE4qYg6jJlAdffHhWv72hJW2MCXhuXp8gJs/Do XLRHGwSBAoGBAMdNtsbe4yae/QeHUPGxNW0ipa0yoTF6i+VYoxvqiRMzDM3+3L8k dgI1rr4330SivqDahMA/odWtM/9rVwJI2B2QhZLMHA0n9ytH007OO9TghgVB12nN xosRYBpKdHXyyvV/MUZl7Jux6zKIzRDWOkF95VVYPcAaxJqd1E5/jJ6JAoGBAN51 QrebA1w/jfydeqQTz1sK01sbO4HYj4qGfo/JarVqGEkm1azeBBPPRnHz3jNKnCkM S4PpqRDased3NIcViXlAgoqPqivZ8mQa/Rb146l7WaTErASHsZ023OGrxsr/Ed6N P3GrmvxVJjebaFNaQ9sP80dLkpgeas0t2TY8iQNRAoGATOcnx8TpUVW3vNfx29DN FLdxxkrq9/SZVn3FMlhlXAsuva3B799ZybB9JNjaRdmmRNsMrkHfaFvU3JHGmRMS kRXa9LHdgRYSwZiNaLMbUyDvlce6HxFPswmZU4u3NGvi9KeHk+pwSgN1BaLTvdNr 1ymE/FF4QlAR3LdZ3JBK6kECgYEA0wW4/CJ31ZIURoW8SNjh4iMqy0nR8SJVR7q9 Y/hU2TKDRyEnoIwaohAFayNCrLUh3W5kVAXa8roB+OgDVAECH5sqOfZ+HorofD19 x8II7ESujLZj1whBXDkm3ovsT7QWZ17lyBZZNvQvBKDPHgKKS8udowv1S4fPGENd wS07a4ECgYEAwLSbmMIVJme0jFjsp5d1wOGA2Qi2ZwGIAVlsbnJtygrU/hSBfnu8 VfyJSCgg3fPe7kChWKlfcOebVKSb68LKRsz1Lz1KdbY0HOJFp/cT4lKmDAlRY9gq LB4rdf46lV0mUkvd2/oofIbTrzukjQSnyfLawb/2uJGV1IkTcZcn9CI= -----END RSA PRIVATE KEY-----`) golang-k8s-apimachinery-0.29.0/pkg/util/rand/000077500000000000000000000000001453143165200207115ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/rand/rand.go000066400000000000000000000070071453143165200221700ustar00rootroot00000000000000/* Copyright 2015 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 rand provides utilities related to randomization. package rand import ( "math/rand" "sync" "time" ) var rng = struct { sync.Mutex rand *rand.Rand }{ rand: rand.New(rand.NewSource(time.Now().UnixNano())), } // Int returns a non-negative pseudo-random int. func Int() int { rng.Lock() defer rng.Unlock() return rng.rand.Int() } // Intn generates an integer in range [0,max). // By design this should panic if input is invalid, <= 0. func Intn(max int) int { rng.Lock() defer rng.Unlock() return rng.rand.Intn(max) } // IntnRange generates an integer in range [min,max). // By design this should panic if input is invalid, <= 0. func IntnRange(min, max int) int { rng.Lock() defer rng.Unlock() return rng.rand.Intn(max-min) + min } // IntnRange generates an int64 integer in range [min,max). // By design this should panic if input is invalid, <= 0. func Int63nRange(min, max int64) int64 { rng.Lock() defer rng.Unlock() return rng.rand.Int63n(max-min) + min } // Seed seeds the rng with the provided seed. func Seed(seed int64) { rng.Lock() defer rng.Unlock() rng.rand = rand.New(rand.NewSource(seed)) } // Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n) // from the default Source. func Perm(n int) []int { rng.Lock() defer rng.Unlock() return rng.rand.Perm(n) } const ( // We omit vowels from the set of available characters to reduce the chances // of "bad words" being formed. alphanums = "bcdfghjklmnpqrstvwxz2456789" // No. of bits required to index into alphanums string. alphanumsIdxBits = 5 // Mask used to extract last alphanumsIdxBits of an int. alphanumsIdxMask = 1<>= alphanumsIdxBits remaining-- } return string(b) } // SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and // ensures that strings generated from hash functions appear consistent throughout the API. func SafeEncodeString(s string) string { r := make([]byte, len(s)) for i, b := range []rune(s) { r[i] = alphanums[(int(b) % len(alphanums))] } return string(r) } golang-k8s-apimachinery-0.29.0/pkg/util/rand/rand_test.go000066400000000000000000000047531453143165200232340ustar00rootroot00000000000000/* Copyright 2015 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 rand import ( "math/rand" "strings" "testing" ) const ( maxRangeTestCount = 500 testStringLength = 32 ) func TestString(t *testing.T) { valid := "bcdfghjklmnpqrstvwxz2456789" for _, l := range []int{0, 1, 2, 10, 123} { s := String(l) if len(s) != l { t.Errorf("expected string of size %d, got %q", l, s) } for _, c := range s { if !strings.ContainsRune(valid, c) { t.Errorf("expected valid characters, got %v", c) } } } } // Confirm that panic occurs on invalid input. func TestRangePanic(t *testing.T) { defer func() { if err := recover(); err == nil { t.Errorf("Panic didn't occur!") } }() // Should result in an error... Intn(0) } func TestIntn(t *testing.T) { // 0 is invalid. for _, max := range []int{1, 2, 10, 123} { inrange := Intn(max) if inrange < 0 || inrange > max { t.Errorf("%v out of range (0,%v)", inrange, max) } } } func TestPerm(t *testing.T) { Seed(5) rand.Seed(5) for i := 1; i < 20; i++ { actual := Perm(i) expected := rand.Perm(i) for j := 0; j < i; j++ { if actual[j] != expected[j] { t.Errorf("Perm call result is unexpected") } } } } func TestIntnRange(t *testing.T) { // 0 is invalid. for min, max := range map[int]int{1: 2, 10: 123, 100: 500} { for i := 0; i < maxRangeTestCount; i++ { inrange := IntnRange(min, max) if inrange < min || inrange >= max { t.Errorf("%v out of range (%v,%v)", inrange, min, max) } } } } func TestInt63nRange(t *testing.T) { // 0 is invalid. for min, max := range map[int64]int64{1: 2, 10: 123, 100: 500} { for i := 0; i < maxRangeTestCount; i++ { inrange := Int63nRange(min, max) if inrange < min || inrange >= max { t.Errorf("%v out of range (%v,%v)", inrange, min, max) } } } } func BenchmarkRandomStringGeneration(b *testing.B) { b.ResetTimer() var s string for i := 0; i < b.N; i++ { s = String(testStringLength) } b.StopTimer() if len(s) == 0 { b.Fatal(s) } } golang-k8s-apimachinery-0.29.0/pkg/util/remotecommand/000077500000000000000000000000001453143165200226175ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/remotecommand/constants.go000066400000000000000000000046001453143165200251620ustar00rootroot00000000000000/* Copyright 2016 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 remotecommand import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( DefaultStreamCreationTimeout = 30 * time.Second // The SPDY subprotocol "channel.k8s.io" is used for remote command // attachment/execution. This represents the initial unversioned subprotocol, // which has the known bugs https://issues.k8s.io/13394 and // https://issues.k8s.io/13395. StreamProtocolV1Name = "channel.k8s.io" // The SPDY subprotocol "v2.channel.k8s.io" is used for remote command // attachment/execution. It is the second version of the subprotocol and // resolves the issues present in the first version. StreamProtocolV2Name = "v2.channel.k8s.io" // The SPDY subprotocol "v3.channel.k8s.io" is used for remote command // attachment/execution. It is the third version of the subprotocol and // adds support for resizing container terminals. StreamProtocolV3Name = "v3.channel.k8s.io" // The SPDY subprotocol "v4.channel.k8s.io" is used for remote command // attachment/execution. It is the 4th version of the subprotocol and // adds support for exit codes. StreamProtocolV4Name = "v4.channel.k8s.io" // The subprotocol "v5.channel.k8s.io" is used for remote command // attachment/execution. It is the 5th version of the subprotocol and // adds support for a CLOSE signal. StreamProtocolV5Name = "v5.channel.k8s.io" NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode") ExitCodeCauseType = metav1.CauseType("ExitCode") // RemoteCommand stream identifiers. The first three identifiers (for STDIN, // STDOUT, STDERR) are the same as their file descriptors. StreamStdIn = 0 StreamStdOut = 1 StreamStdErr = 2 StreamErr = 3 StreamResize = 4 StreamClose = 255 ) var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name} golang-k8s-apimachinery-0.29.0/pkg/util/runtime/000077500000000000000000000000001453143165200214505ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/runtime/runtime.go000066400000000000000000000132301453143165200234610ustar00rootroot00000000000000/* 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 runtime import ( "fmt" "net/http" "runtime" "sync" "time" "k8s.io/klog/v2" ) var ( // ReallyCrash controls the behavior of HandleCrash and defaults to // true. It's exposed so components can optionally set to false // to restore prior behavior. This flag is mostly used for tests to validate // crash conditions. ReallyCrash = true ) // PanicHandlers is a list of functions which will be invoked when a panic happens. var PanicHandlers = []func(interface{}){logPanic} // HandleCrash simply catches a crash and logs an error. Meant to be called via // defer. Additional context-specific handlers can be provided, and will be // called in case of panic. HandleCrash actually crashes, after calling the // handlers and logging the panic message. // // E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully. func HandleCrash(additionalHandlers ...func(interface{})) { if r := recover(); r != nil { for _, fn := range PanicHandlers { fn(r) } for _, fn := range additionalHandlers { fn(r) } if ReallyCrash { // Actually proceed to panic. panic(r) } } } // logPanic logs the caller tree when a panic occurs (except in the special case of http.ErrAbortHandler). func logPanic(r interface{}) { if r == http.ErrAbortHandler { // honor the http.ErrAbortHandler sentinel panic value: // ErrAbortHandler is a sentinel panic value to abort a handler. // While any panic from ServeHTTP aborts the response to the client, // panicking with ErrAbortHandler also suppresses logging of a stack trace to the server's error log. return } // Same as stdlib http server code. Manually allocate stack trace buffer size // to prevent excessively large logs const size = 64 << 10 stacktrace := make([]byte, size) stacktrace = stacktrace[:runtime.Stack(stacktrace, false)] if _, ok := r.(string); ok { klog.Errorf("Observed a panic: %s\n%s", r, stacktrace) } else { klog.Errorf("Observed a panic: %#v (%v)\n%s", r, r, stacktrace) } } // ErrorHandlers is a list of functions which will be invoked when a nonreturnable // error occurs. // TODO(lavalamp): for testability, this and the below HandleError function // should be packaged up into a testable and reusable object. var ErrorHandlers = []func(error){ logError, (&rudimentaryErrorBackoff{ lastErrorTime: time.Now(), // 1ms was the number folks were able to stomach as a global rate limit. // If you need to log errors more than 1000 times a second you // should probably consider fixing your code instead. :) minPeriod: time.Millisecond, }).OnError, } // HandlerError is a method to invoke when a non-user facing piece of code cannot // return an error and needs to indicate it has been ignored. Invoking this method // is preferable to logging the error - the default behavior is to log but the // errors may be sent to a remote server for analysis. func HandleError(err error) { // this is sometimes called with a nil error. We probably shouldn't fail and should do nothing instead if err == nil { return } for _, fn := range ErrorHandlers { fn(err) } } // logError prints an error with the call stack of the location it was reported func logError(err error) { klog.ErrorDepth(2, err) } type rudimentaryErrorBackoff struct { minPeriod time.Duration // immutable // TODO(lavalamp): use the clock for testability. Need to move that // package for that to be accessible here. lastErrorTimeLock sync.Mutex lastErrorTime time.Time } // OnError will block if it is called more often than the embedded period time. // This will prevent overly tight hot error loops. func (r *rudimentaryErrorBackoff) OnError(error) { now := time.Now() // start the timer before acquiring the lock r.lastErrorTimeLock.Lock() d := now.Sub(r.lastErrorTime) r.lastErrorTime = time.Now() r.lastErrorTimeLock.Unlock() // Do not sleep with the lock held because that causes all callers of HandleError to block. // We only want the current goroutine to block. // A negative or zero duration causes time.Sleep to return immediately. // If the time moves backwards for any reason, do nothing. time.Sleep(r.minPeriod - d) } // GetCaller returns the caller of the function that calls it. func GetCaller() string { var pc [1]uintptr runtime.Callers(3, pc[:]) f := runtime.FuncForPC(pc[0]) if f == nil { return "Unable to find caller" } return f.Name() } // RecoverFromPanic replaces the specified error with an error containing the // original error, and the call tree when a panic occurs. This enables error // handlers to handle errors and panics the same way. func RecoverFromPanic(err *error) { if r := recover(); r != nil { // Same as stdlib http server code. Manually allocate stack trace buffer size // to prevent excessively large logs const size = 64 << 10 stacktrace := make([]byte, size) stacktrace = stacktrace[:runtime.Stack(stacktrace, false)] *err = fmt.Errorf( "recovered from panic %q. (err=%v) Call stack:\n%s", r, *err, stacktrace) } } // Must panics on non-nil errors. Useful to handling programmer level errors. func Must(err error) { if err != nil { panic(err) } } golang-k8s-apimachinery-0.29.0/pkg/util/runtime/runtime_test.go000066400000000000000000000105411453143165200245220ustar00rootroot00000000000000/* 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 runtime import ( "bytes" "fmt" "io" "net/http" "os" "regexp" "strings" "sync" "testing" "time" ) func TestHandleCrash(t *testing.T) { defer func() { if x := recover(); x == nil { t.Errorf("Expected a panic to recover from") } }() defer HandleCrash() panic("Test Panic") } func TestCustomHandleCrash(t *testing.T) { old := PanicHandlers defer func() { PanicHandlers = old }() var result interface{} PanicHandlers = []func(interface{}){ func(r interface{}) { result = r }, } func() { defer func() { if x := recover(); x == nil { t.Errorf("Expected a panic to recover from") } }() defer HandleCrash() panic("test") }() if result != "test" { t.Errorf("did not receive custom handler") } } func TestCustomHandleError(t *testing.T) { old := ErrorHandlers defer func() { ErrorHandlers = old }() var result error ErrorHandlers = []func(error){ func(err error) { result = err }, } err := fmt.Errorf("test") HandleError(err) if result != err { t.Errorf("did not receive custom handler") } } func TestHandleCrashLog(t *testing.T) { log, err := captureStderr(func() { defer func() { if r := recover(); r == nil { t.Fatalf("expected a panic to recover from") } }() defer HandleCrash() panic("test panic") }) if err != nil { t.Fatalf("%v", err) } // Example log: // // ...] Observed a panic: test panic // goroutine 6 [running]: // command-line-arguments.logPanic(0x..., 0x...) // .../src/k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go:69 +0x... lines := strings.Split(log, "\n") if len(lines) < 4 { t.Fatalf("panic log should have 1 line of message, 1 line per goroutine and 2 lines per function call") } if match, _ := regexp.MatchString("Observed a panic: test panic", lines[0]); !match { t.Errorf("mismatch panic message: %s", lines[0]) } // The following regexp's verify that Kubernetes panic log matches Golang stdlib // stacktrace pattern. We need to update these regexp's if stdlib changes its pattern. if match, _ := regexp.MatchString(`goroutine [0-9]+ \[.+\]:`, lines[1]); !match { t.Errorf("mismatch goroutine: %s", lines[1]) } if match, _ := regexp.MatchString(`logPanic(.*)`, lines[2]); !match { t.Errorf("mismatch symbolized function name: %s", lines[2]) } if match, _ := regexp.MatchString(`runtime\.go:[0-9]+ \+0x`, lines[3]); !match { t.Errorf("mismatch file/line/offset information: %s", lines[3]) } } func TestHandleCrashLogSilenceHTTPErrAbortHandler(t *testing.T) { log, err := captureStderr(func() { defer func() { if r := recover(); r != http.ErrAbortHandler { t.Fatalf("expected to recover from http.ErrAbortHandler") } }() defer HandleCrash() panic(http.ErrAbortHandler) }) if err != nil { t.Fatalf("%v", err) } if len(log) > 0 { t.Fatalf("expected no stderr log, got: %s", log) } } // captureStderr redirects stderr to result string, and then restore stderr from backup func captureStderr(f func()) (string, error) { r, w, err := os.Pipe() if err != nil { return "", err } bak := os.Stderr os.Stderr = w defer func() { os.Stderr = bak }() resultCh := make(chan string) // copy the output in a separate goroutine so printing can't block indefinitely go func() { var buf bytes.Buffer io.Copy(&buf, r) resultCh <- buf.String() }() f() w.Close() return <-resultCh, nil } func Test_rudimentaryErrorBackoff_OnError_ParallelSleep(t *testing.T) { r := &rudimentaryErrorBackoff{ minPeriod: time.Second, } start := make(chan struct{}) var wg sync.WaitGroup for i := 0; i < 30; i++ { wg.Add(1) go func() { <-start r.OnError(nil) // input error is ignored wg.Done() }() } st := time.Now() close(start) wg.Wait() if since := time.Since(st); since > 5*time.Second { t.Errorf("OnError slept for too long: %s", since) } } golang-k8s-apimachinery-0.29.0/pkg/util/sets/000077500000000000000000000000001453143165200207435ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/sets/byte.go000066400000000000000000000075571453143165200222530ustar00rootroot00000000000000/* Copyright 2022 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 sets // Byte is a set of bytes, implemented via map[byte]struct{} for minimal memory consumption. // // Deprecated: use generic Set instead. // new ways: // s1 := Set[byte]{} // s2 := New[byte]() type Byte map[byte]Empty // NewByte creates a Byte from a list of values. func NewByte(items ...byte) Byte { return Byte(New[byte](items...)) } // ByteKeySet creates a Byte from a keys of a map[byte](? extends interface{}). // If the value passed in is not actually a map, this will panic. func ByteKeySet[T any](theMap map[byte]T) Byte { return Byte(KeySet(theMap)) } // Insert adds items to the set. func (s Byte) Insert(items ...byte) Byte { return Byte(cast(s).Insert(items...)) } // Delete removes all items from the set. func (s Byte) Delete(items ...byte) Byte { return Byte(cast(s).Delete(items...)) } // Has returns true if and only if item is contained in the set. func (s Byte) Has(item byte) bool { return cast(s).Has(item) } // HasAll returns true if and only if all items are contained in the set. func (s Byte) HasAll(items ...byte) bool { return cast(s).HasAll(items...) } // HasAny returns true if any items are contained in the set. func (s Byte) HasAny(items ...byte) bool { return cast(s).HasAny(items...) } // Clone returns a new set which is a copy of the current set. func (s Byte) Clone() Byte { return Byte(cast(s).Clone()) } // Difference returns a set of objects that are not in s2. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s1 Byte) Difference(s2 Byte) Byte { return Byte(cast(s1).Difference(cast(s2))) } // SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.SymmetricDifference(s2) = {a3, a4, a5} // s2.SymmetricDifference(s1) = {a3, a4, a5} func (s1 Byte) SymmetricDifference(s2 Byte) Byte { return Byte(cast(s1).SymmetricDifference(cast(s2))) } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 Byte) Union(s2 Byte) Byte { return Byte(cast(s1).Union(cast(s2))) } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 Byte) Intersection(s2 Byte) Byte { return Byte(cast(s1).Intersection(cast(s2))) } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 Byte) IsSuperset(s2 Byte) bool { return cast(s1).IsSuperset(cast(s2)) } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 Byte) Equal(s2 Byte) bool { return cast(s1).Equal(cast(s2)) } // List returns the contents as a sorted byte slice. func (s Byte) List() []byte { return List(cast(s)) } // UnsortedList returns the slice with contents in random order. func (s Byte) UnsortedList() []byte { return cast(s).UnsortedList() } // PopAny returns a single element from the set. func (s Byte) PopAny() (byte, bool) { return cast(s).PopAny() } // Len returns the size of the set. func (s Byte) Len() int { return len(s) } golang-k8s-apimachinery-0.29.0/pkg/util/sets/doc.go000066400000000000000000000013241453143165200220370ustar00rootroot00000000000000/* Copyright 2022 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 sets has generic set and specified sets. Generic set will // replace specified ones over time. And specific ones are deprecated. package sets golang-k8s-apimachinery-0.29.0/pkg/util/sets/empty.go000066400000000000000000000014251453143165200224320ustar00rootroot00000000000000/* Copyright 2022 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 sets // Empty is public since it is used by some internal API objects for conversions between external // string arrays and internal sets, and conversion logic requires public types today. type Empty struct{} golang-k8s-apimachinery-0.29.0/pkg/util/sets/int.go000066400000000000000000000074551453143165200220770ustar00rootroot00000000000000/* Copyright 2022 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 sets // Int is a set of ints, implemented via map[int]struct{} for minimal memory consumption. // // Deprecated: use generic Set instead. // new ways: // s1 := Set[int]{} // s2 := New[int]() type Int map[int]Empty // NewInt creates a Int from a list of values. func NewInt(items ...int) Int { return Int(New[int](items...)) } // IntKeySet creates a Int from a keys of a map[int](? extends interface{}). // If the value passed in is not actually a map, this will panic. func IntKeySet[T any](theMap map[int]T) Int { return Int(KeySet(theMap)) } // Insert adds items to the set. func (s Int) Insert(items ...int) Int { return Int(cast(s).Insert(items...)) } // Delete removes all items from the set. func (s Int) Delete(items ...int) Int { return Int(cast(s).Delete(items...)) } // Has returns true if and only if item is contained in the set. func (s Int) Has(item int) bool { return cast(s).Has(item) } // HasAll returns true if and only if all items are contained in the set. func (s Int) HasAll(items ...int) bool { return cast(s).HasAll(items...) } // HasAny returns true if any items are contained in the set. func (s Int) HasAny(items ...int) bool { return cast(s).HasAny(items...) } // Clone returns a new set which is a copy of the current set. func (s Int) Clone() Int { return Int(cast(s).Clone()) } // Difference returns a set of objects that are not in s2. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s1 Int) Difference(s2 Int) Int { return Int(cast(s1).Difference(cast(s2))) } // SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.SymmetricDifference(s2) = {a3, a4, a5} // s2.SymmetricDifference(s1) = {a3, a4, a5} func (s1 Int) SymmetricDifference(s2 Int) Int { return Int(cast(s1).SymmetricDifference(cast(s2))) } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 Int) Union(s2 Int) Int { return Int(cast(s1).Union(cast(s2))) } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 Int) Intersection(s2 Int) Int { return Int(cast(s1).Intersection(cast(s2))) } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 Int) IsSuperset(s2 Int) bool { return cast(s1).IsSuperset(cast(s2)) } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 Int) Equal(s2 Int) bool { return cast(s1).Equal(cast(s2)) } // List returns the contents as a sorted int slice. func (s Int) List() []int { return List(cast(s)) } // UnsortedList returns the slice with contents in random order. func (s Int) UnsortedList() []int { return cast(s).UnsortedList() } // PopAny returns a single element from the set. func (s Int) PopAny() (int, bool) { return cast(s).PopAny() } // Len returns the size of the set. func (s Int) Len() int { return len(s) } golang-k8s-apimachinery-0.29.0/pkg/util/sets/int32.go000066400000000000000000000076611453143165200222430ustar00rootroot00000000000000/* Copyright 2022 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 sets // Int32 is a set of int32s, implemented via map[int32]struct{} for minimal memory consumption. // // Deprecated: use generic Set instead. // new ways: // s1 := Set[int32]{} // s2 := New[int32]() type Int32 map[int32]Empty // NewInt32 creates a Int32 from a list of values. func NewInt32(items ...int32) Int32 { return Int32(New[int32](items...)) } // Int32KeySet creates a Int32 from a keys of a map[int32](? extends interface{}). // If the value passed in is not actually a map, this will panic. func Int32KeySet[T any](theMap map[int32]T) Int32 { return Int32(KeySet(theMap)) } // Insert adds items to the set. func (s Int32) Insert(items ...int32) Int32 { return Int32(cast(s).Insert(items...)) } // Delete removes all items from the set. func (s Int32) Delete(items ...int32) Int32 { return Int32(cast(s).Delete(items...)) } // Has returns true if and only if item is contained in the set. func (s Int32) Has(item int32) bool { return cast(s).Has(item) } // HasAll returns true if and only if all items are contained in the set. func (s Int32) HasAll(items ...int32) bool { return cast(s).HasAll(items...) } // HasAny returns true if any items are contained in the set. func (s Int32) HasAny(items ...int32) bool { return cast(s).HasAny(items...) } // Clone returns a new set which is a copy of the current set. func (s Int32) Clone() Int32 { return Int32(cast(s).Clone()) } // Difference returns a set of objects that are not in s2. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s1 Int32) Difference(s2 Int32) Int32 { return Int32(cast(s1).Difference(cast(s2))) } // SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.SymmetricDifference(s2) = {a3, a4, a5} // s2.SymmetricDifference(s1) = {a3, a4, a5} func (s1 Int32) SymmetricDifference(s2 Int32) Int32 { return Int32(cast(s1).SymmetricDifference(cast(s2))) } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 Int32) Union(s2 Int32) Int32 { return Int32(cast(s1).Union(cast(s2))) } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 Int32) Intersection(s2 Int32) Int32 { return Int32(cast(s1).Intersection(cast(s2))) } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 Int32) IsSuperset(s2 Int32) bool { return cast(s1).IsSuperset(cast(s2)) } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 Int32) Equal(s2 Int32) bool { return cast(s1).Equal(cast(s2)) } // List returns the contents as a sorted int32 slice. func (s Int32) List() []int32 { return List(cast(s)) } // UnsortedList returns the slice with contents in random order. func (s Int32) UnsortedList() []int32 { return cast(s).UnsortedList() } // PopAny returns a single element from the set. func (s Int32) PopAny() (int32, bool) { return cast(s).PopAny() } // Len returns the size of the set. func (s Int32) Len() int { return len(s) } golang-k8s-apimachinery-0.29.0/pkg/util/sets/int64.go000066400000000000000000000076611453143165200222500ustar00rootroot00000000000000/* Copyright 2022 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 sets // Int64 is a set of int64s, implemented via map[int64]struct{} for minimal memory consumption. // // Deprecated: use generic Set instead. // new ways: // s1 := Set[int64]{} // s2 := New[int64]() type Int64 map[int64]Empty // NewInt64 creates a Int64 from a list of values. func NewInt64(items ...int64) Int64 { return Int64(New[int64](items...)) } // Int64KeySet creates a Int64 from a keys of a map[int64](? extends interface{}). // If the value passed in is not actually a map, this will panic. func Int64KeySet[T any](theMap map[int64]T) Int64 { return Int64(KeySet(theMap)) } // Insert adds items to the set. func (s Int64) Insert(items ...int64) Int64 { return Int64(cast(s).Insert(items...)) } // Delete removes all items from the set. func (s Int64) Delete(items ...int64) Int64 { return Int64(cast(s).Delete(items...)) } // Has returns true if and only if item is contained in the set. func (s Int64) Has(item int64) bool { return cast(s).Has(item) } // HasAll returns true if and only if all items are contained in the set. func (s Int64) HasAll(items ...int64) bool { return cast(s).HasAll(items...) } // HasAny returns true if any items are contained in the set. func (s Int64) HasAny(items ...int64) bool { return cast(s).HasAny(items...) } // Clone returns a new set which is a copy of the current set. func (s Int64) Clone() Int64 { return Int64(cast(s).Clone()) } // Difference returns a set of objects that are not in s2. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s1 Int64) Difference(s2 Int64) Int64 { return Int64(cast(s1).Difference(cast(s2))) } // SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.SymmetricDifference(s2) = {a3, a4, a5} // s2.SymmetricDifference(s1) = {a3, a4, a5} func (s1 Int64) SymmetricDifference(s2 Int64) Int64 { return Int64(cast(s1).SymmetricDifference(cast(s2))) } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 Int64) Union(s2 Int64) Int64 { return Int64(cast(s1).Union(cast(s2))) } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 Int64) Intersection(s2 Int64) Int64 { return Int64(cast(s1).Intersection(cast(s2))) } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 Int64) IsSuperset(s2 Int64) bool { return cast(s1).IsSuperset(cast(s2)) } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 Int64) Equal(s2 Int64) bool { return cast(s1).Equal(cast(s2)) } // List returns the contents as a sorted int64 slice. func (s Int64) List() []int64 { return List(cast(s)) } // UnsortedList returns the slice with contents in random order. func (s Int64) UnsortedList() []int64 { return cast(s).UnsortedList() } // PopAny returns a single element from the set. func (s Int64) PopAny() (int64, bool) { return cast(s).PopAny() } // Len returns the size of the set. func (s Int64) Len() int { return len(s) } golang-k8s-apimachinery-0.29.0/pkg/util/sets/ordered.go000066400000000000000000000034601453143165200227210ustar00rootroot00000000000000/* Copyright 2022 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 sets // ordered is a constraint that permits any ordered type: any type // that supports the operators < <= >= >. // If future releases of Go add new ordered types, // this constraint will be modified to include them. type ordered interface { integer | float | ~string } // integer is a constraint that permits any integer type. // If future releases of Go add new predeclared integer types, // this constraint will be modified to include them. type integer interface { signed | unsigned } // float is a constraint that permits any floating-point type. // If future releases of Go add new predeclared floating-point types, // this constraint will be modified to include them. type float interface { ~float32 | ~float64 } // signed is a constraint that permits any signed integer type. // If future releases of Go add new predeclared signed integer types, // this constraint will be modified to include them. type signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } // unsigned is a constraint that permits any unsigned integer type. // If future releases of Go add new predeclared unsigned integer types, // this constraint will be modified to include them. type unsigned interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } golang-k8s-apimachinery-0.29.0/pkg/util/sets/set.go000066400000000000000000000140151453143165200220660ustar00rootroot00000000000000/* Copyright 2022 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 sets import ( "sort" ) // Set is a set of the same type elements, implemented via map[comparable]struct{} for minimal memory consumption. type Set[T comparable] map[T]Empty // cast transforms specified set to generic Set[T]. func cast[T comparable](s map[T]Empty) Set[T] { return s } // New creates a Set from a list of values. // NOTE: type param must be explicitly instantiated if given items are empty. func New[T comparable](items ...T) Set[T] { ss := make(Set[T], len(items)) ss.Insert(items...) return ss } // KeySet creates a Set from a keys of a map[comparable](? extends interface{}). // If the value passed in is not actually a map, this will panic. func KeySet[T comparable, V any](theMap map[T]V) Set[T] { ret := Set[T]{} for keyValue := range theMap { ret.Insert(keyValue) } return ret } // Insert adds items to the set. func (s Set[T]) Insert(items ...T) Set[T] { for _, item := range items { s[item] = Empty{} } return s } func Insert[T comparable](set Set[T], items ...T) Set[T] { return set.Insert(items...) } // Delete removes all items from the set. func (s Set[T]) Delete(items ...T) Set[T] { for _, item := range items { delete(s, item) } return s } // Clear empties the set. // It is preferable to replace the set with a newly constructed set, // but not all callers can do that (when there are other references to the map). // In some cases the set *won't* be fully cleared, e.g. a Set[float32] containing NaN // can't be cleared because NaN can't be removed. // For sets containing items of a type that is reflexive for ==, // this is optimized to a single call to runtime.mapclear(). func (s Set[T]) Clear() Set[T] { for key := range s { delete(s, key) } return s } // Has returns true if and only if item is contained in the set. func (s Set[T]) Has(item T) bool { _, contained := s[item] return contained } // HasAll returns true if and only if all items are contained in the set. func (s Set[T]) HasAll(items ...T) bool { for _, item := range items { if !s.Has(item) { return false } } return true } // HasAny returns true if any items are contained in the set. func (s Set[T]) HasAny(items ...T) bool { for _, item := range items { if s.Has(item) { return true } } return false } // Clone returns a new set which is a copy of the current set. func (s Set[T]) Clone() Set[T] { result := make(Set[T], len(s)) for key := range s { result.Insert(key) } return result } // Difference returns a set of objects that are not in s2. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s1 Set[T]) Difference(s2 Set[T]) Set[T] { result := New[T]() for key := range s1 { if !s2.Has(key) { result.Insert(key) } } return result } // SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.SymmetricDifference(s2) = {a3, a4, a5} // s2.SymmetricDifference(s1) = {a3, a4, a5} func (s1 Set[T]) SymmetricDifference(s2 Set[T]) Set[T] { return s1.Difference(s2).Union(s2.Difference(s1)) } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 Set[T]) Union(s2 Set[T]) Set[T] { result := s1.Clone() for key := range s2 { result.Insert(key) } return result } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 Set[T]) Intersection(s2 Set[T]) Set[T] { var walk, other Set[T] result := New[T]() if s1.Len() < s2.Len() { walk = s1 other = s2 } else { walk = s2 other = s1 } for key := range walk { if other.Has(key) { result.Insert(key) } } return result } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 Set[T]) IsSuperset(s2 Set[T]) bool { for item := range s2 { if !s1.Has(item) { return false } } return true } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 Set[T]) Equal(s2 Set[T]) bool { return len(s1) == len(s2) && s1.IsSuperset(s2) } type sortableSliceOfGeneric[T ordered] []T func (g sortableSliceOfGeneric[T]) Len() int { return len(g) } func (g sortableSliceOfGeneric[T]) Less(i, j int) bool { return less[T](g[i], g[j]) } func (g sortableSliceOfGeneric[T]) Swap(i, j int) { g[i], g[j] = g[j], g[i] } // List returns the contents as a sorted T slice. // // This is a separate function and not a method because not all types supported // by Generic are ordered and only those can be sorted. func List[T ordered](s Set[T]) []T { res := make(sortableSliceOfGeneric[T], 0, len(s)) for key := range s { res = append(res, key) } sort.Sort(res) return res } // UnsortedList returns the slice with contents in random order. func (s Set[T]) UnsortedList() []T { res := make([]T, 0, len(s)) for key := range s { res = append(res, key) } return res } // PopAny returns a single element from the set. func (s Set[T]) PopAny() (T, bool) { for key := range s { s.Delete(key) return key, true } var zeroValue T return zeroValue, false } // Len returns the size of the set. func (s Set[T]) Len() int { return len(s) } func less[T ordered](lhs, rhs T) bool { return lhs < rhs } golang-k8s-apimachinery-0.29.0/pkg/util/sets/set_generic_test.go000066400000000000000000000173241453143165200246270ustar00rootroot00000000000000/* Copyright 2022 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 sets_test import ( "reflect" "testing" "k8s.io/apimachinery/pkg/util/sets" ) func TestSet(t *testing.T) { s := sets.Set[string]{} s2 := sets.Set[string]{} if len(s) != 0 { t.Errorf("Expected len=0: %d", len(s)) } s.Insert("a", "b") if len(s) != 2 { t.Errorf("Expected len=2: %d", len(s)) } s.Insert("c") if s.Has("d") { t.Errorf("Unexpected contents: %#v", s) } if !s.Has("a") { t.Errorf("Missing contents: %#v", s) } s.Delete("a") if s.Has("a") { t.Errorf("Unexpected contents: %#v", s) } s.Insert("a") if s.HasAll("a", "b", "d") { t.Errorf("Unexpected contents: %#v", s) } if !s.HasAll("a", "b") { t.Errorf("Missing contents: %#v", s) } s2.Insert("a", "b", "d") if s.IsSuperset(s2) { t.Errorf("Unexpected contents: %#v", s) } s2.Delete("d") if !s.IsSuperset(s2) { t.Errorf("Missing contents: %#v", s) } } func TestSetDeleteMultiples(t *testing.T) { s := sets.Set[string]{} s.Insert("a", "b", "c") if len(s) != 3 { t.Errorf("Expected len=3: %d", len(s)) } s.Delete("a", "c") if len(s) != 1 { t.Errorf("Expected len=1: %d", len(s)) } if s.Has("a") { t.Errorf("Unexpected contents: %#v", s) } if s.Has("c") { t.Errorf("Unexpected contents: %#v", s) } if !s.Has("b") { t.Errorf("Missing contents: %#v", s) } } func TestSetClear(t *testing.T) { s := sets.Set[string]{} s.Insert("a", "b", "c") if s.Len() != 3 { t.Errorf("Expected len=3: %d", s.Len()) } s.Clear() if s.Len() != 0 { t.Errorf("Expected len=0: %d", s.Len()) } } func TestSetClearWithSharedReference(t *testing.T) { s := sets.Set[string]{} s.Insert("a", "b", "c") if s.Len() != 3 { t.Errorf("Expected len=3: %d", s.Len()) } m := s s.Clear() if s.Len() != 0 { t.Errorf("Expected len=0 on the cleared set: %d", s.Len()) } if m.Len() != 0 { t.Errorf("Expected len=0 on the shared reference: %d", m.Len()) } } func TestSetClearInSeparateFunction(t *testing.T) { s := sets.Set[string]{} s.Insert("a", "b", "c") if s.Len() != 3 { t.Errorf("Expected len=3: %d", s.Len()) } clearSetAndAdd(s, "d") if s.Len() != 1 { t.Errorf("Expected len=1: %d", s.Len()) } if !s.Has("d") { t.Errorf("Unexpected contents: %#v", s) } } func clearSetAndAdd[T comparable](s sets.Set[T], a T) { s.Clear() s.Insert(a) } func TestNewSet(t *testing.T) { s := sets.New("a", "b", "c") if len(s) != 3 { t.Errorf("Expected len=3: %d", len(s)) } if !s.Has("a") || !s.Has("b") || !s.Has("c") { t.Errorf("Unexpected contents: %#v", s) } } func TestKeySet(t *testing.T) { m := map[string]int{"a": 1, "b": 2, "c": 3} ss := sets.KeySet[string](m) if !ss.Equal(sets.New("a", "b", "c")) { t.Errorf("Unexpected contents: %#v", sets.List(ss)) } } func TestNewEmptySet(t *testing.T) { s := sets.New[string]() if len(s) != 0 { t.Errorf("Expected len=0: %d", len(s)) } s.Insert("a", "b", "c") if len(s) != 3 { t.Errorf("Expected len=3: %d", len(s)) } if !s.Has("a") || !s.Has("b") || !s.Has("c") { t.Errorf("Unexpected contents: %#v", s) } } func TestSortedList(t *testing.T) { s := sets.New("z", "y", "x", "a") if !reflect.DeepEqual(sets.List(s), []string{"a", "x", "y", "z"}) { t.Errorf("List gave unexpected result: %#v", sets.List(s)) } } func TestSetDifference(t *testing.T) { a := sets.New("1", "2", "3") b := sets.New("1", "2", "4", "5") c := a.Difference(b) d := b.Difference(a) if len(c) != 1 { t.Errorf("Expected len=1: %d", len(c)) } if !c.Has("3") { t.Errorf("Unexpected contents: %#v", sets.List(c)) } if len(d) != 2 { t.Errorf("Expected len=2: %d", len(d)) } if !d.Has("4") || !d.Has("5") { t.Errorf("Unexpected contents: %#v", sets.List(d)) } } func TestSetSymmetricDifference(t *testing.T) { a := sets.New("1", "2", "3") b := sets.New("1", "2", "4", "5") c := a.SymmetricDifference(b) d := b.SymmetricDifference(a) if !c.Equal(sets.New("3", "4", "5")) { t.Errorf("Unexpected contents: %#v", sets.List(c)) } if !d.Equal(sets.New("3", "4", "5")) { t.Errorf("Unexpected contents: %#v", sets.List(d)) } } func TestSetHasAny(t *testing.T) { a := sets.New("1", "2", "3") if !a.HasAny("1", "4") { t.Errorf("expected true, got false") } if a.HasAny("0", "4") { t.Errorf("expected false, got true") } } func TestSetEquals(t *testing.T) { // Simple case (order doesn't matter) a := sets.New("1", "2") b := sets.New("2", "1") if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } // It is a set; duplicates are ignored b = sets.New("2", "2", "1") if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } // Edge cases around empty sets / empty strings a = sets.New[string]() b = sets.New[string]() if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } b = sets.New("1", "2", "3") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } b = sets.New("1", "2", "") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } // Check for equality after mutation a = sets.New[string]() a.Insert("1") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } a.Insert("2") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } a.Insert("") if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } a.Delete("") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } } func TestUnion(t *testing.T) { tests := []struct { s1 sets.Set[string] s2 sets.Set[string] expected sets.Set[string] }{ { sets.New("1", "2", "3", "4"), sets.New("3", "4", "5", "6"), sets.New("1", "2", "3", "4", "5", "6"), }, { sets.New("1", "2", "3", "4"), sets.New[string](), sets.New("1", "2", "3", "4"), }, { sets.New[string](), sets.New("1", "2", "3", "4"), sets.New("1", "2", "3", "4"), }, { sets.New[string](), sets.New[string](), sets.New[string](), }, } for _, test := range tests { union := test.s1.Union(test.s2) if union.Len() != test.expected.Len() { t.Errorf("Expected union.Len()=%d but got %d", test.expected.Len(), union.Len()) } if !union.Equal(test.expected) { t.Errorf("Expected union.Equal(expected) but not true. union:%v expected:%v", sets.List(union), sets.List(test.expected)) } } } func TestIntersection(t *testing.T) { tests := []struct { s1 sets.Set[string] s2 sets.Set[string] expected sets.Set[string] }{ { sets.New("1", "2", "3", "4"), sets.New("3", "4", "5", "6"), sets.New("3", "4"), }, { sets.New("1", "2", "3", "4"), sets.New("1", "2", "3", "4"), sets.New("1", "2", "3", "4"), }, { sets.New("1", "2", "3", "4"), sets.New[string](), sets.New[string](), }, { sets.New[string](), sets.New("1", "2", "3", "4"), sets.New[string](), }, { sets.New[string](), sets.New[string](), sets.New[string](), }, } for _, test := range tests { intersection := test.s1.Intersection(test.s2) if intersection.Len() != test.expected.Len() { t.Errorf("Expected intersection.Len()=%d but got %d", test.expected.Len(), intersection.Len()) } if !intersection.Equal(test.expected) { t.Errorf("Expected intersection.Equal(expected) but not true. intersection:%v expected:%v", sets.List(intersection), sets.List(intersection)) } } } golang-k8s-apimachinery-0.29.0/pkg/util/sets/set_test.go000066400000000000000000000200361453143165200231250ustar00rootroot00000000000000/* 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 sets import ( "fmt" "math/rand" "reflect" "testing" ) func TestStringSet(t *testing.T) { s := String{} s2 := String{} if len(s) != 0 { t.Errorf("Expected len=0: %d", len(s)) } s.Insert("a", "b") if len(s) != 2 { t.Errorf("Expected len=2: %d", len(s)) } s.Insert("c") if s.Has("d") { t.Errorf("Unexpected contents: %#v", s) } if !s.Has("a") { t.Errorf("Missing contents: %#v", s) } s.Delete("a") if s.Has("a") { t.Errorf("Unexpected contents: %#v", s) } s.Insert("a") if s.HasAll("a", "b", "d") { t.Errorf("Unexpected contents: %#v", s) } if !s.HasAll("a", "b") { t.Errorf("Missing contents: %#v", s) } s2.Insert("a", "b", "d") if s.IsSuperset(s2) { t.Errorf("Unexpected contents: %#v", s) } s2.Delete("d") if !s.IsSuperset(s2) { t.Errorf("Missing contents: %#v", s) } } func TestStringSetDeleteMultiples(t *testing.T) { s := String{} s.Insert("a", "b", "c") if len(s) != 3 { t.Errorf("Expected len=3: %d", len(s)) } s.Delete("a", "c") if len(s) != 1 { t.Errorf("Expected len=1: %d", len(s)) } if s.Has("a") { t.Errorf("Unexpected contents: %#v", s) } if s.Has("c") { t.Errorf("Unexpected contents: %#v", s) } if !s.Has("b") { t.Errorf("Missing contents: %#v", s) } } func TestNewStringSet(t *testing.T) { s := NewString("a", "b", "c") if len(s) != 3 { t.Errorf("Expected len=3: %d", len(s)) } if !s.Has("a") || !s.Has("b") || !s.Has("c") { t.Errorf("Unexpected contents: %#v", s) } } func TestStringSetList(t *testing.T) { s := NewString("z", "y", "x", "a") if !reflect.DeepEqual(s.List(), []string{"a", "x", "y", "z"}) { t.Errorf("List gave unexpected result: %#v", s.List()) } } func TestStringSetDifference(t *testing.T) { a := NewString("1", "2", "3") b := NewString("1", "2", "4", "5") c := a.Difference(b) d := b.Difference(a) if len(c) != 1 { t.Errorf("Expected len=1: %d", len(c)) } if !c.Has("3") { t.Errorf("Unexpected contents: %#v", c.List()) } if len(d) != 2 { t.Errorf("Expected len=2: %d", len(d)) } if !d.Has("4") || !d.Has("5") { t.Errorf("Unexpected contents: %#v", d.List()) } } func TestStringSetSymmetricDifference(t *testing.T) { a := NewString("1", "2", "3") b := NewString("1", "2", "4", "5") c := a.SymmetricDifference(b) d := b.SymmetricDifference(a) if !c.Equal(NewString("3", "4", "5")) { t.Errorf("Unexpected contents: %#v", c.List()) } if !d.Equal(NewString("3", "4", "5")) { t.Errorf("Unexpected contents: %#v", d.List()) } } func TestStringSetHasAny(t *testing.T) { a := NewString("1", "2", "3") if !a.HasAny("1", "4") { t.Errorf("expected true, got false") } if a.HasAny("0", "4") { t.Errorf("expected false, got true") } } func TestStringSetEquals(t *testing.T) { // Simple case (order doesn't matter) a := NewString("1", "2") b := NewString("2", "1") if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } // It is a set; duplicates are ignored b = NewString("2", "2", "1") if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } // Edge cases around empty sets / empty strings a = NewString() b = NewString() if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } b = NewString("1", "2", "3") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } b = NewString("1", "2", "") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } // Check for equality after mutation a = NewString() a.Insert("1") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } a.Insert("2") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } a.Insert("") if !a.Equal(b) { t.Errorf("Expected to be equal: %v vs %v", a, b) } a.Delete("") if a.Equal(b) { t.Errorf("Expected to be not-equal: %v vs %v", a, b) } } func TestStringUnion(t *testing.T) { tests := []struct { s1 String s2 String expected String }{ { NewString("1", "2", "3", "4"), NewString("3", "4", "5", "6"), NewString("1", "2", "3", "4", "5", "6"), }, { NewString("1", "2", "3", "4"), NewString(), NewString("1", "2", "3", "4"), }, { NewString(), NewString("1", "2", "3", "4"), NewString("1", "2", "3", "4"), }, { NewString(), NewString(), NewString(), }, } for _, test := range tests { union := test.s1.Union(test.s2) if union.Len() != test.expected.Len() { t.Errorf("Expected union.Len()=%d but got %d", test.expected.Len(), union.Len()) } if !union.Equal(test.expected) { t.Errorf("Expected union.Equal(expected) but not true. union:%v expected:%v", union.List(), test.expected.List()) } } } func TestStringIntersection(t *testing.T) { tests := []struct { s1 String s2 String expected String }{ { NewString("1", "2", "3", "4"), NewString("3", "4", "5", "6"), NewString("3", "4"), }, { NewString("1", "2", "3", "4"), NewString("1", "2", "3", "4"), NewString("1", "2", "3", "4"), }, { NewString("1", "2", "3", "4"), NewString(), NewString(), }, { NewString(), NewString("1", "2", "3", "4"), NewString(), }, { NewString(), NewString(), NewString(), }, } for _, test := range tests { intersection := test.s1.Intersection(test.s2) if intersection.Len() != test.expected.Len() { t.Errorf("Expected intersection.Len()=%d but got %d", test.expected.Len(), intersection.Len()) } if !intersection.Equal(test.expected) { t.Errorf("Expected intersection.Equal(expected) but not true. intersection:%v expected:%v", intersection.List(), test.expected.List()) } } } type randomStringAlphabet string func (a randomStringAlphabet) makeString(minLen, maxLen int) string { n := minLen if minLen < maxLen { n += rand.Intn(maxLen - minLen) } var s string for i := 0; i < n; i++ { s += string(a[rand.Intn(len(a))]) } return s } var randomStringMaker = randomStringAlphabet("abcdefghijklmnopqrstuvwxyz0123456789") func BenchmarkStringSet(b *testing.B) { cases := []struct { size int minStringLen int maxStringLen int }{ {20, 10, 20}, {50, 10, 30}, {100, 20, 40}, {500, 20, 50}, {1000, 20, 60}, } for i := range cases { here := cases[i] makeSet := func() String { s := NewString() for j := 0; j < here.size; j++ { s.Insert(randomStringMaker.makeString(here.minStringLen, here.maxStringLen)) } return s } operands := make([]String, 500) for i := range operands { operands[i] = makeSet() } randOperand := func() String { return operands[rand.Intn(len(operands))] } b.Run(fmt.Sprintf("insert-%v", here.size), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { makeSet() } }) b.Run(fmt.Sprintf("key-set-%v", here.size), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { StringKeySet(randOperand()) } }) b.Run(fmt.Sprintf("has-%v", here.size), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { randOperand().Has(randomStringMaker.makeString(here.minStringLen, here.maxStringLen)) } }) b.Run(fmt.Sprintf("intersection-%v", here.size), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { randOperand().Intersection(randOperand()) } }) b.Run(fmt.Sprintf("symmetric-difference-%v", here.size), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { randOperand().SymmetricDifference(randOperand()) } }) b.Run(fmt.Sprintf("list-%v", here.size), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { randOperand().List() } }) } } golang-k8s-apimachinery-0.29.0/pkg/util/sets/string.go000066400000000000000000000077631453143165200226150ustar00rootroot00000000000000/* Copyright 2022 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 sets // String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. // // Deprecated: use generic Set instead. // new ways: // s1 := Set[string]{} // s2 := New[string]() type String map[string]Empty // NewString creates a String from a list of values. func NewString(items ...string) String { return String(New[string](items...)) } // StringKeySet creates a String from a keys of a map[string](? extends interface{}). // If the value passed in is not actually a map, this will panic. func StringKeySet[T any](theMap map[string]T) String { return String(KeySet(theMap)) } // Insert adds items to the set. func (s String) Insert(items ...string) String { return String(cast(s).Insert(items...)) } // Delete removes all items from the set. func (s String) Delete(items ...string) String { return String(cast(s).Delete(items...)) } // Has returns true if and only if item is contained in the set. func (s String) Has(item string) bool { return cast(s).Has(item) } // HasAll returns true if and only if all items are contained in the set. func (s String) HasAll(items ...string) bool { return cast(s).HasAll(items...) } // HasAny returns true if any items are contained in the set. func (s String) HasAny(items ...string) bool { return cast(s).HasAny(items...) } // Clone returns a new set which is a copy of the current set. func (s String) Clone() String { return String(cast(s).Clone()) } // Difference returns a set of objects that are not in s2. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s1 String) Difference(s2 String) String { return String(cast(s1).Difference(cast(s2))) } // SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.SymmetricDifference(s2) = {a3, a4, a5} // s2.SymmetricDifference(s1) = {a3, a4, a5} func (s1 String) SymmetricDifference(s2 String) String { return String(cast(s1).SymmetricDifference(cast(s2))) } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 String) Union(s2 String) String { return String(cast(s1).Union(cast(s2))) } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 String) Intersection(s2 String) String { return String(cast(s1).Intersection(cast(s2))) } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 String) IsSuperset(s2 String) bool { return cast(s1).IsSuperset(cast(s2)) } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 String) Equal(s2 String) bool { return cast(s1).Equal(cast(s2)) } // List returns the contents as a sorted string slice. func (s String) List() []string { return List(cast(s)) } // UnsortedList returns the slice with contents in random order. func (s String) UnsortedList() []string { return cast(s).UnsortedList() } // PopAny returns a single element from the set. func (s String) PopAny() (string, bool) { return cast(s).PopAny() } // Len returns the size of the set. func (s String) Len() int { return len(s) } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/000077500000000000000000000000001453143165200227725ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/OWNERS000066400000000000000000000002211453143165200237250ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners approvers: - apelisse - pwittrock reviewers: - apelisse emeritus_approvers: - mengqiy golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/errors.go000066400000000000000000000022641453143165200246410ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package strategicpatch import ( "fmt" ) type LookupPatchMetaError struct { Path string Err error } func (e LookupPatchMetaError) Error() string { return fmt.Sprintf("LookupPatchMetaError(%s): %v", e.Path, e.Err) } type FieldNotFoundError struct { Path string Field string } func (e FieldNotFoundError) Error() string { return fmt.Sprintf("unable to find api field %q in %s", e.Field, e.Path) } type InvalidTypeError struct { Path string Expected string Actual string } func (e InvalidTypeError) Error() string { return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected) } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/meta.go000066400000000000000000000173521453143165200242570ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package strategicpatch import ( "errors" "fmt" "reflect" "strings" "k8s.io/apimachinery/pkg/util/mergepatch" forkedjson "k8s.io/apimachinery/third_party/forked/golang/json" openapi "k8s.io/kube-openapi/pkg/util/proto" "k8s.io/kube-openapi/pkg/validation/spec" ) const patchMergeKey = "x-kubernetes-patch-merge-key" const patchStrategy = "x-kubernetes-patch-strategy" type PatchMeta struct { patchStrategies []string patchMergeKey string } func (pm *PatchMeta) GetPatchStrategies() []string { if pm.patchStrategies == nil { return []string{} } return pm.patchStrategies } func (pm *PatchMeta) SetPatchStrategies(ps []string) { pm.patchStrategies = ps } func (pm *PatchMeta) GetPatchMergeKey() string { return pm.patchMergeKey } func (pm *PatchMeta) SetPatchMergeKey(pmk string) { pm.patchMergeKey = pmk } type LookupPatchMeta interface { // LookupPatchMetadataForStruct gets subschema and the patch metadata (e.g. patch strategy and merge key) for map. LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) // LookupPatchMetadataForSlice get subschema and the patch metadata for slice. LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) // Get the type name of the field Name() string } type PatchMetaFromStruct struct { T reflect.Type } func NewPatchMetaFromStruct(dataStruct interface{}) (PatchMetaFromStruct, error) { t, err := getTagStructType(dataStruct) return PatchMetaFromStruct{T: t}, err } var _ LookupPatchMeta = PatchMetaFromStruct{} func (s PatchMetaFromStruct) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadataForStruct(s.T, key) if err != nil { return nil, PatchMeta{}, err } return PatchMetaFromStruct{T: fieldType}, PatchMeta{ patchStrategies: fieldPatchStrategies, patchMergeKey: fieldPatchMergeKey, }, nil } func (s PatchMetaFromStruct) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { subschema, patchMeta, err := s.LookupPatchMetadataForStruct(key) if err != nil { return nil, PatchMeta{}, err } elemPatchMetaFromStruct := subschema.(PatchMetaFromStruct) t := elemPatchMetaFromStruct.T var elemType reflect.Type switch t.Kind() { // If t is an array or a slice, get the element type. // If element is still an array or a slice, return an error. // Otherwise, return element type. case reflect.Array, reflect.Slice: elemType = t.Elem() if elemType.Kind() == reflect.Array || elemType.Kind() == reflect.Slice { return nil, PatchMeta{}, errors.New("unexpected slice of slice") } // If t is an pointer, get the underlying element. // If the underlying element is neither an array nor a slice, the pointer is pointing to a slice, // e.g. https://github.com/kubernetes/kubernetes/blob/bc22e206c79282487ea0bf5696d5ccec7e839a76/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go#L2782-L2822 // If the underlying element is either an array or a slice, return its element type. case reflect.Pointer: t = t.Elem() if t.Kind() == reflect.Array || t.Kind() == reflect.Slice { t = t.Elem() } elemType = t default: return nil, PatchMeta{}, fmt.Errorf("expected slice or array type, but got: %s", s.T.Kind().String()) } return PatchMetaFromStruct{T: elemType}, patchMeta, nil } func (s PatchMetaFromStruct) Name() string { return s.T.Kind().String() } func getTagStructType(dataStruct interface{}) (reflect.Type, error) { if dataStruct == nil { return nil, mergepatch.ErrBadArgKind(struct{}{}, nil) } t := reflect.TypeOf(dataStruct) // Get the underlying type for pointers if t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() != reflect.Struct { return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct) } return t, nil } func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type { t, err := getTagStructType(dataStruct) if err != nil { panic(err) } return t } type PatchMetaFromOpenAPIV3 struct { // SchemaList is required to resolve OpenAPI V3 references SchemaList map[string]*spec.Schema Schema *spec.Schema } func (s PatchMetaFromOpenAPIV3) traverse(key string) (PatchMetaFromOpenAPIV3, error) { if s.Schema == nil { return PatchMetaFromOpenAPIV3{}, nil } if len(s.Schema.Properties) == 0 { return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key) } subschema, ok := s.Schema.Properties[key] if !ok { return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key) } return PatchMetaFromOpenAPIV3{SchemaList: s.SchemaList, Schema: &subschema}, nil } func resolve(l *PatchMetaFromOpenAPIV3) error { if len(l.Schema.AllOf) > 0 { l.Schema = &l.Schema.AllOf[0] } if refString := l.Schema.Ref.String(); refString != "" { str := strings.TrimPrefix(refString, "#/components/schemas/") sch, ok := l.SchemaList[str] if ok { l.Schema = sch } else { return fmt.Errorf("unable to resolve %s in OpenAPI V3", refString) } } return nil } func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { l, err := s.traverse(key) if err != nil { return l, PatchMeta{}, err } p := PatchMeta{} f, ok := l.Schema.Extensions[patchMergeKey] if ok { p.SetPatchMergeKey(f.(string)) } g, ok := l.Schema.Extensions[patchStrategy] if ok { p.SetPatchStrategies(strings.Split(g.(string), ",")) } err = resolve(&l) return l, p, err } func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { l, err := s.traverse(key) if err != nil { return l, PatchMeta{}, err } p := PatchMeta{} f, ok := l.Schema.Extensions[patchMergeKey] if ok { p.SetPatchMergeKey(f.(string)) } g, ok := l.Schema.Extensions[patchStrategy] if ok { p.SetPatchStrategies(strings.Split(g.(string), ",")) } if l.Schema.Items != nil { l.Schema = l.Schema.Items.Schema } err = resolve(&l) return l, p, err } func (s PatchMetaFromOpenAPIV3) Name() string { schema := s.Schema if len(schema.Type) > 0 { return strings.Join(schema.Type, "") } return "Struct" } type PatchMetaFromOpenAPI struct { Schema openapi.Schema } func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI { return PatchMetaFromOpenAPI{Schema: s} } var _ LookupPatchMeta = PatchMetaFromOpenAPI{} func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { if s.Schema == nil { return nil, PatchMeta{}, nil } kindItem := NewKindItem(key, s.Schema.GetPath()) s.Schema.Accept(kindItem) err := kindItem.Error() if err != nil { return nil, PatchMeta{}, err } return PatchMetaFromOpenAPI{Schema: kindItem.subschema}, kindItem.patchmeta, nil } func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { if s.Schema == nil { return nil, PatchMeta{}, nil } sliceItem := NewSliceItem(key, s.Schema.GetPath()) s.Schema.Accept(sliceItem) err := sliceItem.Error() if err != nil { return nil, PatchMeta{}, err } return PatchMetaFromOpenAPI{Schema: sliceItem.subschema}, sliceItem.patchmeta, nil } func (s PatchMetaFromOpenAPI) Name() string { schema := s.Schema return schema.GetName() } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/patch.go000066400000000000000000002270161453143165200244300ustar00rootroot00000000000000/* 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 strategicpatch import ( "fmt" "reflect" "sort" "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/mergepatch" ) // An alternate implementation of JSON Merge Patch // (https://tools.ietf.org/html/rfc7386) which supports the ability to annotate // certain fields with metadata that indicates whether the elements of JSON // lists should be merged or replaced. // // For more information, see the PATCH section of docs/devel/api-conventions.md. // // Some of the content of this package was borrowed with minor adaptations from // evanphx/json-patch and openshift/origin. const ( directiveMarker = "$patch" deleteDirective = "delete" replaceDirective = "replace" mergeDirective = "merge" retainKeysStrategy = "retainKeys" deleteFromPrimitiveListDirectivePrefix = "$deleteFromPrimitiveList" retainKeysDirective = "$" + retainKeysStrategy setElementOrderDirectivePrefix = "$setElementOrder" ) // JSONMap is a representations of JSON object encoded as map[string]interface{} // where the children can be either map[string]interface{}, []interface{} or // primitive type). // Operating on JSONMap representation is much faster as it doesn't require any // json marshaling and/or unmarshaling operations. type JSONMap map[string]interface{} type DiffOptions struct { // SetElementOrder determines whether we generate the $setElementOrder parallel list. SetElementOrder bool // IgnoreChangesAndAdditions indicates if we keep the changes and additions in the patch. IgnoreChangesAndAdditions bool // IgnoreDeletions indicates if we keep the deletions in the patch. IgnoreDeletions bool // We introduce a new value retainKeys for patchStrategy. // It indicates that all fields needing to be preserved must be // present in the `retainKeys` list. // And the fields that are present will be merged with live object. // All the missing fields will be cleared when patching. BuildRetainKeysDirective bool } type MergeOptions struct { // MergeParallelList indicates if we are merging the parallel list. // We don't merge parallel list when calling mergeMap() in CreateThreeWayMergePatch() // which is called client-side. // We merge parallel list iff when calling mergeMap() in StrategicMergeMapPatch() // which is called server-side MergeParallelList bool // IgnoreUnmatchedNulls indicates if we should process the unmatched nulls. IgnoreUnmatchedNulls bool } // The following code is adapted from github.com/openshift/origin/pkg/util/jsonmerge. // Instead of defining a Delta that holds an original, a patch and a set of preconditions, // the reconcile method accepts a set of preconditions as an argument. // CreateTwoWayMergePatch creates a patch that can be passed to StrategicMergePatch from an original // document and a modified document, which are passed to the method as json encoded content. It will // return a patch that yields the modified document when applied to the original document, or an error // if either of the two documents is invalid. func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) ([]byte, error) { schema, err := NewPatchMetaFromStruct(dataStruct) if err != nil { return nil, err } return CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema, fns...) } func CreateTwoWayMergePatchUsingLookupPatchMeta( original, modified []byte, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { return nil, mergepatch.ErrBadJSONDoc } } modifiedMap := map[string]interface{}{} if len(modified) > 0 { if err := json.Unmarshal(modified, &modifiedMap); err != nil { return nil, mergepatch.ErrBadJSONDoc } } patchMap, err := CreateTwoWayMergeMapPatchUsingLookupPatchMeta(originalMap, modifiedMap, schema, fns...) if err != nil { return nil, err } return json.Marshal(patchMap) } // CreateTwoWayMergeMapPatch creates a patch from an original and modified JSON objects, // encoded JSONMap. // The serialized version of the map can then be passed to StrategicMergeMapPatch. func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { schema, err := NewPatchMetaFromStruct(dataStruct) if err != nil { return nil, err } return CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified, schema, fns...) } func CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified JSONMap, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { diffOptions := DiffOptions{ SetElementOrder: true, } patchMap, err := diffMaps(original, modified, schema, diffOptions) if err != nil { return nil, err } // Apply the preconditions to the patch, and return an error if any of them fail. for _, fn := range fns { if !fn(patchMap) { return nil, mergepatch.NewErrPreconditionFailed(patchMap) } } return patchMap, nil } // Returns a (recursive) strategic merge patch that yields modified when applied to original. // Including: // - Adding fields to the patch present in modified, missing from original // - Setting fields to the patch present in modified and original with different values // - Delete fields present in original, missing from modified through // - IFF map field - set to nil in patch // - IFF list of maps && merge strategy - use deleteDirective for the elements // - IFF list of primitives && merge strategy - use parallel deletion list // - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified // - Build $retainKeys directive for fields with retainKeys patch strategy func diffMaps(original, modified map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) (map[string]interface{}, error) { patch := map[string]interface{}{} // This will be used to build the $retainKeys directive sent in the patch retainKeysList := make([]interface{}, 0, len(modified)) // Compare each value in the modified map against the value in the original map for key, modifiedValue := range modified { // Get the underlying type for pointers if diffOptions.BuildRetainKeysDirective && modifiedValue != nil { retainKeysList = append(retainKeysList, key) } originalValue, ok := original[key] if !ok { // Key was added, so add to patch if !diffOptions.IgnoreChangesAndAdditions { patch[key] = modifiedValue } continue } // The patch may have a patch directive // TODO: figure out if we need this. This shouldn't be needed by apply. When would the original map have patch directives in it? foundDirectiveMarker, err := handleDirectiveMarker(key, originalValue, modifiedValue, patch) if err != nil { return nil, err } if foundDirectiveMarker { continue } if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) { // Types have changed, so add to patch if !diffOptions.IgnoreChangesAndAdditions { patch[key] = modifiedValue } continue } // Types are the same, so compare values switch originalValueTyped := originalValue.(type) { case map[string]interface{}: modifiedValueTyped := modifiedValue.(map[string]interface{}) err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions) case []interface{}: modifiedValueTyped := modifiedValue.([]interface{}) err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions) default: replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions) } if err != nil { return nil, err } } updatePatchIfMissing(original, modified, patch, diffOptions) // Insert the retainKeysList iff there are values present in the retainKeysList and // either of the following is true: // - the patch is not empty // - there are additional field in original that need to be cleared if len(retainKeysList) > 0 && (len(patch) > 0 || hasAdditionalNewField(original, modified)) { patch[retainKeysDirective] = sortScalars(retainKeysList) } return patch, nil } // handleDirectiveMarker handles how to diff directive marker between 2 objects func handleDirectiveMarker(key string, originalValue, modifiedValue interface{}, patch map[string]interface{}) (bool, error) { if key == directiveMarker { originalString, ok := originalValue.(string) if !ok { return false, fmt.Errorf("invalid value for special key: %s", directiveMarker) } modifiedString, ok := modifiedValue.(string) if !ok { return false, fmt.Errorf("invalid value for special key: %s", directiveMarker) } if modifiedString != originalString { patch[directiveMarker] = modifiedValue } return true, nil } return false, nil } // handleMapDiff diff between 2 maps `originalValueTyped` and `modifiedValue`, // puts the diff in the `patch` associated with `key` // key is the key associated with originalValue and modifiedValue. // originalValue, modifiedValue are the old and new value respectively.They are both maps // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue // diffOptions contains multiple options to control how we do the diff. func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) error { subschema, patchMeta, err := schema.LookupPatchMetadataForStruct(key) if err != nil { // We couldn't look up metadata for the field // If the values are identical, this doesn't matter, no patch is needed if reflect.DeepEqual(originalValue, modifiedValue) { return nil } // Otherwise, return the error return err } retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return err } diffOptions.BuildRetainKeysDirective = retainKeys switch patchStrategy { // The patch strategic from metadata tells us to replace the entire object instead of diffing it case replaceDirective: if !diffOptions.IgnoreChangesAndAdditions { patch[key] = modifiedValue } default: patchValue, err := diffMaps(originalValue, modifiedValue, subschema, diffOptions) if err != nil { return err } // Maps were not identical, use provided patch value if len(patchValue) > 0 { patch[key] = patchValue } } return nil } // handleSliceDiff diff between 2 slices `originalValueTyped` and `modifiedValue`, // puts the diff in the `patch` associated with `key` // key is the key associated with originalValue and modifiedValue. // originalValue, modifiedValue are the old and new value respectively.They are both slices // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue // diffOptions contains multiple options to control how we do the diff. func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, patch map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) error { subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(key) if err != nil { // We couldn't look up metadata for the field // If the values are identical, this doesn't matter, no patch is needed if reflect.DeepEqual(originalValue, modifiedValue) { return nil } // Otherwise, return the error return err } retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return err } switch patchStrategy { // Merge the 2 slices using mergePatchKey case mergeDirective: diffOptions.BuildRetainKeysDirective = retainKeys addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, subschema, patchMeta.GetPatchMergeKey(), diffOptions) if err != nil { return err } if len(addList) > 0 { patch[key] = addList } // generate a parallel list for deletion if len(deletionList) > 0 { parallelDeletionListKey := fmt.Sprintf("%s/%s", deleteFromPrimitiveListDirectivePrefix, key) patch[parallelDeletionListKey] = deletionList } if len(setOrderList) > 0 { parallelSetOrderListKey := fmt.Sprintf("%s/%s", setElementOrderDirectivePrefix, key) patch[parallelSetOrderListKey] = setOrderList } default: replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions) } return nil } // replacePatchFieldIfNotEqual updates the patch if original and modified are not deep equal // if diffOptions.IgnoreChangesAndAdditions is false. // original is the old value, maybe either the live cluster object or the last applied configuration // modified is the new value, is always the users new config func replacePatchFieldIfNotEqual(key string, original, modified interface{}, patch map[string]interface{}, diffOptions DiffOptions) { if diffOptions.IgnoreChangesAndAdditions { // Ignoring changes - do nothing return } if reflect.DeepEqual(original, modified) { // Contents are identical - do nothing return } // Create a patch to replace the old value with the new one patch[key] = modified } // updatePatchIfMissing iterates over `original` when ignoreDeletions is false. // Clear the field whose key is not present in `modified`. // original is the old value, maybe either the live cluster object or the last applied configuration // modified is the new value, is always the users new config func updatePatchIfMissing(original, modified, patch map[string]interface{}, diffOptions DiffOptions) { if diffOptions.IgnoreDeletions { // Ignoring deletion - do nothing return } // Add nils for deleted values for key := range original { if _, found := modified[key]; !found { patch[key] = nil } } } // validateMergeKeyInLists checks if each map in the list has the mentryerge key. func validateMergeKeyInLists(mergeKey string, lists ...[]interface{}) error { for _, list := range lists { for _, item := range list { m, ok := item.(map[string]interface{}) if !ok { return mergepatch.ErrBadArgType(m, item) } if _, ok = m[mergeKey]; !ok { return mergepatch.ErrNoMergeKey(m, mergeKey) } } } return nil } // normalizeElementOrder sort `patch` list by `patchOrder` and sort `serverOnly` list by `serverOrder`. // Then it merges the 2 sorted lists. // It guarantee the relative order in the patch list and in the serverOnly list is kept. // `patch` is a list of items in the patch, and `serverOnly` is a list of items in the live object. // `patchOrder` is the order we want `patch` list to have and // `serverOrder` is the order we want `serverOnly` list to have. // kind is the kind of each item in the lists `patch` and `serverOnly`. func normalizeElementOrder(patch, serverOnly, patchOrder, serverOrder []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) { patch, err := normalizeSliceOrder(patch, patchOrder, mergeKey, kind) if err != nil { return nil, err } serverOnly, err = normalizeSliceOrder(serverOnly, serverOrder, mergeKey, kind) if err != nil { return nil, err } all := mergeSortedSlice(serverOnly, patch, serverOrder, mergeKey, kind) return all, nil } // mergeSortedSlice merges the 2 sorted lists by serverOrder with best effort. // It will insert each item in `left` list to `right` list. In most cases, the 2 lists will be interleaved. // The relative order of left and right are guaranteed to be kept. // They have higher precedence than the order in the live list. // The place for a item in `left` is found by: // scan from the place of last insertion in `right` to the end of `right`, // the place is before the first item that is greater than the item we want to insert. // example usage: using server-only items as left and patch items as right. We insert server-only items // to patch list. We use the order of live object as record for comparison. func mergeSortedSlice(left, right, serverOrder []interface{}, mergeKey string, kind reflect.Kind) []interface{} { // Returns if l is less than r, and if both have been found. // If l and r both present and l is in front of r, l is less than r. less := func(l, r interface{}) (bool, bool) { li := index(serverOrder, l, mergeKey, kind) ri := index(serverOrder, r, mergeKey, kind) if li >= 0 && ri >= 0 { return li < ri, true } else { return false, false } } // left and right should be non-overlapping. size := len(left) + len(right) i, j := 0, 0 s := make([]interface{}, size, size) for k := 0; k < size; k++ { if i >= len(left) && j < len(right) { // have items left in `right` list s[k] = right[j] j++ } else if j >= len(right) && i < len(left) { // have items left in `left` list s[k] = left[i] i++ } else { // compare them if i and j are both in bound less, foundBoth := less(left[i], right[j]) if foundBoth && less { s[k] = left[i] i++ } else { s[k] = right[j] j++ } } } return s } // index returns the index of the item in the given items, or -1 if it doesn't exist // l must NOT be a slice of slices, this should be checked before calling. func index(l []interface{}, valToLookUp interface{}, mergeKey string, kind reflect.Kind) int { var getValFn func(interface{}) interface{} // Get the correct `getValFn` based on item `kind`. // It should return the value of merge key for maps and // return the item for other kinds. switch kind { case reflect.Map: getValFn = func(item interface{}) interface{} { typedItem, ok := item.(map[string]interface{}) if !ok { return nil } val := typedItem[mergeKey] return val } default: getValFn = func(item interface{}) interface{} { return item } } for i, v := range l { if getValFn(valToLookUp) == getValFn(v) { return i } } return -1 } // extractToDeleteItems takes a list and // returns 2 lists: one contains items that should be kept and the other contains items to be deleted. func extractToDeleteItems(l []interface{}) ([]interface{}, []interface{}, error) { var nonDelete, toDelete []interface{} for _, v := range l { m, ok := v.(map[string]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(m, v) } directive, foundDirective := m[directiveMarker] if foundDirective && directive == deleteDirective { toDelete = append(toDelete, v) } else { nonDelete = append(nonDelete, v) } } return nonDelete, toDelete, nil } // normalizeSliceOrder sort `toSort` list by `order` func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) { var toDelete []interface{} if kind == reflect.Map { // make sure each item in toSort, order has merge key err := validateMergeKeyInLists(mergeKey, toSort, order) if err != nil { return nil, err } toSort, toDelete, err = extractToDeleteItems(toSort) if err != nil { return nil, err } } sort.SliceStable(toSort, func(i, j int) bool { if ii := index(order, toSort[i], mergeKey, kind); ii >= 0 { if ij := index(order, toSort[j], mergeKey, kind); ij >= 0 { return ii < ij } } return true }) toSort = append(toSort, toDelete...) return toSort, nil } // Returns a (recursive) strategic merge patch, a parallel deletion list if necessary and // another list to set the order of the list // Only list of primitives with merge strategy will generate a parallel deletion list. // These two lists should yield modified when applied to original, for lists with merge semantics. func diffLists(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) { if len(original) == 0 { // Both slices are empty - do nothing if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions { return nil, nil, nil, nil } // Old slice was empty - add all elements from the new slice return modified, nil, nil, nil } elementType, err := sliceElementType(original, modified) if err != nil { return nil, nil, nil, err } var patchList, deleteList, setOrderList []interface{} kind := elementType.Kind() switch kind { case reflect.Map: patchList, deleteList, err = diffListsOfMaps(original, modified, schema, mergeKey, diffOptions) if err != nil { return nil, nil, nil, err } patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) if err != nil { return nil, nil, nil, err } orderSame, err := isOrderSame(original, modified, mergeKey) if err != nil { return nil, nil, nil, err } // append the deletions to the end of the patch list. patchList = append(patchList, deleteList...) deleteList = nil // generate the setElementOrder list when there are content changes or order changes if diffOptions.SetElementOrder && ((!diffOptions.IgnoreChangesAndAdditions && (len(patchList) > 0 || !orderSame)) || (!diffOptions.IgnoreDeletions && len(patchList) > 0)) { // Generate a list of maps that each item contains only the merge key. setOrderList = make([]interface{}, len(modified)) for i, v := range modified { typedV := v.(map[string]interface{}) setOrderList[i] = map[string]interface{}{ mergeKey: typedV[mergeKey], } } } case reflect.Slice: // Lists of Lists are not permitted by the api return nil, nil, nil, mergepatch.ErrNoListOfLists default: patchList, deleteList, err = diffListsOfScalars(original, modified, diffOptions) if err != nil { return nil, nil, nil, err } patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) // generate the setElementOrder list when there are content changes or order changes if diffOptions.SetElementOrder && ((!diffOptions.IgnoreDeletions && len(deleteList) > 0) || (!diffOptions.IgnoreChangesAndAdditions && !reflect.DeepEqual(original, modified))) { setOrderList = modified } } return patchList, deleteList, setOrderList, err } // isOrderSame checks if the order in a list has changed func isOrderSame(original, modified []interface{}, mergeKey string) (bool, error) { if len(original) != len(modified) { return false, nil } for i, modifiedItem := range modified { equal, err := mergeKeyValueEqual(original[i], modifiedItem, mergeKey) if err != nil || !equal { return equal, err } } return true, nil } // diffListsOfScalars returns 2 lists, the first one is addList and the second one is deletionList. // Argument diffOptions.IgnoreChangesAndAdditions controls if calculate addList. true means not calculate. // Argument diffOptions.IgnoreDeletions controls if calculate deletionList. true means not calculate. // original may be changed, but modified is guaranteed to not be changed func diffListsOfScalars(original, modified []interface{}, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { modifiedCopy := make([]interface{}, len(modified)) copy(modifiedCopy, modified) // Sort the scalars for easier calculating the diff originalScalars := sortScalars(original) modifiedScalars := sortScalars(modifiedCopy) originalIndex, modifiedIndex := 0, 0 addList := []interface{}{} deletionList := []interface{}{} for { originalInBounds := originalIndex < len(originalScalars) modifiedInBounds := modifiedIndex < len(modifiedScalars) if !originalInBounds && !modifiedInBounds { break } // we need to compare the string representation of the scalar, // because the scalar is an interface which doesn't support either < or > // And that's how func sortScalars compare scalars. var originalString, modifiedString string var originalValue, modifiedValue interface{} if originalInBounds { originalValue = originalScalars[originalIndex] originalString = fmt.Sprintf("%v", originalValue) } if modifiedInBounds { modifiedValue = modifiedScalars[modifiedIndex] modifiedString = fmt.Sprintf("%v", modifiedValue) } originalV, modifiedV := compareListValuesAtIndex(originalInBounds, modifiedInBounds, originalString, modifiedString) switch { case originalV == nil && modifiedV == nil: originalIndex++ modifiedIndex++ case originalV != nil && modifiedV == nil: if !diffOptions.IgnoreDeletions { deletionList = append(deletionList, originalValue) } originalIndex++ case originalV == nil && modifiedV != nil: if !diffOptions.IgnoreChangesAndAdditions { addList = append(addList, modifiedValue) } modifiedIndex++ default: return nil, nil, fmt.Errorf("Unexpected returned value from compareListValuesAtIndex: %v and %v", originalV, modifiedV) } } return addList, deduplicateScalars(deletionList), nil } // If first return value is non-nil, list1 contains an element not present in list2 // If second return value is non-nil, list2 contains an element not present in list1 func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, list2Value string) (interface{}, interface{}) { bothInBounds := list1Inbounds && list2Inbounds switch { // scalars are identical case bothInBounds && list1Value == list2Value: return nil, nil // only list2 is in bound case !list1Inbounds: fallthrough // list2 has additional scalar case bothInBounds && list1Value > list2Value: return nil, list2Value // only original is in bound case !list2Inbounds: fallthrough // original has additional scalar case bothInBounds && list1Value < list2Value: return list1Value, nil default: return nil, nil } } // diffListsOfMaps takes a pair of lists and // returns a (recursive) strategic merge patch list contains additions and changes and // a deletion list contains deletions func diffListsOfMaps(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { patch := make([]interface{}, 0, len(modified)) deletionList := make([]interface{}, 0, len(original)) originalSorted, err := sortMergeListsByNameArray(original, schema, mergeKey, false) if err != nil { return nil, nil, err } modifiedSorted, err := sortMergeListsByNameArray(modified, schema, mergeKey, false) if err != nil { return nil, nil, err } originalIndex, modifiedIndex := 0, 0 for { originalInBounds := originalIndex < len(originalSorted) modifiedInBounds := modifiedIndex < len(modifiedSorted) bothInBounds := originalInBounds && modifiedInBounds if !originalInBounds && !modifiedInBounds { break } var originalElementMergeKeyValueString, modifiedElementMergeKeyValueString string var originalElementMergeKeyValue, modifiedElementMergeKeyValue interface{} var originalElement, modifiedElement map[string]interface{} if originalInBounds { originalElement, originalElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(originalIndex, mergeKey, originalSorted) if err != nil { return nil, nil, err } originalElementMergeKeyValueString = fmt.Sprintf("%v", originalElementMergeKeyValue) } if modifiedInBounds { modifiedElement, modifiedElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(modifiedIndex, mergeKey, modifiedSorted) if err != nil { return nil, nil, err } modifiedElementMergeKeyValueString = fmt.Sprintf("%v", modifiedElementMergeKeyValue) } switch { case bothInBounds && ItemMatchesOriginalAndModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): // Merge key values are equal, so recurse patchValue, err := diffMaps(originalElement, modifiedElement, schema, diffOptions) if err != nil { return nil, nil, err } if len(patchValue) > 0 { patchValue[mergeKey] = modifiedElementMergeKeyValue patch = append(patch, patchValue) } originalIndex++ modifiedIndex++ // only modified is in bound case !originalInBounds: fallthrough // modified has additional map case bothInBounds && ItemAddedToModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): if !diffOptions.IgnoreChangesAndAdditions { patch = append(patch, modifiedElement) } modifiedIndex++ // only original is in bound case !modifiedInBounds: fallthrough // original has additional map case bothInBounds && ItemRemovedFromModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): if !diffOptions.IgnoreDeletions { // Item was deleted, so add delete directive deletionList = append(deletionList, CreateDeleteDirective(mergeKey, originalElementMergeKeyValue)) } originalIndex++ } } return patch, deletionList, nil } // getMapAndMergeKeyValueByIndex return a map in the list and its merge key value given the index of the map. func getMapAndMergeKeyValueByIndex(index int, mergeKey string, listOfMaps []interface{}) (map[string]interface{}, interface{}, error) { m, ok := listOfMaps[index].(map[string]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(m, listOfMaps[index]) } val, ok := m[mergeKey] if !ok { return nil, nil, mergepatch.ErrNoMergeKey(m, mergeKey) } return m, val, nil } // StrategicMergePatch applies a strategic merge patch. The patch and the original document // must be json encoded content. A patch can be created from an original and a modified document // by calling CreateStrategicMergePatch. func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) { schema, err := NewPatchMetaFromStruct(dataStruct) if err != nil { return nil, err } return StrategicMergePatchUsingLookupPatchMeta(original, patch, schema) } func StrategicMergePatchUsingLookupPatchMeta(original, patch []byte, schema LookupPatchMeta) ([]byte, error) { originalMap, err := handleUnmarshal(original) if err != nil { return nil, err } patchMap, err := handleUnmarshal(patch) if err != nil { return nil, err } result, err := StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, schema) if err != nil { return nil, err } return json.Marshal(result) } func handleUnmarshal(j []byte) (map[string]interface{}, error) { if j == nil { j = []byte("{}") } m := map[string]interface{}{} err := json.Unmarshal(j, &m) if err != nil { return nil, mergepatch.ErrBadJSONDoc } return m, nil } // StrategicMergeMapPatch applies a strategic merge patch. The original and patch documents // must be JSONMap. A patch can be created from an original and modified document by // calling CreateTwoWayMergeMapPatch. // Warning: the original and patch JSONMap objects are mutated by this function and should not be reused. func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JSONMap, error) { schema, err := NewPatchMetaFromStruct(dataStruct) if err != nil { return nil, err } // We need the go struct tags `patchMergeKey` and `patchStrategy` for fields that support a strategic merge patch. // For native resources, we can easily figure out these tags since we know the fields. // Because custom resources are decoded as Unstructured and because we're missing the metadata about how to handle // each field in a strategic merge patch, we can't find the go struct tags. Hence, we can't easily do a strategic merge // for custom resources. So we should fail fast and return an error. if _, ok := dataStruct.(*unstructured.Unstructured); ok { return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat } return StrategicMergeMapPatchUsingLookupPatchMeta(original, patch, schema) } func StrategicMergeMapPatchUsingLookupPatchMeta(original, patch JSONMap, schema LookupPatchMeta) (JSONMap, error) { mergeOptions := MergeOptions{ MergeParallelList: true, IgnoreUnmatchedNulls: true, } return mergeMap(original, patch, schema, mergeOptions) } // MergeStrategicMergeMapPatchUsingLookupPatchMeta merges strategic merge // patches retaining `null` fields and parallel lists. If 2 patches change the // same fields and the latter one will override the former one. If you don't // want that happen, you need to run func MergingMapsHaveConflicts before // merging these patches. Applying the resulting merged merge patch to a JSONMap // yields the same as merging each strategic merge patch to the JSONMap in // succession. func MergeStrategicMergeMapPatchUsingLookupPatchMeta(schema LookupPatchMeta, patches ...JSONMap) (JSONMap, error) { mergeOptions := MergeOptions{ MergeParallelList: false, IgnoreUnmatchedNulls: false, } merged := JSONMap{} var err error for _, patch := range patches { merged, err = mergeMap(merged, patch, schema, mergeOptions) if err != nil { return nil, err } } return merged, nil } // handleDirectiveInMergeMap handles the patch directive when merging 2 maps. func handleDirectiveInMergeMap(directive interface{}, patch map[string]interface{}) (map[string]interface{}, error) { if directive == replaceDirective { // If the patch contains "$patch: replace", don't merge it, just use the // patch directly. Later on, we can add a single level replace that only // affects the map that the $patch is in. delete(patch, directiveMarker) return patch, nil } if directive == deleteDirective { // If the patch contains "$patch: delete", don't merge it, just return // an empty map. return map[string]interface{}{}, nil } return nil, mergepatch.ErrBadPatchType(directive, patch) } func containsDirectiveMarker(item interface{}) bool { m, ok := item.(map[string]interface{}) if ok { if _, foundDirectiveMarker := m[directiveMarker]; foundDirectiveMarker { return true } } return false } func mergeKeyValueEqual(left, right interface{}, mergeKey string) (bool, error) { if len(mergeKey) == 0 { return left == right, nil } typedLeft, ok := left.(map[string]interface{}) if !ok { return false, mergepatch.ErrBadArgType(typedLeft, left) } typedRight, ok := right.(map[string]interface{}) if !ok { return false, mergepatch.ErrBadArgType(typedRight, right) } mergeKeyLeft, ok := typedLeft[mergeKey] if !ok { return false, mergepatch.ErrNoMergeKey(typedLeft, mergeKey) } mergeKeyRight, ok := typedRight[mergeKey] if !ok { return false, mergepatch.ErrNoMergeKey(typedRight, mergeKey) } return mergeKeyLeft == mergeKeyRight, nil } // extractKey trims the prefix and return the original key func extractKey(s, prefix string) (string, error) { substrings := strings.SplitN(s, "/", 2) if len(substrings) <= 1 || substrings[0] != prefix { switch prefix { case deleteFromPrimitiveListDirectivePrefix: return "", mergepatch.ErrBadPatchFormatForPrimitiveList case setElementOrderDirectivePrefix: return "", mergepatch.ErrBadPatchFormatForSetElementOrderList default: return "", fmt.Errorf("fail to find unknown prefix %q in %s\n", prefix, s) } } return substrings[1], nil } // validatePatchUsingSetOrderList verifies: // the relative order of any two items in the setOrderList list matches that in the patch list. // the items in the patch list must be a subset or the same as the $setElementOrder list (deletions are ignored). func validatePatchWithSetOrderList(patchList, setOrderList interface{}, mergeKey string) error { typedSetOrderList, ok := setOrderList.([]interface{}) if !ok { return mergepatch.ErrBadPatchFormatForSetElementOrderList } typedPatchList, ok := patchList.([]interface{}) if !ok { return mergepatch.ErrBadPatchFormatForSetElementOrderList } if len(typedSetOrderList) == 0 || len(typedPatchList) == 0 { return nil } var nonDeleteList []interface{} var err error if len(mergeKey) > 0 { nonDeleteList, _, err = extractToDeleteItems(typedPatchList) if err != nil { return err } } else { nonDeleteList = typedPatchList } patchIndex, setOrderIndex := 0, 0 for patchIndex < len(nonDeleteList) && setOrderIndex < len(typedSetOrderList) { if containsDirectiveMarker(nonDeleteList[patchIndex]) { patchIndex++ continue } mergeKeyEqual, err := mergeKeyValueEqual(nonDeleteList[patchIndex], typedSetOrderList[setOrderIndex], mergeKey) if err != nil { return err } if mergeKeyEqual { patchIndex++ } setOrderIndex++ } // If patchIndex is inbound but setOrderIndex if out of bound mean there are items mismatching between the patch list and setElementOrder list. // the second check is a sanity check, and should always be true if the first is true. if patchIndex < len(nonDeleteList) && setOrderIndex >= len(typedSetOrderList) { return fmt.Errorf("The order in patch list:\n%v\n doesn't match %s list:\n%v\n", typedPatchList, setElementOrderDirectivePrefix, setOrderList) } return nil } // preprocessDeletionListForMerging preprocesses the deletion list. // it returns shouldContinue, isDeletionList, noPrefixKey func preprocessDeletionListForMerging(key string, original map[string]interface{}, patchVal interface{}, mergeDeletionList bool) (bool, bool, string, error) { // If found a parallel list for deletion and we are going to merge the list, // overwrite the key to the original key and set flag isDeleteList foundParallelListPrefix := strings.HasPrefix(key, deleteFromPrimitiveListDirectivePrefix) if foundParallelListPrefix { if !mergeDeletionList { original[key] = patchVal return true, false, "", nil } originalKey, err := extractKey(key, deleteFromPrimitiveListDirectivePrefix) return false, true, originalKey, err } return false, false, "", nil } // applyRetainKeysDirective looks for a retainKeys directive and applies to original // - if no directive exists do nothing // - if directive is found, clear keys in original missing from the directive list // - validate that all keys present in the patch are present in the retainKeys directive // note: original may be another patch request, e.g. applying the add+modified patch to the deletions patch. In this case it may have directives func applyRetainKeysDirective(original, patch map[string]interface{}, options MergeOptions) error { retainKeysInPatch, foundInPatch := patch[retainKeysDirective] if !foundInPatch { return nil } // cleanup the directive delete(patch, retainKeysDirective) if !options.MergeParallelList { // If original is actually a patch, make sure the retainKeys directives are the same in both patches if present in both. // If not present in the original patch, copy from the modified patch. retainKeysInOriginal, foundInOriginal := original[retainKeysDirective] if foundInOriginal { if !reflect.DeepEqual(retainKeysInOriginal, retainKeysInPatch) { // This error actually should never happen. return fmt.Errorf("%v and %v are not deep equal: this may happen when calculating the 3-way diff patch", retainKeysInOriginal, retainKeysInPatch) } } else { original[retainKeysDirective] = retainKeysInPatch } return nil } retainKeysList, ok := retainKeysInPatch.([]interface{}) if !ok { return mergepatch.ErrBadPatchFormatForRetainKeys } // validate patch to make sure all fields in the patch are present in the retainKeysList. // The map is used only as a set, the value is never referenced m := map[interface{}]struct{}{} for _, v := range retainKeysList { m[v] = struct{}{} } for k, v := range patch { if v == nil || strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) || strings.HasPrefix(k, setElementOrderDirectivePrefix) { continue } // If there is an item present in the patch but not in the retainKeys list, // the patch is invalid. if _, found := m[k]; !found { return mergepatch.ErrBadPatchFormatForRetainKeys } } // clear not present fields for k := range original { if _, found := m[k]; !found { delete(original, k) } } return nil } // mergePatchIntoOriginal processes $setElementOrder list. // When not merging the directive, it will make sure $setElementOrder list exist only in original. // When merging the directive, it will try to find the $setElementOrder list and // its corresponding patch list, validate it and merge it. // Then, sort them by the relative order in setElementOrder, patch list and live list. // The precedence is $setElementOrder > order in patch list > order in live list. // This function will delete the item after merging it to prevent process it again in the future. // Ref: https://git.k8s.io/design-proposals-archive/cli/preserve-order-in-strategic-merge-patch.md func mergePatchIntoOriginal(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) error { for key, patchV := range patch { // Do nothing if there is no ordering directive if !strings.HasPrefix(key, setElementOrderDirectivePrefix) { continue } setElementOrderInPatch := patchV // Copies directive from the second patch (`patch`) to the first patch (`original`) // and checks they are equal and delete the directive in the second patch if !mergeOptions.MergeParallelList { setElementOrderListInOriginal, ok := original[key] if ok { // check if the setElementOrder list in original and the one in patch matches if !reflect.DeepEqual(setElementOrderListInOriginal, setElementOrderInPatch) { return mergepatch.ErrBadPatchFormatForSetElementOrderList } } else { // move the setElementOrder list from patch to original original[key] = setElementOrderInPatch } } delete(patch, key) var ( ok bool originalFieldValue, patchFieldValue, merged []interface{} patchStrategy string patchMeta PatchMeta subschema LookupPatchMeta ) typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{}) if !ok { return mergepatch.ErrBadArgType(typedSetElementOrderList, setElementOrderInPatch) } // Trim the setElementOrderDirectivePrefix to get the key of the list field in original. originalKey, err := extractKey(key, setElementOrderDirectivePrefix) if err != nil { return err } // try to find the list with `originalKey` in `original` and `modified` and merge them. originalList, foundOriginal := original[originalKey] patchList, foundPatch := patch[originalKey] if foundOriginal { originalFieldValue, ok = originalList.([]interface{}) if !ok { return mergepatch.ErrBadArgType(originalFieldValue, originalList) } } if foundPatch { patchFieldValue, ok = patchList.([]interface{}) if !ok { return mergepatch.ErrBadArgType(patchFieldValue, patchList) } } subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(originalKey) if err != nil { return err } _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return err } // Check for consistency between the element order list and the field it applies to err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, patchMeta.GetPatchMergeKey()) if err != nil { return err } switch { case foundOriginal && !foundPatch: // no change to list contents merged = originalFieldValue case !foundOriginal && foundPatch: // list was added v, keep := removeDirectives(patchFieldValue) if !keep { // Shouldn't be possible since patchFieldValue is a slice continue } merged = v.([]interface{}) case foundOriginal && foundPatch: merged, err = mergeSliceHandler(originalList, patchList, subschema, patchStrategy, patchMeta.GetPatchMergeKey(), false, mergeOptions) if err != nil { return err } case !foundOriginal && !foundPatch: continue } // Split all items into patch items and server-only items and then enforce the order. var patchItems, serverOnlyItems []interface{} if len(patchMeta.GetPatchMergeKey()) == 0 { // Primitives doesn't need merge key to do partitioning. patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList) } else { // Maps need merge key to do partitioning. patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, patchMeta.GetPatchMergeKey()) if err != nil { return err } } elementType, err := sliceElementType(originalFieldValue, patchFieldValue) if err != nil { return err } kind := elementType.Kind() // normalize merged list // typedSetElementOrderList contains all the relative order in typedPatchList, // so don't need to use typedPatchList both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, patchMeta.GetPatchMergeKey(), kind) if err != nil { return err } original[originalKey] = both // delete patch list from patch to prevent process again in the future delete(patch, originalKey) } return nil } // partitionPrimitivesByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. func partitionPrimitivesByPresentInList(original, partitionBy []interface{}) ([]interface{}, []interface{}) { patch := make([]interface{}, 0, len(original)) serverOnly := make([]interface{}, 0, len(original)) inPatch := map[interface{}]bool{} for _, v := range partitionBy { inPatch[v] = true } for _, v := range original { if !inPatch[v] { serverOnly = append(serverOnly, v) } else { patch = append(patch, v) } } return patch, serverOnly } // partitionMapsByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. func partitionMapsByPresentInList(original, partitionBy []interface{}, mergeKey string) ([]interface{}, []interface{}, error) { patch := make([]interface{}, 0, len(original)) serverOnly := make([]interface{}, 0, len(original)) for _, v := range original { typedV, ok := v.(map[string]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(typedV, v) } mergeKeyValue, foundMergeKey := typedV[mergeKey] if !foundMergeKey { return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) } _, _, found, err := findMapInSliceBasedOnKeyValue(partitionBy, mergeKey, mergeKeyValue) if err != nil { return nil, nil, err } if !found { serverOnly = append(serverOnly, v) } else { patch = append(patch, v) } } return patch, serverOnly, nil } // Removes directives from an object and returns value to use instead and whether // or not the field/index should even be kept // May modify input func removeDirectives(obj interface{}) (interface{}, bool) { if obj == nil { return obj, true } else if typedV, ok := obj.(map[string]interface{}); ok { if _, hasDirective := typedV[directiveMarker]; hasDirective { return nil, false } for k, v := range typedV { var keep bool typedV[k], keep = removeDirectives(v) if !keep { delete(typedV, k) } } return typedV, true } else if typedV, ok := obj.([]interface{}); ok { var res []interface{} if typedV != nil { // Make sure res is non-nil if patch is non-nil res = []interface{}{} } for _, v := range typedV { if newV, keep := removeDirectives(v); keep { res = append(res, newV) } } return res, true } else { return obj, true } } // Merge fields from a patch map into the original map. Note: This may modify // both the original map and the patch because getting a deep copy of a map in // golang is highly non-trivial. // flag mergeOptions.MergeParallelList controls if using the parallel list to delete or keeping the list. // If patch contains any null field (e.g. field_1: null) that is not // present in original, then to propagate it to the end result use // mergeOptions.IgnoreUnmatchedNulls == false. func mergeMap(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) (map[string]interface{}, error) { if v, ok := patch[directiveMarker]; ok { return handleDirectiveInMergeMap(v, patch) } // nil is an accepted value for original to simplify logic in other places. // If original is nil, replace it with an empty map and then apply the patch. if original == nil { original = map[string]interface{}{} } err := applyRetainKeysDirective(original, patch, mergeOptions) if err != nil { return nil, err } // Process $setElementOrder list and other lists sharing the same key. // When not merging the directive, it will make sure $setElementOrder list exist only in original. // When merging the directive, it will process $setElementOrder and its patch list together. // This function will delete the merged elements from patch so they will not be reprocessed err = mergePatchIntoOriginal(original, patch, schema, mergeOptions) if err != nil { return nil, err } // Start merging the patch into the original. for k, patchV := range patch { skipProcessing, isDeleteList, noPrefixKey, err := preprocessDeletionListForMerging(k, original, patchV, mergeOptions.MergeParallelList) if err != nil { return nil, err } if skipProcessing { continue } if len(noPrefixKey) > 0 { k = noPrefixKey } // If the value of this key is null, delete the key if it exists in the // original. Otherwise, check if we want to preserve it or skip it. // Preserving the null value is useful when we want to send an explicit // delete to the API server. if patchV == nil { delete(original, k) if mergeOptions.IgnoreUnmatchedNulls { continue } } _, ok := original[k] if !ok { if !isDeleteList { // If it's not in the original document, just take the patch value. if mergeOptions.IgnoreUnmatchedNulls { discardNullValuesFromPatch(patchV) } original[k], ok = removeDirectives(patchV) if !ok { delete(original, k) } } continue } originalType := reflect.TypeOf(original[k]) patchType := reflect.TypeOf(patchV) if originalType != patchType { if !isDeleteList { if mergeOptions.IgnoreUnmatchedNulls { discardNullValuesFromPatch(patchV) } original[k], ok = removeDirectives(patchV) if !ok { delete(original, k) } } continue } // If they're both maps or lists, recurse into the value. switch originalType.Kind() { case reflect.Map: subschema, patchMeta, err2 := schema.LookupPatchMetadataForStruct(k) if err2 != nil { return nil, err2 } _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err2 != nil { return nil, err2 } original[k], err = mergeMapHandler(original[k], patchV, subschema, patchStrategy, mergeOptions) case reflect.Slice: subschema, patchMeta, err2 := schema.LookupPatchMetadataForSlice(k) if err2 != nil { return nil, err2 } _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err2 != nil { return nil, err2 } original[k], err = mergeSliceHandler(original[k], patchV, subschema, patchStrategy, patchMeta.GetPatchMergeKey(), isDeleteList, mergeOptions) default: original[k], ok = removeDirectives(patchV) if !ok { // if patchV itself is a directive, then don't keep it delete(original, k) } } if err != nil { return nil, err } } return original, nil } // discardNullValuesFromPatch discards all null property values from patch. // It traverses all slices and map types. func discardNullValuesFromPatch(patchV interface{}) { switch patchV := patchV.(type) { case map[string]interface{}: for k, v := range patchV { if v == nil { delete(patchV, k) } else { discardNullValuesFromPatch(v) } } case []interface{}: for _, v := range patchV { discardNullValuesFromPatch(v) } } } // mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting // fieldPatchStrategy and mergeOptions. func mergeMapHandler(original, patch interface{}, schema LookupPatchMeta, fieldPatchStrategy string, mergeOptions MergeOptions) (map[string]interface{}, error) { typedOriginal, typedPatch, err := mapTypeAssertion(original, patch) if err != nil { return nil, err } if fieldPatchStrategy != replaceDirective { return mergeMap(typedOriginal, typedPatch, schema, mergeOptions) } else { return typedPatch, nil } } // mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting // fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions. func mergeSliceHandler(original, patch interface{}, schema LookupPatchMeta, fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) ([]interface{}, error) { typedOriginal, typedPatch, err := sliceTypeAssertion(original, patch) if err != nil { return nil, err } // Delete lists are handled the same way regardless of what the field's patch strategy is if fieldPatchStrategy == mergeDirective || isDeleteList { return mergeSlice(typedOriginal, typedPatch, schema, fieldPatchMergeKey, mergeOptions, isDeleteList) } else { return typedPatch, nil } } // Merge two slices together. Note: This may modify both the original slice and // the patch because getting a deep copy of a slice in golang is highly // non-trivial. func mergeSlice(original, patch []interface{}, schema LookupPatchMeta, mergeKey string, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) { if len(original) == 0 && len(patch) == 0 { return original, nil } // All the values must be of the same type, but not a list. t, err := sliceElementType(original, patch) if err != nil { return nil, err } var merged []interface{} kind := t.Kind() // If the elements are not maps, merge the slices of scalars. if kind != reflect.Map { if mergeOptions.MergeParallelList && isDeleteList { return deleteFromSlice(original, patch), nil } // Maybe in the future add a "concat" mode that doesn't // deduplicate. both := append(original, patch...) merged = deduplicateScalars(both) } else { if mergeKey == "" { return nil, fmt.Errorf("cannot merge lists without merge key for %s", schema.Name()) } original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey) if err != nil { return nil, err } merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, schema, mergeOptions) if err != nil { return nil, err } } // enforce the order var patchItems, serverOnlyItems []interface{} if len(mergeKey) == 0 { patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, patch) } else { patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, patch, mergeKey) if err != nil { return nil, err } } return normalizeElementOrder(patchItems, serverOnlyItems, patch, original, mergeKey, kind) } // mergeSliceWithSpecialElements handles special elements with directiveMarker // before merging the slices. It returns a updated `original` and a patch without special elements. // original and patch must be slices of maps, they should be checked before calling this function. func mergeSliceWithSpecialElements(original, patch []interface{}, mergeKey string) ([]interface{}, []interface{}, error) { patchWithoutSpecialElements := []interface{}{} replace := false for _, v := range patch { typedV := v.(map[string]interface{}) patchType, ok := typedV[directiveMarker] if !ok { patchWithoutSpecialElements = append(patchWithoutSpecialElements, v) } else { switch patchType { case deleteDirective: mergeValue, ok := typedV[mergeKey] if ok { var err error original, err = deleteMatchingEntries(original, mergeKey, mergeValue) if err != nil { return nil, nil, err } } else { return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) } case replaceDirective: replace = true // Continue iterating through the array to prune any other $patch elements. case mergeDirective: return nil, nil, fmt.Errorf("merging lists cannot yet be specified in the patch") default: return nil, nil, mergepatch.ErrBadPatchType(patchType, typedV) } } } if replace { return patchWithoutSpecialElements, nil, nil } return original, patchWithoutSpecialElements, nil } // delete all matching entries (based on merge key) from a merging list func deleteMatchingEntries(original []interface{}, mergeKey string, mergeValue interface{}) ([]interface{}, error) { for { _, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue) if err != nil { return nil, err } if !found { break } // Delete the element at originalKey. original = append(original[:originalKey], original[originalKey+1:]...) } return original, nil } // mergeSliceWithoutSpecialElements merges slices with non-special elements. // original and patch must be slices of maps, they should be checked before calling this function. func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, schema LookupPatchMeta, mergeOptions MergeOptions) ([]interface{}, error) { for _, v := range patch { typedV := v.(map[string]interface{}) mergeValue, ok := typedV[mergeKey] if !ok { return nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) } // If we find a value with this merge key value in original, merge the // maps. Otherwise append onto original. originalMap, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue) if err != nil { return nil, err } if found { var mergedMaps interface{} var err error // Merge into original. mergedMaps, err = mergeMap(originalMap, typedV, schema, mergeOptions) if err != nil { return nil, err } original[originalKey] = mergedMaps } else { original = append(original, v) } } return original, nil } // deleteFromSlice uses the parallel list to delete the items in a list of scalars func deleteFromSlice(current, toDelete []interface{}) []interface{} { toDeleteMap := map[interface{}]interface{}{} processed := make([]interface{}, 0, len(current)) for _, v := range toDelete { toDeleteMap[v] = true } for _, v := range current { if _, found := toDeleteMap[v]; !found { processed = append(processed, v) } } return processed } // This method no longer panics if any element of the slice is not a map. func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{}) (map[string]interface{}, int, bool, error) { for k, v := range m { typedV, ok := v.(map[string]interface{}) if !ok { return nil, 0, false, fmt.Errorf("value for key %v is not a map", k) } valueToMatch, ok := typedV[key] if ok && valueToMatch == value { return typedV, k, true, nil } } return nil, 0, false, nil } // This function takes a JSON map and sorts all the lists that should be merged // by key. This is needed by tests because in JSON, list order is significant, // but in Strategic Merge Patch, merge lists do not have significant order. // Sorting the lists allows for order-insensitive comparison of patched maps. func sortMergeListsByName(mapJSON []byte, schema LookupPatchMeta) ([]byte, error) { var m map[string]interface{} err := json.Unmarshal(mapJSON, &m) if err != nil { return nil, mergepatch.ErrBadJSONDoc } newM, err := sortMergeListsByNameMap(m, schema) if err != nil { return nil, err } return json.Marshal(newM) } // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map. func sortMergeListsByNameMap(s map[string]interface{}, schema LookupPatchMeta) (map[string]interface{}, error) { newS := map[string]interface{}{} for k, v := range s { if k == retainKeysDirective { typedV, ok := v.([]interface{}) if !ok { return nil, mergepatch.ErrBadPatchFormatForRetainKeys } v = sortScalars(typedV) } else if strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) { typedV, ok := v.([]interface{}) if !ok { return nil, mergepatch.ErrBadPatchFormatForPrimitiveList } v = sortScalars(typedV) } else if strings.HasPrefix(k, setElementOrderDirectivePrefix) { _, ok := v.([]interface{}) if !ok { return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList } } else if k != directiveMarker { // recurse for map and slice. switch typedV := v.(type) { case map[string]interface{}: subschema, _, err := schema.LookupPatchMetadataForStruct(k) if err != nil { return nil, err } v, err = sortMergeListsByNameMap(typedV, subschema) if err != nil { return nil, err } case []interface{}: subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(k) if err != nil { return nil, err } _, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) if err != nil { return nil, err } if patchStrategy == mergeDirective { var err error v, err = sortMergeListsByNameArray(typedV, subschema, patchMeta.GetPatchMergeKey(), true) if err != nil { return nil, err } } } } newS[k] = v } return newS, nil } // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array. func sortMergeListsByNameArray(s []interface{}, schema LookupPatchMeta, mergeKey string, recurse bool) ([]interface{}, error) { if len(s) == 0 { return s, nil } // We don't support lists of lists yet. t, err := sliceElementType(s) if err != nil { return nil, err } // If the elements are not maps... if t.Kind() != reflect.Map { // Sort the elements, because they may have been merged out of order. return deduplicateAndSortScalars(s), nil } // Elements are maps - if one of the keys of the map is a map or a // list, we may need to recurse into it. newS := []interface{}{} for _, elem := range s { if recurse { typedElem := elem.(map[string]interface{}) newElem, err := sortMergeListsByNameMap(typedElem, schema) if err != nil { return nil, err } newS = append(newS, newElem) } else { newS = append(newS, elem) } } // Sort the maps. newS = sortMapsBasedOnField(newS, mergeKey) return newS, nil } func sortMapsBasedOnField(m []interface{}, fieldName string) []interface{} { mapM := mapSliceFromSlice(m) ss := SortableSliceOfMaps{mapM, fieldName} sort.Sort(ss) newS := sliceFromMapSlice(ss.s) return newS } func mapSliceFromSlice(m []interface{}) []map[string]interface{} { newM := []map[string]interface{}{} for _, v := range m { vt := v.(map[string]interface{}) newM = append(newM, vt) } return newM } func sliceFromMapSlice(s []map[string]interface{}) []interface{} { newS := []interface{}{} for _, v := range s { newS = append(newS, v) } return newS } type SortableSliceOfMaps struct { s []map[string]interface{} k string // key to sort on } func (ss SortableSliceOfMaps) Len() int { return len(ss.s) } func (ss SortableSliceOfMaps) Less(i, j int) bool { iStr := fmt.Sprintf("%v", ss.s[i][ss.k]) jStr := fmt.Sprintf("%v", ss.s[j][ss.k]) return sort.StringsAreSorted([]string{iStr, jStr}) } func (ss SortableSliceOfMaps) Swap(i, j int) { tmp := ss.s[i] ss.s[i] = ss.s[j] ss.s[j] = tmp } func deduplicateAndSortScalars(s []interface{}) []interface{} { s = deduplicateScalars(s) return sortScalars(s) } func sortScalars(s []interface{}) []interface{} { ss := SortableSliceOfScalars{s} sort.Sort(ss) return ss.s } func deduplicateScalars(s []interface{}) []interface{} { // Clever algorithm to deduplicate. length := len(s) - 1 for i := 0; i < length; i++ { for j := i + 1; j <= length; j++ { if s[i] == s[j] { s[j] = s[length] s = s[0:length] length-- j-- } } } return s } type SortableSliceOfScalars struct { s []interface{} } func (ss SortableSliceOfScalars) Len() int { return len(ss.s) } func (ss SortableSliceOfScalars) Less(i, j int) bool { iStr := fmt.Sprintf("%v", ss.s[i]) jStr := fmt.Sprintf("%v", ss.s[j]) return sort.StringsAreSorted([]string{iStr, jStr}) } func (ss SortableSliceOfScalars) Swap(i, j int) { tmp := ss.s[i] ss.s[i] = ss.s[j] ss.s[j] = tmp } // Returns the type of the elements of N slice(s). If the type is different, // another slice or undefined, returns an error. func sliceElementType(slices ...[]interface{}) (reflect.Type, error) { var prevType reflect.Type for _, s := range slices { // Go through elements of all given slices and make sure they are all the same type. for _, v := range s { currentType := reflect.TypeOf(v) if prevType == nil { prevType = currentType // We don't support lists of lists yet. if prevType.Kind() == reflect.Slice { return nil, mergepatch.ErrNoListOfLists } } else { if prevType != currentType { return nil, fmt.Errorf("list element types are not identical: %v", fmt.Sprint(slices)) } prevType = currentType } } } if prevType == nil { return nil, fmt.Errorf("no elements in any of the given slices") } return prevType, nil } // MergingMapsHaveConflicts returns true if the left and right JSON interface // objects overlap with different values in any key. All keys are required to be // strings. Since patches of the same Type have congruent keys, this is valid // for multiple patch types. This method supports strategic merge patch semantics. func MergingMapsHaveConflicts(left, right map[string]interface{}, schema LookupPatchMeta) (bool, error) { return mergingMapFieldsHaveConflicts(left, right, schema, "", "") } func mergingMapFieldsHaveConflicts( left, right interface{}, schema LookupPatchMeta, fieldPatchStrategy, fieldPatchMergeKey string, ) (bool, error) { switch leftType := left.(type) { case map[string]interface{}: rightType, ok := right.(map[string]interface{}) if !ok { return true, nil } leftMarker, okLeft := leftType[directiveMarker] rightMarker, okRight := rightType[directiveMarker] // if one or the other has a directive marker, // then we need to consider that before looking at the individual keys, // since a directive operates on the whole map. if okLeft || okRight { // if one has a directive marker and the other doesn't, // then we have a conflict, since one is deleting or replacing the whole map, // and the other is doing things to individual keys. if okLeft != okRight { return true, nil } // if they both have markers, but they are not the same directive, // then we have a conflict because they're doing different things to the map. if leftMarker != rightMarker { return true, nil } } if fieldPatchStrategy == replaceDirective { return false, nil } // Check the individual keys. return mapsHaveConflicts(leftType, rightType, schema) case []interface{}: rightType, ok := right.([]interface{}) if !ok { return true, nil } return slicesHaveConflicts(leftType, rightType, schema, fieldPatchStrategy, fieldPatchMergeKey) case string, float64, bool, int64, nil: return !reflect.DeepEqual(left, right), nil default: return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left)) } } func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) { for key, leftValue := range typedLeft { if key != directiveMarker && key != retainKeysDirective { if rightValue, ok := typedRight[key]; ok { var subschema LookupPatchMeta var patchMeta PatchMeta var patchStrategy string var err error switch leftValue.(type) { case []interface{}: subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(key) if err != nil { return true, err } _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies) if err != nil { return true, err } case map[string]interface{}: subschema, patchMeta, err = schema.LookupPatchMetadataForStruct(key) if err != nil { return true, err } _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies) if err != nil { return true, err } } if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, subschema, patchStrategy, patchMeta.GetPatchMergeKey()); hasConflicts { return true, err } } } } return false, nil } func slicesHaveConflicts( typedLeft, typedRight []interface{}, schema LookupPatchMeta, fieldPatchStrategy, fieldPatchMergeKey string, ) (bool, error) { elementType, err := sliceElementType(typedLeft, typedRight) if err != nil { return true, err } if fieldPatchStrategy == mergeDirective { // Merging lists of scalars have no conflicts by definition // So we only need to check further if the elements are maps if elementType.Kind() != reflect.Map { return false, nil } // Build a map for each slice and then compare the two maps leftMap, err := sliceOfMapsToMapOfMaps(typedLeft, fieldPatchMergeKey) if err != nil { return true, err } rightMap, err := sliceOfMapsToMapOfMaps(typedRight, fieldPatchMergeKey) if err != nil { return true, err } return mapsOfMapsHaveConflicts(leftMap, rightMap, schema) } // Either we don't have type information, or these are non-merging lists if len(typedLeft) != len(typedRight) { return true, nil } // Sort scalar slices to prevent ordering issues // We have no way to sort non-merging lists of maps if elementType.Kind() != reflect.Map { typedLeft = deduplicateAndSortScalars(typedLeft) typedRight = deduplicateAndSortScalars(typedRight) } // Compare the slices element by element in order // This test will fail if the slices are not sorted for i := range typedLeft { if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], schema, "", ""); hasConflicts { return true, err } } return false, nil } func sliceOfMapsToMapOfMaps(slice []interface{}, mergeKey string) (map[string]interface{}, error) { result := make(map[string]interface{}, len(slice)) for _, value := range slice { typedValue, ok := value.(map[string]interface{}) if !ok { return nil, fmt.Errorf("invalid element type in merging list:%v", slice) } mergeValue, ok := typedValue[mergeKey] if !ok { return nil, fmt.Errorf("cannot find merge key `%s` in merging list element:%v", mergeKey, typedValue) } result[fmt.Sprintf("%s", mergeValue)] = typedValue } return result, nil } func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) { for key, leftValue := range typedLeft { if rightValue, ok := typedRight[key]; ok { if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, schema, "", ""); hasConflicts { return true, err } } } return false, nil } // CreateThreeWayMergePatch reconciles a modified configuration with an original configuration, // while preserving any changes or deletions made to the original configuration in the interim, // and not overridden by the current configuration. All three documents must be passed to the // method as json encoded content. It will return a strategic merge patch, or an error if any // of the documents is invalid, or if there are any preconditions that fail against the modified // configuration, or, if overwrite is false and there are conflicts between the modified and current // configurations. Conflicts are defined as keys changed differently from original to modified // than from original to current. In other words, a conflict occurs if modified changes any key // in a way that is different from how it is changed in current (e.g., deleting it, changing its // value). We also propagate values fields that do not exist in original but are explicitly // defined in modified. func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) { originalMap := map[string]interface{}{} if len(original) > 0 { if err := json.Unmarshal(original, &originalMap); err != nil { return nil, mergepatch.ErrBadJSONDoc } } modifiedMap := map[string]interface{}{} if len(modified) > 0 { if err := json.Unmarshal(modified, &modifiedMap); err != nil { return nil, mergepatch.ErrBadJSONDoc } } currentMap := map[string]interface{}{} if len(current) > 0 { if err := json.Unmarshal(current, ¤tMap); err != nil { return nil, mergepatch.ErrBadJSONDoc } } // The patch is the difference from current to modified without deletions, plus deletions // from original to modified. To find it, we compute deletions, which are the deletions from // original to modified, and delta, which is the difference from current to modified without // deletions, and then apply delta to deletions as a patch, which should be strictly additive. deltaMapDiffOptions := DiffOptions{ IgnoreDeletions: true, SetElementOrder: true, } deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions) if err != nil { return nil, err } deletionsMapDiffOptions := DiffOptions{ SetElementOrder: true, IgnoreChangesAndAdditions: true, } deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions) if err != nil { return nil, err } mergeOptions := MergeOptions{} patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions) if err != nil { return nil, err } // Apply the preconditions to the patch, and return an error if any of them fail. for _, fn := range fns { if !fn(patchMap) { return nil, mergepatch.NewErrPreconditionFailed(patchMap) } } // If overwrite is false, and the patch contains any keys that were changed differently, // then return a conflict error. if !overwrite { changeMapDiffOptions := DiffOptions{} changedMap, err := diffMaps(originalMap, currentMap, schema, changeMapDiffOptions) if err != nil { return nil, err } hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, schema) if err != nil { return nil, err } if hasConflicts { return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(patchMap), mergepatch.ToYAMLOrError(changedMap)) } } return json.Marshal(patchMap) } func ItemAddedToModifiedSlice(original, modified string) bool { return original > modified } func ItemRemovedFromModifiedSlice(original, modified string) bool { return original < modified } func ItemMatchesOriginalAndModifiedSlice(original, modified string) bool { return original == modified } func CreateDeleteDirective(mergeKey string, mergeKeyValue interface{}) map[string]interface{} { return map[string]interface{}{mergeKey: mergeKeyValue, directiveMarker: deleteDirective} } func mapTypeAssertion(original, patch interface{}) (map[string]interface{}, map[string]interface{}, error) { typedOriginal, ok := original.(map[string]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(typedOriginal, original) } typedPatch, ok := patch.(map[string]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(typedPatch, patch) } return typedOriginal, typedPatch, nil } func sliceTypeAssertion(original, patch interface{}) ([]interface{}, []interface{}, error) { typedOriginal, ok := original.([]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(typedOriginal, original) } typedPatch, ok := patch.([]interface{}) if !ok { return nil, nil, mergepatch.ErrBadArgType(typedPatch, patch) } return typedOriginal, typedPatch, nil } // extractRetainKeysPatchStrategy process patch strategy, which is a string may contains multiple // patch strategies separated by ",". It returns a boolean var indicating if it has // retainKeys strategies and a string for the other strategy. func extractRetainKeysPatchStrategy(strategies []string) (bool, string, error) { switch len(strategies) { case 0: return false, "", nil case 1: singleStrategy := strategies[0] switch singleStrategy { case retainKeysStrategy: return true, "", nil default: return false, singleStrategy, nil } case 2: switch { case strategies[0] == retainKeysStrategy: return true, strategies[1], nil case strategies[1] == retainKeysStrategy: return true, strategies[0], nil default: return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies) } default: return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies) } } // hasAdditionalNewField returns if original map has additional key with non-nil value than modified. func hasAdditionalNewField(original, modified map[string]interface{}) bool { for k, v := range original { if v == nil { continue } if _, found := modified[k]; !found { return true } } return false } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/patch_test.go000066400000000000000000004045511453143165200254700ustar00rootroot00000000000000/* 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 strategicpatch import ( "fmt" "path/filepath" "reflect" "strings" "testing" "sigs.k8s.io/yaml" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/dump" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/mergepatch" "k8s.io/apimachinery/pkg/util/sets" sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing" ) var ( fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")} fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")} fakeMergeItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-merge-item-v3.json")} fakePrecisionItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-precision-item-v3.json")} ) type SortMergeListTestCases struct { TestCases []SortMergeListTestCase } type SortMergeListTestCase struct { Description string Original map[string]interface{} Sorted map[string]interface{} } type StrategicMergePatchTestCases struct { TestCases []StrategicMergePatchTestCase } type StrategicMergePatchTestCase struct { Description string StrategicMergePatchTestCaseData } type StrategicMergePatchRawTestCase struct { Description string StrategicMergePatchRawTestCaseData } type StrategicMergePatchTestCaseData struct { // Original is the original object (last-applied config in annotation) Original map[string]interface{} // Modified is the modified object (new config we want) Modified map[string]interface{} // Current is the current object (live config in the server) Current map[string]interface{} // TwoWay is the expected two-way merge patch diff between original and modified TwoWay map[string]interface{} // ThreeWay is the expected three-way merge patch ThreeWay map[string]interface{} // Result is the expected object after applying the three-way patch on current object. Result map[string]interface{} // TwoWayResult is the expected object after applying the two-way patch on current object. // If nil, Modified is used. TwoWayResult map[string]interface{} } // The meaning of each field is the same as StrategicMergePatchTestCaseData's. // The difference is that all the fields in StrategicMergePatchRawTestCaseData are json-encoded data. type StrategicMergePatchRawTestCaseData struct { Original []byte Modified []byte Current []byte TwoWay []byte ThreeWay []byte Result []byte TwoWayResult []byte ExpectedError string } type MergeItem struct { Name string `json:"name,omitempty"` Value string `json:"value,omitempty"` Other string `json:"other,omitempty"` MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"` NonMergingList []MergeItem `json:"nonMergingList,omitempty"` MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"` NonMergingIntList []int `json:"nonMergingIntList,omitempty"` MergeItemPtr *MergeItem `json:"mergeItemPtr,omitempty" patchStrategy:"merge" patchMergeKey:"name"` SimpleMap map[string]string `json:"simpleMap,omitempty"` ReplacingItem runtime.RawExtension `json:"replacingItem,omitempty" patchStrategy:"replace"` RetainKeysMap RetainKeysMergeItem `json:"retainKeysMap,omitempty" patchStrategy:"retainKeys"` RetainKeysMergingList []MergeItem `json:"retainKeysMergingList,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` } type RetainKeysMergeItem struct { Name string `json:"name,omitempty"` Value string `json:"value,omitempty"` Other string `json:"other,omitempty"` SimpleMap map[string]string `json:"simpleMap,omitempty"` MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"` MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"` NonMergingList []MergeItem `json:"nonMergingList,omitempty"` } var ( mergeItem MergeItem mergeItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(mergeItem)} ) // These are test cases for SortMergeList, used to assert that it (recursively) // sorts both merging and non merging lists correctly. var sortMergeListTestCaseData = []byte(` testCases: - description: sort one list of maps original: mergingList: - name: 1 - name: 3 - name: 2 sorted: mergingList: - name: 1 - name: 2 - name: 3 - description: sort lists of maps but not nested lists of maps original: mergingList: - name: 2 nonMergingList: - name: 1 - name: 3 - name: 2 - name: 1 nonMergingList: - name: 2 - name: 1 sorted: mergingList: - name: 1 nonMergingList: - name: 2 - name: 1 - name: 2 nonMergingList: - name: 1 - name: 3 - name: 2 - description: sort lists of maps and nested lists of maps original: mergingList: - name: 2 mergingList: - name: 1 - name: 3 - name: 2 - name: 1 mergingList: - name: 2 - name: 1 sorted: mergingList: - name: 1 mergingList: - name: 1 - name: 2 - name: 2 mergingList: - name: 1 - name: 2 - name: 3 - description: merging list should NOT sort when nested in non merging list original: nonMergingList: - name: 2 mergingList: - name: 1 - name: 3 - name: 2 - name: 1 mergingList: - name: 2 - name: 1 sorted: nonMergingList: - name: 2 mergingList: - name: 1 - name: 3 - name: 2 - name: 1 mergingList: - name: 2 - name: 1 - description: sort very nested list of maps fieldTypes: original: mergingList: - mergingList: - mergingList: - name: 2 - name: 1 sorted: mergingList: - mergingList: - mergingList: - name: 1 - name: 2 - description: sort nested lists of ints original: mergingList: - name: 2 mergingIntList: - 1 - 3 - 2 - name: 1 mergingIntList: - 2 - 1 sorted: mergingList: - name: 1 mergingIntList: - 1 - 2 - name: 2 mergingIntList: - 1 - 2 - 3 - description: sort nested pointers of ints original: mergeItemPtr: - name: 2 mergingIntList: - 1 - 3 - 2 - name: 1 mergingIntList: - 2 - 1 sorted: mergeItemPtr: - name: 1 mergingIntList: - 1 - 2 - name: 2 mergingIntList: - 1 - 2 - 3 - description: sort merging list by pointer original: mergeItemPtr: - name: 1 - name: 3 - name: 2 sorted: mergeItemPtr: - name: 1 - name: 2 - name: 3 `) func TestSortMergeLists(t *testing.T) { mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), } mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], } schemas := []LookupPatchMeta{ mergeItemStructSchema, mergeItemOpenapiSchema, mergeItemOpenapiV3Schema, } tc := SortMergeListTestCases{} err := yaml.Unmarshal(sortMergeListTestCaseData, &tc) if err != nil { t.Errorf("can't unmarshal test cases: %s\n", err) return } for _, schema := range schemas { for _, c := range tc.TestCases { temp := testObjectToJSONOrFail(t, c.Original) got := sortJsonOrFail(t, temp, c.Description, schema) expected := testObjectToJSONOrFail(t, c.Sorted) if !reflect.DeepEqual(got, expected) { t.Errorf("using %s error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n", getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.Original), mergepatch.ToYAMLOrError(c.Sorted), jsonToYAMLOrError(got)) } } } } // These are test cases for StrategicMergePatch that cannot be generated using // CreateTwoWayMergePatch because it may be one of the following cases: // - not use the replace directive. // - generate duplicate integers for a merging list patch. // - generate empty merging lists. // - use patch format from an old client. var customStrategicMergePatchTestCaseData = []byte(` testCases: - description: unique scalars when merging lists original: mergingIntList: - 1 - 2 twoWay: mergingIntList: - 2 - 3 modified: mergingIntList: - 1 - 2 - 3 - description: delete map from nested map original: simpleMap: key1: 1 key2: 1 twoWay: simpleMap: $patch: delete modified: simpleMap: {} - description: delete all items from merging list original: mergingList: - name: 1 - name: 2 twoWay: mergingList: - $patch: replace modified: mergingList: [] - description: merge empty merging lists original: mergingList: [] twoWay: mergingList: [] modified: mergingList: [] - description: delete all keys from map original: name: 1 value: 1 twoWay: $patch: replace modified: {} - description: add key and delete all keys from map original: name: 1 value: 1 twoWay: other: a $patch: replace modified: other: a - description: delete all duplicate entries in a merging list original: mergingList: - name: 1 - name: 1 - name: 2 value: a - name: 3 - name: 3 twoWay: mergingList: - name: 1 $patch: delete - name: 3 $patch: delete modified: mergingList: - name: 2 value: a - description: retainKeys map can add a field when no retainKeys directive present original: retainKeysMap: name: foo twoWay: retainKeysMap: value: bar modified: retainKeysMap: name: foo value: bar - description: retainKeys map can change a field when no retainKeys directive present original: retainKeysMap: name: foo value: a twoWay: retainKeysMap: value: b modified: retainKeysMap: name: foo value: b - description: retainKeys map can delete a field when no retainKeys directive present original: retainKeysMap: name: foo value: a twoWay: retainKeysMap: value: null modified: retainKeysMap: name: foo - description: retainKeys map merge an empty map original: retainKeysMap: name: foo value: a twoWay: retainKeysMap: {} modified: retainKeysMap: name: foo value: a - description: retainKeys list can add a field when no retainKeys directive present original: retainKeysMergingList: - name: bar - name: foo twoWay: retainKeysMergingList: - name: foo value: a modified: retainKeysMergingList: - name: bar - name: foo value: a - description: retainKeys list can change a field when no retainKeys directive present original: retainKeysMergingList: - name: bar - name: foo value: a twoWay: retainKeysMergingList: - name: foo value: b modified: retainKeysMergingList: - name: bar - name: foo value: b - description: retainKeys list can delete a field when no retainKeys directive present original: retainKeysMergingList: - name: bar - name: foo value: a twoWay: retainKeysMergingList: - name: foo value: null modified: retainKeysMergingList: - name: bar - name: foo - description: preserve the order from the patch in a merging list original: mergingList: - name: 1 - name: 2 value: b - name: 3 twoWay: mergingList: - name: 3 value: c - name: 1 value: a - name: 2 other: x modified: mergingList: - name: 3 value: c - name: 1 value: a - name: 2 value: b other: x - description: preserve the order from the patch in a merging list 2 original: mergingList: - name: 1 - name: 2 value: b - name: 3 twoWay: mergingList: - name: 3 value: c - name: 1 value: a modified: mergingList: - name: 2 value: b - name: 3 value: c - name: 1 value: a - description: preserve the order from the patch in a merging int list original: mergingIntList: - 1 - 2 - 3 twoWay: mergingIntList: - 3 - 1 - 2 modified: mergingIntList: - 3 - 1 - 2 - description: preserve the order from the patch in a merging int list original: mergingIntList: - 1 - 2 - 3 twoWay: mergingIntList: - 3 - 1 modified: mergingIntList: - 2 - 3 - 1 `) var customStrategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{ { Description: "$setElementOrder contains item that is not present in the list to be merged", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 3 - name: 2 - name: 1 mergingList: - name: 3 value: 3 - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 3 value: 3 - name: 1 value: 1 `), }, }, { Description: "$setElementOrder contains item that is not present in the int list to be merged", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 3 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 3 - 2 - 1 `), Modified: []byte(` mergingIntList: - 3 - 1 `), }, }, { Description: "should check if order in $setElementOrder and patch list match", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 - name: 3 mergingList: - name: 3 value: 3 - name: 1 value: 1 `), ExpectedError: "doesn't match", }, }, { Description: "$setElementOrder contains item that is not present in the int list to be merged", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 3 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 - 3 mergingIntList: - 3 - 1 `), ExpectedError: "doesn't match", }, }, { Description: "missing merge key should error out", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: a `), TwoWay: []byte(` mergingList: - value: b `), ExpectedError: "does not contain declared merge key", }, }, { Description: "$deleteFromPrimitiveList of nonexistent item in primitive list should not add the item to the list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 1 - 2 `), }, }, { Description: "$deleteFromPrimitiveList on empty primitive list should not add the item to the list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: `), }, }, { Description: "$deleteFromPrimitiveList on nonexistent primitive list should not add the primitive list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` foo: - bar `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` foo: - bar `), }, }, { Description: "$deleteFromPrimitiveList should delete item from a list with merge patch strategy", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 `), TwoWay: []byte(` $deleteFromPrimitiveList/mergingIntList: - 2 `), Modified: []byte(` mergingIntList: - 1 - 3 `), }, }, { Description: "$deleteFromPrimitiveList should delete item from a list without merge patch strategy", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` nonMergingIntList: - 1 - 2 - 3 `), TwoWay: []byte(` $deleteFromPrimitiveList/nonMergingIntList: - 2 `), Modified: []byte(` nonMergingIntList: - 1 - 3 `), }, }, } func TestCustomStrategicMergePatch(t *testing.T) { mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), } mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], } schemas := []LookupPatchMeta{ mergeItemStructSchema, mergeItemOpenapiSchema, mergeItemOpenapiV3Schema, } tc := StrategicMergePatchTestCases{} err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc) if err != nil { t.Errorf("can't unmarshal test cases: %v\n", err) return } for _, c := range tc.TestCases { t.Run(c.Description, func(t *testing.T) { for _, schema := range schemas { t.Run(schema.Name(), func(t *testing.T) { original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema) testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "", schema) }) for _, c := range customStrategicMergePatchRawTestCases { original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError, schema) } } }) } } // These are test cases for StrategicMergePatch, to assert that applying a patch // yields the correct outcome. They are also test cases for CreateTwoWayMergePatch // and CreateThreeWayMergePatch, to assert that they both generate the correct patch // for the given set of input documents. var createStrategicMergePatchTestCaseData = []byte(` testCases: - description: nil original twoWay: name: 1 value: 1 modified: name: 1 value: 1 current: name: 1 other: a threeWay: value: 1 result: name: 1 value: 1 other: a - description: nil patch original: name: 1 twoWay: {} modified: name: 1 current: name: 1 threeWay: {} result: name: 1 - description: add field to map original: name: 1 twoWay: value: 1 modified: name: 1 value: 1 current: name: 1 other: a threeWay: value: 1 result: name: 1 value: 1 other: a - description: add field to map with conflict original: name: 1 twoWay: value: 1 modified: name: 1 value: 1 current: name: a other: a threeWay: name: 1 value: 1 result: name: 1 value: 1 other: a - description: add field and delete field from map original: name: 1 twoWay: name: null value: 1 modified: value: 1 current: name: 1 other: a threeWay: name: null value: 1 result: value: 1 other: a - description: add field and delete field from map with conflict original: name: 1 twoWay: name: null value: 1 modified: value: 1 current: name: a other: a threeWay: name: null value: 1 result: value: 1 other: a - description: delete field from nested map original: simpleMap: key1: 1 key2: 1 twoWay: simpleMap: key2: null modified: simpleMap: key1: 1 current: simpleMap: key1: 1 key2: 1 other: a threeWay: simpleMap: key2: null result: simpleMap: key1: 1 other: a - description: delete field from nested map with conflict original: simpleMap: key1: 1 key2: 1 twoWay: simpleMap: key2: null modified: simpleMap: key1: 1 current: simpleMap: key1: a key2: 1 other: a threeWay: simpleMap: key1: 1 key2: null result: simpleMap: key1: 1 other: a - description: delete all fields from map original: name: 1 value: 1 twoWay: name: null value: null modified: {} current: name: 1 value: 1 other: a threeWay: name: null value: null result: other: a - description: delete all fields from map with conflict original: name: 1 value: 1 twoWay: name: null value: null modified: {} current: name: 1 value: a other: a threeWay: name: null value: null result: other: a - description: add field and delete all fields from map original: name: 1 value: 1 twoWay: name: null value: null other: a modified: other: a current: name: 1 value: 1 other: a threeWay: name: null value: null result: other: a - description: add field and delete all fields from map with conflict original: name: 1 value: 1 twoWay: name: null value: null other: a modified: other: a current: name: 1 value: 1 other: b threeWay: name: null value: null other: a result: other: a - description: replace list of scalars original: nonMergingIntList: - 1 - 2 twoWay: nonMergingIntList: - 2 - 3 modified: nonMergingIntList: - 2 - 3 current: nonMergingIntList: - 1 - 2 threeWay: nonMergingIntList: - 2 - 3 result: nonMergingIntList: - 2 - 3 - description: replace list of scalars with conflict original: nonMergingIntList: - 1 - 2 twoWay: nonMergingIntList: - 2 - 3 modified: nonMergingIntList: - 2 - 3 current: nonMergingIntList: - 1 - 4 threeWay: nonMergingIntList: - 2 - 3 result: nonMergingIntList: - 2 - 3 - description: delete all maps from merging list original: mergingList: - name: 1 - name: 2 twoWay: mergingList: - name: 1 $patch: delete - name: 2 $patch: delete modified: mergingList: [] current: mergingList: - name: 1 - name: 2 threeWay: mergingList: - name: 1 $patch: delete - name: 2 $patch: delete result: mergingList: [] - description: delete all maps from merging list with conflict original: mergingList: - name: 1 - name: 2 twoWay: mergingList: - name: 1 $patch: delete - name: 2 $patch: delete modified: mergingList: [] current: mergingList: - name: 1 other: a - name: 2 other: b threeWay: mergingList: - name: 1 $patch: delete - name: 2 $patch: delete result: mergingList: [] - description: delete all maps from empty merging list original: mergingList: - name: 1 - name: 2 twoWay: mergingList: - name: 1 $patch: delete - name: 2 $patch: delete modified: mergingList: [] current: mergingList: [] threeWay: mergingList: - name: 1 $patch: delete - name: 2 $patch: delete result: mergingList: [] - description: merge empty merging lists original: mergingList: [] twoWay: {} modified: mergingList: [] current: mergingList: [] threeWay: {} result: mergingList: [] - description: defined null values should propagate overwrite current fields (with conflict) original: name: 2 twoWay: name: 1 value: 1 other: null twoWayResult: name: 1 value: 1 modified: name: 1 value: 1 other: null current: name: a other: a threeWay: name: 1 value: 1 other: null result: name: 1 value: 1 - description: defined null values should propagate removing original fields original: name: original-name value: original-value current: name: original-name value: original-value other: current-other modified: name: modified-name value: null twoWay: name: modified-name value: null twoWayResult: name: modified-name threeWay: name: modified-name value: null result: name: modified-name other: current-other - description: nil patch with retainKeys map original: name: a retainKeysMap: name: foo current: name: a value: b retainKeysMap: name: foo modified: name: a retainKeysMap: name: foo twoWay: {} threeWay: {} result: name: a value: b retainKeysMap: name: foo - description: retainKeys map with no change should not be present original: name: a retainKeysMap: name: foo current: name: a other: c retainKeysMap: name: foo modified: name: a value: b retainKeysMap: name: foo twoWay: value: b threeWay: value: b result: name: a value: b other: c retainKeysMap: name: foo `) var strategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{ { Description: "nested patch merge with empty list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` name: hi `), Current: []byte(` name: hi mergingList: - name: hello2 `), Modified: []byte(` name: hi mergingList: - name: hello - $patch: delete name: doesntexist `), TwoWay: []byte(` mergingList: - name: hello - $patch: delete name: doesntexist `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: hello - name: doesntexist mergingList: - name: hello `), TwoWayResult: []byte(` name: hi mergingList: - name: hello `), Result: []byte(` name: hi mergingList: - name: hello - name: hello2 `), }, }, { Description: "delete items in lists of scalars", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 1 - 2 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 - 4 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 1 - 2 - 4 `), }, }, { Description: "delete all duplicate items in lists of scalars", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 - 3 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 1 - 2 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 - 3 - 4 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 $deleteFromPrimitiveList/mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 1 - 2 - 4 `), }, }, { Description: "add and delete items in lists of scalars", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 - 4 $deleteFromPrimitiveList/mergingIntList: - 3 mergingIntList: - 4 `), Modified: []byte(` mergingIntList: - 1 - 2 - 4 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 - 4 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 - 4 $deleteFromPrimitiveList/mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 1 - 2 - 4 `), }, }, { Description: "merge lists of maps", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 1 - name: 2 - name: 3 mergingList: - name: 4 value: 4 - name: 3 value: 3 `), Modified: []byte(` mergingList: - name: 4 value: 4 - name: 1 - name: 2 value: 2 - name: 3 value: 3 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 1 - name: 2 - name: 3 mergingList: - name: 4 value: 4 - name: 3 value: 3 `), Result: []byte(` mergingList: - name: 4 value: 4 - name: 1 other: a - name: 2 value: 2 other: b - name: 3 value: 3 `), }, }, { Description: "merge lists of maps with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 - name: 3 mergingList: - name: 3 value: 3 `), Modified: []byte(` mergingList: - name: 1 - name: 2 value: 2 - name: 3 value: 3 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 3 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 - name: 3 mergingList: - name: 2 value: 2 - name: 3 value: 3 `), Result: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b - name: 3 value: 3 `), }, }, { Description: "add field to map in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "add field to map in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "add field to map in merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 3 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Result: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 - name: 3 value: 2 other: b `), }, }, { Description: "add duplicate field to map in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(`{}`), Result: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "add an item that already exists in current object in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: a - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 - name: 3 mergingList: - name: 3 `), Modified: []byte(` mergingList: - name: 1 value: a - name: 2 - name: 3 `), Current: []byte(` mergingList: - name: 1 value: a other: x - name: 2 - name: 3 `), ThreeWay: []byte(`{}`), Result: []byte(` mergingList: - name: 1 value: a other: x - name: 2 - name: 3 `), }, }, { Description: "add duplicate field to map in merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 3 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 2 value: 2 `), Result: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "replace map field value in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: a `), Modified: []byte(` mergingList: - name: 1 value: a - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: a `), Result: []byte(` mergingList: - name: 1 value: a other: a - name: 2 value: 2 other: b `), }, }, { Description: "replace map field value in merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: a `), Modified: []byte(` mergingList: - name: 1 value: a - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 value: 3 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: a `), Result: []byte(` mergingList: - name: 1 value: a other: a - name: 2 value: 2 other: b `), }, }, { Description: "delete map from merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Modified: []byte(` mergingList: - name: 2 `), Current: []byte(` mergingList: - name: 1 - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Result: []byte(` mergingList: - name: 2 other: b `), }, }, { Description: "delete map from merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Modified: []byte(` mergingList: - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Result: []byte(` mergingList: - name: 2 other: b `), }, }, { Description: "delete missing map from merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Modified: []byte(` mergingList: - name: 2 `), Current: []byte(` mergingList: - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Result: []byte(` mergingList: - name: 2 other: b `), }, }, { Description: "delete missing map from merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 1 $patch: delete `), Modified: []byte(` mergingList: - name: 2 `), Current: []byte(` mergingList: - name: 3 other: a `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 mergingList: - name: 2 - name: 1 $patch: delete `), Result: []byte(` mergingList: - name: 2 - name: 3 other: a `), }, }, { Description: "add map and delete map from merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 3 mergingList: - name: 3 - name: 1 $patch: delete `), Modified: []byte(` mergingList: - name: 2 - name: 3 `), Current: []byte(` mergingList: - name: 1 - name: 2 other: b - name: 4 other: c `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 3 mergingList: - name: 3 - name: 1 $patch: delete `), Result: []byte(` mergingList: - name: 2 other: b - name: 4 other: c - name: 3 `), }, }, { Description: "add map and delete map from merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 3 mergingList: - name: 3 - name: 1 $patch: delete `), Modified: []byte(` mergingList: - name: 2 - name: 3 `), Current: []byte(` mergingList: - name: 1 other: a - name: 4 other: c `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 3 mergingList: - name: 2 - name: 3 - name: 1 $patch: delete `), Result: []byte(` mergingList: - name: 4 other: c - name: 2 - name: 3 `), }, }, { Description: "delete field from map in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Modified: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 value: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Result: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "delete field from map in merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Modified: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 value: a other: a - name: 2 value: 2 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Result: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 `), }, }, { Description: "delete missing field from map in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Modified: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Result: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "delete missing field from map in merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 value: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null `), Modified: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 value: null - name: 2 value: 2 `), Result: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "replace non merging list nested in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 nonMergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 nonMergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 nonMergingList: - name: 1 value: 1 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a nonMergingList: - name: 1 - name: 2 value: 2 - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 nonMergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 other: a nonMergingList: - name: 1 value: 1 - name: 2 other: b `), }, }, { Description: "replace non merging list nested in merging list with value conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 nonMergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 nonMergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 nonMergingList: - name: 1 value: 1 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a nonMergingList: - name: 1 value: c - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 nonMergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 other: a nonMergingList: - name: 1 value: 1 - name: 2 other: b `), }, }, { Description: "replace non merging list nested in merging list with deletion conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 nonMergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 nonMergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 nonMergingList: - name: 1 value: 1 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a nonMergingList: - name: 2 value: 2 - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 1 nonMergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 other: a nonMergingList: - name: 1 value: 1 - name: 2 other: b `), }, }, { Description: "add field to map in merging list nested in merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 mergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 1 - name: 2 name: 1 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 mergingList: - name: 1 value: 1 - name: 2 value: 2 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a mergingList: - name: 1 - name: 2 value: 2 - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 1 - name: 2 name: 1 mergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 other: a mergingList: - name: 1 value: 1 - name: 2 value: 2 - name: 2 other: b `), }, }, { Description: "add field to map in merging list nested in merging list with value conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 mergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 1 - name: 2 name: 1 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 mergingList: - name: 1 value: 1 - name: 2 value: 2 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a mergingList: - name: 1 value: a other: c - name: 2 value: b other: d - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 1 - name: 2 name: 1 mergingList: - name: 1 value: 1 - name: 2 value: 2 `), Result: []byte(` mergingList: - name: 1 other: a mergingList: - name: 1 value: 1 other: c - name: 2 value: 2 other: d - name: 2 other: b `), }, }, { Description: "add field to map in merging list nested in merging list with deletion conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 mergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 1 - name: 2 name: 1 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 mergingList: - name: 1 value: 1 - name: 2 value: 2 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a mergingList: - name: 2 value: 2 other: d - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 1 - name: 2 name: 1 mergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 other: a mergingList: - name: 1 value: 1 - name: 2 value: 2 other: d - name: 2 other: b `), }, }, { Description: "add field to map in merging list nested in merging list with deletion conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 mergingList: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 2 - name: 1 name: 1 mergingList: - name: 1 value: 1 `), Modified: []byte(` mergingList: - name: 1 mergingList: - name: 2 value: 2 - name: 1 value: 1 - name: 2 `), Current: []byte(` mergingList: - name: 1 other: a mergingList: - name: 2 value: 2 other: d - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - $setElementOrder/mergingList: - name: 2 - name: 1 name: 1 mergingList: - name: 1 value: 1 `), Result: []byte(` mergingList: - name: 1 other: a mergingList: - name: 2 value: 2 other: d - name: 1 value: 1 - name: 2 other: b `), }, }, { Description: "add map to merging list by pointer", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergeItemPtr: - name: 1 `), TwoWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - name: 2 `), Modified: []byte(` mergeItemPtr: - name: 1 - name: 2 `), Current: []byte(` mergeItemPtr: - name: 1 other: a - name: 3 `), ThreeWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - name: 2 `), Result: []byte(` mergeItemPtr: - name: 1 other: a - name: 2 - name: 3 `), }, }, { Description: "add map to merging list by pointer with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergeItemPtr: - name: 1 `), TwoWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - name: 2 `), Modified: []byte(` mergeItemPtr: - name: 1 - name: 2 `), Current: []byte(` mergeItemPtr: - name: 3 `), ThreeWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - name: 1 - name: 2 `), Result: []byte(` mergeItemPtr: - name: 1 - name: 2 - name: 3 `), }, }, { Description: "add field to map in merging list by pointer", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergeItemPtr: - name: 1 mergeItemPtr: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - $setElementOrder/mergeItemPtr: - name: 1 - name: 2 name: 1 mergeItemPtr: - name: 1 value: 1 `), Modified: []byte(` mergeItemPtr: - name: 1 mergeItemPtr: - name: 1 value: 1 - name: 2 value: 2 - name: 2 `), Current: []byte(` mergeItemPtr: - name: 1 other: a mergeItemPtr: - name: 1 other: a - name: 2 value: 2 other: b - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - $setElementOrder/mergeItemPtr: - name: 1 - name: 2 name: 1 mergeItemPtr: - name: 1 value: 1 `), Result: []byte(` mergeItemPtr: - name: 1 other: a mergeItemPtr: - name: 1 value: 1 other: a - name: 2 value: 2 other: b - name: 2 other: b `), }, }, { Description: "add field to map in merging list by pointer with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergeItemPtr: - name: 1 mergeItemPtr: - name: 1 - name: 2 value: 2 - name: 2 `), TwoWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - $setElementOrder/mergeItemPtr: - name: 1 - name: 2 name: 1 mergeItemPtr: - name: 1 value: 1 `), Modified: []byte(` mergeItemPtr: - name: 1 mergeItemPtr: - name: 1 value: 1 - name: 2 value: 2 - name: 2 `), Current: []byte(` mergeItemPtr: - name: 1 other: a mergeItemPtr: - name: 1 value: a - name: 2 value: 2 other: b - name: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergeItemPtr: - name: 1 - name: 2 mergeItemPtr: - $setElementOrder/mergeItemPtr: - name: 1 - name: 2 name: 1 mergeItemPtr: - name: 1 value: 1 `), Result: []byte(` mergeItemPtr: - name: 1 other: a mergeItemPtr: - name: 1 value: 1 - name: 2 value: 2 other: b - name: 2 other: b `), }, }, { Description: "merge lists of scalars", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 - 3 mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 1 - 2 - 3 `), Current: []byte(` mergingIntList: - 1 - 2 - 4 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 - 3 mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 1 - 2 - 3 - 4 `), }, }, { Description: "add duplicate field to map in merging int list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 2 - 3 mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 1 - 2 - 3 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 `), ThreeWay: []byte(`{}`), Result: []byte(` mergingIntList: - 1 - 2 - 3 `), }, }, // test case for setElementOrder { Description: "add an item in a list of primitives and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 3 - 1 - 2 mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 3 - 1 - 2 `), Current: []byte(` mergingIntList: - 1 - 4 - 2 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 3 - 1 - 2 mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 3 - 1 - 4 - 2 `), }, }, { Description: "delete an item in a list of primitives and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 2 - 1 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 $deleteFromPrimitiveList/mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 2 - 1 `), }, }, { Description: "add an item in a list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 3 - name: 1 - name: 2 mergingList: - name: 3 value: 3 `), Modified: []byte(` mergingList: - name: 3 value: 3 - name: 1 - name: 2 value: 2 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 3 - name: 1 - name: 2 mergingList: - name: 3 value: 3 `), Result: []byte(` mergingList: - name: 3 value: 3 - name: 1 other: a - name: 2 value: 2 other: b `), }, }, { Description: "add multiple items in a list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 4 - name: 2 - name: 3 mergingList: - name: 4 value: 4 - name: 3 value: 3 `), Modified: []byte(` mergingList: - name: 1 - name: 4 value: 4 - name: 2 value: 2 - name: 3 value: 3 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 4 - name: 2 - name: 3 mergingList: - name: 4 value: 4 - name: 3 value: 3 `), Result: []byte(` mergingList: - name: 1 other: a - name: 4 value: 4 - name: 2 value: 2 other: b - name: 3 value: 3 `), }, }, { Description: "delete an item in a list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 value: 3 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 mergingList: - name: 3 $patch: delete `), Modified: []byte(` mergingList: - name: 2 value: 2 - name: 1 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b - name: 3 value: 3 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 mergingList: - name: 3 $patch: delete `), Result: []byte(` mergingList: - name: 2 value: 2 other: b - name: 1 other: a `), }, }, { Description: "change an item in a list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 value: 3 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 3 - name: 1 mergingList: - name: 3 value: x `), Modified: []byte(` mergingList: - name: 2 value: 2 - name: 3 value: x - name: 1 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b - name: 3 value: 3 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 3 - name: 1 mergingList: - name: 3 value: x `), Result: []byte(` mergingList: - name: 2 value: 2 other: b - name: 3 value: x - name: 1 other: a `), }, }, { Description: "add and delete an item in a list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 value: 3 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 2 - name: 1 mergingList: - name: 4 value: 4 - name: 3 $patch: delete `), Modified: []byte(` mergingList: - name: 4 value: 4 - name: 2 value: 2 - name: 1 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 other: b - name: 3 value: 3 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 2 - name: 1 mergingList: - name: 4 value: 4 - name: 3 $patch: delete `), Result: []byte(` mergingList: - name: 4 value: 4 - name: 2 value: 2 other: b - name: 1 other: a `), }, }, { Description: "set elements order in a list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 value: 3 - name: 4 value: 4 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 2 - name: 3 - name: 1 `), Modified: []byte(` mergingList: - name: 4 value: 4 - name: 2 value: 2 - name: 3 value: 3 - name: 1 `), Current: []byte(` mergingList: - name: 1 other: a - name: 3 value: 3 - name: 4 value: 4 - name: 2 value: 2 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 2 - name: 3 - name: 1 `), Result: []byte(` mergingList: - name: 4 value: 4 - name: 2 value: 2 - name: 3 value: 3 - name: 1 other: a `), }, }, { Description: "set elements order in a list with server-only items", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 3 value: 3 - name: 4 value: 4 - name: 2 value: 2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 2 - name: 3 - name: 1 `), Modified: []byte(` mergingList: - name: 4 value: 4 - name: 2 value: 2 - name: 3 value: 3 - name: 1 `), Current: []byte(` mergingList: - name: 1 other: a - name: 3 value: 3 - name: 4 value: 4 - name: 2 value: 2 - name: 9 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 4 - name: 2 - name: 3 - name: 1 `), Result: []byte(` mergingList: - name: 4 value: 4 - name: 2 value: 2 - name: 3 value: 3 - name: 1 other: a - name: 9 `), }, }, { Description: "set elements order in a list with server-only items 2", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 - name: 3 value: 3 - name: 4 value: 4 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 - name: 4 - name: 3 `), Modified: []byte(` mergingList: - name: 2 value: 2 - name: 1 - name: 4 value: 4 - name: 3 value: 3 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 - name: 9 - name: 3 value: 3 - name: 4 value: 4 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 - name: 4 - name: 3 `), Result: []byte(` mergingList: - name: 2 value: 2 - name: 1 other: a - name: 9 - name: 4 value: 4 - name: 3 value: 3 `), }, }, { Description: "set elements order in a list with server-only items 3", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: 2 - name: 3 value: 3 - name: 4 value: 4 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 - name: 4 - name: 3 `), Modified: []byte(` mergingList: - name: 2 value: 2 - name: 1 - name: 4 value: 4 - name: 3 value: 3 `), Current: []byte(` mergingList: - name: 1 other: a - name: 2 value: 2 - name: 7 - name: 9 - name: 8 - name: 3 value: 3 - name: 4 value: 4 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 - name: 4 - name: 3 `), Result: []byte(` mergingList: - name: 2 value: 2 - name: 1 other: a - name: 7 - name: 9 - name: 8 - name: 4 value: 4 - name: 3 value: 3 `), }, }, { Description: "add an item in a int list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 3 - 1 - 2 mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 3 - 1 - 2 `), Current: []byte(` mergingIntList: - 1 - 2 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 3 - 1 - 2 mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 3 - 1 - 2 `), }, }, { Description: "add multiple items in a int list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 1 - 4 - 2 - 3 mergingIntList: - 4 - 3 `), Modified: []byte(` mergingIntList: - 1 - 4 - 2 - 3 `), Current: []byte(` mergingIntList: - 1 - 2 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 1 - 4 - 2 - 3 mergingIntList: - 4 - 3 `), Result: []byte(` mergingIntList: - 1 - 4 - 2 - 3 `), }, }, { Description: "delete an item in a int list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 3 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 2 - 1 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 $deleteFromPrimitiveList/mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 2 - 1 `), }, }, { Description: "add and delete an item in a int list and preserve order", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 3 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 4 - 2 - 1 mergingIntList: - 4 $deleteFromPrimitiveList/mergingIntList: - 3 `), Modified: []byte(` mergingIntList: - 4 - 2 - 1 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 4 - 2 - 1 mergingIntList: - 4 $deleteFromPrimitiveList/mergingIntList: - 3 `), Result: []byte(` mergingIntList: - 4 - 2 - 1 `), }, }, { Description: "set elements order in a int list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 3 - 4 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 4 - 2 - 3 - 1 `), Modified: []byte(` mergingIntList: - 4 - 2 - 3 - 1 `), Current: []byte(` mergingIntList: - 1 - 3 - 4 - 2 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 4 - 2 - 3 - 1 `), Result: []byte(` mergingIntList: - 4 - 2 - 3 - 1 `), }, }, { Description: "set elements order in a int list with server-only items", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 3 - 4 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 4 - 2 - 3 - 1 `), Modified: []byte(` mergingIntList: - 4 - 2 - 3 - 1 `), Current: []byte(` mergingIntList: - 1 - 3 - 4 - 2 - 9 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 4 - 2 - 3 - 1 `), Result: []byte(` mergingIntList: - 4 - 2 - 3 - 1 - 9 `), }, }, { Description: "set elements order in a int list with server-only items 2", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 - 4 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 - 4 - 3 `), Modified: []byte(` mergingIntList: - 2 - 1 - 4 - 3 `), Current: []byte(` mergingIntList: - 1 - 2 - 9 - 3 - 4 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 - 4 - 3 `), Result: []byte(` mergingIntList: - 2 - 1 - 9 - 4 - 3 `), }, }, { Description: "set elements order in a int list with server-only items 3", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 - 4 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 - 4 - 3 `), Modified: []byte(` mergingIntList: - 2 - 1 - 4 - 3 `), Current: []byte(` mergingIntList: - 1 - 2 - 7 - 9 - 8 - 3 - 4 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 - 4 - 3 `), Result: []byte(` mergingIntList: - 2 - 1 - 7 - 9 - 8 - 4 - 3 `), }, }, { // This test case is used just to demonstrate the behavior when dealing with a list with duplicate Description: "behavior of set element order for a merging list with duplicate", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: 1 - name: 2 value: dup1 - name: 3 - name: 2 value: dup2 - name: 4 `), Current: []byte(` mergingList: - name: 1 - name: 2 value: dup1 - name: 3 - name: 2 value: dup2 - name: 4 `), Modified: []byte(` mergingList: - name: 2 value: dup1 - name: 1 - name: 4 - name: 3 - name: 2 value: dup2 `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 - name: 4 - name: 3 - name: 2 `), TwoWayResult: []byte(` mergingList: - name: 2 value: dup1 - name: 2 value: dup2 - name: 1 - name: 4 - name: 3 `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 2 - name: 1 - name: 4 - name: 3 - name: 2 `), Result: []byte(` mergingList: - name: 2 value: dup1 - name: 2 value: dup2 - name: 1 - name: 4 - name: 3 `), }, }, { // This test case is used just to demonstrate the behavior when dealing with a list with duplicate Description: "behavior of set element order for a merging int list with duplicate", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingIntList: - 1 - 2 - 3 - 2 - 4 `), Current: []byte(` mergingIntList: - 1 - 2 - 3 - 2 - 4 `), Modified: []byte(` mergingIntList: - 2 - 1 - 4 - 3 - 2 `), TwoWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 - 4 - 3 - 2 `), TwoWayResult: []byte(` mergingIntList: - 2 - 2 - 1 - 4 - 3 `), ThreeWay: []byte(` $setElementOrder/mergingIntList: - 2 - 1 - 4 - 3 - 2 `), Result: []byte(` mergingIntList: - 2 - 2 - 1 - 4 - 3 `), }, }, { Description: "retainKeys map should clear defaulted field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(`{}`), Current: []byte(` retainKeysMap: value: foo `), Modified: []byte(` retainKeysMap: other: bar `), TwoWay: []byte(` retainKeysMap: other: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - other other: bar `), Result: []byte(` retainKeysMap: other: bar `), }, }, { Description: "retainKeys map should clear defaulted field with conflict (discriminated union)", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(`{}`), Current: []byte(` retainKeysMap: name: type1 value: foo `), Modified: []byte(` retainKeysMap: name: type2 other: bar `), TwoWay: []byte(` retainKeysMap: name: type2 other: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - other name: type2 other: bar `), Result: []byte(` retainKeysMap: name: type2 other: bar `), }, }, { Description: "retainKeys map adds a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo `), Current: []byte(` retainKeysMap: name: foo `), Modified: []byte(` retainKeysMap: name: foo value: bar `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar `), Result: []byte(` retainKeysMap: name: foo value: bar `), }, }, { Description: "retainKeys map adds a field and clear a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo `), Current: []byte(` retainKeysMap: name: foo other: a `), Modified: []byte(` retainKeysMap: name: foo value: bar `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar `), Result: []byte(` retainKeysMap: name: foo value: bar `), }, }, { Description: "retainKeys map deletes a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar `), Current: []byte(` retainKeysMap: name: foo value: bar `), Modified: []byte(` retainKeysMap: name: foo `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name value: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name value: null `), Result: []byte(` retainKeysMap: name: foo `), }, }, { Description: "retainKeys map deletes a field and clears a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar `), Current: []byte(` retainKeysMap: name: foo value: bar other: a `), Modified: []byte(` retainKeysMap: name: foo `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name value: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name value: null `), Result: []byte(` retainKeysMap: name: foo `), }, }, { Description: "retainKeys map clears a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar `), Current: []byte(` retainKeysMap: name: foo value: bar other: a `), Modified: []byte(` retainKeysMap: name: foo value: bar `), TwoWay: []byte(`{}`), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - value `), Result: []byte(` retainKeysMap: name: foo value: bar `), }, }, { Description: "retainKeys map nested map with no change", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo simpleMap: key1: a `), Current: []byte(` retainKeysMap: name: foo simpleMap: key1: a `), Modified: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value value: bar `), Result: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a `), }, }, { Description: "retainKeys map adds a field in a nested map", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a `), Current: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key3: c `), Modified: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key2: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key2: b `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key2: b `), Result: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key2: b key3: c `), }, }, { Description: "retainKeys map deletes a field in a nested map", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key2: b `), Current: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key2: b key3: c `), Modified: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key2: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key2: null `), Result: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key3: c `), }, }, { Description: "retainKeys map changes a field in a nested map", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key2: b `), Current: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: a key2: b key3: c `), Modified: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: x key2: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key1: x `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key1: x `), Result: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: x key2: b key3: c `), }, }, { Description: "retainKeys map changes a field in a nested map with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: old key2: b `), Current: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: new key2: b key3: c `), Modified: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: modified key2: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key1: modified `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - simpleMap - value simpleMap: key1: modified `), Result: []byte(` retainKeysMap: name: foo value: bar simpleMap: key1: modified key2: b key3: c `), }, }, { Description: "retainKeys map replaces non-merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: b `), Modified: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: c - name: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - nonMergingList - value nonMergingList: - name: a - name: c - name: b `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - nonMergingList - value nonMergingList: - name: a - name: c - name: b `), Result: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: c - name: b `), }, }, { Description: "retainKeys map nested non-merging list with no change", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b `), Modified: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - nonMergingList - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - nonMergingList - value value: bar `), Result: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: b `), }, }, { Description: "retainKeys map nested non-merging list with no change with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b - name: c `), Modified: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - nonMergingList - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - nonMergingList - value value: bar nonMergingList: - name: a - name: b `), Result: []byte(` retainKeysMap: name: foo value: bar nonMergingList: - name: a - name: b `), }, }, { Description: "retainKeys map deletes nested non-merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b `), Modified: []byte(` retainKeysMap: name: foo value: bar `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar nonMergingList: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar nonMergingList: null `), Result: []byte(` retainKeysMap: name: foo value: bar `), }, }, { Description: "retainKeys map delete nested non-merging list with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo nonMergingList: - name: a - name: b - name: c `), Modified: []byte(` retainKeysMap: name: foo value: bar `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar nonMergingList: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar nonMergingList: null `), Result: []byte(` retainKeysMap: name: foo value: bar `), }, }, { Description: "retainKeys map nested merging int list with no change", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 `), Current: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 `), Modified: []byte(` retainKeysMap: name: foo value: bar mergingIntList: - 1 - 2 `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingIntList - name - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingIntList - name - value $setElementOrder/mergingIntList: - 1 - 2 value: bar `), Result: []byte(` retainKeysMap: name: foo value: bar mergingIntList: - 1 - 2 - 3 `), }, }, { Description: "retainKeys map adds an item in nested merging int list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 `), Current: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 `), Modified: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 4 `), TwoWay: []byte(` retainKeysMap: $setElementOrder/mergingIntList: - 1 - 2 - 4 $retainKeys: - mergingIntList - name mergingIntList: - 4 `), ThreeWay: []byte(` retainKeysMap: $setElementOrder/mergingIntList: - 1 - 2 - 4 $retainKeys: - mergingIntList - name mergingIntList: - 4 `), Result: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 4 - 3 `), }, }, { Description: "retainKeys map deletes an item in nested merging int list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 `), Current: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 - 4 `), Modified: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 3 `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingIntList - name $deleteFromPrimitiveList/mergingIntList: - 2 $setElementOrder/mergingIntList: - 1 - 3 `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingIntList - name $deleteFromPrimitiveList/mergingIntList: - 2 $setElementOrder/mergingIntList: - 1 - 3 `), Result: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 3 - 4 `), }, }, { Description: "retainKeys map adds an item and deletes an item in nested merging int list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 `), Current: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 - 4 `), Modified: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 3 - 5 `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingIntList - name mergingIntList: - 5 $deleteFromPrimitiveList/mergingIntList: - 2 $setElementOrder/mergingIntList: - 1 - 3 - 5 `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingIntList - name mergingIntList: - 5 $deleteFromPrimitiveList/mergingIntList: - 2 $setElementOrder/mergingIntList: - 1 - 3 - 5 `), Result: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 3 - 5 - 4 `), }, }, { Description: "retainKeys map deletes nested merging int list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 `), Current: []byte(` retainKeysMap: name: foo mergingIntList: - 1 - 2 - 3 `), Modified: []byte(` retainKeysMap: name: foo `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name mergingIntList: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name mergingIntList: null `), Result: []byte(` retainKeysMap: name: foo `), }, }, { Description: "retainKeys map nested merging list with no change", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b - name: c `), Modified: []byte(` retainKeysMap: name: foo value: bar mergingList: - name: a - name: b `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name - value value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name - value $setElementOrder/mergingList: - name: a - name: b value: bar `), Result: []byte(` retainKeysMap: name: foo value: bar mergingList: - name: a - name: b - name: c `), }, }, { Description: "retainKeys map adds an item in nested merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b - name: x `), Modified: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b - name: c `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name $setElementOrder/mergingList: - name: a - name: b - name: c mergingList: - name: c `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name $setElementOrder/mergingList: - name: a - name: b - name: c mergingList: - name: c `), Result: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b - name: c - name: x `), }, }, { Description: "retainKeys map changes an item in nested merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b value: foo `), Current: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b value: foo - name: x `), Modified: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b value: bar `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name $setElementOrder/mergingList: - name: a - name: b mergingList: - name: b value: bar `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name $setElementOrder/mergingList: - name: a - name: b mergingList: - name: b value: bar `), Result: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b value: bar - name: x `), }, }, { Description: "retainKeys map deletes nested merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b `), Modified: []byte(` retainKeysMap: name: foo value: bar `), TwoWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar mergingList: null `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - name - value value: bar mergingList: null `), Result: []byte(` retainKeysMap: name: foo value: bar `), }, }, { Description: "retainKeys map deletes an item in nested merging list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b `), Current: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: b - name: x `), Modified: []byte(` retainKeysMap: name: foo mergingList: - name: a `), TwoWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name $setElementOrder/mergingList: - name: a mergingList: - name: b $patch: delete `), ThreeWay: []byte(` retainKeysMap: $retainKeys: - mergingList - name $setElementOrder/mergingList: - name: a mergingList: - name: b $patch: delete `), Result: []byte(` retainKeysMap: name: foo mergingList: - name: a - name: x `), }, }, { Description: "retainKeys list of maps clears a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo value: a other: x `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), TwoWay: []byte(`{}`), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), }, }, { Description: "retainKeys list of maps clears a field with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo value: old `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo value: new other: x `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo value: modified `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: modified `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: modified `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo value: modified `), }, }, { Description: "retainKeys list of maps changes a field and clear a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo value: old `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo value: old other: x `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo value: new `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: new `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: new `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo value: new `), }, }, { Description: "retainKeys list of maps changes a field and clear a field with conflict", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo value: old `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo value: modified other: x `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo value: new `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: new `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: new `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo value: new `), }, }, { Description: "retainKeys list of maps adds a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: a `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: a `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), }, }, { Description: "retainKeys list of maps adds a field and clear a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo other: x `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: a `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name - value name: foo value: a `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), }, }, { Description: "retainKeys list of maps deletes a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name name: foo value: null `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name name: foo value: null `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo `), }, }, { Description: "retainKeys list of maps deletes a field and clear a field", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` retainKeysMergingList: - name: bar - name: foo value: a `), Current: []byte(` retainKeysMergingList: - name: bar - name: foo value: a other: x `), Modified: []byte(` retainKeysMergingList: - name: bar - name: foo `), TwoWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name name: foo value: null `), ThreeWay: []byte(` $setElementOrder/retainKeysMergingList: - name: bar - name: foo retainKeysMergingList: - $retainKeys: - name name: foo value: null `), Result: []byte(` retainKeysMergingList: - name: bar - name: foo `), }, }, { Description: "delete and reorder in one list, reorder in another", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` mergingList: - name: a value: a - name: b value: b mergeItemPtr: - name: c value: c - name: d value: d `), Current: []byte(` mergingList: - name: a value: a - name: b value: b mergeItemPtr: - name: c value: c - name: d value: d `), Modified: []byte(` mergingList: - name: b value: b mergeItemPtr: - name: d value: d - name: c value: c `), TwoWay: []byte(` $setElementOrder/mergingList: - name: b $setElementOrder/mergeItemPtr: - name: d - name: c mergingList: - $patch: delete name: a `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: b $setElementOrder/mergeItemPtr: - name: d - name: c mergingList: - $patch: delete name: a `), Result: []byte(` mergingList: - name: b value: b mergeItemPtr: - name: d value: d - name: c value: c `), }, }, } func TestStrategicMergePatch(t *testing.T) { testStrategicMergePatchWithCustomArgumentsUsingStruct(t, "bad struct", "{}", "{}", []byte(""), mergepatch.ErrBadArgKind(struct{}{}, []byte{})) mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), } mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], } schemas := []LookupPatchMeta{ mergeItemStructSchema, mergeItemOpenapiSchema, mergeItemOpenapiV3Schema, } tc := StrategicMergePatchTestCases{} err := yaml.Unmarshal(createStrategicMergePatchTestCaseData, &tc) if err != nil { t.Errorf("can't unmarshal test cases: %s\n", err) return } for _, schema := range schemas { t.Run(schema.Name(), func(t *testing.T) { testStrategicMergePatchWithCustomArguments(t, "bad original", "", "{}", schema, mergepatch.ErrBadJSONDoc) testStrategicMergePatchWithCustomArguments(t, "bad patch", "{}", "", schema, mergepatch.ErrBadJSONDoc) testStrategicMergePatchWithCustomArguments(t, "nil struct", "{}", "{}", nil, mergepatch.ErrBadArgKind(struct{}{}, nil)) for _, c := range tc.TestCases { t.Run(c.Description+"/TwoWay", func(t *testing.T) { testTwoWayPatch(t, c, schema) }) t.Run(c.Description+"/ThreeWay", func(t *testing.T) { testThreeWayPatch(t, c, schema) }) } }) // run multiple times to exercise different map traversal orders for i := 0; i < 10; i++ { for _, c := range strategicMergePatchRawTestCases { t.Run(c.Description+"/TwoWay", func(t *testing.T) { testTwoWayPatchForRawTestCase(t, c, schema) }) t.Run(c.Description+"/ThreeWay", func(t *testing.T) { testThreeWayPatchForRawTestCase(t, c, schema) }) } } } } func testStrategicMergePatchWithCustomArgumentsUsingStruct(t *testing.T, description, original, patch string, dataStruct interface{}, expected error) { schema, actual := NewPatchMetaFromStruct(dataStruct) // If actual is not nil, check error. If errors match, return. if actual != nil { checkErrorsEqual(t, description, expected, actual, schema) return } testStrategicMergePatchWithCustomArguments(t, description, original, patch, schema, expected) } func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, schema LookupPatchMeta, expected error) { _, actual := StrategicMergePatch([]byte(original), []byte(patch), schema) checkErrorsEqual(t, description, expected, actual, schema) } func checkErrorsEqual(t *testing.T, description string, expected, actual error, schema LookupPatchMeta) { if actual != expected { if actual == nil { t.Errorf("using %s expected error: %s\ndid not occur in test case: %s", getSchemaType(schema), expected, description) return } if expected == nil || actual.Error() != expected.Error() { t.Errorf("using %s unexpected error: %s\noccurred in test case: %s", getSchemaType(schema), actual, description) return } } } func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) { original, expectedPatch, modified, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema) actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema) if err != nil { t.Errorf("using %s error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n", getSchemaType(schema), err, c.Description, original, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } testPatchCreation(t, expectedPatch, actualPatch, c.Description) testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "", schema) } func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) { original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema) if err != nil { t.Errorf("error: %s\nin test case: %s\ncannot create two way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreation(t, expectedPatch, actualPatch, c.Description) testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError, schema) } func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte) { expectedResult := c.TwoWayResult if expectedResult == nil { expectedResult = c.Modified } return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, c.TwoWay), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, expectedResult), c.Description, schema) } func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte) { expectedResult := c.TwoWayResult if expectedResult == nil { expectedResult = c.Modified } return yamlToJSONOrError(t, c.Original), yamlToJSONOrError(t, c.TwoWay), yamlToJSONOrError(t, c.Modified), yamlToJSONOrError(t, expectedResult) } func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) { original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c, schema) actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false) if err != nil { if !mergepatch.IsConflict(err) { t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\n%s\n", getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } if !strings.Contains(c.Description, "conflict") { t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n", getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } if len(c.Result) > 0 { actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true) if err != nil { t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n", getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } testPatchCreation(t, expected, actual, c.Description) testPatchApplication(t, current, actual, result, c.Description, "", schema) } return } if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { t.Errorf("using %s error in test case: %s\nexpected conflict did not occur:\n%s\n", getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) return } testPatchCreation(t, expected, actual, c.Description) testPatchApplication(t, current, actual, result, c.Description, "", schema) } func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) { original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c) actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false) if err != nil { if !mergepatch.IsConflict(err) { t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } if !strings.Contains(c.Description, "conflict") { t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } if len(c.Result) > 0 { actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true) if err != nil { t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreation(t, expected, actual, c.Description) testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema) } return } if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { t.Errorf("using %s error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreation(t, expected, actual, c.Description) testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema) } func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte, []byte) { return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Current), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, c.ThreeWay), c.Description, schema), sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Result), c.Description, schema) } func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte, []byte) { return yamlToJSONOrError(t, c.Original), yamlToJSONOrError(t, c.Modified), yamlToJSONOrError(t, c.Current), yamlToJSONOrError(t, c.ThreeWay), yamlToJSONOrError(t, c.Result) } func testPatchCreation(t *testing.T, expected, actual []byte, description string) { if !reflect.DeepEqual(actual, expected) { t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) return } } func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string, schema LookupPatchMeta) { result, err := StrategicMergePatchUsingLookupPatchMeta(original, patch, schema) if len(expectedError) != 0 { if err != nil && strings.Contains(err.Error(), expectedError) { return } t.Errorf("using %s expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", getSchemaType(schema), expectedError, description, err) } if err != nil { t.Errorf("using %s error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", getSchemaType(schema), err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) return } if !reflect.DeepEqual(result, expected) { format := "using error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" t.Errorf(format, description, jsonToYAMLOrError(original), jsonToYAMLOrError(patch), jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) return } } func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte { if o == nil { return nil } j, err := toJSON(o) if err != nil { t.Error(err) } return j } func sortJsonOrFail(t *testing.T, j []byte, description string, schema LookupPatchMeta) []byte { if j == nil { return nil } r, err := sortMergeListsByName(j, schema) if err != nil { t.Errorf("using %s error: %s\n in test case: %s\ncannot sort object:\n%s\n", getSchemaType(schema), err, description, j) return nil } return r } func getSchemaType(schema LookupPatchMeta) string { return reflect.TypeOf(schema).String() } func jsonToYAMLOrError(j []byte) string { y, err := jsonToYAML(j) if err != nil { return err.Error() } return string(y) } func toJSON(v interface{}) ([]byte, error) { j, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, dump.Pretty(v)) } return j, nil } func jsonToYAML(j []byte) ([]byte, error) { y, err := yaml.JSONToYAML(j) if err != nil { return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j) } return y, nil } func yamlToJSON(y []byte) ([]byte, error) { j, err := yaml.YAMLToJSON(y) if err != nil { return nil, fmt.Errorf("yaml to json failed: %v\n%v\n", err, y) } return j, nil } func yamlToJSONOrError(t *testing.T, y []byte) []byte { j, err := yamlToJSON(y) if err != nil { t.Errorf("%v", err) } return j } type PrecisionItem struct { Name string `json:"name,omitempty"` Int32 int32 `json:"int32,omitempty"` Int64 int64 `json:"int64,omitempty"` Float32 float32 `json:"float32,omitempty"` Float64 float64 `json:"float64,omitempty"` } var ( precisionItem PrecisionItem precisionItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(precisionItem)} ) func TestNumberConversion(t *testing.T) { testcases := map[string]struct { Old string New string ExpectedPatch string ExpectedResult string }{ "empty": { Old: `{}`, New: `{}`, ExpectedPatch: `{}`, ExpectedResult: `{}`, }, "int32 medium": { Old: `{"int32":1000000}`, New: `{"int32":1000000,"name":"newname"}`, ExpectedPatch: `{"name":"newname"}`, ExpectedResult: `{"int32":1000000,"name":"newname"}`, }, "int32 max": { Old: `{"int32":2147483647}`, New: `{"int32":2147483647,"name":"newname"}`, ExpectedPatch: `{"name":"newname"}`, ExpectedResult: `{"int32":2147483647,"name":"newname"}`, }, "int64 medium": { Old: `{"int64":1000000}`, New: `{"int64":1000000,"name":"newname"}`, ExpectedPatch: `{"name":"newname"}`, ExpectedResult: `{"int64":1000000,"name":"newname"}`, }, "int64 max": { Old: `{"int64":9223372036854775807}`, New: `{"int64":9223372036854775807,"name":"newname"}`, ExpectedPatch: `{"name":"newname"}`, ExpectedResult: `{"int64":9223372036854775807,"name":"newname"}`, }, "float32 max": { Old: `{"float32":3.4028234663852886e+38}`, New: `{"float32":3.4028234663852886e+38,"name":"newname"}`, ExpectedPatch: `{"name":"newname"}`, ExpectedResult: `{"float32":3.4028234663852886e+38,"name":"newname"}`, }, "float64 max": { Old: `{"float64":1.7976931348623157e+308}`, New: `{"float64":1.7976931348623157e+308,"name":"newname"}`, ExpectedPatch: `{"name":"newname"}`, ExpectedResult: `{"float64":1.7976931348623157e+308,"name":"newname"}`, }, } precisionItemOpenapiSchema := PatchMetaFromOpenAPI{ Schema: sptest.GetSchemaOrDie(&fakePrecisionItemSchema, "precisionItem"), } precisionItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ SchemaList: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas, Schema: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas["precisionItem"], } precisionItemSchemas := []LookupPatchMeta{ precisionItemStructSchema, precisionItemOpenapiSchema, precisionItemOpenapiV3Schema, } for _, schema := range precisionItemSchemas { for k, tc := range testcases { patch, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Old), []byte(tc.New), schema) if err != nil { t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err) continue } if tc.ExpectedPatch != string(patch) { t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedPatch, string(patch)) continue } result, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Old), patch, schema) if err != nil { t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err) continue } if tc.ExpectedResult != string(result) { t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedResult, string(result)) continue } } } } var replaceRawExtensionPatchTestCases = []StrategicMergePatchRawTestCase{ { Description: "replace RawExtension field, rest unchanched", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` name: my-object value: some-value other: current-other replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Current: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 - name: 3 replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Modified: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWay: []byte(` mergingList: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWayResult: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), ThreeWay: []byte(` replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), Result: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), }, }, { Description: "replace RawExtension field and merge list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Current: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 3 replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Modified: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWayResult: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), ThreeWay: []byte(` $setElementOrder/mergingList: - name: 1 - name: 2 mergingList: - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), Result: []byte(` name: my-object value: some-value other: current-other mergingList: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), }, }, } func TestReplaceWithRawExtension(t *testing.T) { mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), } mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], } schemas := []LookupPatchMeta{ mergeItemStructSchema, mergeItemOpenapiSchema, mergeItemOpenapiV3Schema, } for _, schema := range schemas { for _, c := range replaceRawExtensionPatchTestCases { testTwoWayPatchForRawTestCase(t, c, schema) testThreeWayPatchForRawTestCase(t, c, schema) } } } func TestUnknownField(t *testing.T) { testcases := map[string]struct { Original string Current string Modified string ExpectedTwoWay string ExpectedTwoWayErr string ExpectedTwoWayResult string ExpectedThreeWay string ExpectedThreeWayErr string ExpectedThreeWayResult string }{ // cases we can successfully strategically merge "no diff": { Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Modified: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, ExpectedTwoWay: `{}`, ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, ExpectedThreeWay: `{}`, ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, }, "no diff even if modified null": { Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Modified: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":null},"name":"foo","scalar":true}`, ExpectedTwoWay: `{"complex_nullable":{"key":null}}`, ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{},"name":"foo","scalar":true}`, ExpectedThreeWay: `{"complex_nullable":{"key":null}}`, ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{},"name":"foo","scalar":true}`, }, "discard nulls in nested and adds not nulls": { Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Modified: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":{"keynotnull":"value","keynull":null}},"name":"foo","scalar":true}`, ExpectedTwoWay: `{"complex_nullable":{"key":{"keynotnull":"value","keynull":null}}}`, ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":{"keynotnull":"value"}},"name":"foo","scalar":true}`, ExpectedThreeWay: `{"complex_nullable":{"key":{"keynotnull":"value","keynull":null}}}`, ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":{"keynotnull":"value"}},"name":"foo","scalar":true}`, }, "discard if modified all nulls": { Original: `{}`, Current: `{}`, Modified: `{"complex":{"nested":null}}`, ExpectedTwoWay: `{"complex":{"nested":null}}`, ExpectedTwoWayResult: `{"complex":{}}`, ExpectedThreeWay: `{"complex":{"nested":null}}`, ExpectedThreeWayResult: `{"complex":{}}`, }, "add only not nulls": { Original: `{}`, Current: `{}`, Modified: `{"complex":{"nested":null,"nested2":"foo"}}`, ExpectedTwoWay: `{"complex":{"nested":null,"nested2":"foo"}}`, ExpectedTwoWayResult: `{"complex":{"nested2":"foo"}}`, ExpectedThreeWay: `{"complex":{"nested":null,"nested2":"foo"}}`, ExpectedThreeWayResult: `{"complex":{"nested2":"foo"}}`, }, "null values in original are preserved": { Original: `{"thing":null}`, Current: `{"thing":null}`, Modified: `{"nested":{"value":5},"thing":null}`, ExpectedTwoWay: `{"nested":{"value":5}}`, ExpectedTwoWayResult: `{"nested":{"value":5},"thing":null}`, ExpectedThreeWay: `{"nested":{"value":5}}`, ExpectedThreeWayResult: `{"nested":{"value":5},"thing":null}`, }, "nested null values in original are preserved": { Original: `{"complex":{"key":null},"thing":null}`, Current: `{"complex":{"key":null},"thing":null}`, Modified: `{"complex":{"key":null},"nested":{"value":5},"thing":null}`, ExpectedTwoWay: `{"nested":{"value":5}}`, ExpectedTwoWayResult: `{"complex":{"key":null},"nested":{"value":5},"thing":null}`, ExpectedThreeWay: `{"nested":{"value":5}}`, ExpectedThreeWayResult: `{"complex":{"key":null},"nested":{"value":5},"thing":null}`, }, "add empty slices": { Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Modified: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":[],"name":"foo","scalar":true}`, ExpectedTwoWay: `{"complex_nullable":[]}`, ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":[],"name":"foo","scalar":true}`, ExpectedThreeWay: `{"complex_nullable":[]}`, ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":[],"name":"foo","scalar":true}`, }, "filter nulls from nested slices": { Original: `{}`, Current: `{}`, Modified: `{"complex_nullable":[{"inner_one":{"key_one":"foo","key_two":null}}]}`, ExpectedTwoWay: `{"complex_nullable":[{"inner_one":{"key_one":"foo","key_two":null}}]}`, ExpectedTwoWayResult: `{"complex_nullable":[{"inner_one":{"key_one":"foo"}}]}`, ExpectedThreeWay: `{"complex_nullable":[{"inner_one":{"key_one":"foo","key_two":null}}]}`, ExpectedThreeWayResult: `{"complex_nullable":[{"inner_one":{"key_one":"foo"}}]}`, }, "filter if slice is all empty": { Original: `{}`, Current: `{}`, Modified: `{"complex_nullable":[{"inner_one":{"key_one":null,"key_two":null}}]}`, ExpectedTwoWay: `{"complex_nullable":[{"inner_one":{"key_one":null,"key_two":null}}]}`, ExpectedTwoWayResult: `{"complex_nullable":[{"inner_one":{}}]}`, ExpectedThreeWay: `{"complex_nullable":[{"inner_one":{"key_one":null,"key_two":null}}]}`, ExpectedThreeWayResult: `{"complex_nullable":[{"inner_one":{}}]}`, }, "not filter nulls from non-associative slice": { Original: `{}`, Current: `{}`, Modified: `{"complex_nullable":["key1",null,"key2"]}`, ExpectedTwoWay: `{"complex_nullable":["key1",null,"key2"]}`, ExpectedTwoWayResult: `{"complex_nullable":["key1",null,"key2"]}`, ExpectedThreeWay: `{"complex_nullable":["key1",null,"key2"]}`, ExpectedThreeWayResult: `{"complex_nullable":["key1",null,"key2"]}`, }, "added only": { Original: `{"name":"foo"}`, Current: `{"name":"foo"}`, Modified: `{"name":"foo","scalar":true,"complex":{"nested":true},"array":[1,2,3]}`, ExpectedTwoWay: `{"array":[1,2,3],"complex":{"nested":true},"scalar":true}`, ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, ExpectedThreeWay: `{"array":[1,2,3],"complex":{"nested":true},"scalar":true}`, ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, }, "removed only": { Original: `{"name":"foo","scalar":true,"complex":{"nested":true}}`, Current: `{"name":"foo","scalar":true,"complex":{"nested":true},"array":[1,2,3]}`, Modified: `{"name":"foo"}`, ExpectedTwoWay: `{"complex":null,"scalar":null}`, ExpectedTwoWayResult: `{"name":"foo"}`, ExpectedThreeWay: `{"complex":null,"scalar":null}`, ExpectedThreeWayResult: `{"array":[1,2,3],"name":"foo"}`, }, // cases we cannot successfully strategically merge (expect errors) "diff": { Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, Modified: `{"array":[1,2,3],"complex":{"nested":false},"name":"foo","scalar":true}`, ExpectedTwoWayErr: `unable to find api field`, ExpectedThreeWayErr: `unable to find api field`, }, } mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), } mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], } schemas := []LookupPatchMeta{ mergeItemStructSchema, mergeItemOpenapiSchema, mergeItemOpenapiV3Schema, } for _, k := range sets.StringKeySet(testcases).List() { t.Run(k, func(t *testing.T) { tc := testcases[k] for _, schema := range schemas { t.Run(schema.Name()+"/TwoWay", func(t *testing.T) { twoWay, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Original), []byte(tc.Modified), schema) if err != nil { if len(tc.ExpectedTwoWayErr) == 0 { t.Errorf("using %s in testcase %s: error making two-way patch: %v", getSchemaType(schema), k, err) } if !strings.Contains(err.Error(), tc.ExpectedTwoWayErr) { t.Errorf("using %s in testcase %s: expected error making two-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedTwoWayErr, err) } return } if string(twoWay) != tc.ExpectedTwoWay { t.Errorf("using %s in testcase %s: expected two-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWay), string(twoWay)) return } twoWayResult, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Original), twoWay, schema) if err != nil { t.Errorf("using %s in testcase %s: error applying two-way patch: %v", getSchemaType(schema), k, err) return } if string(twoWayResult) != tc.ExpectedTwoWayResult { t.Errorf("using %s in testcase %s: expected two-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWayResult), string(twoWayResult)) return } }) t.Run(schema.Name()+"/ThreeWay", func(t *testing.T) { threeWay, err := CreateThreeWayMergePatch([]byte(tc.Original), []byte(tc.Modified), []byte(tc.Current), schema, false) if err != nil { if len(tc.ExpectedThreeWayErr) == 0 { t.Errorf("using %s in testcase %s: error making three-way patch: %v", getSchemaType(schema), k, err) } else if !strings.Contains(err.Error(), tc.ExpectedThreeWayErr) { t.Errorf("using %s in testcase %s: expected error making three-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedThreeWayErr, err) } return } if string(threeWay) != tc.ExpectedThreeWay { t.Errorf("using %s in testcase %s: expected three-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWay), string(threeWay)) return } threeWayResult, err := StrategicMergePatch([]byte(tc.Current), threeWay, schema) if err != nil { t.Errorf("using %s in testcase %s: error applying three-way patch: %v", getSchemaType(schema), k, err) return } else if string(threeWayResult) != tc.ExpectedThreeWayResult { t.Errorf("using %s in testcase %s: expected three-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWayResult), string(threeWayResult)) return } }) } }) } } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testdata/000077500000000000000000000000001453143165200246035ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testdata/swagger-merge-item-v3.json000066400000000000000000000124231453143165200315160ustar00rootroot00000000000000{ "openapi": "3.0", "info": { "title": "StrategicMergePatchTestingMergeItem", "version": "v3.0" }, "paths": {}, "components": { "schemas": { "mergeItem": { "description": "MergeItem is type definition for testing strategic merge.", "required": [], "properties": { "name": { "description": "Name field.", "type": "string" }, "value": { "description": "Value field.", "type": "string" }, "other": { "description": "Other field.", "type": "string" }, "mergingList": { "description": "MergingList field.", "type": "array", "items": { "default": {}, "allOf": [ {"$ref": "#/components/schemas/mergeItem"} ] }, "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "nonMergingList": { "description": "NonMergingList field.", "type": "array", "items": { "$ref": "#/components/schemas/mergeItem" } }, "mergingIntList": { "description": "MergingIntList field.", "type": "array", "items": { "type": "integer", "format": "int32" }, "x-kubernetes-patch-strategy": "merge" }, "nonMergingIntList": { "description": "NonMergingIntList field.", "type": "array", "items": { "type": "integer", "format": "int32" } }, "mergeItemPtr": { "description": "MergeItemPtr field.", "allOf": [ {"$ref": "#/components/schemas/mergeItem"} ], "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "simpleMap": { "description": "SimpleMap field.", "type": "object", "additionalProperties": { "type": "string" } }, "replacingItem": { "description": "ReplacingItem field.", "allOf": [ {"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension"} ], "x-kubernetes-patch-strategy": "replace" }, "retainKeysMap": { "description": "RetainKeysMap field.", "allOf": [ {"$ref": "#/components/schemas/retainKeysMergeItem"} ], "x-kubernetes-patch-strategy": "retainKeys" }, "retainKeysMergingList": { "description": "RetainKeysMergingList field.", "type": "array", "items": { "$ref": "#/components/schemas/mergeItem" }, "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" } }, "x-kubernetes-group-version-kind": [ { "group": "fake-group", "kind": "mergeItem", "version": "some-version" } ] }, "retainKeysMergeItem": { "description": "RetainKeysMergeItem is type definition for testing strategic merge.", "required": [], "properties": { "name": { "description": "Name field.", "type": "string" }, "value": { "description": "Value field.", "type": "string" }, "other": { "description": "Other field.", "type": "string" }, "simpleMap": { "description": "SimpleMap field.", "items": { "type": "string" } }, "mergingList": { "description": "MergingList field.", "type": "array", "items": { "$ref": "#/components/schemas/mergeItem" }, "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "nonMergingList": { "description": "NonMergingList field.", "type": "array", "items": { "$ref": "#/components/schemas/mergeItem" } }, "mergingIntList": { "description": "MergingIntList field.", "type": "array", "items": { "type": "integer", "format": "int32" }, "x-kubernetes-patch-strategy": "merge" } }, "x-kubernetes-group-version-kind": [ { "group": "fake-group", "kind": "retainKeysMergeItem", "version": "some-version" } ] }, "io.k8s.apimachinery.pkg.runtime.RawExtension": { "description": "RawExtension is used to hold extensions in external versions.", "required": [ "Raw" ], "properties": { "Raw": { "description": "Raw is the underlying serialization of this object.", "type": "string", "format": "byte" } } } } } } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testdata/swagger-merge-item.json000066400000000000000000000113601453143165200311670ustar00rootroot00000000000000{ "swagger": "2.0", "info": { "title": "StrategicMergePatchTestingMergeItem", "version": "v1.9.0" }, "paths": {}, "definitions": { "mergeItem": { "description": "MergeItem is type definition for testing strategic merge.", "required": [], "properties": { "name": { "description": "Name field.", "type": "string" }, "value": { "description": "Value field.", "type": "string" }, "other": { "description": "Other field.", "type": "string" }, "mergingList": { "description": "MergingList field.", "type": "array", "items": { "$ref": "#/definitions/mergeItem" }, "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "nonMergingList": { "description": "NonMergingList field.", "type": "array", "items": { "$ref": "#/definitions/mergeItem" } }, "mergingIntList": { "description": "MergingIntList field.", "type": "array", "items": { "type": "integer", "format": "int32" }, "x-kubernetes-patch-strategy": "merge" }, "nonMergingIntList": { "description": "NonMergingIntList field.", "type": "array", "items": { "type": "integer", "format": "int32" } }, "mergeItemPtr": { "description": "MergeItemPtr field.", "$ref": "#/definitions/mergeItem", "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "simpleMap": { "description": "SimpleMap field.", "type": "object", "additionalProperties": { "type": "string" } }, "replacingItem": { "description": "ReplacingItem field.", "$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension", "x-kubernetes-patch-strategy": "replace" }, "retainKeysMap": { "description": "RetainKeysMap field.", "$ref": "#/definitions/retainKeysMergeItem", "x-kubernetes-patch-strategy": "retainKeys" }, "retainKeysMergingList": { "description": "RetainKeysMergingList field.", "type": "array", "items": { "$ref": "#/definitions/mergeItem" }, "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge,retainKeys" } }, "x-kubernetes-group-version-kind": [ { "group": "fake-group", "kind": "mergeItem", "version": "some-version" } ] }, "retainKeysMergeItem": { "description": "RetainKeysMergeItem is type definition for testing strategic merge.", "required": [], "properties": { "name": { "description": "Name field.", "type": "string" }, "value": { "description": "Value field.", "type": "string" }, "other": { "description": "Other field.", "type": "string" }, "simpleMap": { "description": "SimpleMap field.", "additionalProperties": "object", "items": { "type": "string" } }, "mergingList": { "description": "MergingList field.", "type": "array", "items": { "$ref": "#/definitions/mergeItem" }, "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, "nonMergingList": { "description": "NonMergingList field.", "type": "array", "items": { "$ref": "#/definitions/mergeItem" } }, "mergingIntList": { "description": "MergingIntList field.", "type": "array", "items": { "type": "integer", "format": "int32" }, "x-kubernetes-patch-strategy": "merge" } }, "x-kubernetes-group-version-kind": [ { "group": "fake-group", "kind": "retainKeysMergeItem", "version": "some-version" } ] }, "io.k8s.apimachinery.pkg.runtime.RawExtension": { "description": "RawExtension is used to hold extensions in external versions.", "required": [ "Raw" ], "properties": { "Raw": { "description": "Raw is the underlying serialization of this object.", "type": "string", "format": "byte" } } } } } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testdata/swagger-precision-item-v3.json000066400000000000000000000022761453143165200324170ustar00rootroot00000000000000{ "openapi": "3.0", "info": { "title": "StrategicMergePatchTestingPrecisionItem", "version": "v1.9.0" }, "paths": {}, "components": { "schemas": { "precisionItem": { "description": "PrecisionItem is type definition for testing strategic merge.", "required": [], "properties": { "name": { "description": "Name field.", "type": "string" }, "int32": { "description": "Int32 field.", "type": "integer", "format": "int32" }, "int64": { "description": "Int64 field.", "type": "integer", "format": "int64" }, "float32": { "description": "Float32 field.", "type": "number", "format": "float32" }, "float64": { "description": "Float64 field.", "type": "number", "format": "float64" } }, "x-kubernetes-group-version-kind": [ { "group": "fake-group", "kind": "precisionItem", "version": "some-version" } ] } } } }golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testdata/swagger-precision-item.json000066400000000000000000000021371453143165200320650ustar00rootroot00000000000000{ "swagger": "2.0", "info": { "title": "StrategicMergePatchTestingPrecisionItem", "version": "v1.9.0" }, "paths": {}, "definitions": { "precisionItem": { "description": "PrecisionItem is type definition for testing strategic merge.", "required": [], "properties": { "name": { "description": "Name field.", "type": "string" }, "int32": { "description": "Int32 field.", "type": "integer", "format": "int32" }, "int64": { "description": "Int64 field.", "type": "integer", "format": "int64" }, "float32": { "description": "Float32 field.", "type": "number", "format": "float32" }, "float64": { "description": "Float64 field.", "type": "number", "format": "float64" } }, "x-kubernetes-group-version-kind": [ { "group": "fake-group", "kind": "precisionItem", "version": "some-version" } ] } } } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testing/000077500000000000000000000000001453143165200244475ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testing/openapi.go000066400000000000000000000033531453143165200264350ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testing import ( "os" "sync" openapi_v2 "github.com/google/gnostic-models/openapiv2" openapi "k8s.io/kube-openapi/pkg/util/proto" ) // Fake opens and returns a openapi swagger from a file Path. It will // parse only once and then return the same copy everytime. type Fake struct { Path string once sync.Once document *openapi_v2.Document err error } // OpenAPISchema returns the openapi document and a potential error. func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) { f.once.Do(func() { _, err := os.Stat(f.Path) if err != nil { f.err = err return } spec, err := os.ReadFile(f.Path) if err != nil { f.err = err return } f.document, f.err = openapi_v2.ParseDocument(spec) }) return f.document, f.err } func getSchema(f *Fake, model string) (openapi.Schema, error) { s, err := f.OpenAPISchema() if err != nil { return nil, err } m, err := openapi.NewOpenAPIData(s) if err != nil { return nil, err } return m.LookupModel(model), nil } // GetSchemaOrDie returns the openapi schema. func GetSchemaOrDie(f *Fake, model string) openapi.Schema { s, err := getSchema(f, model) if err != nil { panic(err) } return s } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/testing/openapi3.go000066400000000000000000000024601453143165200265160ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testing import ( "os" "sync" "k8s.io/kube-openapi/pkg/spec3" ) type OpenAPIV3Getter struct { Path string once sync.Once bytes []byte openapiv3 spec3.OpenAPI } func (f *OpenAPIV3Getter) SchemaBytesOrDie() []byte { f.once.Do(func() { _, err := os.Stat(f.Path) if err != nil { panic(err) } spec, err := os.ReadFile(f.Path) if err != nil { panic(err) } f.bytes = spec }) return f.bytes } func (f *OpenAPIV3Getter) SchemaOrDie() *spec3.OpenAPI { f.once.Do(func() { _, err := os.Stat(f.Path) if err != nil { panic(err) } spec, err := os.ReadFile(f.Path) if err != nil { panic(err) } err = f.openapiv3.UnmarshalJSON(spec) if err != nil { panic(err) } }) return &f.openapiv3 } golang-k8s-apimachinery-0.29.0/pkg/util/strategicpatch/types.go000066400000000000000000000107101453143165200244640ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package strategicpatch import ( "errors" "strings" "k8s.io/apimachinery/pkg/util/mergepatch" openapi "k8s.io/kube-openapi/pkg/util/proto" ) const ( patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy" patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key" ) type LookupPatchItem interface { openapi.SchemaVisitor Error() error Path() *openapi.Path } type kindItem struct { key string path *openapi.Path err error patchmeta PatchMeta subschema openapi.Schema hasVisitKind bool } func NewKindItem(key string, path *openapi.Path) *kindItem { return &kindItem{ key: key, path: path, } } var _ LookupPatchItem = &kindItem{} func (item *kindItem) Error() error { return item.err } func (item *kindItem) Path() *openapi.Path { return item.path } func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) { item.err = errors.New("expected kind, but got primitive") } func (item *kindItem) VisitArray(schema *openapi.Array) { item.err = errors.New("expected kind, but got slice") } func (item *kindItem) VisitMap(schema *openapi.Map) { item.err = errors.New("expected kind, but got map") } func (item *kindItem) VisitReference(schema openapi.Reference) { if !item.hasVisitKind { schema.SubSchema().Accept(item) } } func (item *kindItem) VisitKind(schema *openapi.Kind) { subschema, ok := schema.Fields[item.key] if !ok { item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key} return } mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions()) if err != nil { item.err = err return } item.patchmeta = PatchMeta{ patchStrategies: patchStrategies, patchMergeKey: mergeKey, } item.subschema = subschema } type sliceItem struct { key string path *openapi.Path err error patchmeta PatchMeta subschema openapi.Schema hasVisitKind bool } func NewSliceItem(key string, path *openapi.Path) *sliceItem { return &sliceItem{ key: key, path: path, } } var _ LookupPatchItem = &sliceItem{} func (item *sliceItem) Error() error { return item.err } func (item *sliceItem) Path() *openapi.Path { return item.path } func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) { item.err = errors.New("expected slice, but got primitive") } func (item *sliceItem) VisitArray(schema *openapi.Array) { if !item.hasVisitKind { item.err = errors.New("expected visit kind first, then visit array") } subschema := schema.SubType item.subschema = subschema } func (item *sliceItem) VisitMap(schema *openapi.Map) { item.err = errors.New("expected slice, but got map") } func (item *sliceItem) VisitReference(schema openapi.Reference) { if !item.hasVisitKind { schema.SubSchema().Accept(item) } else { item.subschema = schema.SubSchema() } } func (item *sliceItem) VisitKind(schema *openapi.Kind) { subschema, ok := schema.Fields[item.key] if !ok { item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key} return } mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions()) if err != nil { item.err = err return } item.patchmeta = PatchMeta{ patchStrategies: patchStrategies, patchMergeKey: mergeKey, } item.hasVisitKind = true subschema.Accept(item) } func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) { ps, foundPS := extensions[patchStrategyOpenapiextensionKey] var patchStrategies []string var mergeKey, patchStrategy string var ok bool if foundPS { patchStrategy, ok = ps.(string) if ok { patchStrategies = strings.Split(patchStrategy, ",") } else { return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps) } } mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey] if foundMK { mergeKey, ok = mk.(string) if !ok { return "", nil, mergepatch.ErrBadArgType(mergeKey, mk) } } return mergeKey, patchStrategies, nil } golang-k8s-apimachinery-0.29.0/pkg/util/uuid/000077500000000000000000000000001453143165200207335ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/uuid/uuid.go000066400000000000000000000013251453143165200222310ustar00rootroot00000000000000/* 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 uuid import ( "github.com/google/uuid" "k8s.io/apimachinery/pkg/types" ) func NewUUID() types.UID { return types.UID(uuid.New().String()) } golang-k8s-apimachinery-0.29.0/pkg/util/validation/000077500000000000000000000000001453143165200221175ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/validation/field/000077500000000000000000000000001453143165200232025ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/validation/field/errors.go000066400000000000000000000261611453143165200250530ustar00rootroot00000000000000/* 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 field import ( "fmt" "reflect" "strconv" "strings" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" ) // Error is an implementation of the 'error' interface, which represents a // field-level validation error. type Error struct { Type ErrorType Field string BadValue interface{} Detail string } var _ error = &Error{} // Error implements the error interface. func (v *Error) Error() string { return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody()) } type OmitValueType struct{} var omitValue = OmitValueType{} // ErrorBody returns the error message without the field name. This is useful // for building nice-looking higher-level error reporting. func (v *Error) ErrorBody() string { var s string switch { case v.Type == ErrorTypeRequired: s = v.Type.String() case v.Type == ErrorTypeForbidden: s = v.Type.String() case v.Type == ErrorTypeTooLong: s = v.Type.String() case v.Type == ErrorTypeInternal: s = v.Type.String() case v.BadValue == omitValue: s = v.Type.String() default: value := v.BadValue valueType := reflect.TypeOf(value) if value == nil || valueType == nil { value = "null" } else if valueType.Kind() == reflect.Pointer { if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() { value = "null" } else { value = reflectValue.Elem().Interface() } } switch t := value.(type) { case int64, int32, float64, float32, bool: // use simple printer for simple types s = fmt.Sprintf("%s: %v", v.Type, value) case string: s = fmt.Sprintf("%s: %q", v.Type, t) case fmt.Stringer: // anything that defines String() is better than raw struct s = fmt.Sprintf("%s: %s", v.Type, t.String()) default: // fallback to raw struct // TODO: internal types have panic guards against json.Marshalling to prevent // accidental use of internal types in external serialized form. For now, use // %#v, although it would be better to show a more expressive output in the future s = fmt.Sprintf("%s: %#v", v.Type, value) } } if len(v.Detail) != 0 { s += fmt.Sprintf(": %s", v.Detail) } return s } // ErrorType is a machine readable value providing more detail about why // a field is invalid. These values are expected to match 1-1 with // CauseType in api/types.go. type ErrorType string // TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it. const ( // ErrorTypeNotFound is used to report failure to find a requested value // (e.g. looking up an ID). See NotFound(). ErrorTypeNotFound ErrorType = "FieldValueNotFound" // ErrorTypeRequired is used to report required values that are not // provided (e.g. empty strings, null values, or empty arrays). See // Required(). ErrorTypeRequired ErrorType = "FieldValueRequired" // ErrorTypeDuplicate is used to report collisions of values that must be // unique (e.g. unique IDs). See Duplicate(). ErrorTypeDuplicate ErrorType = "FieldValueDuplicate" // ErrorTypeInvalid is used to report malformed values (e.g. failed regex // match, too long, out of bounds). See Invalid(). ErrorTypeInvalid ErrorType = "FieldValueInvalid" // ErrorTypeNotSupported is used to report unknown values for enumerated // fields (e.g. a list of valid values). See NotSupported(). ErrorTypeNotSupported ErrorType = "FieldValueNotSupported" // ErrorTypeForbidden is used to report valid (as per formatting rules) // values which would be accepted under some conditions, but which are not // permitted by the current conditions (such as security policy). See // Forbidden(). ErrorTypeForbidden ErrorType = "FieldValueForbidden" // ErrorTypeTooLong is used to report that the given value is too long. // This is similar to ErrorTypeInvalid, but the error will not include the // too-long value. See TooLong(). ErrorTypeTooLong ErrorType = "FieldValueTooLong" // ErrorTypeTooMany is used to report "too many". This is used to // report that a given list has too many items. This is similar to FieldValueTooLong, // but the error indicates quantity instead of length. ErrorTypeTooMany ErrorType = "FieldValueTooMany" // ErrorTypeInternal is used to report other errors that are not related // to user input. See InternalError(). ErrorTypeInternal ErrorType = "InternalError" // ErrorTypeTypeInvalid is for the value did not match the schema type for that field ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid" ) // String converts a ErrorType into its corresponding canonical error message. func (t ErrorType) String() string { switch t { case ErrorTypeNotFound: return "Not found" case ErrorTypeRequired: return "Required value" case ErrorTypeDuplicate: return "Duplicate value" case ErrorTypeInvalid: return "Invalid value" case ErrorTypeNotSupported: return "Unsupported value" case ErrorTypeForbidden: return "Forbidden" case ErrorTypeTooLong: return "Too long" case ErrorTypeTooMany: return "Too many" case ErrorTypeInternal: return "Internal error" case ErrorTypeTypeInvalid: return "Invalid value" default: panic(fmt.Sprintf("unrecognized validation error: %q", string(t))) } } // TypeInvalid returns a *Error indicating "type is invalid" func TypeInvalid(field *Path, value interface{}, detail string) *Error { return &Error{ErrorTypeTypeInvalid, field.String(), value, detail} } // NotFound returns a *Error indicating "value not found". This is // used to report failure to find a requested value (e.g. looking up an ID). func NotFound(field *Path, value interface{}) *Error { return &Error{ErrorTypeNotFound, field.String(), value, ""} } // Required returns a *Error indicating "value required". This is used // to report required values that are not provided (e.g. empty strings, null // values, or empty arrays). func Required(field *Path, detail string) *Error { return &Error{ErrorTypeRequired, field.String(), "", detail} } // Duplicate returns a *Error indicating "duplicate value". This is // used to report collisions of values that must be unique (e.g. names or IDs). func Duplicate(field *Path, value interface{}) *Error { return &Error{ErrorTypeDuplicate, field.String(), value, ""} } // Invalid returns a *Error indicating "invalid value". This is used // to report malformed values (e.g. failed regex match, too long, out of bounds). func Invalid(field *Path, value interface{}, detail string) *Error { return &Error{ErrorTypeInvalid, field.String(), value, detail} } // NotSupported returns a *Error indicating "unsupported value". // This is used to report unknown values for enumerated fields (e.g. a list of // valid values). func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *Error { detail := "" if len(validValues) > 0 { quotedValues := make([]string, len(validValues)) for i, v := range validValues { quotedValues[i] = strconv.Quote(fmt.Sprint(v)) } detail = "supported values: " + strings.Join(quotedValues, ", ") } return &Error{ErrorTypeNotSupported, field.String(), value, detail} } // Forbidden returns a *Error indicating "forbidden". This is used to // report valid (as per formatting rules) values which would be accepted under // some conditions, but which are not permitted by current conditions (e.g. // security policy). func Forbidden(field *Path, detail string) *Error { return &Error{ErrorTypeForbidden, field.String(), "", detail} } // TooLong returns a *Error indicating "too long". This is used to // report that the given value is too long. This is similar to // Invalid, but the returned error will not include the too-long // value. func TooLong(field *Path, value interface{}, maxLength int) *Error { return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d bytes", maxLength)} } // TooLongMaxLength returns a *Error indicating "too long". This is used to // report that the given value is too long. This is similar to // Invalid, but the returned error will not include the too-long // value. If maxLength is negative, no max length will be included in the message. func TooLongMaxLength(field *Path, value interface{}, maxLength int) *Error { var msg string if maxLength >= 0 { msg = fmt.Sprintf("may not be longer than %d", maxLength) } else { msg = "value is too long" } return &Error{ErrorTypeTooLong, field.String(), value, msg} } // TooMany returns a *Error indicating "too many". This is used to // report that a given list has too many items. This is similar to TooLong, // but the returned error indicates quantity instead of length. func TooMany(field *Path, actualQuantity, maxQuantity int) *Error { var msg string if maxQuantity >= 0 { msg = fmt.Sprintf("must have at most %d items", maxQuantity) } else { msg = "has too many items" } var actual interface{} if actualQuantity >= 0 { actual = actualQuantity } else { actual = omitValue } return &Error{ErrorTypeTooMany, field.String(), actual, msg} } // InternalError returns a *Error indicating "internal error". This is used // to signal that an error was found that was not directly related to user // input. The err argument must be non-nil. func InternalError(field *Path, err error) *Error { return &Error{ErrorTypeInternal, field.String(), nil, err.Error()} } // ErrorList holds a set of Errors. It is plausible that we might one day have // non-field errors in this same umbrella package, but for now we don't, so // we can keep it simple and leave ErrorList here. type ErrorList []*Error // NewErrorTypeMatcher returns an errors.Matcher that returns true // if the provided error is a Error and has the provided ErrorType. func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher { return func(err error) bool { if e, ok := err.(*Error); ok { return e.Type == t } return false } } // ToAggregate converts the ErrorList into an errors.Aggregate. func (list ErrorList) ToAggregate() utilerrors.Aggregate { if len(list) == 0 { return nil } errs := make([]error, 0, len(list)) errorMsgs := sets.NewString() for _, err := range list { msg := fmt.Sprintf("%v", err) if errorMsgs.Has(msg) { continue } errorMsgs.Insert(msg) errs = append(errs, err) } return utilerrors.NewAggregate(errs) } func fromAggregate(agg utilerrors.Aggregate) ErrorList { errs := agg.Errors() list := make(ErrorList, len(errs)) for i := range errs { list[i] = errs[i].(*Error) } return list } // Filter removes items from the ErrorList that match the provided fns. func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList { err := utilerrors.FilterOut(list.ToAggregate(), fns...) if err == nil { return nil } // FilterOut takes an Aggregate and returns an Aggregate return fromAggregate(err.(utilerrors.Aggregate)) } golang-k8s-apimachinery-0.29.0/pkg/util/validation/field/errors_test.go000066400000000000000000000103011453143165200260770ustar00rootroot00000000000000/* 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 field import ( "fmt" "strings" "testing" ) func TestMakeFuncs(t *testing.T) { testCases := []struct { fn func() *Error expected ErrorType }{ { func() *Error { return Invalid(NewPath("f"), "v", "d") }, ErrorTypeInvalid, }, { func() *Error { return NotSupported[string](NewPath("f"), "v", nil) }, ErrorTypeNotSupported, }, { func() *Error { return Duplicate(NewPath("f"), "v") }, ErrorTypeDuplicate, }, { func() *Error { return NotFound(NewPath("f"), "v") }, ErrorTypeNotFound, }, { func() *Error { return Required(NewPath("f"), "d") }, ErrorTypeRequired, }, { func() *Error { return InternalError(NewPath("f"), fmt.Errorf("e")) }, ErrorTypeInternal, }, } for _, testCase := range testCases { err := testCase.fn() if err.Type != testCase.expected { t.Errorf("expected Type %q, got %q", testCase.expected, err.Type) } } } func TestErrorUsefulMessage(t *testing.T) { { s := Invalid(nil, nil, "").Error() t.Logf("message: %v", s) if !strings.Contains(s, "null") { t.Errorf("error message did not contain 'null': %s", s) } } s := Invalid(NewPath("foo"), "bar", "deet").Error() t.Logf("message: %v", s) for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} { if !strings.Contains(s, part) { t.Errorf("error message did not contain expected part '%v'", part) } } type complicated struct { Baz int Qux string Inner interface{} KV map[string]int } s = Invalid( NewPath("foo"), &complicated{ Baz: 1, Qux: "aoeu", Inner: &complicated{Qux: "asdf"}, KV: map[string]int{"Billy": 2}, }, "detail", ).Error() t.Logf("message: %v", s) for _, part := range []string{ "foo", ErrorTypeInvalid.String(), "Baz", "Qux", "Inner", "KV", "detail", "1", "aoeu", "Billy", "2", // "asdf", TODO: re-enable once we have a better nested printer } { if !strings.Contains(s, part) { t.Errorf("error message did not contain expected part '%v'", part) } } } func TestToAggregate(t *testing.T) { testCases := struct { ErrList []ErrorList NumExpectedErrs []int }{ []ErrorList{ nil, {}, {Invalid(NewPath("f"), "v", "d")}, {Invalid(NewPath("f"), "v", "d"), Invalid(NewPath("f"), "v", "d")}, {Invalid(NewPath("f"), "v", "d"), InternalError(NewPath(""), fmt.Errorf("e"))}, }, []int{ 0, 0, 1, 1, 2, }, } if len(testCases.ErrList) != len(testCases.NumExpectedErrs) { t.Errorf("Mismatch: length of NumExpectedErrs does not match length of ErrList") } for i, tc := range testCases.ErrList { agg := tc.ToAggregate() numErrs := 0 if agg != nil { numErrs = len(agg.Errors()) } if numErrs != testCases.NumExpectedErrs[i] { t.Errorf("[%d] Expected %d, got %d", i, testCases.NumExpectedErrs[i], numErrs) } if len(tc) == 0 { if agg != nil { t.Errorf("[%d] Expected nil, got %#v", i, agg) } } else if agg == nil { t.Errorf("[%d] Expected non-nil", i) } } } func TestErrListFilter(t *testing.T) { list := ErrorList{ Invalid(NewPath("test.field"), "", ""), Invalid(NewPath("field.test"), "", ""), Duplicate(NewPath("test"), "value"), } if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 { t.Errorf("should not filter") } if len(list.Filter(NewErrorTypeMatcher(ErrorTypeInvalid))) != 1 { t.Errorf("should filter") } } func TestNotSupported(t *testing.T) { notSupported := NotSupported(NewPath("f"), "v", []string{"a", "b", "c"}) expected := `Unsupported value: "v": supported values: "a", "b", "c"` if notSupported.ErrorBody() != expected { t.Errorf("Expected: %s\n, but got: %s\n", expected, notSupported.ErrorBody()) } } golang-k8s-apimachinery-0.29.0/pkg/util/validation/field/path.go000066400000000000000000000055701453143165200244740ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package field import ( "bytes" "fmt" "strconv" ) type pathOptions struct { path *Path } // PathOption modifies a pathOptions type PathOption func(o *pathOptions) // WithPath generates a PathOption func WithPath(p *Path) PathOption { return func(o *pathOptions) { o.path = p } } // ToPath produces *Path from a set of PathOption func ToPath(opts ...PathOption) *Path { c := &pathOptions{} for _, opt := range opts { opt(c) } return c.path } // Path represents the path from some root to a particular field. type Path struct { name string // the name of this field or "" if this is an index index string // if name == "", this is a subscript (index or map key) of the previous element parent *Path // nil if this is the root element } // NewPath creates a root Path object. func NewPath(name string, moreNames ...string) *Path { r := &Path{name: name, parent: nil} for _, anotherName := range moreNames { r = &Path{name: anotherName, parent: r} } return r } // Root returns the root element of this Path. func (p *Path) Root() *Path { for ; p.parent != nil; p = p.parent { // Do nothing. } return p } // Child creates a new Path that is a child of the method receiver. func (p *Path) Child(name string, moreNames ...string) *Path { r := NewPath(name, moreNames...) r.Root().parent = p return r } // Index indicates that the previous Path is to be subscripted by an int. // This sets the same underlying value as Key. func (p *Path) Index(index int) *Path { return &Path{index: strconv.Itoa(index), parent: p} } // Key indicates that the previous Path is to be subscripted by a string. // This sets the same underlying value as Index. func (p *Path) Key(key string) *Path { return &Path{index: key, parent: p} } // String produces a string representation of the Path. func (p *Path) String() string { if p == nil { return "" } // make a slice to iterate elems := []*Path{} for ; p != nil; p = p.parent { elems = append(elems, p) } // iterate, but it has to be backwards buf := bytes.NewBuffer(nil) for i := range elems { p := elems[len(elems)-1-i] if p.parent != nil && len(p.name) > 0 { // This is either the root or it is a subscript. buf.WriteString(".") } if len(p.name) > 0 { buf.WriteString(p.name) } else { fmt.Fprintf(buf, "[%s]", p.index) } } return buf.String() } golang-k8s-apimachinery-0.29.0/pkg/util/validation/field/path_test.go000066400000000000000000000051701453143165200255270ustar00rootroot00000000000000/* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package field import "testing" func TestPath(t *testing.T) { testCases := []struct { op func(*Path) *Path expected string }{ { func(p *Path) *Path { return p }, "root", }, { func(p *Path) *Path { return p.Child("first") }, "root.first", }, { func(p *Path) *Path { return p.Child("second") }, "root.first.second", }, { func(p *Path) *Path { return p.Index(0) }, "root.first.second[0]", }, { func(p *Path) *Path { return p.Child("third") }, "root.first.second[0].third", }, { func(p *Path) *Path { return p.Index(93) }, "root.first.second[0].third[93]", }, { func(p *Path) *Path { return p.parent }, "root.first.second[0].third", }, { func(p *Path) *Path { return p.parent }, "root.first.second[0]", }, { func(p *Path) *Path { return p.Key("key") }, "root.first.second[0][key]", }, } root := NewPath("root") p := root for i, tc := range testCases { p = tc.op(p) if p.String() != tc.expected { t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String()) } if p.Root() != root { t.Errorf("[%d] Wrong root: %#v", i, p.Root()) } } } func TestPathMultiArg(t *testing.T) { testCases := []struct { op func(*Path) *Path expected string }{ { func(p *Path) *Path { return p }, "root.first", }, { func(p *Path) *Path { return p.Child("second", "third") }, "root.first.second.third", }, { func(p *Path) *Path { return p.Index(0) }, "root.first.second.third[0]", }, { func(p *Path) *Path { return p.parent }, "root.first.second.third", }, { func(p *Path) *Path { return p.parent }, "root.first.second", }, { func(p *Path) *Path { return p.parent }, "root.first", }, { func(p *Path) *Path { return p.parent }, "root", }, } root := NewPath("root", "first") p := root for i, tc := range testCases { p = tc.op(p) if p.String() != tc.expected { t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String()) } if p.Root() != root.Root() { t.Errorf("[%d] Wrong root: %#v", i, p.Root()) } } } golang-k8s-apimachinery-0.29.0/pkg/util/validation/validation.go000066400000000000000000000445711453143165200246130ustar00rootroot00000000000000/* 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 validation import ( "fmt" "math" "net" "regexp" "strconv" "strings" "k8s.io/apimachinery/pkg/util/validation/field" netutils "k8s.io/utils/net" ) const qnameCharFmt string = "[A-Za-z0-9]" const qnameExtCharFmt string = "[-A-Za-z0-9_.]" const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" const qualifiedNameMaxLength int = 63 var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$") // IsQualifiedName tests whether the value passed is what Kubernetes calls a // "qualified name". This is a format used in various places throughout the // system. If the value is not valid, a list of error strings is returned. // Otherwise an empty list (or nil) is returned. func IsQualifiedName(value string) []string { var errs []string parts := strings.Split(value, "/") var name string switch len(parts) { case 1: name = parts[0] case 2: var prefix string prefix, name = parts[0], parts[1] if len(prefix) == 0 { errs = append(errs, "prefix part "+EmptyError()) } else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 { errs = append(errs, prefixEach(msgs, "prefix part ")...) } default: return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+ " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')") } if len(name) == 0 { errs = append(errs, "name part "+EmptyError()) } else if len(name) > qualifiedNameMaxLength { errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength)) } if !qualifiedNameRegexp.MatchString(name) { errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")) } return errs } // IsFullyQualifiedName checks if the name is fully qualified. This is similar // to IsFullyQualifiedDomainName but requires a minimum of 3 segments instead of // 2 and does not accept a trailing . as valid. // TODO: This function is deprecated and preserved until all callers migrate to // IsFullyQualifiedDomainName; please don't add new callers. func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList { var allErrors field.ErrorList if len(name) == 0 { return append(allErrors, field.Required(fldPath, "")) } if errs := IsDNS1123Subdomain(name); len(errs) > 0 { return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ","))) } if len(strings.Split(name, ".")) < 3 { return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least three segments separated by dots")) } return allErrors } // IsFullyQualifiedDomainName checks if the domain name is fully qualified. This // is similar to IsFullyQualifiedName but only requires a minimum of 2 segments // instead of 3 and accepts a trailing . as valid. func IsFullyQualifiedDomainName(fldPath *field.Path, name string) field.ErrorList { var allErrors field.ErrorList if len(name) == 0 { return append(allErrors, field.Required(fldPath, "")) } if strings.HasSuffix(name, ".") { name = name[:len(name)-1] } if errs := IsDNS1123Subdomain(name); len(errs) > 0 { return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ","))) } if len(strings.Split(name, ".")) < 2 { return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least two segments separated by dots")) } for _, label := range strings.Split(name, ".") { if errs := IsDNS1123Label(label); len(errs) > 0 { return append(allErrors, field.Invalid(fldPath, label, strings.Join(errs, ","))) } } return allErrors } // Allowed characters in an HTTP Path as defined by RFC 3986. A HTTP path may // contain: // * unreserved characters (alphanumeric, '-', '.', '_', '~') // * percent-encoded octets // * sub-delims ("!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=") // * a colon character (":") const httpPathFmt string = `[A-Za-z0-9/\-._~%!$&'()*+,;=:]+` var httpPathRegexp = regexp.MustCompile("^" + httpPathFmt + "$") // IsDomainPrefixedPath checks if the given string is a domain-prefixed path // (e.g. acme.io/foo). All characters before the first "/" must be a valid // subdomain as defined by RFC 1123. All characters trailing the first "/" must // be valid HTTP Path characters as defined by RFC 3986. func IsDomainPrefixedPath(fldPath *field.Path, dpPath string) field.ErrorList { var allErrs field.ErrorList if len(dpPath) == 0 { return append(allErrs, field.Required(fldPath, "")) } segments := strings.SplitN(dpPath, "/", 2) if len(segments) != 2 || len(segments[0]) == 0 || len(segments[1]) == 0 { return append(allErrs, field.Invalid(fldPath, dpPath, "must be a domain-prefixed path (such as \"acme.io/foo\")")) } host := segments[0] for _, err := range IsDNS1123Subdomain(host) { allErrs = append(allErrs, field.Invalid(fldPath, host, err)) } path := segments[1] if !httpPathRegexp.MatchString(path) { return append(allErrs, field.Invalid(fldPath, path, RegexError("Invalid path", httpPathFmt))) } return allErrs } const labelValueFmt string = "(" + qualifiedNameFmt + ")?" const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" // LabelValueMaxLength is a label's max length const LabelValueMaxLength int = 63 var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$") // IsValidLabelValue tests whether the value passed is a valid label value. If // the value is not valid, a list of error strings is returned. Otherwise an // empty list (or nil) is returned. func IsValidLabelValue(value string) []string { var errs []string if len(value) > LabelValueMaxLength { errs = append(errs, MaxLenError(LabelValueMaxLength)) } if !labelValueRegexp.MatchString(value) { errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345")) } return errs } const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" // DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123) const DNS1123LabelMaxLength int = 63 var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$") // IsDNS1123Label tests for a string that conforms to the definition of a label in // DNS (RFC 1123). func IsDNS1123Label(value string) []string { var errs []string if len(value) > DNS1123LabelMaxLength { errs = append(errs, MaxLenError(DNS1123LabelMaxLength)) } if !dns1123LabelRegexp.MatchString(value) { if dns1123SubdomainRegexp.MatchString(value) { // It was a valid subdomain and not a valid label. Since we // already checked length, it must be dots. errs = append(errs, "must not contain dots") } else { errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc")) } } return errs } const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*" const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" // DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123) const DNS1123SubdomainMaxLength int = 253 var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") // IsDNS1123Subdomain tests for a string that conforms to the definition of a // subdomain in DNS (RFC 1123). func IsDNS1123Subdomain(value string) []string { var errs []string if len(value) > DNS1123SubdomainMaxLength { errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) } if !dns1123SubdomainRegexp.MatchString(value) { errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com")) } return errs } const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?" const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character" // DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035) const DNS1035LabelMaxLength int = 63 var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$") // IsDNS1035Label tests for a string that conforms to the definition of a label in // DNS (RFC 1035). func IsDNS1035Label(value string) []string { var errs []string if len(value) > DNS1035LabelMaxLength { errs = append(errs, MaxLenError(DNS1035LabelMaxLength)) } if !dns1035LabelRegexp.MatchString(value) { errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123")) } return errs } // wildcard definition - RFC 1034 section 4.3.3. // examples: // - valid: *.bar.com, *.foo.bar.com // - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, * const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character" // IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a // wildcard subdomain in DNS (RFC 1034 section 4.3.3). func IsWildcardDNS1123Subdomain(value string) []string { wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$") var errs []string if len(value) > DNS1123SubdomainMaxLength { errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) } if !wildcardDNS1123SubdomainRegexp.MatchString(value) { errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com")) } return errs } const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*" const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'" var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$") // IsCIdentifier tests for a string that conforms the definition of an identifier // in C. This checks the format, but not the length. func IsCIdentifier(value string) []string { if !cIdentifierRegexp.MatchString(value) { return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")} } return nil } // IsValidPortNum tests that the argument is a valid, non-zero port number. func IsValidPortNum(port int) []string { if 1 <= port && port <= 65535 { return nil } return []string{InclusiveRangeError(1, 65535)} } // IsInRange tests that the argument is in an inclusive range. func IsInRange(value int, min int, max int) []string { if value >= min && value <= max { return nil } return []string{InclusiveRangeError(min, max)} } // Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1 // TODO: once we have a type for UID/GID we should make these that type. const ( minUserID = 0 maxUserID = math.MaxInt32 minGroupID = 0 maxGroupID = math.MaxInt32 ) // IsValidGroupID tests that the argument is a valid Unix GID. func IsValidGroupID(gid int64) []string { if minGroupID <= gid && gid <= maxGroupID { return nil } return []string{InclusiveRangeError(minGroupID, maxGroupID)} } // IsValidUserID tests that the argument is a valid Unix UID. func IsValidUserID(uid int64) []string { if minUserID <= uid && uid <= maxUserID { return nil } return []string{InclusiveRangeError(minUserID, maxUserID)} } var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$") var portNameOneLetterRegexp = regexp.MustCompile("[a-z]") // IsValidPortName check that the argument is valid syntax. It must be // non-empty and no more than 15 characters long. It may contain only [-a-z0-9] // and must contain at least one letter [a-z]. It must not start or end with a // hyphen, nor contain adjacent hyphens. // // Note: We only allow lower-case characters, even though RFC 6335 is case // insensitive. func IsValidPortName(port string) []string { var errs []string if len(port) > 15 { errs = append(errs, MaxLenError(15)) } if !portNameCharsetRegex.MatchString(port) { errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)") } if !portNameOneLetterRegexp.MatchString(port) { errs = append(errs, "must contain at least one letter (a-z)") } if strings.Contains(port, "--") { errs = append(errs, "must not contain consecutive hyphens") } if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') { errs = append(errs, "must not begin or end with a hyphen") } return errs } // IsValidIP tests that the argument is a valid IP address. func IsValidIP(value string) []string { if netutils.ParseIPSloppy(value) == nil { return []string{"must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)"} } return nil } // IsValidIPv4Address tests that the argument is a valid IPv4 address. func IsValidIPv4Address(fldPath *field.Path, value string) field.ErrorList { var allErrors field.ErrorList ip := netutils.ParseIPSloppy(value) if ip == nil || ip.To4() == nil { allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv4 address")) } return allErrors } // IsValidIPv6Address tests that the argument is a valid IPv6 address. func IsValidIPv6Address(fldPath *field.Path, value string) field.ErrorList { var allErrors field.ErrorList ip := netutils.ParseIPSloppy(value) if ip == nil || ip.To4() != nil { allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv6 address")) } return allErrors } const percentFmt string = "[0-9]+%" const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'" var percentRegexp = regexp.MustCompile("^" + percentFmt + "$") // IsValidPercent checks that string is in the form of a percentage func IsValidPercent(percent string) []string { if !percentRegexp.MatchString(percent) { return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")} } return nil } const httpHeaderNameFmt string = "[-A-Za-z0-9]+" const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'" var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$") // IsHTTPHeaderName checks that a string conforms to the Go HTTP library's // definition of a valid header field name (a stricter subset than RFC7230). func IsHTTPHeaderName(value string) []string { if !httpHeaderNameRegexp.MatchString(value) { return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")} } return nil } const envVarNameFmt = "[-._a-zA-Z][-._a-zA-Z0-9]*" const envVarNameFmtErrMsg string = "a valid environment variable name must consist of alphabetic characters, digits, '_', '-', or '.', and must not start with a digit" var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$") // IsEnvVarName tests if a string is a valid environment variable name. func IsEnvVarName(value string) []string { var errs []string if !envVarNameRegexp.MatchString(value) { errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1")) } errs = append(errs, hasChDirPrefix(value)...) return errs } const configMapKeyFmt = `[-._a-zA-Z0-9]+` const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'" var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$") // IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret func IsConfigMapKey(value string) []string { var errs []string if len(value) > DNS1123SubdomainMaxLength { errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) } if !configMapKeyRegexp.MatchString(value) { errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name")) } errs = append(errs, hasChDirPrefix(value)...) return errs } // MaxLenError returns a string explanation of a "string too long" validation // failure. func MaxLenError(length int) string { return fmt.Sprintf("must be no more than %d characters", length) } // RegexError returns a string explanation of a regex validation failure. func RegexError(msg string, fmt string, examples ...string) string { if len(examples) == 0 { return msg + " (regex used for validation is '" + fmt + "')" } msg += " (e.g. " for i := range examples { if i > 0 { msg += " or " } msg += "'" + examples[i] + "', " } msg += "regex used for validation is '" + fmt + "')" return msg } // EmptyError returns a string explanation of a "must not be empty" validation // failure. func EmptyError() string { return "must be non-empty" } func prefixEach(msgs []string, prefix string) []string { for i := range msgs { msgs[i] = prefix + msgs[i] } return msgs } // InclusiveRangeError returns a string explanation of a numeric "must be // between" validation failure. func InclusiveRangeError(lo, hi int) string { return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi) } func hasChDirPrefix(value string) []string { var errs []string switch { case value == ".": errs = append(errs, `must not be '.'`) case value == "..": errs = append(errs, `must not be '..'`) case strings.HasPrefix(value, ".."): errs = append(errs, `must not start with '..'`) } return errs } // IsValidSocketAddr checks that string represents a valid socket address // as defined in RFC 789. (e.g 0.0.0.0:10254 or [::]:10254)) func IsValidSocketAddr(value string) []string { var errs []string ip, port, err := net.SplitHostPort(value) if err != nil { errs = append(errs, "must be a valid socket address format, (e.g. 0.0.0.0:10254 or [::]:10254)") return errs } portInt, _ := strconv.Atoi(port) errs = append(errs, IsValidPortNum(portInt)...) errs = append(errs, IsValidIP(ip)...) return errs } golang-k8s-apimachinery-0.29.0/pkg/util/validation/validation_test.go000066400000000000000000000447461453143165200256560ustar00rootroot00000000000000/* 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 validation import ( "strings" "testing" "k8s.io/apimachinery/pkg/util/validation/field" ) func TestIsDNS1123Label(t *testing.T) { goodValues := []string{ "a", "ab", "abc", "a1", "a-1", "a--1--2--b", "0", "01", "012", "1a", "1-a", "1--a--b--2", strings.Repeat("a", 63), } for _, val := range goodValues { if msgs := IsDNS1123Label(val); len(msgs) != 0 { t.Errorf("expected true for '%s': %v", val, msgs) } } badValues := []string{ "", "A", "ABC", "aBc", "A1", "A-1", "1-A", "-", "a-", "-a", "1-", "-1", "_", "a_", "_a", "a_b", "1_", "_1", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2", " ", "a ", " a", "a b", "1 ", " 1", "1 2", strings.Repeat("a", 64), } for _, val := range badValues { if msgs := IsDNS1123Label(val); len(msgs) == 0 { t.Errorf("expected false for '%s'", val) } } } func TestIsDNS1123Subdomain(t *testing.T) { goodValues := []string{ "a", "ab", "abc", "a1", "a-1", "a--1--2--b", "0", "01", "012", "1a", "1-a", "1--a--b--2", "a.a", "ab.a", "abc.a", "a1.a", "a-1.a", "a--1--2--b.a", "a.1", "ab.1", "abc.1", "a1.1", "a-1.1", "a--1--2--b.1", "0.a", "01.a", "012.a", "1a.a", "1-a.a", "1--a--b--2", "0.1", "01.1", "012.1", "1a.1", "1-a.1", "1--a--b--2.1", "a.b.c.d.e", "aa.bb.cc.dd.ee", "1.2.3.4.5", "11.22.33.44.55", strings.Repeat("a", 253), } for _, val := range goodValues { if msgs := IsDNS1123Subdomain(val); len(msgs) != 0 { t.Errorf("expected true for '%s': %v", val, msgs) } } badValues := []string{ "", "A", "ABC", "aBc", "A1", "A-1", "1-A", "-", "a-", "-a", "1-", "-1", "_", "a_", "_a", "a_b", "1_", "_1", "1_2", ".", "a.", ".a", "a..b", "1.", ".1", "1..2", " ", "a ", " a", "a b", "1 ", " 1", "1 2", "A.a", "aB.a", "ab.A", "A1.a", "a1.A", "A.1", "aB.1", "A1.1", "1A.1", "0.A", "01.A", "012.A", "1A.a", "1a.A", "A.B.C.D.E", "AA.BB.CC.DD.EE", "a.B.c.d.e", "aa.bB.cc.dd.ee", "a@b", "a,b", "a_b", "a;b", "a:b", "a%b", "a?b", "a$b", strings.Repeat("a", 254), } for _, val := range badValues { if msgs := IsDNS1123Subdomain(val); len(msgs) == 0 { t.Errorf("expected false for '%s'", val) } } } func TestIsDNS1035Label(t *testing.T) { goodValues := []string{ "a", "ab", "abc", "a1", "a-1", "a--1--2--b", strings.Repeat("a", 63), } for _, val := range goodValues { if msgs := IsDNS1035Label(val); len(msgs) != 0 { t.Errorf("expected true for '%s': %v", val, msgs) } } badValues := []string{ "0", "01", "012", "1a", "1-a", "1--a--b--2", "", "A", "ABC", "aBc", "A1", "A-1", "1-A", "-", "a-", "-a", "1-", "-1", "_", "a_", "_a", "a_b", "1_", "_1", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2", " ", "a ", " a", "a b", "1 ", " 1", "1 2", strings.Repeat("a", 64), } for _, val := range badValues { if msgs := IsDNS1035Label(val); len(msgs) == 0 { t.Errorf("expected false for '%s'", val) } } } func TestIsCIdentifier(t *testing.T) { goodValues := []string{ "a", "ab", "abc", "a1", "_a", "a_", "a_b", "a_1", "a__1__2__b", "__abc_123", "A", "AB", "AbC", "A1", "_A", "A_", "A_B", "A_1", "A__1__2__B", "__123_ABC", } for _, val := range goodValues { if msgs := IsCIdentifier(val); len(msgs) != 0 { t.Errorf("expected true for '%s': %v", val, msgs) } } badValues := []string{ "", "1", "123", "1a", "-", "a-", "-a", "1-", "-1", "1_", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2", " ", "a ", " a", "a b", "1 ", " 1", "1 2", "#a#", } for _, val := range badValues { if msgs := IsCIdentifier(val); len(msgs) == 0 { t.Errorf("expected false for '%s'", val) } } } func TestIsValidPortNum(t *testing.T) { goodValues := []int{1, 2, 1000, 16384, 32768, 65535} for _, val := range goodValues { if msgs := IsValidPortNum(val); len(msgs) != 0 { t.Errorf("expected true for %d, got %v", val, msgs) } } badValues := []int{0, -1, 65536, 100000} for _, val := range badValues { if msgs := IsValidPortNum(val); len(msgs) == 0 { t.Errorf("expected false for %d", val) } } } func TestIsInRange(t *testing.T) { goodValues := []struct { value int min int max int }{{1, 0, 10}, {5, 5, 20}, {25, 10, 25}} for _, val := range goodValues { if msgs := IsInRange(val.value, val.min, val.max); len(msgs) > 0 { t.Errorf("expected no errors for %#v, but got %v", val, msgs) } } badValues := []struct { value int min int max int }{{1, 2, 10}, {5, -4, 2}, {25, 100, 120}} for _, val := range badValues { if msgs := IsInRange(val.value, val.min, val.max); len(msgs) == 0 { t.Errorf("expected errors for %#v", val) } } } func createGroupIDs(ids ...int64) []int64 { var output []int64 for _, id := range ids { output = append(output, int64(id)) } return output } func createUserIDs(ids ...int64) []int64 { var output []int64 for _, id := range ids { output = append(output, int64(id)) } return output } func TestIsValidGroupID(t *testing.T) { goodValues := createGroupIDs(0, 1, 1000, 65535, 2147483647) for _, val := range goodValues { if msgs := IsValidGroupID(val); len(msgs) != 0 { t.Errorf("expected true for '%d': %v", val, msgs) } } badValues := createGroupIDs(-1, -1003, 2147483648, 4147483647) for _, val := range badValues { if msgs := IsValidGroupID(val); len(msgs) == 0 { t.Errorf("expected false for '%d'", val) } } } func TestIsValidUserID(t *testing.T) { goodValues := createUserIDs(0, 1, 1000, 65535, 2147483647) for _, val := range goodValues { if msgs := IsValidUserID(val); len(msgs) != 0 { t.Errorf("expected true for '%d': %v", val, msgs) } } badValues := createUserIDs(-1, -1003, 2147483648, 4147483647) for _, val := range badValues { if msgs := IsValidUserID(val); len(msgs) == 0 { t.Errorf("expected false for '%d'", val) } } } func TestIsValidPortName(t *testing.T) { goodValues := []string{"telnet", "re-mail-ck", "pop3", "a", "a-1", "1-a", "a-1-b-2-c", "1-a-2-b-3"} for _, val := range goodValues { if msgs := IsValidPortName(val); len(msgs) != 0 { t.Errorf("expected true for %q: %v", val, msgs) } } badValues := []string{"longerthan15characters", "", strings.Repeat("a", 16), "12345", "1-2-3-4", "-begin", "end-", "two--hyphens", "whois++"} for _, val := range badValues { if msgs := IsValidPortName(val); len(msgs) == 0 { t.Errorf("expected false for %q", val) } } } func TestIsQualifiedName(t *testing.T) { successCases := []string{ "simple", "now-with-dashes", "1-starts-with-num", "1234", "simple/simple", "now-with-dashes/simple", "now-with-dashes/now-with-dashes", "now.with.dots/simple", "now-with.dashes-and.dots/simple", "1-num.2-num/3-num", "1234/5678", "1.2.3.4/5678", "Uppercase_Is_OK_123", "example.com/Uppercase_Is_OK_123", "requests.storage-foo", strings.Repeat("a", 63), strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), } for i := range successCases { if errs := IsQualifiedName(successCases[i]); len(errs) != 0 { t.Errorf("case[%d]: %q: expected success: %v", i, successCases[i], errs) } } errorCases := []string{ "nospecialchars%^=@", "cantendwithadash-", "-cantstartwithadash-", "only/one/slash", "Example.com/abc", "example_com/abc", "example.com/", "/simple", strings.Repeat("a", 64), strings.Repeat("a", 254) + "/abc", } for i := range errorCases { if errs := IsQualifiedName(errorCases[i]); len(errs) == 0 { t.Errorf("case[%d]: %q: expected failure", i, errorCases[i]) } } } func TestIsValidLabelValue(t *testing.T) { successCases := []string{ "simple", "now-with-dashes", "1-starts-with-num", "end-with-num-1", "1234", // only num strings.Repeat("a", 63), // to the limit "", // empty value } for i := range successCases { if errs := IsValidLabelValue(successCases[i]); len(errs) != 0 { t.Errorf("case %s expected success: %v", successCases[i], errs) } } errorCases := []string{ "nospecialchars%^=@", "Tama-nui-te-rā.is.Māori.sun", "\\backslashes\\are\\bad", "-starts-with-dash", "ends-with-dash-", ".starts.with.dot", "ends.with.dot.", strings.Repeat("a", 64), // over the limit } for i := range errorCases { if errs := IsValidLabelValue(errorCases[i]); len(errs) == 0 { t.Errorf("case[%d] expected failure", i) } } } func TestIsValidIP(t *testing.T) { goodValues := []string{ "::1", "2a00:79e0:2:0:f1c3:e797:93c1:df80", "::", "2001:4860:4860::8888", "::fff:1.1.1.1", "1.1.1.1", "1.1.1.01", "255.0.0.1", "1.0.0.0", "0.0.0.0", } for _, val := range goodValues { if msgs := IsValidIP(val); len(msgs) != 0 { t.Errorf("expected true for %q: %v", val, msgs) } } badValues := []string{ "[2001:db8:0:1]:80", "myhost.mydomain", "-1.0.0.0", "[2001:db8:0:1]", "a", } for _, val := range badValues { if msgs := IsValidIP(val); len(msgs) == 0 { t.Errorf("expected false for %q", val) } } } func TestIsValidIPv4Address(t *testing.T) { goodValues := []string{ "1.1.1.1", "1.1.1.01", "255.0.0.1", "1.0.0.0", "0.0.0.0", } for _, val := range goodValues { if msgs := IsValidIPv4Address(field.NewPath(""), val); len(msgs) != 0 { t.Errorf("expected %q to be valid IPv4 address: %v", val, msgs) } } badValues := []string{ "[2001:db8:0:1]:80", "myhost.mydomain", "-1.0.0.0", "[2001:db8:0:1]", "a", "2001:4860:4860::8888", "::fff:1.1.1.1", "::1", "2a00:79e0:2:0:f1c3:e797:93c1:df80", "::", } for _, val := range badValues { if msgs := IsValidIPv4Address(field.NewPath(""), val); len(msgs) == 0 { t.Errorf("expected %q to be invalid IPv4 address", val) } } } func TestIsValidIPv6Address(t *testing.T) { goodValues := []string{ "2001:4860:4860::8888", "2a00:79e0:2:0:f1c3:e797:93c1:df80", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "::fff:1.1.1.1", "::1", "::", } for _, val := range goodValues { if msgs := IsValidIPv6Address(field.NewPath(""), val); len(msgs) != 0 { t.Errorf("expected %q to be valid IPv6 address: %v", val, msgs) } } badValues := []string{ "1.1.1.1", "1.1.1.01", "255.0.0.1", "1.0.0.0", "0.0.0.0", "[2001:db8:0:1]:80", "myhost.mydomain", "2001:0db8:85a3:0000:0000:8a2e:0370:7334:2001:0db8:85a3:0000:0000:8a2e:0370:7334", "-1.0.0.0", "[2001:db8:0:1]", "a", } for _, val := range badValues { if msgs := IsValidIPv6Address(field.NewPath(""), val); len(msgs) == 0 { t.Errorf("expected %q to be invalid IPv6 address", val) } } } func TestIsHTTPHeaderName(t *testing.T) { goodValues := []string{ // Common ones "Accept-Encoding", "Host", "If-Modified-Since", "X-Forwarded-For", // Weirdo, but still conforming names "a", "ab", "abc", "a1", "-a", "a-", "a-b", "a-1", "a--1--2--b", "--abc-123", "A", "AB", "AbC", "A1", "-A", "A-", "A-B", "A-1", "A--1--2--B", "--123-ABC", } for _, val := range goodValues { if msgs := IsHTTPHeaderName(val); len(msgs) != 0 { t.Errorf("expected true for '%s': %v", val, msgs) } } badValues := []string{ "Host:", "X-Forwarded-For:", "X-@Home", "", "_", "a_", "_a", "1_", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2", " ", "a ", " a", "a b", "1 ", " 1", "1 2", "#a#", "^", ",", ";", "=", "<", "?", "@", "{", } for _, val := range badValues { if msgs := IsHTTPHeaderName(val); len(msgs) == 0 { t.Errorf("expected false for '%s'", val) } } } func TestIsValidPercent(t *testing.T) { goodValues := []string{ "0%", "00000%", "1%", "01%", "99%", "100%", "101%", } for _, val := range goodValues { if msgs := IsValidPercent(val); len(msgs) != 0 { t.Errorf("expected true for %q: %v", val, msgs) } } badValues := []string{ "", "0", "100", "0.0%", "99.9%", "hundred", " 1%", "1% ", "-0%", "-1%", "+1%", } for _, val := range badValues { if msgs := IsValidPercent(val); len(msgs) == 0 { t.Errorf("expected false for %q", val) } } } func TestIsConfigMapKey(t *testing.T) { successCases := []string{ "a", "good", "good-good", "still.good", "this.is.also.good", ".so.is.this", "THIS_IS_GOOD", "so_is_this_17", } for i := range successCases { if errs := IsConfigMapKey(successCases[i]); len(errs) != 0 { t.Errorf("[%d] expected success: %v", i, errs) } } failureCases := []string{ ".", "..", "..bad", "b*d", "bad!&bad", } for i := range failureCases { if errs := IsConfigMapKey(failureCases[i]); len(errs) == 0 { t.Errorf("[%d] expected failure", i) } } } func TestIsWildcardDNS1123Subdomain(t *testing.T) { goodValues := []string{ "*.example.com", "*.bar.com", "*.foo.bar.com", } for _, val := range goodValues { if errs := IsWildcardDNS1123Subdomain(val); len(errs) != 0 { t.Errorf("expected no errors for %q: %v", val, errs) } } badValues := []string{ "*.*.bar.com", "*.foo.*.com", "*bar.com", "f*.bar.com", "*", } for _, val := range badValues { if errs := IsWildcardDNS1123Subdomain(val); len(errs) == 0 { t.Errorf("expected errors for %q", val) } } } func TestIsFullyQualifiedDomainName(t *testing.T) { goodValues := []string{ "a.com", "k8s.io", "dev.k8s.io", "dev.k8s.io.", "foo.example.com", "this.is.a.really.long.fqdn", "bbc.co.uk", "10.0.0.1", // DNS labels can start with numbers and there is no requirement for letters. "hyphens-are-good.k8s.io", strings.Repeat("a", 63) + ".k8s.io", strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 54) + ".k8s.io", } for _, val := range goodValues { if err := IsFullyQualifiedDomainName(field.NewPath(""), val).ToAggregate(); err != nil { t.Errorf("expected no errors for %q: %v", val, err) } } badValues := []string{ ".", "...", ".io", "com", ".com", "Dev.k8s.io", ".foo.example.com", "*.example.com", "*.bar.com", "*.foo.bar.com", "underscores_are_bad.k8s.io", "foo@bar.example.com", "http://foo.example.com", strings.Repeat("a", 64) + ".k8s.io", strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 55) + ".k8s.io", } for _, val := range badValues { if err := IsFullyQualifiedDomainName(field.NewPath(""), val).ToAggregate(); err == nil { t.Errorf("expected errors for %q", val) } } } func TestIsFullyQualifiedName(t *testing.T) { goodValues := []string{ "dev.k8s.io", "foo.example.com", "this.is.a.really.long.fqdn", "bbc.co.uk", "10.0.0.1", // DNS labels can start with numbers and there is no requirement for letters. "hyphens-are-good.k8s.io", strings.Repeat("a", 246) + ".k8s.io", } for _, val := range goodValues { if err := IsFullyQualifiedName(field.NewPath(""), val).ToAggregate(); err != nil { t.Errorf("expected no errors for %q: %v", val, err) } } badValues := []string{ "...", "dev.k8s.io.", ".io", "Dev.k8s.io", "k8s.io", "*.example.com", "*.bar.com", "*.foo.bar.com", "underscores_are_bad.k8s.io", "foo@bar.example.com", "http://foo.example.com", strings.Repeat("a", 247) + ".k8s.io", } for _, val := range badValues { if err := IsFullyQualifiedName(field.NewPath(""), val).ToAggregate(); err == nil { t.Errorf("expected errors for %q", val) } } messageTests := []struct { name string targetName string err string }{{ name: "name needs to be fully qualified, i.e., contains at least 2 dots", targetName: "k8s.io", err: "should be a domain with at least three segments separated by dots", }, { name: "name should not include scheme", targetName: "http://foo.k8s.io", err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", }, { name: "email should be invalid", targetName: "example@foo.k8s.io", err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", }, { name: "name cannot be empty", targetName: "", err: "Required value", }, { name: "name must conform to RFC 1123", targetName: "A.B.C", err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", }} for _, tc := range messageTests { err := IsFullyQualifiedName(field.NewPath(""), tc.targetName).ToAggregate() switch { case tc.err == "" && err != nil: t.Errorf("%q: unexpected error: %v", tc.name, err) case tc.err != "" && err == nil: t.Errorf("%q: unexpected no error, expected %s", tc.name, tc.err) case tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err): t.Errorf("%q: expected %s, got %v", tc.name, tc.err, err) } } } func TestIsDomainPrefixedPath(t *testing.T) { goodValues := []string{ "a/b", "a/b/c/d", "a.com/foo", "a.b.c.d/foo", "k8s.io/foo/bar", "k8s.io/FOO/BAR", "dev.k8s.io/more/path", "this.is.a.really.long.fqdn/even/longer/path/just/because", "bbc.co.uk/path/goes/here", "10.0.0.1/foo", "hyphens-are-good.k8s.io/and-in-paths-too", strings.Repeat("a", 240) + ".k8s.io/a", "k8s.io/" + strings.Repeat("a", 240), } for _, val := range goodValues { if err := IsDomainPrefixedPath(field.NewPath(""), val).ToAggregate(); err != nil { t.Errorf("expected no errors for %q: %v", val, err) } } badValues := []string{ ".", "...", "/b", "com", ".com", "a.b.c.d/foo?a=b", "a.b.c.d/foo#a", "Dev.k8s.io", ".foo.example.com", "*.example.com", "example.com/foo{}[]@^`", "underscores_are_bad.k8s.io", "underscores_are_bad.k8s.io/foo", "foo@bar.example.com", "foo@bar.example.com/foo", strings.Repeat("a", 247) + ".k8s.io", } for _, val := range badValues { if err := IsDomainPrefixedPath(field.NewPath(""), val).ToAggregate(); err == nil { t.Errorf("expected errors for %q", val) } } } func TestIsValidSocketAddr(t *testing.T) { goodValues := []string{ "0.0.0.0:10254", "127.0.0.1:8888", "[2001:db8:1f70::999:de8:7648:6e8]:10254", "[::]:10254", } for _, val := range goodValues { if errs := IsValidSocketAddr(val); len(errs) != 0 { t.Errorf("expected no errors for %q: %v", val, errs) } } badValues := []string{ "0.0.0.0.0:2020", "0.0.0.0", "6.6.6.6:909090", "2001:db8:1f70::999:de8:7648:6e8:87567:102545", "", "*", } for _, val := range badValues { if errs := IsValidSocketAddr(val); len(errs) == 0 { t.Errorf("expected errors for %q", val) } } } golang-k8s-apimachinery-0.29.0/pkg/util/version/000077500000000000000000000000001453143165200214525ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/version/doc.go000066400000000000000000000013011453143165200225410ustar00rootroot00000000000000/* Copyright 2016 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 version provides utilities for version number comparisons package version // import "k8s.io/apimachinery/pkg/util/version" golang-k8s-apimachinery-0.29.0/pkg/util/version/version.go000066400000000000000000000250111453143165200234650ustar00rootroot00000000000000/* Copyright 2016 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 version import ( "bytes" "errors" "fmt" "regexp" "strconv" "strings" ) // Version is an opaque representation of a version number type Version struct { components []uint semver bool preRelease string buildMetadata string } var ( // versionMatchRE splits a version string into numeric and "extra" parts versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`) // extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`) ) func parse(str string, semver bool) (*Version, error) { parts := versionMatchRE.FindStringSubmatch(str) if parts == nil { return nil, fmt.Errorf("could not parse %q as version", str) } numbers, extra := parts[1], parts[2] components := strings.Split(numbers, ".") if (semver && len(components) != 3) || (!semver && len(components) < 2) { return nil, fmt.Errorf("illegal version string %q", str) } v := &Version{ components: make([]uint, len(components)), semver: semver, } for i, comp := range components { if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" { return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) } num, err := strconv.ParseUint(comp, 10, 0) if err != nil { return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err) } v.components[i] = uint(num) } if semver && extra != "" { extraParts := extraMatchRE.FindStringSubmatch(extra) if extraParts == nil { return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str) } v.preRelease, v.buildMetadata = extraParts[1], extraParts[2] for _, comp := range strings.Split(v.preRelease, ".") { if _, err := strconv.ParseUint(comp, 10, 0); err == nil { if strings.HasPrefix(comp, "0") && comp != "0" { return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) } } } } return v, nil } // HighestSupportedVersion returns the highest supported version // This function assumes that the highest supported version must be v1.x. func HighestSupportedVersion(versions []string) (*Version, error) { if len(versions) == 0 { return nil, errors.New("empty array for supported versions") } var ( highestSupportedVersion *Version theErr error ) for i := len(versions) - 1; i >= 0; i-- { currentHighestVer, err := ParseGeneric(versions[i]) if err != nil { theErr = err continue } if currentHighestVer.Major() > 1 { continue } if highestSupportedVersion == nil || highestSupportedVersion.LessThan(currentHighestVer) { highestSupportedVersion = currentHighestVer } } if highestSupportedVersion == nil { return nil, fmt.Errorf( "could not find a highest supported version from versions (%v) reported: %+v", versions, theErr) } if highestSupportedVersion.Major() != 1 { return nil, fmt.Errorf("highest supported version reported is %v, must be v1.x", highestSupportedVersion) } return highestSupportedVersion, nil } // ParseGeneric parses a "generic" version string. The version string must consist of two // or more dot-separated numeric fields (the first of which can't have leading zeroes), // followed by arbitrary uninterpreted data (which need not be separated from the final // numeric field by punctuation). For convenience, leading and trailing whitespace is // ignored, and the version can be preceded by the letter "v". See also ParseSemantic. func ParseGeneric(str string) (*Version, error) { return parse(str, false) } // MustParseGeneric is like ParseGeneric except that it panics on error func MustParseGeneric(str string) *Version { v, err := ParseGeneric(str) if err != nil { panic(err) } return v } // ParseSemantic parses a version string that exactly obeys the syntax and semantics of // the "Semantic Versioning" specification (http://semver.org/) (although it ignores // leading and trailing whitespace, and allows the version to be preceded by "v"). For // version strings that are not guaranteed to obey the Semantic Versioning syntax, use // ParseGeneric. func ParseSemantic(str string) (*Version, error) { return parse(str, true) } // MustParseSemantic is like ParseSemantic except that it panics on error func MustParseSemantic(str string) *Version { v, err := ParseSemantic(str) if err != nil { panic(err) } return v } // MajorMinor returns a version with the provided major and minor version. func MajorMinor(major, minor uint) *Version { return &Version{components: []uint{major, minor}} } // Major returns the major release number func (v *Version) Major() uint { return v.components[0] } // Minor returns the minor release number func (v *Version) Minor() uint { return v.components[1] } // Patch returns the patch release number if v is a Semantic Version, or 0 func (v *Version) Patch() uint { if len(v.components) < 3 { return 0 } return v.components[2] } // BuildMetadata returns the build metadata, if v is a Semantic Version, or "" func (v *Version) BuildMetadata() string { return v.buildMetadata } // PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" func (v *Version) PreRelease() string { return v.preRelease } // Components returns the version number components func (v *Version) Components() []uint { return v.components } // WithMajor returns copy of the version object with requested major number func (v *Version) WithMajor(major uint) *Version { result := *v result.components = []uint{major, v.Minor(), v.Patch()} return &result } // WithMinor returns copy of the version object with requested minor number func (v *Version) WithMinor(minor uint) *Version { result := *v result.components = []uint{v.Major(), minor, v.Patch()} return &result } // WithPatch returns copy of the version object with requested patch number func (v *Version) WithPatch(patch uint) *Version { result := *v result.components = []uint{v.Major(), v.Minor(), patch} return &result } // WithPreRelease returns copy of the version object with requested prerelease func (v *Version) WithPreRelease(preRelease string) *Version { result := *v result.components = []uint{v.Major(), v.Minor(), v.Patch()} result.preRelease = preRelease return &result } // WithBuildMetadata returns copy of the version object with requested buildMetadata func (v *Version) WithBuildMetadata(buildMetadata string) *Version { result := *v result.components = []uint{v.Major(), v.Minor(), v.Patch()} result.buildMetadata = buildMetadata return &result } // String converts a Version back to a string; note that for versions parsed with // ParseGeneric, this will not include the trailing uninterpreted portion of the version // number. func (v *Version) String() string { if v == nil { return "" } var buffer bytes.Buffer for i, comp := range v.components { if i > 0 { buffer.WriteString(".") } buffer.WriteString(fmt.Sprintf("%d", comp)) } if v.preRelease != "" { buffer.WriteString("-") buffer.WriteString(v.preRelease) } if v.buildMetadata != "" { buffer.WriteString("+") buffer.WriteString(v.buildMetadata) } return buffer.String() } // compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 // if they are equal func (v *Version) compareInternal(other *Version) int { vLen := len(v.components) oLen := len(other.components) for i := 0; i < vLen && i < oLen; i++ { switch { case other.components[i] < v.components[i]: return 1 case other.components[i] > v.components[i]: return -1 } } // If components are common but one has more items and they are not zeros, it is bigger switch { case oLen < vLen && !onlyZeros(v.components[oLen:]): return 1 case oLen > vLen && !onlyZeros(other.components[vLen:]): return -1 } if !v.semver || !other.semver { return 0 } switch { case v.preRelease == "" && other.preRelease != "": return 1 case v.preRelease != "" && other.preRelease == "": return -1 case v.preRelease == other.preRelease: // includes case where both are "" return 0 } vPR := strings.Split(v.preRelease, ".") oPR := strings.Split(other.preRelease, ".") for i := 0; i < len(vPR) && i < len(oPR); i++ { vNum, err := strconv.ParseUint(vPR[i], 10, 0) if err == nil { oNum, err := strconv.ParseUint(oPR[i], 10, 0) if err == nil { switch { case oNum < vNum: return 1 case oNum > vNum: return -1 default: continue } } } if oPR[i] < vPR[i] { return 1 } else if oPR[i] > vPR[i] { return -1 } } switch { case len(oPR) < len(vPR): return 1 case len(oPR) > len(vPR): return -1 } return 0 } // returns false if array contain any non-zero element func onlyZeros(array []uint) bool { for _, num := range array { if num != 0 { return false } } return true } // AtLeast tests if a version is at least equal to a given minimum version. If both // Versions are Semantic Versions, this will use the Semantic Version comparison // algorithm. Otherwise, it will compare only the numeric components, with non-present // components being considered "0" (ie, "1.4" is equal to "1.4.0"). func (v *Version) AtLeast(min *Version) bool { return v.compareInternal(min) != -1 } // LessThan tests if a version is less than a given version. (It is exactly the opposite // of AtLeast, for situations where asking "is v too old?" makes more sense than asking // "is v new enough?".) func (v *Version) LessThan(other *Version) bool { return v.compareInternal(other) == -1 } // Compare compares v against a version string (which will be parsed as either Semantic // or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if // it is greater than other, or 0 if they are equal. func (v *Version) Compare(other string) (int, error) { ov, err := parse(other, v.semver) if err != nil { return 0, err } return v.compareInternal(ov), nil } golang-k8s-apimachinery-0.29.0/pkg/util/version/version_test.go000066400000000000000000000326221453143165200245320ustar00rootroot00000000000000/* Copyright 2016 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 version import ( "fmt" "reflect" "testing" ) type testItem struct { version string unparsed string equalsPrev bool } func testOne(v *Version, item, prev testItem) error { str := v.String() if item.unparsed == "" { if str != item.version { return fmt.Errorf("bad round-trip: %q -> %q", item.version, str) } } else { if str != item.unparsed { return fmt.Errorf("bad unparse: %q -> %q, expected %q", item.version, str, item.unparsed) } } if prev.version != "" { cmp, err := v.Compare(prev.version) if err != nil { return fmt.Errorf("unexpected parse error: %v", err) } rv, err := parse(prev.version, v.semver) if err != nil { return fmt.Errorf("unexpected parse error: %v", err) } rcmp, err := rv.Compare(item.version) if err != nil { return fmt.Errorf("unexpected parse error: %v", err) } switch { case cmp == -1: return fmt.Errorf("unexpected ordering %q < %q", item.version, prev.version) case cmp == 0 && !item.equalsPrev: return fmt.Errorf("unexpected comparison %q == %q", item.version, prev.version) case cmp == 1 && item.equalsPrev: return fmt.Errorf("unexpected comparison %q != %q", item.version, prev.version) case cmp != -rcmp: return fmt.Errorf("unexpected reverse comparison %q <=> %q %v %v %v %v", item.version, prev.version, cmp, rcmp, v.Components(), rv.Components()) } } return nil } func TestSemanticVersions(t *testing.T) { tests := []testItem{ // This is every version string that appears in the 2.0 semver spec, // sorted in strictly increasing order except as noted. {version: "0.1.0"}, {version: "1.0.0-0.3.7"}, {version: "1.0.0-alpha"}, {version: "1.0.0-alpha+001", equalsPrev: true}, {version: "1.0.0-alpha.1"}, {version: "1.0.0-alpha.beta"}, {version: "1.0.0-beta"}, {version: "1.0.0-beta+exp.sha.5114f85", equalsPrev: true}, {version: "1.0.0-beta.2"}, {version: "1.0.0-beta.11"}, {version: "1.0.0-rc.1"}, {version: "1.0.0-x.7.z.92"}, {version: "1.0.0"}, {version: "1.0.0+20130313144700", equalsPrev: true}, {version: "1.8.0-alpha.3"}, {version: "1.8.0-alpha.3.673+73326ef01d2d7c"}, {version: "1.9.0"}, {version: "1.10.0"}, {version: "1.11.0"}, {version: "2.0.0"}, {version: "2.1.0"}, {version: "2.1.1"}, {version: "42.0.0"}, // We also allow whitespace and "v" prefix {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, {version: "43.0.0-1", unparsed: "43.0.0-1"}, {version: "43.0.0-1 ", unparsed: "43.0.0-1", equalsPrev: true}, {version: "v43.0.0-1", unparsed: "43.0.0-1", equalsPrev: true}, {version: " v43.0.0", unparsed: "43.0.0"}, {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, } var prev testItem for _, item := range tests { v, err := ParseSemantic(item.version) if err != nil { t.Errorf("unexpected parse error: %v", err) continue } err = testOne(v, item, prev) if err != nil { t.Errorf("%v", err) } prev = item } } func TestBadSemanticVersions(t *testing.T) { tests := []string{ // "MUST take the form X.Y.Z" "1", "1.2", "1.2.3.4", ".2.3", "1..3", "1.2.", "", "..", // "where X, Y, and Z are non-negative integers" "-1.2.3", "1.-2.3", "1.2.-3", "1a.2.3", "1.2a.3", "1.2.3a", "a1.2.3", "a.b.c", "1 .2.3", "1. 2.3", // "and MUST NOT contain leading zeroes." "01.2.3", "1.02.3", "1.2.03", // "[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen" "1.2.3-/", // "[pre-release] identifiers MUST NOT be empty" "1.2.3-", "1.2.3-.", "1.2.3-foo.", "1.2.3-.foo", // "Numeric [pre-release] identifiers MUST NOT include leading zeroes" "1.2.3-01", // "[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen" "1.2.3+/", // "[build metadata] identifiers MUST NOT be empty" "1.2.3+", "1.2.3+.", "1.2.3+foo.", "1.2.3+.foo", // whitespace/"v"-prefix checks "v 1.2.3", "vv1.2.3", } for i := range tests { _, err := ParseSemantic(tests[i]) if err == nil { t.Errorf("unexpected success parsing invalid semver %q", tests[i]) } } } func TestGenericVersions(t *testing.T) { tests := []testItem{ // This is all of the strings from TestSemanticVersions, plus some strings // from TestBadSemanticVersions that should parse as generic versions, // plus some additional strings. {version: "0.1.0", unparsed: "0.1.0"}, {version: "1.0.0-0.3.7", unparsed: "1.0.0"}, {version: "1.0.0-alpha", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-alpha+001", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-alpha.1", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-alpha.beta", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.beta", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-beta+exp.sha.5114f85", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.beta.2", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.beta.11", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.rc.1", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-x.7.z.92", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0+20130313144700", unparsed: "1.0.0", equalsPrev: true}, {version: "1.2", unparsed: "1.2"}, {version: "1.2a.3", unparsed: "1.2", equalsPrev: true}, {version: "1.2.3", unparsed: "1.2.3"}, {version: "1.2.3.0", unparsed: "1.2.3.0", equalsPrev: true}, {version: "1.2.3a", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3-foo.", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3-.foo", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3-01", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3+", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3+foo.", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3+.foo", unparsed: "1.2.3", equalsPrev: true}, {version: "1.02.3", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.03", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.003", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3.4", unparsed: "1.2.3.4"}, {version: "1.2.3.4b3", unparsed: "1.2.3.4", equalsPrev: true}, {version: "1.2.3.4.5", unparsed: "1.2.3.4.5"}, {version: "1.9.0", unparsed: "1.9.0"}, {version: "1.9.0.0.0.0.0.0", unparsed: "1.9.0.0.0.0.0.0", equalsPrev: true}, {version: "1.10.0", unparsed: "1.10.0"}, {version: "1.11.0", unparsed: "1.11.0"}, {version: "1.11.0.0.5", unparsed: "1.11.0.0.5"}, {version: "2.0.0", unparsed: "2.0.0"}, {version: "2.1.0", unparsed: "2.1.0"}, {version: "2.1.1", unparsed: "2.1.1"}, {version: "42.0.0", unparsed: "42.0.0"}, {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, {version: "42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, {version: "42.0.0-1 ", unparsed: "42.0.0", equalsPrev: true}, {version: "v42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, {version: " v43.0.0", unparsed: "43.0.0"}, {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, } var prev testItem for _, item := range tests { v, err := ParseGeneric(item.version) if err != nil { t.Errorf("unexpected parse error: %v", err) continue } err = testOne(v, item, prev) if err != nil { t.Errorf("%v", err) } prev = item } } func TestBadGenericVersions(t *testing.T) { tests := []string{ "1", "01.2.3", "-1.2.3", "1.-2.3", ".2.3", "1..3", "1a.2.3", "a1.2.3", "1 .2.3", "1. 2.3", "1.bob", "bob", "v 1.2.3", "vv1.2.3", "", ".", } for i := range tests { _, err := ParseGeneric(tests[i]) if err == nil { t.Errorf("unexpected success parsing invalid version %q", tests[i]) } } } func TestComponents(t *testing.T) { var tests = []struct { version string semver bool expectedComponents []uint expectedMajor uint expectedMinor uint expectedPatch uint expectedPreRelease string expectedBuildMetadata string }{ { version: "1.0.2", semver: true, expectedComponents: []uint{1, 0, 2}, expectedMajor: 1, expectedMinor: 0, expectedPatch: 2, }, { version: "1.0.2-alpha+001", semver: true, expectedComponents: []uint{1, 0, 2}, expectedMajor: 1, expectedMinor: 0, expectedPatch: 2, expectedPreRelease: "alpha", expectedBuildMetadata: "001", }, { version: "1.2", semver: false, expectedComponents: []uint{1, 2}, expectedMajor: 1, expectedMinor: 2, }, { version: "1.0.2-beta+exp.sha.5114f85", semver: true, expectedComponents: []uint{1, 0, 2}, expectedMajor: 1, expectedMinor: 0, expectedPatch: 2, expectedPreRelease: "beta", expectedBuildMetadata: "exp.sha.5114f85", }, } for _, test := range tests { version, _ := parse(test.version, test.semver) if !reflect.DeepEqual(test.expectedComponents, version.Components()) { t.Error("parse returned un'expected components") } if test.expectedMajor != version.Major() { t.Errorf("parse returned version.Major %d, expected %d", test.expectedMajor, version.Major()) } if test.expectedMinor != version.Minor() { t.Errorf("parse returned version.Minor %d, expected %d", test.expectedMinor, version.Minor()) } if test.expectedPatch != version.Patch() { t.Errorf("parse returned version.Patch %d, expected %d", test.expectedPatch, version.Patch()) } if test.expectedPreRelease != version.PreRelease() { t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease()) } if test.expectedBuildMetadata != version.BuildMetadata() { t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata()) } } } func TestHighestSupportedVersion(t *testing.T) { testCases := []struct { versions []string expectedHighestSupportedVersion string shouldFail bool }{ { versions: []string{"v1.0.0"}, expectedHighestSupportedVersion: "1.0.0", shouldFail: false, }, { versions: []string{"0.3.0"}, shouldFail: true, }, { versions: []string{"0.2.0"}, shouldFail: true, }, { versions: []string{"1.0.0"}, expectedHighestSupportedVersion: "1.0.0", shouldFail: false, }, { versions: []string{"v0.3.0"}, shouldFail: true, }, { versions: []string{"v0.2.0"}, shouldFail: true, }, { versions: []string{"0.2.0", "v0.3.0"}, shouldFail: true, }, { versions: []string{"0.2.0", "v1.0.0"}, expectedHighestSupportedVersion: "1.0.0", shouldFail: false, }, { versions: []string{"0.2.0", "v1.2.3"}, expectedHighestSupportedVersion: "1.2.3", shouldFail: false, }, { versions: []string{"v1.2.3", "v0.3.0"}, expectedHighestSupportedVersion: "1.2.3", shouldFail: false, }, { versions: []string{"v1.2.3", "v0.3.0", "2.0.1"}, expectedHighestSupportedVersion: "1.2.3", shouldFail: false, }, { versions: []string{"v1.2.3", "4.9.12", "v0.3.0", "2.0.1"}, expectedHighestSupportedVersion: "1.2.3", shouldFail: false, }, { versions: []string{"4.9.12", "2.0.1"}, expectedHighestSupportedVersion: "", shouldFail: true, }, { versions: []string{"v1.2.3", "boo", "v0.3.0", "2.0.1"}, expectedHighestSupportedVersion: "1.2.3", shouldFail: false, }, { versions: []string{}, expectedHighestSupportedVersion: "", shouldFail: true, }, { versions: []string{"var", "boo", "foo"}, expectedHighestSupportedVersion: "", shouldFail: true, }, } for _, tc := range testCases { // Arrange & Act actual, err := HighestSupportedVersion(tc.versions) // Assert if tc.shouldFail && err == nil { t.Fatalf("expecting highestSupportedVersion to fail, but got nil error for testcase: %#v", tc) } if !tc.shouldFail && err != nil { t.Fatalf("unexpected error during ValidatePlugin for testcase: %#v\r\n err:%v", tc, err) } if tc.expectedHighestSupportedVersion != "" { result, err := actual.Compare(tc.expectedHighestSupportedVersion) if err != nil { t.Fatalf("comparison failed with %v for testcase %#v", err, tc) } if result != 0 { t.Fatalf("expectedHighestSupportedVersion %v, but got %v for tc: %#v", tc.expectedHighestSupportedVersion, actual, tc) } } } } golang-k8s-apimachinery-0.29.0/pkg/util/wait/000077500000000000000000000000001453143165200207315ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/wait/backoff.go000066400000000000000000000410211453143165200226510ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "math" "sync" "time" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/utils/clock" ) // Backoff holds parameters applied to a Backoff function. type Backoff struct { // The initial duration. Duration time.Duration // Duration is multiplied by factor each iteration, if factor is not zero // and the limits imposed by Steps and Cap have not been reached. // Should not be negative. // The jitter does not contribute to the updates to the duration parameter. Factor float64 // The sleep at each iteration is the duration plus an additional // amount chosen uniformly at random from the interval between // zero and `jitter*duration`. Jitter float64 // The remaining number of iterations in which the duration // parameter may change (but progress can be stopped earlier by // hitting the cap). If not positive, the duration is not // changed. Used for exponential backoff in combination with // Factor and Cap. Steps int // A limit on revised values of the duration parameter. If a // multiplication by the factor parameter would make the duration // exceed the cap then the duration is set to the cap and the // steps parameter is set to zero. Cap time.Duration } // Step returns an amount of time to sleep determined by the original // Duration and Jitter. The backoff is mutated to update its Steps and // Duration. A nil Backoff always has a zero-duration step. func (b *Backoff) Step() time.Duration { if b == nil { return 0 } var nextDuration time.Duration nextDuration, b.Duration, b.Steps = delay(b.Steps, b.Duration, b.Cap, b.Factor, b.Jitter) return nextDuration } // DelayFunc returns a function that will compute the next interval to // wait given the arguments in b. It does not mutate the original backoff // but the function is safe to use only from a single goroutine. func (b Backoff) DelayFunc() DelayFunc { steps := b.Steps duration := b.Duration cap := b.Cap factor := b.Factor jitter := b.Jitter return func() time.Duration { var nextDuration time.Duration // jitter is applied per step and is not cumulative over multiple steps nextDuration, duration, steps = delay(steps, duration, cap, factor, jitter) return nextDuration } } // Timer returns a timer implementation appropriate to this backoff's parameters // for use with wait functions. func (b Backoff) Timer() Timer { if b.Steps > 1 || b.Jitter != 0 { return &variableTimer{new: internalClock.NewTimer, fn: b.DelayFunc()} } if b.Duration > 0 { return &fixedTimer{new: internalClock.NewTicker, interval: b.Duration} } return newNoopTimer() } // delay implements the core delay algorithm used in this package. func delay(steps int, duration, cap time.Duration, factor, jitter float64) (_ time.Duration, next time.Duration, nextSteps int) { // when steps is non-positive, do not alter the base duration if steps < 1 { if jitter > 0 { return Jitter(duration, jitter), duration, 0 } return duration, duration, 0 } steps-- // calculate the next step's interval if factor != 0 { next = time.Duration(float64(duration) * factor) if cap > 0 && next > cap { next = cap steps = 0 } } else { next = duration } // add jitter for this step if jitter > 0 { duration = Jitter(duration, jitter) } return duration, next, steps } // DelayWithReset returns a DelayFunc that will return the appropriate next interval to // wait. Every resetInterval the backoff parameters are reset to their initial state. // This method is safe to invoke from multiple goroutines, but all calls will advance // the backoff state when Factor is set. If Factor is zero, this method is the same as // invoking b.DelayFunc() since Steps has no impact without Factor. If resetInterval is // zero no backoff will be performed as the same calling DelayFunc with a zero factor // and steps. func (b Backoff) DelayWithReset(c clock.Clock, resetInterval time.Duration) DelayFunc { if b.Factor <= 0 { return b.DelayFunc() } if resetInterval <= 0 { b.Steps = 0 b.Factor = 0 return b.DelayFunc() } return (&backoffManager{ backoff: b, initialBackoff: b, resetInterval: resetInterval, clock: c, lastStart: c.Now(), timer: nil, }).Step } // Until loops until stop channel is closed, running f every period. // // Until is syntactic sugar on top of JitterUntil with zero jitter factor and // with sliding = true (which means the timer for period starts after the f // completes). func Until(f func(), period time.Duration, stopCh <-chan struct{}) { JitterUntil(f, period, 0.0, true, stopCh) } // UntilWithContext loops until context is done, running f every period. // // UntilWithContext is syntactic sugar on top of JitterUntilWithContext // with zero jitter factor and with sliding = true (which means the timer // for period starts after the f completes). func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { JitterUntilWithContext(ctx, f, period, 0.0, true) } // NonSlidingUntil loops until stop channel is closed, running f every // period. // // NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter // factor, with sliding = false (meaning the timer for period starts at the same // time as the function starts). func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) { JitterUntil(f, period, 0.0, false, stopCh) } // NonSlidingUntilWithContext loops until context is done, running f every // period. // // NonSlidingUntilWithContext is syntactic sugar on top of JitterUntilWithContext // with zero jitter factor, with sliding = false (meaning the timer for period // starts at the same time as the function starts). func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { JitterUntilWithContext(ctx, f, period, 0.0, false) } // JitterUntil loops until stop channel is closed, running f every period. // // If jitterFactor is positive, the period is jittered before every run of f. // If jitterFactor is not positive, the period is unchanged and not jittered. // // If sliding is true, the period is computed after f runs. If it is false then // period includes the runtime for f. // // Close stopCh to stop. f may not be invoked if stop channel is already // closed. Pass NeverStop to if you don't want it stop. func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) { BackoffUntil(f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding, stopCh) } // BackoffUntil loops until stop channel is closed, run f every duration given by BackoffManager. // // If sliding is true, the period is computed after f runs. If it is false then // period includes the runtime for f. func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) { var t clock.Timer for { select { case <-stopCh: return default: } if !sliding { t = backoff.Backoff() } func() { defer runtime.HandleCrash() f() }() if sliding { t = backoff.Backoff() } // NOTE: b/c there is no priority selection in golang // it is possible for this to race, meaning we could // trigger t.C and stopCh, and t.C select falls through. // In order to mitigate we re-check stopCh at the beginning // of every loop to prevent extra executions of f(). select { case <-stopCh: if !t.Stop() { <-t.C() } return case <-t.C(): } } } // JitterUntilWithContext loops until context is done, running f every period. // // If jitterFactor is positive, the period is jittered before every run of f. // If jitterFactor is not positive, the period is unchanged and not jittered. // // If sliding is true, the period is computed after f runs. If it is false then // period includes the runtime for f. // // Cancel context to stop. f may not be invoked if context is already expired. func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) { JitterUntil(func() { f(ctx) }, period, jitterFactor, sliding, ctx.Done()) } // backoffManager provides simple backoff behavior in a threadsafe manner to a caller. type backoffManager struct { backoff Backoff initialBackoff Backoff resetInterval time.Duration clock clock.Clock lock sync.Mutex lastStart time.Time timer clock.Timer } // Step returns the expected next duration to wait. func (b *backoffManager) Step() time.Duration { b.lock.Lock() defer b.lock.Unlock() switch { case b.resetInterval == 0: b.backoff = b.initialBackoff case b.clock.Now().Sub(b.lastStart) > b.resetInterval: b.backoff = b.initialBackoff b.lastStart = b.clock.Now() } return b.backoff.Step() } // Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer // for exponential backoff. The returned timer must be drained before calling Backoff() the second // time. func (b *backoffManager) Backoff() clock.Timer { b.lock.Lock() defer b.lock.Unlock() if b.timer == nil { b.timer = b.clock.NewTimer(b.Step()) } else { b.timer.Reset(b.Step()) } return b.timer } // Timer returns a new Timer instance that shares the clock and the reset behavior with all other // timers. func (b *backoffManager) Timer() Timer { return DelayFunc(b.Step).Timer(b.clock) } // BackoffManager manages backoff with a particular scheme based on its underlying implementation. type BackoffManager interface { // Backoff returns a shared clock.Timer that is Reset on every invocation. This method is not // safe for use from multiple threads. It returns a timer for backoff, and caller shall backoff // until Timer.C() drains. If the second Backoff() is called before the timer from the first // Backoff() call finishes, the first timer will NOT be drained and result in undetermined // behavior. Backoff() clock.Timer } // Deprecated: Will be removed when the legacy polling functions are removed. type exponentialBackoffManagerImpl struct { backoff *Backoff backoffTimer clock.Timer lastBackoffStart time.Time initialBackoff time.Duration backoffResetDuration time.Duration clock clock.Clock } // NewExponentialBackoffManager returns a manager for managing exponential backoff. Each backoff is jittered and // backoff will not exceed the given max. If the backoff is not called within resetDuration, the backoff is reset. // This backoff manager is used to reduce load during upstream unhealthiness. // // Deprecated: Will be removed when the legacy Poll methods are removed. Callers should construct a // Backoff struct, use DelayWithReset() to get a DelayFunc that periodically resets itself, and then // invoke Timer() when calling wait.BackoffUntil. // // Instead of: // // bm := wait.NewExponentialBackoffManager(init, max, reset, factor, jitter, clock) // ... // wait.BackoffUntil(..., bm.Backoff, ...) // // Use: // // delayFn := wait.Backoff{ // Duration: init, // Cap: max, // Steps: int(math.Ceil(float64(max) / float64(init))), // now a required argument // Factor: factor, // Jitter: jitter, // }.DelayWithReset(reset, clock) // wait.BackoffUntil(..., delayFn.Timer(), ...) func NewExponentialBackoffManager(initBackoff, maxBackoff, resetDuration time.Duration, backoffFactor, jitter float64, c clock.Clock) BackoffManager { return &exponentialBackoffManagerImpl{ backoff: &Backoff{ Duration: initBackoff, Factor: backoffFactor, Jitter: jitter, // the current impl of wait.Backoff returns Backoff.Duration once steps are used up, which is not // what we ideally need here, we set it to max int and assume we will never use up the steps Steps: math.MaxInt32, Cap: maxBackoff, }, backoffTimer: nil, initialBackoff: initBackoff, lastBackoffStart: c.Now(), backoffResetDuration: resetDuration, clock: c, } } func (b *exponentialBackoffManagerImpl) getNextBackoff() time.Duration { if b.clock.Now().Sub(b.lastBackoffStart) > b.backoffResetDuration { b.backoff.Steps = math.MaxInt32 b.backoff.Duration = b.initialBackoff } b.lastBackoffStart = b.clock.Now() return b.backoff.Step() } // Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for exponential backoff. // The returned timer must be drained before calling Backoff() the second time func (b *exponentialBackoffManagerImpl) Backoff() clock.Timer { if b.backoffTimer == nil { b.backoffTimer = b.clock.NewTimer(b.getNextBackoff()) } else { b.backoffTimer.Reset(b.getNextBackoff()) } return b.backoffTimer } // Deprecated: Will be removed when the legacy polling functions are removed. type jitteredBackoffManagerImpl struct { clock clock.Clock duration time.Duration jitter float64 backoffTimer clock.Timer } // NewJitteredBackoffManager returns a BackoffManager that backoffs with given duration plus given jitter. If the jitter // is negative, backoff will not be jittered. // // Deprecated: Will be removed when the legacy Poll methods are removed. Callers should construct a // Backoff struct and invoke Timer() when calling wait.BackoffUntil. // // Instead of: // // bm := wait.NewJitteredBackoffManager(duration, jitter, clock) // ... // wait.BackoffUntil(..., bm.Backoff, ...) // // Use: // // wait.BackoffUntil(..., wait.Backoff{Duration: duration, Jitter: jitter}.Timer(), ...) func NewJitteredBackoffManager(duration time.Duration, jitter float64, c clock.Clock) BackoffManager { return &jitteredBackoffManagerImpl{ clock: c, duration: duration, jitter: jitter, backoffTimer: nil, } } func (j *jitteredBackoffManagerImpl) getNextBackoff() time.Duration { jitteredPeriod := j.duration if j.jitter > 0.0 { jitteredPeriod = Jitter(j.duration, j.jitter) } return jitteredPeriod } // Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for jittered backoff. // The returned timer must be drained before calling Backoff() the second time func (j *jitteredBackoffManagerImpl) Backoff() clock.Timer { backoff := j.getNextBackoff() if j.backoffTimer == nil { j.backoffTimer = j.clock.NewTimer(backoff) } else { j.backoffTimer.Reset(backoff) } return j.backoffTimer } // ExponentialBackoff repeats a condition check with exponential backoff. // // It repeatedly checks the condition and then sleeps, using `backoff.Step()` // to determine the length of the sleep and adjust Duration and Steps. // Stops and returns as soon as: // 1. the condition check returns true or an error, // 2. `backoff.Steps` checks of the condition have been done, or // 3. a sleep truncated by the cap on duration has been completed. // In case (1) the returned error is what the condition function returned. // In all other cases, ErrWaitTimeout is returned. // // Since backoffs are often subject to cancellation, we recommend using // ExponentialBackoffWithContext and passing a context to the method. func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error { for backoff.Steps > 0 { if ok, err := runConditionWithCrashProtection(condition); err != nil || ok { return err } if backoff.Steps == 1 { break } time.Sleep(backoff.Step()) } return ErrWaitTimeout } // ExponentialBackoffWithContext repeats a condition check with exponential backoff. // It immediately returns an error if the condition returns an error, the context is cancelled // or hits the deadline, or if the maximum attempts defined in backoff is exceeded (ErrWaitTimeout). // If an error is returned by the condition the backoff stops immediately. The condition will // never be invoked more than backoff.Steps times. func ExponentialBackoffWithContext(ctx context.Context, backoff Backoff, condition ConditionWithContextFunc) error { for backoff.Steps > 0 { select { case <-ctx.Done(): return ctx.Err() default: } if ok, err := runConditionWithCrashProtectionWithContext(ctx, condition); err != nil || ok { return err } if backoff.Steps == 1 { break } waitBeforeRetry := backoff.Step() select { case <-ctx.Done(): return ctx.Err() case <-time.After(waitBeforeRetry): } } return ErrWaitTimeout } golang-k8s-apimachinery-0.29.0/pkg/util/wait/delay.go000066400000000000000000000032721453143165200223620ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "sync" "time" "k8s.io/utils/clock" ) // DelayFunc returns the next time interval to wait. type DelayFunc func() time.Duration // Timer takes an arbitrary delay function and returns a timer that can handle arbitrary interval changes. // Use Backoff{...}.Timer() for simple delays and more efficient timers. func (fn DelayFunc) Timer(c clock.Clock) Timer { return &variableTimer{fn: fn, new: c.NewTimer} } // Until takes an arbitrary delay function and runs until cancelled or the condition indicates exit. This // offers all of the functionality of the methods in this package. func (fn DelayFunc) Until(ctx context.Context, immediate, sliding bool, condition ConditionWithContextFunc) error { return loopConditionUntilContext(ctx, &variableTimer{fn: fn, new: internalClock.NewTimer}, immediate, sliding, condition) } // Concurrent returns a version of this DelayFunc that is safe for use by multiple goroutines that // wish to share a single delay timer. func (fn DelayFunc) Concurrent() DelayFunc { var lock sync.Mutex return func() time.Duration { lock.Lock() defer lock.Unlock() return fn() } } golang-k8s-apimachinery-0.29.0/pkg/util/wait/doc.go000066400000000000000000000013151453143165200220250ustar00rootroot00000000000000/* 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 wait provides tools for polling or listening for changes // to a condition. package wait // import "k8s.io/apimachinery/pkg/util/wait" golang-k8s-apimachinery-0.29.0/pkg/util/wait/error.go000066400000000000000000000057371453143165200224250ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "errors" ) // ErrWaitTimeout is returned when the condition was not satisfied in time. // // Deprecated: This type will be made private in favor of Interrupted() // for checking errors or ErrorInterrupted(err) for returning a wrapped error. var ErrWaitTimeout = ErrorInterrupted(errors.New("timed out waiting for the condition")) // Interrupted returns true if the error indicates a Poll, ExponentialBackoff, or // Until loop exited for any reason besides the condition returning true or an // error. A loop is considered interrupted if the calling context is cancelled, // the context reaches its deadline, or a backoff reaches its maximum allowed // steps. // // Callers should use this method instead of comparing the error value directly to // ErrWaitTimeout, as methods that cancel a context may not return that error. // // Instead of: // // err := wait.Poll(...) // if err == wait.ErrWaitTimeout { // log.Infof("Wait for operation exceeded") // } else ... // // Use: // // err := wait.Poll(...) // if wait.Interrupted(err) { // log.Infof("Wait for operation exceeded") // } else ... func Interrupted(err error) bool { switch { case errors.Is(err, errWaitTimeout), errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded): return true default: return false } } // errInterrupted type errInterrupted struct { cause error } // ErrorInterrupted returns an error that indicates the wait was ended // early for a given reason. If no cause is provided a generic error // will be used but callers are encouraged to provide a real cause for // clarity in debugging. func ErrorInterrupted(cause error) error { switch cause.(type) { case errInterrupted: // no need to wrap twice since errInterrupted is only needed // once in a chain return cause default: return errInterrupted{cause} } } // errWaitTimeout is the private version of the previous ErrWaitTimeout // and is private to prevent direct comparison. Use ErrorInterrupted(err) // to get an error that will return true for Interrupted(err). var errWaitTimeout = errInterrupted{} func (e errInterrupted) Unwrap() error { return e.cause } func (e errInterrupted) Is(target error) bool { return target == errWaitTimeout } func (e errInterrupted) Error() string { if e.cause == nil { // returns the same error message as historical behavior return "timed out waiting for the condition" } return e.cause.Error() } golang-k8s-apimachinery-0.29.0/pkg/util/wait/error_test.go000066400000000000000000000054661453143165200234630ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "errors" "fmt" "testing" ) type errWrapper struct { wrapped error } func (w errWrapper) Unwrap() error { return w.wrapped } func (w errWrapper) Error() string { return fmt.Sprintf("wrapped: %v", w.wrapped) } type errNotWrapper struct { wrapped error } func (w errNotWrapper) Error() string { return fmt.Sprintf("wrapped: %v", w.wrapped) } func TestInterrupted(t *testing.T) { tests := []struct { name string err error want bool }{ { err: ErrWaitTimeout, want: true, }, { err: context.Canceled, want: true, }, { err: context.DeadlineExceeded, want: true, }, { err: errWrapper{ErrWaitTimeout}, want: true, }, { err: errWrapper{context.Canceled}, want: true, }, { err: errWrapper{context.DeadlineExceeded}, want: true, }, { err: ErrorInterrupted(nil), want: true, }, { err: ErrorInterrupted(errors.New("unknown")), want: true, }, { err: ErrorInterrupted(context.Canceled), want: true, }, { err: ErrorInterrupted(ErrWaitTimeout), want: true, }, { err: nil, }, { err: errors.New("not a cancellation"), }, { err: errNotWrapper{ErrWaitTimeout}, }, { err: errNotWrapper{context.Canceled}, }, { err: errNotWrapper{context.DeadlineExceeded}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Interrupted(tt.err); got != tt.want { t.Errorf("Interrupted() = %v, want %v", got, tt.want) } }) } } func TestErrorInterrupted(t *testing.T) { internalErr := errInterrupted{} if ErrorInterrupted(internalErr) != internalErr { t.Fatalf("error should not be wrapped twice") } internalErr = errInterrupted{errInterrupted{}} if ErrorInterrupted(internalErr) != internalErr { t.Fatalf("object should be identical") } in := errors.New("test") actual, expected := ErrorInterrupted(in), (errInterrupted{in}) if actual != expected { t.Fatalf("did not wrap error") } if !errors.Is(actual, errWaitTimeout) { t.Fatalf("does not obey errors.Is contract") } if actual.Error() != in.Error() { t.Fatalf("unexpected error output") } if !Interrupted(actual) { t.Fatalf("is not Interrupted") } if Interrupted(in) { t.Fatalf("should not be Interrupted") } } golang-k8s-apimachinery-0.29.0/pkg/util/wait/loop.go000066400000000000000000000054741453143165200222430ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "time" "k8s.io/apimachinery/pkg/util/runtime" ) // loopConditionUntilContext executes the provided condition at intervals defined by // the provided timer until the provided context is cancelled, the condition returns // true, or the condition returns an error. If sliding is true, the period is computed // after condition runs. If it is false then period includes the runtime for condition. // If immediate is false the first delay happens before any call to condition, if // immediate is true the condition will be invoked before waiting and guarantees that // the condition is invoked at least once, regardless of whether the context has been // cancelled. The returned error is the error returned by the last condition or the // context error if the context was terminated. // // This is the common loop construct for all polling in the wait package. func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding bool, condition ConditionWithContextFunc) error { defer t.Stop() var timeCh <-chan time.Time doneCh := ctx.Done() if !sliding { timeCh = t.C() } // if immediate is true the condition is // guaranteed to be executed at least once, // if we haven't requested immediate execution, delay once if immediate { if ok, err := func() (bool, error) { defer runtime.HandleCrash() return condition(ctx) }(); err != nil || ok { return err } } if sliding { timeCh = t.C() } for { // Wait for either the context to be cancelled or the next invocation be called select { case <-doneCh: return ctx.Err() case <-timeCh: } // IMPORTANT: Because there is no channel priority selection in golang // it is possible for very short timers to "win" the race in the previous select // repeatedly even when the context has been canceled. We therefore must // explicitly check for context cancellation on every loop and exit if true to // guarantee that we don't invoke condition more than once after context has // been cancelled. if err := ctx.Err(); err != nil { return err } if !sliding { t.Next() } if ok, err := func() (bool, error) { defer runtime.HandleCrash() return condition(ctx) }(); err != nil || ok { return err } if sliding { t.Next() } } } golang-k8s-apimachinery-0.29.0/pkg/util/wait/loop_test.go000066400000000000000000000375341453143165200233040ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "errors" "fmt" "reflect" "testing" "time" "github.com/google/go-cmp/cmp" "k8s.io/utils/clock" testingclock "k8s.io/utils/clock/testing" ) func timerWithClock(t Timer, c clock.WithTicker) Timer { switch t := t.(type) { case *fixedTimer: t.new = c.NewTicker case *variableTimer: t.new = c.NewTimer default: panic("unrecognized timer type, cannot inject clock") } return t } func Test_loopConditionWithContextImmediateDelay(t *testing.T) { fakeClock := testingclock.NewFakeClock(time.Time{}) backoff := Backoff{Duration: time.Second} ctx, cancel := context.WithCancel(context.Background()) defer cancel() expectedError := errors.New("Expected error") var attempt int f := ConditionFunc(func() (bool, error) { attempt++ return false, expectedError }) doneCh := make(chan struct{}) go func() { defer close(doneCh) if err := loopConditionUntilContext(ctx, timerWithClock(backoff.Timer(), fakeClock), false, true, f.WithContext()); err == nil || err != expectedError { t.Errorf("unexpected error: %v", err) } }() for !fakeClock.HasWaiters() { time.Sleep(time.Microsecond) } fakeClock.Step(time.Second - time.Millisecond) if attempt != 0 { t.Fatalf("should still be waiting for condition") } fakeClock.Step(2 * time.Millisecond) select { case <-doneCh: case <-time.After(time.Second): t.Fatalf("should have exited after a single loop") } if attempt != 1 { t.Fatalf("expected attempt") } } func Test_loopConditionUntilContext_semantic(t *testing.T) { defaultCallback := func(_ int) (bool, error) { return false, nil } conditionErr := errors.New("condition failed") tests := []struct { name string immediate bool sliding bool context func() (context.Context, context.CancelFunc) callback func(calls int) (bool, error) cancelContextAfter int attemptsExpected int errExpected error timer Timer }{ { name: "condition successful is only one attempt", callback: func(attempts int) (bool, error) { return true, nil }, attemptsExpected: 1, }, { name: "delayed condition successful causes return and attempts", callback: func(attempts int) (bool, error) { return attempts > 1, nil }, attemptsExpected: 2, }, { name: "delayed condition successful causes return and attempts many times", callback: func(attempts int) (bool, error) { return attempts >= 100, nil }, attemptsExpected: 100, }, { name: "condition returns error even if ok is true", callback: func(_ int) (bool, error) { return true, conditionErr }, attemptsExpected: 1, errExpected: conditionErr, }, { name: "condition exits after an error", callback: func(_ int) (bool, error) { return false, conditionErr }, attemptsExpected: 1, errExpected: conditionErr, }, { name: "context already canceled no attempts expected", context: cancelledContext, callback: defaultCallback, attemptsExpected: 0, errExpected: context.Canceled, }, { name: "context already canceled condition success and immediate 1 attempt expected", context: cancelledContext, callback: func(_ int) (bool, error) { return true, nil }, immediate: true, attemptsExpected: 1, }, { name: "context already canceled condition fail and immediate 1 attempt expected", context: cancelledContext, callback: func(_ int) (bool, error) { return false, conditionErr }, immediate: true, attemptsExpected: 1, errExpected: conditionErr, }, { name: "context already canceled and immediate 1 attempt expected", context: cancelledContext, callback: defaultCallback, immediate: true, attemptsExpected: 1, errExpected: context.Canceled, }, { name: "context cancelled after 5 attempts", context: defaultContext, callback: defaultCallback, cancelContextAfter: 5, attemptsExpected: 5, errExpected: context.Canceled, }, { name: "context cancelled and immediate after 5 attempts", context: defaultContext, callback: defaultCallback, immediate: true, cancelContextAfter: 5, attemptsExpected: 5, errExpected: context.Canceled, }, { name: "context at deadline and immediate 1 attempt expected", context: deadlinedContext, callback: defaultCallback, immediate: true, attemptsExpected: 1, errExpected: context.DeadlineExceeded, }, { name: "context at deadline no attempts expected", context: deadlinedContext, callback: defaultCallback, attemptsExpected: 0, errExpected: context.DeadlineExceeded, }, { name: "context canceled before the second execution and immediate", immediate: true, context: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Second) }, callback: func(attempts int) (bool, error) { return false, nil }, attemptsExpected: 1, errExpected: context.DeadlineExceeded, timer: Backoff{Duration: 2 * time.Second}.Timer(), }, { name: "immediate and long duration of condition and sliding false", immediate: true, sliding: false, context: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Second) }, callback: func(attempts int) (bool, error) { if attempts >= 4 { return true, nil } time.Sleep(time.Second / 5) return false, nil }, attemptsExpected: 4, timer: Backoff{Duration: time.Second / 5, Jitter: 0.001}.Timer(), }, { name: "immediate and long duration of condition and sliding true", immediate: true, sliding: true, context: func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Second) }, callback: func(attempts int) (bool, error) { if attempts >= 4 { return true, nil } time.Sleep(time.Second / 5) return false, nil }, errExpected: context.DeadlineExceeded, attemptsExpected: 3, timer: Backoff{Duration: time.Second / 5, Jitter: 0.001}.Timer(), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() timer := test.timer if timer == nil { timer = Backoff{Duration: time.Microsecond}.Timer() } attempts := 0 err := loopConditionUntilContext(ctx, timer, test.immediate, test.sliding, func(_ context.Context) (bool, error) { attempts++ defer func() { if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { cancel() } }() return test.callback(attempts) }) if test.errExpected != err { t.Errorf("expected error: %v but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) } }) } } type timerWrapper struct { timer clock.Timer resets []time.Duration onReset func(d time.Duration) } func (w *timerWrapper) C() <-chan time.Time { return w.timer.C() } func (w *timerWrapper) Stop() bool { return w.timer.Stop() } func (w *timerWrapper) Reset(d time.Duration) bool { w.resets = append(w.resets, d) b := w.timer.Reset(d) if w.onReset != nil { w.onReset(d) } return b } func Test_loopConditionUntilContext_timings(t *testing.T) { // Verify that timings returned by the delay func are passed to the timer, and that // the timer advancing is enough to drive the state machine. Not a deep verification // of the behavior of the loop, but tests that we drive the scenario to completion. tests := []struct { name string delayFn DelayFunc immediate bool sliding bool context func() (context.Context, context.CancelFunc) callback func(calls int, lastInterval time.Duration) (bool, error) cancelContextAfter int attemptsExpected int errExpected error expectedIntervals func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) }{ { name: "condition success", delayFn: Backoff{Duration: time.Second, Steps: 2, Factor: 2.0, Jitter: 0}.DelayFunc(), callback: func(attempts int, _ time.Duration) (bool, error) { return true, nil }, attemptsExpected: 1, expectedIntervals: func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) { if reflect.DeepEqual(delays, []time.Duration{time.Second, 2 * time.Second}) { return } if reflect.DeepEqual(delaysRequested, []time.Duration{time.Second}) { return } }, }, { name: "condition success and immediate", immediate: true, delayFn: Backoff{Duration: time.Second, Steps: 2, Factor: 2.0, Jitter: 0}.DelayFunc(), callback: func(attempts int, _ time.Duration) (bool, error) { return true, nil }, attemptsExpected: 1, expectedIntervals: func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) { if reflect.DeepEqual(delays, []time.Duration{time.Second}) { return } if reflect.DeepEqual(delaysRequested, []time.Duration{}) { return } }, }, { name: "condition success and sliding", sliding: true, delayFn: Backoff{Duration: time.Second, Steps: 2, Factor: 2.0, Jitter: 0}.DelayFunc(), callback: func(attempts int, _ time.Duration) (bool, error) { return true, nil }, attemptsExpected: 1, expectedIntervals: func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) { if reflect.DeepEqual(delays, []time.Duration{time.Second}) { return } if !reflect.DeepEqual(delays, delaysRequested) { t.Fatalf("sliding non-immediate should have equal delays: %v", cmp.Diff(delays, delaysRequested)) } }, }, } for _, test := range tests { t.Run(fmt.Sprintf("%s/sliding=%t/immediate=%t", test.name, test.sliding, test.immediate), func(t *testing.T) { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() fakeClock := &testingclock.FakeClock{} var fakeTimers []*timerWrapper timerFn := func(d time.Duration) clock.Timer { t := fakeClock.NewTimer(d) fakeClock.Step(d + 1) w := &timerWrapper{timer: t, resets: []time.Duration{d}, onReset: func(d time.Duration) { fakeClock.Step(d + 1) }} fakeTimers = append(fakeTimers, w) return w } delayFn := test.delayFn if delayFn == nil { delayFn = Backoff{Duration: time.Microsecond}.DelayFunc() } var delays []time.Duration wrappedDelayFn := func() time.Duration { d := delayFn() delays = append(delays, d) return d } timer := &variableTimer{fn: wrappedDelayFn, new: timerFn} attempts := 0 err := loopConditionUntilContext(ctx, timer, test.immediate, test.sliding, func(_ context.Context) (bool, error) { attempts++ defer func() { if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { cancel() } }() lastInterval := time.Duration(-1) if len(delays) > 0 { lastInterval = delays[len(delays)-1] } return test.callback(attempts, lastInterval) }) if test.errExpected != err { t.Errorf("expected error: %v but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) } switch len(fakeTimers) { case 0: test.expectedIntervals(t, delays, nil) case 1: test.expectedIntervals(t, delays, fakeTimers[0].resets) default: t.Fatalf("expected zero or one timers: %#v", fakeTimers) } }) } } // Test_loopConditionUntilContext_timings runs actual timing loops and calculates the delta. This // test depends on high precision wakeups which depends on low CPU contention so it is not a // candidate to run during normal unit test execution (nor is it a benchmark or example). Instead, // it can be run manually if there is a scenario where we suspect the timings are off and other // tests haven't caught it. A final sanity test that would have to be run serially in isolation. func Test_loopConditionUntilContext_Elapsed(t *testing.T) { const maxAttempts = 10 // TODO: this may be too aggressive, but the overhead should be minor const estimatedLoopOverhead = time.Millisecond // estimate how long this delay can be intervalMax := func(backoff Backoff) time.Duration { d := backoff.Duration if backoff.Jitter > 0 { d += time.Duration(backoff.Jitter * float64(d)) } return d } // estimate how short this delay can be intervalMin := func(backoff Backoff) time.Duration { d := backoff.Duration return d } // Because timing is dependent other factors in test environments, such as // whether the OS or go runtime scheduler wake the timers, excess duration // is logged by default and can be converted to a fatal error for testing. // fail := t.Fatalf fail := t.Logf for _, test := range []struct { name string backoff Backoff t reflect.Type }{ {name: "variable timer with jitter", backoff: Backoff{Duration: time.Millisecond, Jitter: 1.0}, t: reflect.TypeOf(&variableTimer{})}, {name: "fixed timer", backoff: Backoff{Duration: time.Millisecond}, t: reflect.TypeOf(&fixedTimer{})}, {name: "no-op timer", backoff: Backoff{}, t: reflect.TypeOf(noopTimer{})}, } { t.Run(test.name, func(t *testing.T) { var attempts int start := time.Now() timer := test.backoff.Timer() if test.t != reflect.ValueOf(timer).Type() { t.Fatalf("unexpected timer type %T: expected %v", timer, test.t) } if err := loopConditionUntilContext(context.Background(), timer, false, false, func(_ context.Context) (bool, error) { attempts++ if attempts > maxAttempts { t.Fatalf("should not reach %d attempts", maxAttempts+1) } return attempts >= maxAttempts, nil }); err != nil { t.Fatal(err) } duration := time.Since(start) if min := maxAttempts * intervalMin(test.backoff); duration < min { fail("elapsed duration %v < expected min duration %v", duration, min) } if max := maxAttempts * (intervalMax(test.backoff) + estimatedLoopOverhead); duration > max { fail("elapsed duration %v > expected max duration %v", duration, max) } }) } } func Benchmark_loopConditionUntilContext_ZeroDuration(b *testing.B) { ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { attempts := 0 if err := loopConditionUntilContext(ctx, Backoff{Duration: 0}.Timer(), true, false, func(_ context.Context) (bool, error) { attempts++ return attempts >= 100, nil }); err != nil { b.Fatalf("unexpected err: %v", err) } } } func Benchmark_loopConditionUntilContext_ShortDuration(b *testing.B) { ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { attempts := 0 if err := loopConditionUntilContext(ctx, Backoff{Duration: time.Microsecond}.Timer(), true, false, func(_ context.Context) (bool, error) { attempts++ return attempts >= 100, nil }); err != nil { b.Fatalf("unexpected err: %v", err) } } } golang-k8s-apimachinery-0.29.0/pkg/util/wait/poll.go000066400000000000000000000337671453143165200222460ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "context" "time" ) // PollUntilContextCancel tries a condition func until it returns true, an error, or the context // is cancelled or hits a deadline. condition will be invoked after the first interval if the // context is not cancelled first. The returned error will be from ctx.Err(), the condition's // err return value, or nil. If invoking condition takes longer than interval the next condition // will be invoked immediately. When using very short intervals, condition may be invoked multiple // times before a context cancellation is detected. If immediate is true, condition will be // invoked before waiting and guarantees that condition is invoked at least once, regardless of // whether the context has been cancelled. func PollUntilContextCancel(ctx context.Context, interval time.Duration, immediate bool, condition ConditionWithContextFunc) error { return loopConditionUntilContext(ctx, Backoff{Duration: interval}.Timer(), immediate, false, condition) } // PollUntilContextTimeout will terminate polling after timeout duration by setting a context // timeout. This is provided as a convenience function for callers not currently executing under // a deadline and is equivalent to: // // deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout) // err := PollUntilContextCancel(deadlineCtx, interval, immediate, condition) // // The deadline context will be cancelled if the Poll succeeds before the timeout, simplifying // inline usage. All other behavior is identical to PollUntilContextCancel. func PollUntilContextTimeout(ctx context.Context, interval, timeout time.Duration, immediate bool, condition ConditionWithContextFunc) error { deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout) defer deadlineCancel() return loopConditionUntilContext(deadlineCtx, Backoff{Duration: interval}.Timer(), immediate, false, condition) } // Poll tries a condition func until it returns true, an error, or the timeout // is reached. // // Poll always waits the interval before the run of 'condition'. // 'condition' will always be invoked at least once. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // If you want to Poll something forever, see PollInfinite. // // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func Poll(interval, timeout time.Duration, condition ConditionFunc) error { return PollWithContext(context.Background(), interval, timeout, condition.WithContext()) } // PollWithContext tries a condition func until it returns true, an error, // or when the context expires or the timeout is reached, whichever // happens first. // // PollWithContext always waits the interval before the run of 'condition'. // 'condition' will always be invoked at least once. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // If you want to Poll something forever, see PollInfinite. // // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { return poll(ctx, false, poller(interval, timeout), condition) } // PollUntil tries a condition func until it returns true, an error or stopCh is // closed. // // PollUntil always waits interval before the first run of 'condition'. // 'condition' will always be invoked at least once. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { return PollUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext()) } // PollUntilWithContext tries a condition func until it returns true, // an error or the specified context is cancelled or expired. // // PollUntilWithContext always waits interval before the first run of 'condition'. // 'condition' will always be invoked at least once. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { return poll(ctx, false, poller(interval, 0), condition) } // PollInfinite tries a condition func until it returns true or an error // // PollInfinite always waits the interval before the run of 'condition'. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollInfinite(interval time.Duration, condition ConditionFunc) error { return PollInfiniteWithContext(context.Background(), interval, condition.WithContext()) } // PollInfiniteWithContext tries a condition func until it returns true or an error // // PollInfiniteWithContext always waits the interval before the run of 'condition'. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { return poll(ctx, false, poller(interval, 0), condition) } // PollImmediate tries a condition func until it returns true, an error, or the timeout // is reached. // // PollImmediate always checks 'condition' before waiting for the interval. 'condition' // will always be invoked at least once. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // If you want to immediately Poll something forever, see PollImmediateInfinite. // // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error { return PollImmediateWithContext(context.Background(), interval, timeout, condition.WithContext()) } // PollImmediateWithContext tries a condition func until it returns true, an error, // or the timeout is reached or the specified context expires, whichever happens first. // // PollImmediateWithContext always checks 'condition' before waiting for the interval. // 'condition' will always be invoked at least once. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // If you want to immediately Poll something forever, see PollImmediateInfinite. // // Deprecated: This method does not return errors from context, use PollUntilContextTimeout. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollImmediateWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { return poll(ctx, true, poller(interval, timeout), condition) } // PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed. // // PollImmediateUntil runs the 'condition' before waiting for the interval. // 'condition' will always be invoked at least once. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { return PollImmediateUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext()) } // PollImmediateUntilWithContext tries a condition func until it returns true, // an error or the specified context is cancelled or expired. // // PollImmediateUntilWithContext runs the 'condition' before waiting for the interval. // 'condition' will always be invoked at least once. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollImmediateUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { return poll(ctx, true, poller(interval, 0), condition) } // PollImmediateInfinite tries a condition func until it returns true or an error // // PollImmediateInfinite runs the 'condition' before waiting for the interval. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error { return PollImmediateInfiniteWithContext(context.Background(), interval, condition.WithContext()) } // PollImmediateInfiniteWithContext tries a condition func until it returns true // or an error or the specified context gets cancelled or expired. // // PollImmediateInfiniteWithContext runs the 'condition' before waiting for the interval. // // Some intervals may be missed if the condition takes too long or the time // window is too short. // // Deprecated: This method does not return errors from context, use PollUntilContextCancel. // Note that the new method will no longer return ErrWaitTimeout and instead return errors // defined by the context package. Will be removed in a future release. func PollImmediateInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { return poll(ctx, true, poller(interval, 0), condition) } // Internally used, each of the public 'Poll*' function defined in this // package should invoke this internal function with appropriate parameters. // ctx: the context specified by the caller, for infinite polling pass // a context that never gets cancelled or expired. // immediate: if true, the 'condition' will be invoked before waiting for the interval, // in this case 'condition' will always be invoked at least once. // wait: user specified WaitFunc function that controls at what interval the condition // function should be invoked periodically and whether it is bound by a timeout. // condition: user specified ConditionWithContextFunc function. // // Deprecated: will be removed in favor of loopConditionUntilContext. func poll(ctx context.Context, immediate bool, wait waitWithContextFunc, condition ConditionWithContextFunc) error { if immediate { done, err := runConditionWithCrashProtectionWithContext(ctx, condition) if err != nil { return err } if done { return nil } } select { case <-ctx.Done(): // returning ctx.Err() will break backward compatibility, use new PollUntilContext* // methods instead return ErrWaitTimeout default: return waitForWithContext(ctx, wait, condition) } } // poller returns a WaitFunc that will send to the channel every interval until // timeout has elapsed and then closes the channel. // // Over very short intervals you may receive no ticks before the channel is // closed. A timeout of 0 is interpreted as an infinity, and in such a case // it would be the caller's responsibility to close the done channel. // Failure to do so would result in a leaked goroutine. // // Output ticks are not buffered. If the channel is not ready to receive an // item, the tick is skipped. // // Deprecated: Will be removed in a future release. func poller(interval, timeout time.Duration) waitWithContextFunc { return waitWithContextFunc(func(ctx context.Context) <-chan struct{} { ch := make(chan struct{}) go func() { defer close(ch) tick := time.NewTicker(interval) defer tick.Stop() var after <-chan time.Time if timeout != 0 { // time.After is more convenient, but it // potentially leaves timers around much longer // than necessary if we exit early. timer := time.NewTimer(timeout) after = timer.C defer timer.Stop() } for { select { case <-tick.C: // If the consumer isn't ready for this signal drop it and // check the other channels. select { case ch <- struct{}{}: default: } case <-after: return case <-ctx.Done(): return } } }() return ch }) } golang-k8s-apimachinery-0.29.0/pkg/util/wait/timer.go000066400000000000000000000056701453143165200224100ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package wait import ( "time" "k8s.io/utils/clock" ) // Timer abstracts how wait functions interact with time runtime efficiently. Test // code may implement this interface directly but package consumers are encouraged // to use the Backoff type as the primary mechanism for acquiring a Timer. The // interface is a simplification of clock.Timer to prevent misuse. Timers are not // expected to be safe for calls from multiple goroutines. type Timer interface { // C returns a channel that will receive a struct{} each time the timer fires. // The channel should not be waited on after Stop() is invoked. It is allowed // to cache the returned value of C() for the lifetime of the Timer. C() <-chan time.Time // Next is invoked by wait functions to signal timers that the next interval // should begin. You may only use Next() if you have drained the channel C(). // You should not call Next() after Stop() is invoked. Next() // Stop releases the timer. It is safe to invoke if no other methods have been // called. Stop() } type noopTimer struct { closedCh <-chan time.Time } // newNoopTimer creates a timer with a unique channel to avoid contention // for the channel's lock across multiple unrelated timers. func newNoopTimer() noopTimer { ch := make(chan time.Time) close(ch) return noopTimer{closedCh: ch} } func (t noopTimer) C() <-chan time.Time { return t.closedCh } func (noopTimer) Next() {} func (noopTimer) Stop() {} type variableTimer struct { fn DelayFunc t clock.Timer new func(time.Duration) clock.Timer } func (t *variableTimer) C() <-chan time.Time { if t.t == nil { d := t.fn() t.t = t.new(d) } return t.t.C() } func (t *variableTimer) Next() { if t.t == nil { return } d := t.fn() t.t.Reset(d) } func (t *variableTimer) Stop() { if t.t == nil { return } t.t.Stop() t.t = nil } type fixedTimer struct { interval time.Duration t clock.Ticker new func(time.Duration) clock.Ticker } func (t *fixedTimer) C() <-chan time.Time { if t.t == nil { t.t = t.new(t.interval) } return t.t.C() } func (t *fixedTimer) Next() { // no-op for fixed timers } func (t *fixedTimer) Stop() { if t.t == nil { return } t.t.Stop() t.t = nil } var ( // RealTimer can be passed to methods that need a clock.Timer. RealTimer = clock.RealClock{}.NewTimer ) var ( // internalClock is used for test injection of clocks internalClock = clock.RealClock{} ) golang-k8s-apimachinery-0.29.0/pkg/util/wait/wait.go000066400000000000000000000162221453143165200222270ustar00rootroot00000000000000/* 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 wait import ( "context" "math/rand" "sync" "time" "k8s.io/apimachinery/pkg/util/runtime" ) // For any test of the style: // // ... // <- time.After(timeout): // t.Errorf("Timed out") // // The value for timeout should effectively be "forever." Obviously we don't want our tests to truly lock up forever, but 30s // is long enough that it is effectively forever for the things that can slow down a run on a heavily contended machine // (GC, seeks, etc), but not so long as to make a developer ctrl-c a test run if they do happen to break that test. var ForeverTestTimeout = time.Second * 30 // NeverStop may be passed to Until to make it never stop. var NeverStop <-chan struct{} = make(chan struct{}) // Group allows to start a group of goroutines and wait for their completion. type Group struct { wg sync.WaitGroup } func (g *Group) Wait() { g.wg.Wait() } // StartWithChannel starts f in a new goroutine in the group. // stopCh is passed to f as an argument. f should stop when stopCh is available. func (g *Group) StartWithChannel(stopCh <-chan struct{}, f func(stopCh <-chan struct{})) { g.Start(func() { f(stopCh) }) } // StartWithContext starts f in a new goroutine in the group. // ctx is passed to f as an argument. f should stop when ctx.Done() is available. func (g *Group) StartWithContext(ctx context.Context, f func(context.Context)) { g.Start(func() { f(ctx) }) } // Start starts f in a new goroutine in the group. func (g *Group) Start(f func()) { g.wg.Add(1) go func() { defer g.wg.Done() f() }() } // Forever calls f every period for ever. // // Forever is syntactic sugar on top of Until. func Forever(f func(), period time.Duration) { Until(f, period, NeverStop) } // Jitter returns a time.Duration between duration and duration + maxFactor * // duration. // // This allows clients to avoid converging on periodic behavior. If maxFactor // is 0.0, a suggested default value will be chosen. func Jitter(duration time.Duration, maxFactor float64) time.Duration { if maxFactor <= 0.0 { maxFactor = 1.0 } wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) return wait } // ConditionFunc returns true if the condition is satisfied, or an error // if the loop should be aborted. type ConditionFunc func() (done bool, err error) // ConditionWithContextFunc returns true if the condition is satisfied, or an error // if the loop should be aborted. // // The caller passes along a context that can be used by the condition function. type ConditionWithContextFunc func(context.Context) (done bool, err error) // WithContext converts a ConditionFunc into a ConditionWithContextFunc func (cf ConditionFunc) WithContext() ConditionWithContextFunc { return func(context.Context) (done bool, err error) { return cf() } } // ContextForChannel provides a context that will be treated as cancelled // when the provided parentCh is closed. The implementation returns // context.Canceled for Err() if and only if the parentCh is closed. func ContextForChannel(parentCh <-chan struct{}) context.Context { return channelContext{stopCh: parentCh} } var _ context.Context = channelContext{} // channelContext will behave as if the context were cancelled when stopCh is // closed. type channelContext struct { stopCh <-chan struct{} } func (c channelContext) Done() <-chan struct{} { return c.stopCh } func (c channelContext) Err() error { select { case <-c.stopCh: return context.Canceled default: return nil } } func (c channelContext) Deadline() (time.Time, bool) { return time.Time{}, false } func (c channelContext) Value(key any) any { return nil } // runConditionWithCrashProtection runs a ConditionFunc with crash protection. // // Deprecated: Will be removed when the legacy polling methods are removed. func runConditionWithCrashProtection(condition ConditionFunc) (bool, error) { defer runtime.HandleCrash() return condition() } // runConditionWithCrashProtectionWithContext runs a ConditionWithContextFunc // with crash protection. // // Deprecated: Will be removed when the legacy polling methods are removed. func runConditionWithCrashProtectionWithContext(ctx context.Context, condition ConditionWithContextFunc) (bool, error) { defer runtime.HandleCrash() return condition(ctx) } // waitFunc creates a channel that receives an item every time a test // should be executed and is closed when the last test should be invoked. // // Deprecated: Will be removed in a future release in favor of // loopConditionUntilContext. type waitFunc func(done <-chan struct{}) <-chan struct{} // WithContext converts the WaitFunc to an equivalent WaitWithContextFunc func (w waitFunc) WithContext() waitWithContextFunc { return func(ctx context.Context) <-chan struct{} { return w(ctx.Done()) } } // waitWithContextFunc creates a channel that receives an item every time a test // should be executed and is closed when the last test should be invoked. // // When the specified context gets cancelled or expires the function // stops sending item and returns immediately. // // Deprecated: Will be removed in a future release in favor of // loopConditionUntilContext. type waitWithContextFunc func(ctx context.Context) <-chan struct{} // waitForWithContext continually checks 'fn' as driven by 'wait'. // // waitForWithContext gets a channel from 'wait()”, and then invokes 'fn' // once for every value placed on the channel and once more when the // channel is closed. If the channel is closed and 'fn' // returns false without error, waitForWithContext returns ErrWaitTimeout. // // If 'fn' returns an error the loop ends and that error is returned. If // 'fn' returns true the loop ends and nil is returned. // // context.Canceled will be returned if the ctx.Done() channel is closed // without fn ever returning true. // // When the ctx.Done() channel is closed, because the golang `select` statement is // "uniform pseudo-random", the `fn` might still run one or multiple times, // though eventually `waitForWithContext` will return. // // Deprecated: Will be removed in a future release in favor of // loopConditionUntilContext. func waitForWithContext(ctx context.Context, wait waitWithContextFunc, fn ConditionWithContextFunc) error { waitCtx, cancel := context.WithCancel(context.Background()) defer cancel() c := wait(waitCtx) for { select { case _, open := <-c: ok, err := runConditionWithCrashProtectionWithContext(ctx, fn) if err != nil { return err } if ok { return nil } if !open { return ErrWaitTimeout } case <-ctx.Done(): // returning ctx.Err() will break backward compatibility, use new PollUntilContext* // methods instead return ErrWaitTimeout } } } golang-k8s-apimachinery-0.29.0/pkg/util/wait/wait_test.go000066400000000000000000001221231453143165200232640ustar00rootroot00000000000000/* 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 wait import ( "context" "errors" "fmt" "math/rand" "sync" "sync/atomic" "testing" "time" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/utils/clock" testingclock "k8s.io/utils/clock/testing" ) func TestUntil(t *testing.T) { ch := make(chan struct{}) close(ch) Until(func() { t.Fatal("should not have been invoked") }, 0, ch) ch = make(chan struct{}) called := make(chan struct{}) go func() { Until(func() { called <- struct{}{} }, 0, ch) close(called) }() <-called close(ch) <-called } func TestUntilWithContext(t *testing.T) { ctx, cancel := context.WithCancel(context.TODO()) cancel() UntilWithContext(ctx, func(context.Context) { t.Fatal("should not have been invoked") }, 0) ctx, cancel = context.WithCancel(context.TODO()) called := make(chan struct{}) go func() { UntilWithContext(ctx, func(context.Context) { called <- struct{}{} }, 0) close(called) }() <-called cancel() <-called } func TestNonSlidingUntil(t *testing.T) { ch := make(chan struct{}) close(ch) NonSlidingUntil(func() { t.Fatal("should not have been invoked") }, 0, ch) ch = make(chan struct{}) called := make(chan struct{}) go func() { NonSlidingUntil(func() { called <- struct{}{} }, 0, ch) close(called) }() <-called close(ch) <-called } func TestNonSlidingUntilWithContext(t *testing.T) { ctx, cancel := context.WithCancel(context.TODO()) cancel() NonSlidingUntilWithContext(ctx, func(context.Context) { t.Fatal("should not have been invoked") }, 0) ctx, cancel = context.WithCancel(context.TODO()) called := make(chan struct{}) go func() { NonSlidingUntilWithContext(ctx, func(context.Context) { called <- struct{}{} }, 0) close(called) }() <-called cancel() <-called } func TestUntilReturnsImmediately(t *testing.T) { now := time.Now() ch := make(chan struct{}) var attempts int Until(func() { attempts++ if attempts > 1 { t.Fatalf("invoked after close of channel") } close(ch) }, 30*time.Second, ch) if now.Add(25 * time.Second).Before(time.Now()) { t.Errorf("Until did not return immediately when the stop chan was closed inside the func") } } func TestJitterUntil(t *testing.T) { ch := make(chan struct{}) // if a channel is closed JitterUntil never calls function f // and returns immediately close(ch) JitterUntil(func() { t.Fatal("should not have been invoked") }, 0, 1.0, true, ch) ch = make(chan struct{}) called := make(chan struct{}) go func() { JitterUntil(func() { called <- struct{}{} }, 0, 1.0, true, ch) close(called) }() <-called close(ch) <-called } func TestJitterUntilWithContext(t *testing.T) { ctx, cancel := context.WithCancel(context.TODO()) cancel() JitterUntilWithContext(ctx, func(context.Context) { t.Fatal("should not have been invoked") }, 0, 1.0, true) ctx, cancel = context.WithCancel(context.TODO()) called := make(chan struct{}) go func() { JitterUntilWithContext(ctx, func(context.Context) { called <- struct{}{} }, 0, 1.0, true) close(called) }() <-called cancel() <-called } func TestJitterUntilReturnsImmediately(t *testing.T) { now := time.Now() ch := make(chan struct{}) JitterUntil(func() { close(ch) }, 30*time.Second, 1.0, true, ch) if now.Add(25 * time.Second).Before(time.Now()) { t.Errorf("JitterUntil did not return immediately when the stop chan was closed inside the func") } } func TestJitterUntilRecoversPanic(t *testing.T) { // Save and restore crash handlers originalReallyCrash := runtime.ReallyCrash originalHandlers := runtime.PanicHandlers defer func() { runtime.ReallyCrash = originalReallyCrash runtime.PanicHandlers = originalHandlers }() called := 0 handled := 0 // Hook up a custom crash handler to ensure it is called when a jitter function panics runtime.ReallyCrash = false runtime.PanicHandlers = []func(interface{}){ func(p interface{}) { handled++ }, } ch := make(chan struct{}) JitterUntil(func() { called++ if called > 2 { close(ch) return } panic("TestJitterUntilRecoversPanic") }, time.Millisecond, 1.0, true, ch) if called != 3 { t.Errorf("Expected panic recovers") } } func TestJitterUntilNegativeFactor(t *testing.T) { now := time.Now() ch := make(chan struct{}) called := make(chan struct{}) received := make(chan struct{}) go func() { JitterUntil(func() { called <- struct{}{} <-received }, time.Second, -30.0, true, ch) }() // first loop <-called received <- struct{}{} // second loop <-called close(ch) received <- struct{}{} // it should take at most 2 seconds + some overhead, not 3 if now.Add(3 * time.Second).Before(time.Now()) { t.Errorf("JitterUntil did not returned after predefined period with negative jitter factor when the stop chan was closed inside the func") } } func TestExponentialBackoff(t *testing.T) { // exits immediately i := 0 err := ExponentialBackoff(Backoff{Factor: 1.0}, func() (bool, error) { i++ return false, nil }) if err != ErrWaitTimeout || i != 0 { t.Errorf("unexpected error: %v", err) } opts := Backoff{Factor: 1.0, Steps: 3} // waits up to steps i = 0 err = ExponentialBackoff(opts, func() (bool, error) { i++ return false, nil }) if err != ErrWaitTimeout || i != opts.Steps { t.Errorf("unexpected error: %v", err) } // returns immediately i = 0 err = ExponentialBackoff(opts, func() (bool, error) { i++ return true, nil }) if err != nil || i != 1 { t.Errorf("unexpected error: %v", err) } // returns immediately on error testErr := fmt.Errorf("some other error") err = ExponentialBackoff(opts, func() (bool, error) { return false, testErr }) if err != testErr { t.Errorf("unexpected error: %v", err) } // invoked multiple times i = 1 err = ExponentialBackoff(opts, func() (bool, error) { if i < opts.Steps { i++ return false, nil } return true, nil }) if err != nil || i != opts.Steps { t.Errorf("unexpected error: %v", err) } } func TestPoller(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() w := poller(time.Millisecond, 2*time.Millisecond) ch := w(ctx) count := 0 DRAIN: for { select { case _, open := <-ch: if !open { break DRAIN } count++ case <-time.After(ForeverTestTimeout): t.Errorf("unexpected timeout after poll") } } if count > 3 { t.Errorf("expected up to three values, got %d", count) } } type fakePoller struct { max int used int32 // accessed with atomics wg sync.WaitGroup } func fakeTicker(max int, used *int32, doneFunc func()) waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) go func() { defer doneFunc() defer close(ch) for i := 0; i < max; i++ { select { case ch <- struct{}{}: case <-done: return } if used != nil { atomic.AddInt32(used, 1) } } }() return ch } } func (fp *fakePoller) GetwaitFunc() waitFunc { fp.wg.Add(1) return fakeTicker(fp.max, &fp.used, fp.wg.Done) } func TestPoll(t *testing.T) { invocations := 0 f := ConditionWithContextFunc(func(ctx context.Context) (bool, error) { invocations++ return true, nil }) fp := fakePoller{max: 1} ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := poll(ctx, false, fp.GetwaitFunc().WithContext(), f); err != nil { t.Fatalf("unexpected error %v", err) } fp.wg.Wait() if invocations != 1 { t.Errorf("Expected exactly one invocation, got %d", invocations) } used := atomic.LoadInt32(&fp.used) if used != 1 { t.Errorf("Expected exactly one tick, got %d", used) } } func TestPollError(t *testing.T) { expectedError := errors.New("Expected error") f := ConditionFunc(func() (bool, error) { return false, expectedError }) fp := fakePoller{max: 1} ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := poll(ctx, false, fp.GetwaitFunc().WithContext(), f.WithContext()); err == nil || err != expectedError { t.Fatalf("Expected error %v, got none %v", expectedError, err) } fp.wg.Wait() used := atomic.LoadInt32(&fp.used) if used != 1 { t.Errorf("Expected exactly one tick, got %d", used) } } func TestPollImmediate(t *testing.T) { invocations := 0 f := ConditionFunc(func() (bool, error) { invocations++ return true, nil }) fp := fakePoller{max: 0} ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := poll(ctx, true, fp.GetwaitFunc().WithContext(), f.WithContext()); err != nil { t.Fatalf("unexpected error %v", err) } // We don't need to wait for fp.wg, as pollImmediate shouldn't call waitFunc at all. if invocations != 1 { t.Errorf("Expected exactly one invocation, got %d", invocations) } used := atomic.LoadInt32(&fp.used) if used != 0 { t.Errorf("Expected exactly zero ticks, got %d", used) } } func TestPollImmediateError(t *testing.T) { expectedError := errors.New("Expected error") f := ConditionFunc(func() (bool, error) { return false, expectedError }) fp := fakePoller{max: 0} ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := poll(ctx, true, fp.GetwaitFunc().WithContext(), f.WithContext()); err == nil || err != expectedError { t.Fatalf("Expected error %v, got none %v", expectedError, err) } // We don't need to wait for fp.wg, as pollImmediate shouldn't call waitFunc at all. used := atomic.LoadInt32(&fp.used) if used != 0 { t.Errorf("Expected exactly zero ticks, got %d", used) } } func TestPollForever(t *testing.T) { ch := make(chan struct{}) errc := make(chan error, 1) done := make(chan struct{}, 1) complete := make(chan struct{}) go func() { f := ConditionFunc(func() (bool, error) { ch <- struct{}{} select { case <-done: return true, nil default: } return false, nil }) if err := PollInfinite(time.Microsecond, f); err != nil { errc <- fmt.Errorf("unexpected error %v", err) } close(ch) complete <- struct{}{} }() // ensure the condition is opened <-ch // ensure channel sends events for i := 0; i < 10; i++ { select { case _, open := <-ch: if !open { if len(errc) != 0 { t.Fatalf("did not expect channel to be closed, %v", <-errc) } t.Fatal("did not expect channel to be closed") } case <-time.After(ForeverTestTimeout): t.Fatalf("channel did not return at least once within the poll interval") } } // at most one poll notification should be sent once we return from the condition done <- struct{}{} go func() { for i := 0; i < 2; i++ { _, open := <-ch if !open { return } } t.Error("expected closed channel after two iterations") }() <-complete if len(errc) != 0 { t.Fatal(<-errc) } } func Test_waitFor(t *testing.T) { var invocations int testCases := map[string]struct { F ConditionFunc Ticks int Invoked int Err bool }{ "invoked once": { ConditionFunc(func() (bool, error) { invocations++ return true, nil }), 2, 1, false, }, "invoked and returns a timeout": { ConditionFunc(func() (bool, error) { invocations++ return false, nil }), 2, 3, // the contract of waitFor() says the func is called once more at the end of the wait true, }, "returns immediately on error": { ConditionFunc(func() (bool, error) { invocations++ return false, errors.New("test") }), 2, 1, true, }, } for k, c := range testCases { invocations = 0 ticker := fakeTicker(c.Ticks, nil, func() {}) err := func() error { done := make(chan struct{}) defer close(done) ctx := ContextForChannel(done) return waitForWithContext(ctx, ticker.WithContext(), c.F.WithContext()) }() switch { case c.Err && err == nil: t.Errorf("%s: Expected error, got nil", k) continue case !c.Err && err != nil: t.Errorf("%s: Expected no error, got: %#v", k, err) continue } if invocations != c.Invoked { t.Errorf("%s: Expected %d invocations, got %d", k, c.Invoked, invocations) } } } // Test_waitForWithEarlyClosing_waitFunc tests WaitFor when the waitFunc closes its channel. The WaitFor should // always return ErrWaitTimeout. func Test_waitForWithEarlyClosing_waitFunc(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) ctx := ContextForChannel(stopCh) start := time.Now() err := waitForWithContext(ctx, func(ctx context.Context) <-chan struct{} { c := make(chan struct{}) close(c) return c }, func(_ context.Context) (bool, error) { return false, nil }) duration := time.Since(start) // The waitFor should return immediately, so the duration is close to 0s. if duration >= ForeverTestTimeout/2 { t.Errorf("expected short timeout duration") } if err != ErrWaitTimeout { t.Errorf("expected ErrWaitTimeout from WaitFunc") } } // Test_waitForWithClosedChannel tests waitFor when it receives a closed channel. The waitFor should // always return ErrWaitTimeout. func Test_waitForWithClosedChannel(t *testing.T) { stopCh := make(chan struct{}) close(stopCh) c := make(chan struct{}) defer close(c) ctx := ContextForChannel(stopCh) start := time.Now() err := waitForWithContext(ctx, func(_ context.Context) <-chan struct{} { return c }, func(_ context.Context) (bool, error) { return false, nil }) duration := time.Since(start) // The waitFor should return immediately, so the duration is close to 0s. if duration >= ForeverTestTimeout/2 { t.Errorf("expected short timeout duration") } // The interval of the poller is ForeverTestTimeout, so the waitFor should always return ErrWaitTimeout. if err != ErrWaitTimeout { t.Errorf("expected ErrWaitTimeout from WaitFunc") } } // Test_waitForWithContextCancelsContext verifies that after the condition func returns true, // waitForWithContext cancels the context it supplies to the WaitWithContextFunc. func Test_waitForWithContextCancelsContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() waitFn := poller(time.Millisecond, ForeverTestTimeout) var ctxPassedToWait context.Context waitForWithContext(ctx, func(ctx context.Context) <-chan struct{} { ctxPassedToWait = ctx return waitFn(ctx) }, func(ctx context.Context) (bool, error) { time.Sleep(10 * time.Millisecond) return true, nil }) // The polling goroutine should be closed after waitForWithContext returning. if ctxPassedToWait.Err() != context.Canceled { t.Errorf("expected the context passed to waitForWithContext to be closed with: %v, but got: %v", context.Canceled, ctxPassedToWait.Err()) } } func TestPollUntil(t *testing.T) { stopCh := make(chan struct{}) called := make(chan bool) pollDone := make(chan struct{}) go func() { PollUntil(time.Microsecond, ConditionFunc(func() (bool, error) { called <- true return false, nil }), stopCh) close(pollDone) }() // make sure we're called once <-called // this should trigger a "done" close(stopCh) go func() { // release the condition func if needed for range called { } }() // make sure we finished the poll <-pollDone close(called) } func TestBackoff_Step(t *testing.T) { tests := []struct { initial *Backoff want []time.Duration }{ {initial: nil, want: []time.Duration{0, 0, 0, 0}}, {initial: &Backoff{Duration: time.Second, Steps: -1}, want: []time.Duration{time.Second, time.Second, time.Second}}, {initial: &Backoff{Duration: time.Second, Steps: 0}, want: []time.Duration{time.Second, time.Second, time.Second}}, {initial: &Backoff{Duration: time.Second, Steps: 1}, want: []time.Duration{time.Second, time.Second, time.Second}}, {initial: &Backoff{Duration: time.Second, Factor: 1.0, Steps: 1}, want: []time.Duration{time.Second, time.Second, time.Second}}, {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 3}, want: []time.Duration{1 * time.Second, 2 * time.Second, 4 * time.Second}}, {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 3, Cap: 3 * time.Second}, want: []time.Duration{1 * time.Second, 2 * time.Second, 3 * time.Second}}, {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 2, Cap: 3 * time.Second, Jitter: 0.5}, want: []time.Duration{2 * time.Second, 3 * time.Second, 3 * time.Second}}, {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 6, Jitter: 4}, want: []time.Duration{1 * time.Second, 2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second, 32 * time.Second}}, } for seed := int64(0); seed < 5; seed++ { for _, tt := range tests { var initial *Backoff if tt.initial != nil { copied := *tt.initial initial = &copied } else { initial = nil } t.Run(fmt.Sprintf("%#v seed=%d", initial, seed), func(t *testing.T) { rand.Seed(seed) for i := 0; i < len(tt.want); i++ { got := initial.Step() t.Logf("[%d]=%s", i, got) if initial != nil && initial.Jitter > 0 { if got == tt.want[i] { // this is statistically unlikely to happen by chance t.Errorf("Backoff.Step(%d) = %v, no jitter", i, got) continue } diff := float64(tt.want[i]-got) / float64(tt.want[i]) if diff > initial.Jitter { t.Errorf("Backoff.Step(%d) = %v, want %v, outside range", i, got, tt.want) continue } } else { if got != tt.want[i] { t.Errorf("Backoff.Step(%d) = %v, want %v", i, got, tt.want) continue } } } }) } } } func TestContextForChannel(t *testing.T) { var wg sync.WaitGroup parentCh := make(chan struct{}) done := make(chan struct{}) for i := 0; i < 3; i++ { wg.Add(1) go func() { defer wg.Done() ctx := ContextForChannel(parentCh) <-ctx.Done() }() } go func() { wg.Wait() close(done) }() // Closing parent channel should cancel all children contexts close(parentCh) select { case <-done: case <-time.After(ForeverTestTimeout): t.Errorf("unexpected timeout waiting for parent to cancel child contexts") } } func TestExponentialBackoffManagerGetNextBackoff(t *testing.T) { fc := testingclock.NewFakeClock(time.Now()) backoff := NewExponentialBackoffManager(1, 10, 10, 2.0, 0.0, fc) durations := []time.Duration{1, 2, 4, 8, 10, 10, 10} for i := 0; i < len(durations); i++ { generatedBackoff := backoff.(*exponentialBackoffManagerImpl).getNextBackoff() if generatedBackoff != durations[i] { t.Errorf("unexpected %d-th backoff: %d, expecting %d", i, generatedBackoff, durations[i]) } } fc.Step(11) resetDuration := backoff.(*exponentialBackoffManagerImpl).getNextBackoff() if resetDuration != 1 { t.Errorf("after reset, backoff should be 1, but got %d", resetDuration) } } func TestJitteredBackoffManagerGetNextBackoff(t *testing.T) { // positive jitter backoffMgr := NewJitteredBackoffManager(1, 1, testingclock.NewFakeClock(time.Now())) for i := 0; i < 5; i++ { backoff := backoffMgr.(*jitteredBackoffManagerImpl).getNextBackoff() if backoff < 1 || backoff > 2 { t.Errorf("backoff out of range: %d", backoff) } } // negative jitter, shall be a fixed backoff backoffMgr = NewJitteredBackoffManager(1, -1, testingclock.NewFakeClock(time.Now())) backoff := backoffMgr.(*jitteredBackoffManagerImpl).getNextBackoff() if backoff != 1 { t.Errorf("backoff should be 1, but got %d", backoff) } } func TestJitterBackoffManagerWithRealClock(t *testing.T) { backoffMgr := NewJitteredBackoffManager(1*time.Millisecond, 0, &clock.RealClock{}) for i := 0; i < 5; i++ { start := time.Now() <-backoffMgr.Backoff().C() passed := time.Since(start) if passed < 1*time.Millisecond { t.Errorf("backoff should be at least 1ms, but got %s", passed.String()) } } } func TestExponentialBackoffManagerWithRealClock(t *testing.T) { // backoff at least 1ms, 2ms, 4ms, 8ms, 10ms, 10ms, 10ms durationFactors := []time.Duration{1, 2, 4, 8, 10, 10, 10} backoffMgr := NewExponentialBackoffManager(1*time.Millisecond, 10*time.Millisecond, 1*time.Hour, 2.0, 0.0, &clock.RealClock{}) for i := range durationFactors { start := time.Now() <-backoffMgr.Backoff().C() passed := time.Since(start) if passed < durationFactors[i]*time.Millisecond { t.Errorf("backoff should be at least %d ms, but got %s", durationFactors[i], passed.String()) } } } func TestBackoffDelayWithResetExponential(t *testing.T) { fc := testingclock.NewFakeClock(time.Now()) backoff := Backoff{Duration: 1, Cap: 10, Factor: 2.0, Jitter: 0.0, Steps: 10}.DelayWithReset(fc, 10) durations := []time.Duration{1, 2, 4, 8, 10, 10, 10} for i := 0; i < len(durations); i++ { generatedBackoff := backoff() if generatedBackoff != durations[i] { t.Errorf("unexpected %d-th backoff: %d, expecting %d", i, generatedBackoff, durations[i]) } } fc.Step(11) resetDuration := backoff() if resetDuration != 1 { t.Errorf("after reset, backoff should be 1, but got %d", resetDuration) } } func TestBackoffDelayWithResetEmpty(t *testing.T) { fc := testingclock.NewFakeClock(time.Now()) backoff := Backoff{Duration: 1, Cap: 10, Factor: 2.0, Jitter: 0.0, Steps: 10}.DelayWithReset(fc, 0) // we reset to initial duration because the resetInterval is 0, immediate durations := []time.Duration{1, 1, 1, 1, 1, 1, 1} for i := 0; i < len(durations); i++ { generatedBackoff := backoff() if generatedBackoff != durations[i] { t.Errorf("unexpected %d-th backoff: %d, expecting %d", i, generatedBackoff, durations[i]) } } fc.Step(11) resetDuration := backoff() if resetDuration != 1 { t.Errorf("after reset, backoff should be 1, but got %d", resetDuration) } } func TestBackoffDelayWithResetJitter(t *testing.T) { // positive jitter backoff := Backoff{Duration: 1, Jitter: 1}.DelayWithReset(testingclock.NewFakeClock(time.Now()), 0) for i := 0; i < 5; i++ { value := backoff() if value < 1 || value > 2 { t.Errorf("backoff out of range: %d", value) } } // negative jitter, shall be a fixed backoff backoff = Backoff{Duration: 1, Jitter: -1}.DelayWithReset(testingclock.NewFakeClock(time.Now()), 0) value := backoff() if value != 1 { t.Errorf("backoff should be 1, but got %d", value) } } func TestBackoffDelayWithResetWithRealClockJitter(t *testing.T) { backoff := Backoff{Duration: 1 * time.Millisecond, Jitter: 0}.DelayWithReset(&clock.RealClock{}, 0) for i := 0; i < 5; i++ { start := time.Now() <-RealTimer(backoff()).C() passed := time.Since(start) if passed < 1*time.Millisecond { t.Errorf("backoff should be at least 1ms, but got %s", passed.String()) } } } func TestBackoffDelayWithResetWithRealClockExponential(t *testing.T) { // backoff at least 1ms, 2ms, 4ms, 8ms, 10ms, 10ms, 10ms durationFactors := []time.Duration{1, 2, 4, 8, 10, 10, 10} backoff := Backoff{Duration: 1 * time.Millisecond, Cap: 10 * time.Millisecond, Factor: 2.0, Jitter: 0.0, Steps: 10}.DelayWithReset(&clock.RealClock{}, 1*time.Hour) for i := range durationFactors { start := time.Now() <-RealTimer(backoff()).C() passed := time.Since(start) if passed < durationFactors[i]*time.Millisecond { t.Errorf("backoff should be at least %d ms, but got %s", durationFactors[i], passed.String()) } } } func defaultContext() (context.Context, context.CancelFunc) { return context.WithCancel(context.Background()) } func cancelledContext() (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) cancel() return ctx, cancel } func deadlinedContext() (context.Context, context.CancelFunc) { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) for ctx.Err() != context.DeadlineExceeded { time.Sleep(501 * time.Microsecond) } return ctx, cancel } func TestExponentialBackoffWithContext(t *testing.T) { defaultCallback := func(_ int) (bool, error) { return false, nil } conditionErr := errors.New("condition failed") tests := []struct { name string steps int zeroDuration bool context func() (context.Context, context.CancelFunc) callback func(calls int) (bool, error) cancelContextAfter int attemptsExpected int errExpected error }{ { name: "no attempts expected with zero backoff steps", steps: 0, callback: defaultCallback, attemptsExpected: 0, errExpected: ErrWaitTimeout, }, { name: "condition returns false with single backoff step", steps: 1, callback: defaultCallback, attemptsExpected: 1, errExpected: ErrWaitTimeout, }, { name: "condition returns true with single backoff step", steps: 1, callback: func(_ int) (bool, error) { return true, nil }, attemptsExpected: 1, errExpected: nil, }, { name: "condition always returns false with multiple backoff steps", steps: 5, callback: defaultCallback, attemptsExpected: 5, errExpected: ErrWaitTimeout, }, { name: "condition returns true after certain attempts with multiple backoff steps", steps: 5, callback: func(attempts int) (bool, error) { if attempts == 3 { return true, nil } return false, nil }, attemptsExpected: 3, errExpected: nil, }, { name: "condition returns error no further attempts expected", steps: 5, callback: func(_ int) (bool, error) { return true, conditionErr }, attemptsExpected: 1, errExpected: conditionErr, }, { name: "context already canceled no attempts expected", steps: 5, context: cancelledContext, callback: defaultCallback, attemptsExpected: 0, errExpected: context.Canceled, }, { name: "context at deadline no attempts expected", steps: 5, context: deadlinedContext, callback: defaultCallback, attemptsExpected: 0, errExpected: context.DeadlineExceeded, }, { name: "no attempts expected with zero backoff steps", steps: 0, callback: defaultCallback, attemptsExpected: 0, errExpected: ErrWaitTimeout, }, { name: "condition returns false with single backoff step", steps: 1, callback: defaultCallback, attemptsExpected: 1, errExpected: ErrWaitTimeout, }, { name: "condition returns true with single backoff step", steps: 1, callback: func(_ int) (bool, error) { return true, nil }, attemptsExpected: 1, errExpected: nil, }, { name: "condition always returns false with multiple backoff steps but is cancelled at step 4", steps: 5, callback: defaultCallback, attemptsExpected: 4, cancelContextAfter: 4, errExpected: context.Canceled, }, { name: "condition returns true after certain attempts with multiple backoff steps and zero duration", steps: 5, zeroDuration: true, callback: func(attempts int) (bool, error) { if attempts == 3 { return true, nil } return false, nil }, attemptsExpected: 3, errExpected: nil, }, { name: "condition returns error no further attempts expected", steps: 5, callback: func(_ int) (bool, error) { return true, conditionErr }, attemptsExpected: 1, errExpected: conditionErr, }, { name: "context already canceled no attempts expected", steps: 5, context: cancelledContext, callback: defaultCallback, attemptsExpected: 0, errExpected: context.Canceled, }, { name: "context at deadline no attempts expected", steps: 5, context: deadlinedContext, callback: defaultCallback, attemptsExpected: 0, errExpected: context.DeadlineExceeded, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { backoff := Backoff{ Duration: 1 * time.Microsecond, Factor: 1.0, Steps: test.steps, } if test.zeroDuration { backoff.Duration = 0 } contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() attempts := 0 err := ExponentialBackoffWithContext(ctx, backoff, func(_ context.Context) (bool, error) { attempts++ defer func() { if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { cancel() } }() return test.callback(attempts) }) if test.errExpected != err { t.Errorf("expected error: %v but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) } }) } } func BenchmarkExponentialBackoffWithContext(b *testing.B) { backoff := Backoff{ Duration: 0, Factor: 0, Steps: 101, } ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { attempts := 0 if err := ExponentialBackoffWithContext(ctx, backoff, func(_ context.Context) (bool, error) { attempts++ return attempts >= 100, nil }); err != nil { b.Fatalf("unexpected err: %v", err) } } } func TestPollImmediateUntilWithContext(t *testing.T) { fakeErr := errors.New("my error") tests := []struct { name string condition func(int) ConditionWithContextFunc context func() (context.Context, context.CancelFunc) cancelContextAfterNthAttempt int errExpected error attemptsExpected int }{ { name: "condition throws error on immediate attempt, no retry is attempted", condition: func(int) ConditionWithContextFunc { return func(context.Context) (done bool, err error) { return false, fakeErr } }, errExpected: fakeErr, attemptsExpected: 1, }, { name: "condition returns done=true on immediate attempt, no retry is attempted", condition: func(int) ConditionWithContextFunc { return func(context.Context) (done bool, err error) { return true, nil } }, errExpected: nil, attemptsExpected: 1, }, { name: "condition returns done=false on immediate attempt, context is already cancelled, no retry is attempted", condition: func(int) ConditionWithContextFunc { return func(context.Context) (done bool, err error) { return false, nil } }, context: cancelledContext, errExpected: ErrWaitTimeout, // this should be context.Canceled but that would break callers that assume all errors are ErrWaitTimeout attemptsExpected: 1, }, { name: "condition returns done=false on immediate attempt, context is not cancelled, retry is attempted", condition: func(attempts int) ConditionWithContextFunc { return func(context.Context) (done bool, err error) { // let first 3 attempts fail and the last one succeed if attempts <= 3 { return false, nil } return true, nil } }, errExpected: nil, attemptsExpected: 4, }, { name: "condition always returns done=false, context gets cancelled after N attempts", condition: func(attempts int) ConditionWithContextFunc { return func(ctx context.Context) (done bool, err error) { return false, nil } }, cancelContextAfterNthAttempt: 4, errExpected: ErrWaitTimeout, // this should be context.Canceled, but this method cannot change attemptsExpected: 4, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() var attempts int conditionWrapper := func(ctx context.Context) (done bool, err error) { attempts++ defer func() { if test.cancelContextAfterNthAttempt == attempts { cancel() } }() c := test.condition(attempts) return c(ctx) } err := PollImmediateUntilWithContext(ctx, time.Millisecond, conditionWrapper) if test.errExpected != err { t.Errorf("Expected error: %v, but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("Expected ConditionFunc to be invoked: %d times, but got: %d", test.attemptsExpected, attempts) } }) } } func Test_waitForWithContext(t *testing.T) { fakeErr := errors.New("fake error") tests := []struct { name string context func() (context.Context, context.CancelFunc) condition ConditionWithContextFunc waitFunc func() waitFunc attemptsExpected int errExpected error }{ { name: "condition returns done=true on first attempt, no retry is attempted", context: defaultContext, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return true, nil }), waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, attemptsExpected: 1, errExpected: nil, }, { name: "condition always returns done=false, timeout error expected", context: defaultContext, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, // the contract of waitForWithContext() says the func is called once more at the end of the wait attemptsExpected: 3, errExpected: ErrWaitTimeout, }, { name: "condition returns an error on first attempt, the error is returned", context: defaultContext, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, fakeErr }), waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, attemptsExpected: 1, errExpected: fakeErr, }, { name: "context is cancelled, context cancelled error expected", context: cancelledContext, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: func() waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) // never tick on this channel return ch } }, attemptsExpected: 0, errExpected: ErrWaitTimeout, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var attempts int conditionWrapper := func(ctx context.Context) (done bool, err error) { attempts++ return test.condition(ctx) } ticker := test.waitFunc() err := func() error { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() return waitForWithContext(ctx, ticker.WithContext(), conditionWrapper) }() if test.errExpected != err { t.Errorf("Expected error: %v, but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("Expected %d invocations, got %d", test.attemptsExpected, attempts) } }) } } func Test_poll(t *testing.T) { fakeErr := errors.New("fake error") tests := []struct { name string context func() (context.Context, context.CancelFunc) immediate bool waitFunc func() waitFunc condition ConditionWithContextFunc cancelContextAfter int attemptsExpected int errExpected error }{ { name: "immediate is true, condition returns an error", immediate: true, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, fakeErr }), waitFunc: nil, attemptsExpected: 1, errExpected: fakeErr, }, { name: "immediate is true, condition returns true", immediate: true, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return true, nil }), waitFunc: nil, attemptsExpected: 1, errExpected: nil, }, { name: "immediate is true, context is cancelled, condition return false", immediate: true, context: cancelledContext, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: nil, attemptsExpected: 1, errExpected: ErrWaitTimeout, }, { name: "immediate is false, context is cancelled", immediate: false, context: cancelledContext, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: nil, attemptsExpected: 0, errExpected: ErrWaitTimeout, }, { name: "immediate is false, condition returns an error", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, fakeErr }), waitFunc: func() waitFunc { return fakeTicker(5, nil, func() {}) }, attemptsExpected: 1, errExpected: fakeErr, }, { name: "immediate is false, condition returns true", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return true, nil }), waitFunc: func() waitFunc { return fakeTicker(5, nil, func() {}) }, attemptsExpected: 1, errExpected: nil, }, { name: "immediate is false, ticker channel is closed, condition returns true", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return true, nil }), waitFunc: func() waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) close(ch) return ch } }, attemptsExpected: 1, errExpected: nil, }, { name: "immediate is false, ticker channel is closed, condition returns error", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, fakeErr }), waitFunc: func() waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) close(ch) return ch } }, attemptsExpected: 1, errExpected: fakeErr, }, { name: "immediate is false, ticker channel is closed, condition returns false", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: func() waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) close(ch) return ch } }, attemptsExpected: 1, errExpected: ErrWaitTimeout, }, { name: "condition always returns false, timeout error expected", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, // the contract of waitForWithContext() says the func is called once more at the end of the wait attemptsExpected: 3, errExpected: ErrWaitTimeout, }, { name: "context is cancelled after N attempts, timeout error expected", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: func() waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) // just tick twice go func() { ch <- struct{}{} ch <- struct{}{} }() return ch } }, cancelContextAfter: 2, attemptsExpected: 2, errExpected: ErrWaitTimeout, }, { name: "context is cancelled after N attempts, context error not expected (legacy behavior)", immediate: false, condition: ConditionWithContextFunc(func(context.Context) (bool, error) { return false, nil }), waitFunc: func() waitFunc { return func(done <-chan struct{}) <-chan struct{} { ch := make(chan struct{}) // just tick twice go func() { ch <- struct{}{} ch <- struct{}{} }() return ch } }, cancelContextAfter: 2, attemptsExpected: 2, errExpected: ErrWaitTimeout, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var attempts int ticker := waitFunc(func(done <-chan struct{}) <-chan struct{} { return nil }) if test.waitFunc != nil { ticker = test.waitFunc() } err := func() error { contextFn := test.context if contextFn == nil { contextFn = defaultContext } ctx, cancel := contextFn() defer cancel() conditionWrapper := func(ctx context.Context) (done bool, err error) { attempts++ defer func() { if test.cancelContextAfter == attempts { cancel() } }() return test.condition(ctx) } return poll(ctx, test.immediate, ticker.WithContext(), conditionWrapper) }() if test.errExpected != err { t.Errorf("Expected error: %v, but got: %v", test.errExpected, err) } if test.attemptsExpected != attempts { t.Errorf("Expected %d invocations, got %d", test.attemptsExpected, attempts) } }) } } func Benchmark_poll(b *testing.B) { ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { attempts := 0 if err := poll(ctx, true, poller(time.Microsecond, 0), func(_ context.Context) (bool, error) { attempts++ return attempts >= 100, nil }); err != nil { b.Fatalf("unexpected err: %v", err) } } } golang-k8s-apimachinery-0.29.0/pkg/util/waitgroup/000077500000000000000000000000001453143165200220065ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/waitgroup/doc.go000066400000000000000000000014371453143165200231070ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package waitgroup implements SafeWaitGroup wrap of sync.WaitGroup. // Add with positive delta when waiting will fail, to prevent sync.WaitGroup race issue. package waitgroup // import "k8s.io/apimachinery/pkg/util/waitgroup" golang-k8s-apimachinery-0.29.0/pkg/util/waitgroup/ratelimited_waitgroup.go000066400000000000000000000073721453143165200267520ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package waitgroup import ( "context" "fmt" "sync" ) // RateLimiter abstracts the rate limiter used by RateLimitedSafeWaitGroup. // The implementation must be thread-safe. type RateLimiter interface { Wait(ctx context.Context) error } // RateLimiterFactoryFunc is used by the RateLimitedSafeWaitGroup to create a new // instance of a RateLimiter that will be used to rate limit the return rate // of the active number of request(s). 'count' is the number of requests in // flight that are expected to invoke 'Done' on this wait group. type RateLimiterFactoryFunc func(count int) (RateLimiter, context.Context, context.CancelFunc) // RateLimitedSafeWaitGroup must not be copied after first use. type RateLimitedSafeWaitGroup struct { wg sync.WaitGroup // Once Wait is initiated, all consecutive Done invocation will be // rate limited using this rate limiter. limiter RateLimiter stopCtx context.Context mu sync.Mutex // wait indicate whether Wait is called, if true, // then any Add with positive delta will return error. wait bool // number of request(s) currently using the wait group count int } // Add adds delta, which may be negative, similar to sync.WaitGroup. // If Add with a positive delta happens after Wait, it will return error, // which prevent unsafe Add. func (wg *RateLimitedSafeWaitGroup) Add(delta int) error { wg.mu.Lock() defer wg.mu.Unlock() if wg.wait && delta > 0 { return fmt.Errorf("add with positive delta after Wait is forbidden") } wg.wg.Add(delta) wg.count += delta return nil } // Done decrements the WaitGroup counter, rate limiting is applied only // when the wait group is in waiting mode. func (wg *RateLimitedSafeWaitGroup) Done() { var limiter RateLimiter func() { wg.mu.Lock() defer wg.mu.Unlock() wg.count -= 1 if wg.wait { // we are using the limiter outside the scope of the lock limiter = wg.limiter } }() defer wg.wg.Done() if limiter != nil { limiter.Wait(wg.stopCtx) } } // Wait blocks until the WaitGroup counter is zero or a hard limit has elapsed. // It returns the number of active request(s) accounted for at the time Wait // has been invoked, number of request(s) that have drianed (done using the // wait group immediately before Wait returns). // Ideally, the both numbers returned should be equal, to indicate that all // request(s) using the wait group have released their lock. func (wg *RateLimitedSafeWaitGroup) Wait(limiterFactory RateLimiterFactoryFunc) (int, int, error) { if limiterFactory == nil { return 0, 0, fmt.Errorf("rate limiter factory must be specified") } var cancel context.CancelFunc var countNow, countAfter int func() { wg.mu.Lock() defer wg.mu.Unlock() wg.limiter, wg.stopCtx, cancel = limiterFactory(wg.count) countNow = wg.count wg.wait = true }() defer cancel() // there should be a hard stop, in case request(s) are not responsive // enough to invoke Done before the grace period is over. waitDoneCh := make(chan struct{}) go func() { defer close(waitDoneCh) wg.wg.Wait() }() var err error select { case <-wg.stopCtx.Done(): err = wg.stopCtx.Err() case <-waitDoneCh: } func() { wg.mu.Lock() defer wg.mu.Unlock() countAfter = wg.count }() return countNow, countAfter, err } golang-k8s-apimachinery-0.29.0/pkg/util/waitgroup/ratelimited_waitgroup_test.go000066400000000000000000000210771453143165200300070ustar00rootroot00000000000000/* Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package waitgroup import ( "context" "strings" "sync" "testing" "time" "golang.org/x/time/rate" "k8s.io/apimachinery/pkg/util/wait" ) func TestRateLimitedSafeWaitGroup(t *testing.T) { // we want to keep track of how many times rate limiter Wait method is // being invoked, both before and after the wait group is in waiting mode. limiter := &limiterWrapper{} // we expect the context passed by the factory to be used var cancelInvoked int factory := &factory{ limiter: limiter, grace: 2 * time.Second, ctx: context.Background(), cancel: func() { cancelInvoked++ }, } target := &rateLimitedSafeWaitGroupWrapper{ RateLimitedSafeWaitGroup: &RateLimitedSafeWaitGroup{limiter: limiter}, } // two set of requests // - n1: this set will finish using this waitgroup before Wait is invoked // - n2: this set will be in flight after Wait is invoked n1, n2 := 100, 101 // so we know when all requests in n1 are done using the waitgroup n1DoneWG := sync.WaitGroup{} // so we know when all requests in n2 have called Add, // but not finished with the waitgroup yet. // this will allow the test to invoke 'Wait' once all requests // in n2 have called `Add`, but none has called `Done` yet. n2BeforeWaitWG := sync.WaitGroup{} // so we know when all requests in n2 have called Done and // are finished using the waitgroup n2DoneWG := sync.WaitGroup{} startCh, blockedCh := make(chan struct{}), make(chan struct{}) n1DoneWG.Add(n1) for i := 0; i < n1; i++ { go func() { defer n1DoneWG.Done() <-startCh target.Add(1) // let's finish using the waitgroup immediately target.Done() }() } n2BeforeWaitWG.Add(n2) n2DoneWG.Add(n2) for i := 0; i < n2; i++ { go func() { func() { defer n2BeforeWaitWG.Done() <-startCh target.Add(1) }() func() { defer n2DoneWG.Done() // let's wait for the test to instruct the requests in n2 // that it is time to finish using the waitgroup. <-blockedCh target.Done() }() }() } // initially the count should be zero if count := target.Count(); count != 0 { t.Errorf("expected count to be zero, but got: %d", count) } // start the test close(startCh) // wait for the first set of requests (n1) to be done n1DoneWG.Wait() // after the first set of requests (n1) are done, the count should be zero if invoked := limiter.invoked(); invoked != 0 { t.Errorf("expected no call to rate limiter before Wait is called, but got: %d", invoked) } // make sure all requetss in the second group (n2) have started using the // waitgroup (Add invoked) but no request is done using the waitgroup yet. n2BeforeWaitWG.Wait() // count should be n2, since every request in n2 is still using the waitgroup if count := target.Count(); count != n2 { t.Errorf("expected count to be: %d, but got: %d", n2, count) } // time for us to mark the waitgroup as `Waiting` waitDoneCh := make(chan waitResult) go func() { factory.grace = 2 * time.Second before, after, err := target.Wait(factory.NewRateLimiter) waitDoneCh <- waitResult{before: before, after: after, err: err} }() // make sure there is no flake in the test due to this race condition var waitingGot bool wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { if waiting := target.Waiting(); waiting { waitingGot = true return true, nil } return false, nil }) // verify that the waitgroup is in 'Waiting' mode if !waitingGot { t.Errorf("expected to be in waiting") } // we should not allow any new request to use this waitgroup any longer if err := target.Add(1); err == nil || !strings.Contains(err.Error(), "add with positive delta after Wait is forbidden") { t.Errorf("expected Add to return error while in waiting mode: %v", err) } // make sure that RateLimitedSafeWaitGroup passes the right // request count to the limiter factory. if factory.countGot != n2 { t.Errorf("expected count passed to factory to be: %d, but got: %d", n2, factory.countGot) } // indicate to all requests (each request in n2) that are // currently using this waitgroup that they can go ahead // and invoke 'Done' to finish using this waitgroup. close(blockedCh) n2DoneWG.Wait() if invoked := limiter.invoked(); invoked != n2 { t.Errorf("expected rate limiter to be called %d times, but got: %d", n2, invoked) } waitResult := <-waitDoneCh if count := target.Count(); count != 0 { t.Errorf("expected count to be zero, but got: %d", count) } if waitResult.before != n2 { t.Errorf("expected count before Wait to be: %d, but got: %d", n2, waitResult.before) } if waitResult.after != 0 { t.Errorf("expected count after Wait to be zero, but got: %d", waitResult.after) } if cancelInvoked != 1 { t.Errorf("expected context cancel to be invoked once, but got: %d", cancelInvoked) } } func TestRateLimitedSafeWaitGroupWithHardTimeout(t *testing.T) { target := &rateLimitedSafeWaitGroupWrapper{ RateLimitedSafeWaitGroup: &RateLimitedSafeWaitGroup{}, } n := 10 wg := sync.WaitGroup{} wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() target.Add(1) }() } wg.Wait() if count := target.Count(); count != n { t.Errorf("expected count to be: %d, but got: %d", n, count) } ctx, cancel := context.WithCancel(context.Background()) cancel() activeAt, activeNow, err := target.Wait(func(count int) (RateLimiter, context.Context, context.CancelFunc) { return nil, ctx, cancel }) if activeAt != n { t.Errorf("expected active at Wait to be: %d, but got: %d", n, activeAt) } if activeNow != n { t.Errorf("expected active after Wait to be: %d, but got: %d", n, activeNow) } if err != context.Canceled { t.Errorf("expected error: %v, but got: %v", context.Canceled, err) } } func TestRateLimitedSafeWaitGroupWithBurstOfOne(t *testing.T) { target := &rateLimitedSafeWaitGroupWrapper{ RateLimitedSafeWaitGroup: &RateLimitedSafeWaitGroup{}, } n := 200 grace := 5 * time.Second wg := sync.WaitGroup{} wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() target.Add(1) }() } wg.Wait() waitingCh := make(chan struct{}) wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() <-waitingCh target.Done() }() } defer wg.Wait() now := time.Now() t.Logf("Wait starting, N=%d, grace: %s, at: %s", n, grace, now) activeAt, activeNow, err := target.Wait(func(count int) (RateLimiter, context.Context, context.CancelFunc) { defer close(waitingCh) // no deadline in context, Wait will wait forever, we want to measure // how long it takes for the requests to drain. return rate.NewLimiter(rate.Limit(n/int(grace.Seconds())), 1), context.Background(), func() {} }) took := time.Since(now) t.Logf("Wait finished, count(before): %d, count(after): %d, took: %s, err: %v", activeAt, activeNow, took, err) // in CPU starved environment, the go routines may not finish in time if took > 2*grace { t.Errorf("expected Wait to take: %s, but it took: %s", grace, took) } } type waitResult struct { before, after int err error } type rateLimitedSafeWaitGroupWrapper struct { *RateLimitedSafeWaitGroup } // used by test only func (wg *rateLimitedSafeWaitGroupWrapper) Count() int { wg.mu.Lock() defer wg.mu.Unlock() return wg.count } func (wg *rateLimitedSafeWaitGroupWrapper) Waiting() bool { wg.mu.Lock() defer wg.mu.Unlock() return wg.wait } type limiterWrapper struct { delegate RateLimiter lock sync.Mutex invokedN int } func (w *limiterWrapper) invoked() int { w.lock.Lock() defer w.lock.Unlock() return w.invokedN } func (w *limiterWrapper) Wait(ctx context.Context) error { w.lock.Lock() w.invokedN++ w.lock.Unlock() if w.delegate != nil { w.delegate.Wait(ctx) } return nil } type factory struct { limiter *limiterWrapper grace time.Duration ctx context.Context cancel context.CancelFunc countGot int } func (f *factory) NewRateLimiter(count int) (RateLimiter, context.Context, context.CancelFunc) { f.countGot = count f.limiter.delegate = rate.NewLimiter(rate.Limit(count/int(f.grace.Seconds())), 20) return f.limiter, f.ctx, f.cancel } golang-k8s-apimachinery-0.29.0/pkg/util/waitgroup/waitgroup.go000066400000000000000000000027201453143165200243570ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package waitgroup import ( "fmt" "sync" ) // SafeWaitGroup must not be copied after first use. type SafeWaitGroup struct { wg sync.WaitGroup mu sync.RWMutex // wait indicate whether Wait is called, if true, // then any Add with positive delta will return error. wait bool } // Add adds delta, which may be negative, similar to sync.WaitGroup. // If Add with a positive delta happens after Wait, it will return error, // which prevent unsafe Add. func (wg *SafeWaitGroup) Add(delta int) error { wg.mu.RLock() defer wg.mu.RUnlock() if wg.wait && delta > 0 { return fmt.Errorf("add with positive delta after Wait is forbidden") } wg.wg.Add(delta) return nil } // Done decrements the WaitGroup counter. func (wg *SafeWaitGroup) Done() { wg.wg.Done() } // Wait blocks until the WaitGroup counter is zero. func (wg *SafeWaitGroup) Wait() { wg.mu.Lock() wg.wait = true wg.mu.Unlock() wg.wg.Wait() } golang-k8s-apimachinery-0.29.0/pkg/util/waitgroup/waitgroup_test.go000066400000000000000000000026141453143165200254200ustar00rootroot00000000000000/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package waitgroup test cases reference golang sync.WaitGroup https://golang.org/src/sync/waitgroup_test.go. package waitgroup import ( "testing" ) func TestWaitGroup(t *testing.T) { wg1 := &SafeWaitGroup{} wg2 := &SafeWaitGroup{} n := 16 wg1.Add(n) wg2.Add(n) exited := make(chan bool, n) for i := 0; i != n; i++ { go func(i int) { wg1.Done() wg2.Wait() exited <- true }(i) } wg1.Wait() for i := 0; i != n; i++ { select { case <-exited: t.Fatal("SafeWaitGroup released group too soon") default: } wg2.Done() } for i := 0; i != n; i++ { <-exited // Will block if barrier fails to unlock someone. } } func TestWaitGroupAddFail(t *testing.T) { wg := &SafeWaitGroup{} wg.Add(1) wg.Done() wg.Wait() if err := wg.Add(1); err == nil { t.Errorf("Should return error when add positive after Wait") } } golang-k8s-apimachinery-0.29.0/pkg/util/yaml/000077500000000000000000000000001453143165200207275ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/util/yaml/decoder.go000066400000000000000000000243741453143165200226750ustar00rootroot00000000000000/* 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 yaml import ( "bufio" "bytes" "encoding/json" "fmt" "io" "strings" "unicode" jsonutil "k8s.io/apimachinery/pkg/util/json" "sigs.k8s.io/yaml" ) // Unmarshal unmarshals the given data // If v is a *map[string]interface{}, *[]interface{}, or *interface{} numbers // are converted to int64 or float64 func Unmarshal(data []byte, v interface{}) error { preserveIntFloat := func(d *json.Decoder) *json.Decoder { d.UseNumber() return d } switch v := v.(type) { case *map[string]interface{}: if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil { return err } return jsonutil.ConvertMapNumbers(*v, 0) case *[]interface{}: if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil { return err } return jsonutil.ConvertSliceNumbers(*v, 0) case *interface{}: if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil { return err } return jsonutil.ConvertInterfaceNumbers(v, 0) default: return yaml.Unmarshal(data, v) } } // UnmarshalStrict unmarshals the given data // strictly (erroring when there are duplicate fields). func UnmarshalStrict(data []byte, v interface{}) error { preserveIntFloat := func(d *json.Decoder) *json.Decoder { d.UseNumber() return d } switch v := v.(type) { case *map[string]interface{}: if err := yaml.UnmarshalStrict(data, v, preserveIntFloat); err != nil { return err } return jsonutil.ConvertMapNumbers(*v, 0) case *[]interface{}: if err := yaml.UnmarshalStrict(data, v, preserveIntFloat); err != nil { return err } return jsonutil.ConvertSliceNumbers(*v, 0) case *interface{}: if err := yaml.UnmarshalStrict(data, v, preserveIntFloat); err != nil { return err } return jsonutil.ConvertInterfaceNumbers(v, 0) default: return yaml.UnmarshalStrict(data, v) } } // ToJSON converts a single YAML document into a JSON document // or returns an error. If the document appears to be JSON the // YAML decoding path is not used (so that error messages are // JSON specific). func ToJSON(data []byte) ([]byte, error) { if hasJSONPrefix(data) { return data, nil } return yaml.YAMLToJSON(data) } // YAMLToJSONDecoder decodes YAML documents from an io.Reader by // separating individual documents. It first converts the YAML // body to JSON, then unmarshals the JSON. type YAMLToJSONDecoder struct { reader Reader } // NewYAMLToJSONDecoder decodes YAML documents from the provided // stream in chunks by converting each document (as defined by // the YAML spec) into its own chunk, converting it to JSON via // yaml.YAMLToJSON, and then passing it to json.Decoder. func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder { reader := bufio.NewReader(r) return &YAMLToJSONDecoder{ reader: NewYAMLReader(reader), } } // Decode reads a YAML document as JSON from the stream or returns // an error. The decoding rules match json.Unmarshal, not // yaml.Unmarshal. func (d *YAMLToJSONDecoder) Decode(into interface{}) error { bytes, err := d.reader.Read() if err != nil && err != io.EOF { return err } if len(bytes) != 0 { err := yaml.Unmarshal(bytes, into) if err != nil { return YAMLSyntaxError{err} } } return err } // YAMLDecoder reads chunks of objects and returns ErrShortBuffer if // the data is not sufficient. type YAMLDecoder struct { r io.ReadCloser scanner *bufio.Scanner remaining []byte } // NewDocumentDecoder decodes YAML documents from the provided // stream in chunks by converting each document (as defined by // the YAML spec) into its own chunk. io.ErrShortBuffer will be // returned if the entire buffer could not be read to assist // the caller in framing the chunk. func NewDocumentDecoder(r io.ReadCloser) io.ReadCloser { scanner := bufio.NewScanner(r) // the size of initial allocation for buffer 4k buf := make([]byte, 4*1024) // the maximum size used to buffer a token 5M scanner.Buffer(buf, 5*1024*1024) scanner.Split(splitYAMLDocument) return &YAMLDecoder{ r: r, scanner: scanner, } } // Read reads the previous slice into the buffer, or attempts to read // the next chunk. // TODO: switch to readline approach. func (d *YAMLDecoder) Read(data []byte) (n int, err error) { left := len(d.remaining) if left == 0 { // return the next chunk from the stream if !d.scanner.Scan() { err := d.scanner.Err() if err == nil { err = io.EOF } return 0, err } out := d.scanner.Bytes() d.remaining = out left = len(out) } // fits within data if left <= len(data) { copy(data, d.remaining) d.remaining = nil return left, nil } // caller will need to reread copy(data, d.remaining[:len(data)]) d.remaining = d.remaining[len(data):] return len(data), io.ErrShortBuffer } func (d *YAMLDecoder) Close() error { return d.r.Close() } const yamlSeparator = "\n---" const separator = "---" // splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents. func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } sep := len([]byte(yamlSeparator)) if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 { // We have a potential document terminator i += sep after := data[i:] if len(after) == 0 { // we can't read any more characters if atEOF { return len(data), data[:len(data)-sep], nil } return 0, nil, nil } if j := bytes.IndexByte(after, '\n'); j >= 0 { return i + j + 1, data[0 : i-sep], nil } return 0, nil, nil } // If we're at EOF, we have a final, non-terminated line. Return it. if atEOF { return len(data), data, nil } // Request more data. return 0, nil, nil } // decoder is a convenience interface for Decode. type decoder interface { Decode(into interface{}) error } // YAMLOrJSONDecoder attempts to decode a stream of JSON documents or // YAML documents by sniffing for a leading { character. type YAMLOrJSONDecoder struct { r io.Reader bufferSize int decoder decoder } type JSONSyntaxError struct { Offset int64 Err error } func (e JSONSyntaxError) Error() string { return fmt.Sprintf("json: offset %d: %s", e.Offset, e.Err.Error()) } type YAMLSyntaxError struct { err error } func (e YAMLSyntaxError) Error() string { return e.err.Error() } // NewYAMLOrJSONDecoder returns a decoder that will process YAML documents // or JSON documents from the given reader as a stream. bufferSize determines // how far into the stream the decoder will look to figure out whether this // is a JSON stream (has whitespace followed by an open brace). func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder { return &YAMLOrJSONDecoder{ r: r, bufferSize: bufferSize, } } // Decode unmarshals the next object from the underlying stream into the // provide object, or returns an error. func (d *YAMLOrJSONDecoder) Decode(into interface{}) error { if d.decoder == nil { buffer, _, isJSON := GuessJSONStream(d.r, d.bufferSize) if isJSON { d.decoder = json.NewDecoder(buffer) } else { d.decoder = NewYAMLToJSONDecoder(buffer) } } err := d.decoder.Decode(into) if syntax, ok := err.(*json.SyntaxError); ok { return JSONSyntaxError{ Offset: syntax.Offset, Err: syntax, } } return err } type Reader interface { Read() ([]byte, error) } type YAMLReader struct { reader Reader } func NewYAMLReader(r *bufio.Reader) *YAMLReader { return &YAMLReader{ reader: &LineReader{reader: r}, } } // Read returns a full YAML document. func (r *YAMLReader) Read() ([]byte, error) { var buffer bytes.Buffer for { line, err := r.reader.Read() if err != nil && err != io.EOF { return nil, err } sep := len([]byte(separator)) if i := bytes.Index(line, []byte(separator)); i == 0 { // We have a potential document terminator i += sep trimmed := strings.TrimSpace(string(line[i:])) // We only allow comments and spaces following the yaml doc separator, otherwise we'll return an error if len(trimmed) > 0 && string(trimmed[0]) != "#" { return nil, YAMLSyntaxError{ err: fmt.Errorf("invalid Yaml document separator: %s", trimmed), } } if buffer.Len() != 0 { return buffer.Bytes(), nil } if err == io.EOF { return nil, err } } if err == io.EOF { if buffer.Len() != 0 { // If we're at EOF, we have a final, non-terminated line. Return it. return buffer.Bytes(), nil } return nil, err } buffer.Write(line) } } type LineReader struct { reader *bufio.Reader } // Read returns a single line (with '\n' ended) from the underlying reader. // An error is returned iff there is an error with the underlying reader. func (r *LineReader) Read() ([]byte, error) { var ( isPrefix bool = true err error = nil line []byte buffer bytes.Buffer ) for isPrefix && err == nil { line, isPrefix, err = r.reader.ReadLine() buffer.Write(line) } buffer.WriteByte('\n') return buffer.Bytes(), err } // GuessJSONStream scans the provided reader up to size, looking // for an open brace indicating this is JSON. It will return the // bufio.Reader it creates for the consumer. func GuessJSONStream(r io.Reader, size int) (io.Reader, []byte, bool) { buffer := bufio.NewReaderSize(r, size) b, _ := buffer.Peek(size) return buffer, b, hasJSONPrefix(b) } // IsJSONBuffer scans the provided buffer, looking // for an open brace indicating this is JSON. func IsJSONBuffer(buf []byte) bool { return hasJSONPrefix(buf) } var jsonPrefix = []byte("{") // hasJSONPrefix returns true if the provided buffer appears to start with // a JSON open brace. func hasJSONPrefix(buf []byte) bool { return hasPrefix(buf, jsonPrefix) } // Return true if the first non-whitespace bytes in buf is // prefix. func hasPrefix(buf []byte, prefix []byte) bool { trim := bytes.TrimLeftFunc(buf, unicode.IsSpace) return bytes.HasPrefix(trim, prefix) } golang-k8s-apimachinery-0.29.0/pkg/util/yaml/decoder_test.go000066400000000000000000000305711453143165200237300ustar00rootroot00000000000000/* 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 yaml import ( "bufio" "bytes" "encoding/json" "fmt" "io" "math/rand" "reflect" "strings" "testing" ) func TestYAMLDecoderReadBytesLength(t *testing.T) { d := `--- stuff: 1 test-foo: 1 ` testCases := []struct { bufLen int expectLen int expectErr error }{ {len(d), len(d), nil}, {len(d) + 10, len(d), nil}, {len(d) - 10, len(d) - 10, io.ErrShortBuffer}, } for i, testCase := range testCases { r := NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(d)))) b := make([]byte, testCase.bufLen) n, err := r.Read(b) if err != testCase.expectErr || n != testCase.expectLen { t.Fatalf("%d: unexpected body: %d / %v", i, n, err) } } } func TestBigYAML(t *testing.T) { d := ` stuff: 1 ` maxLen := 5 * 1024 * 1024 bufferLen := 4 * 1024 // maxLen 5 M dd := strings.Repeat(d, 512*1024) r := NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(dd[:maxLen-1])))) b := make([]byte, bufferLen) n, err := r.Read(b) if err != io.ErrShortBuffer { t.Fatalf("expected ErrShortBuffer: %d / %v", n, err) } b = make([]byte, maxLen) n, err = r.Read(b) if err != nil { t.Fatalf("expected nil: %d / %v", n, err) } r = NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(dd)))) b = make([]byte, maxLen) n, err = r.Read(b) if err != bufio.ErrTooLong { t.Fatalf("bufio.Scanner: token too long: %d / %v", n, err) } } func TestYAMLDecoderCallsAfterErrShortBufferRestOfFrame(t *testing.T) { d := `--- stuff: 1 test-foo: 1` r := NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(d)))) b := make([]byte, 12) n, err := r.Read(b) if err != io.ErrShortBuffer || n != 12 { t.Fatalf("expected ErrShortBuffer: %d / %v", n, err) } expected := "---\nstuff: 1" if string(b) != expected { t.Fatalf("expected bytes read to be: %s got: %s", expected, string(b)) } b = make([]byte, 13) n, err = r.Read(b) if err != nil || n != 13 { t.Fatalf("expected nil: %d / %v", n, err) } expected = "\n\ttest-foo: 1" if string(b) != expected { t.Fatalf("expected bytes read to be: '%s' got: '%s'", expected, string(b)) } b = make([]byte, 15) n, err = r.Read(b) if err != io.EOF || n != 0 { t.Fatalf("expected EOF: %d / %v", n, err) } } func TestSplitYAMLDocument(t *testing.T) { testCases := []struct { input string atEOF bool expect string adv int }{ {"foo", true, "foo", 3}, {"fo", false, "", 0}, {"---", true, "---", 3}, {"---\n", true, "---\n", 4}, {"---\n", false, "", 0}, {"\n---\n", false, "", 5}, {"\n---\n", true, "", 5}, {"abc\n---\ndef", true, "abc", 8}, {"def", true, "def", 3}, {"", true, "", 0}, } for i, testCase := range testCases { adv, token, err := splitYAMLDocument([]byte(testCase.input), testCase.atEOF) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } if adv != testCase.adv { t.Errorf("%d: advance did not match: %d %d", i, testCase.adv, adv) } if testCase.expect != string(token) { t.Errorf("%d: token did not match: %q %q", i, testCase.expect, string(token)) } } } func TestGuessJSON(t *testing.T) { if r, _, isJSON := GuessJSONStream(bytes.NewReader([]byte(" \n{}")), 100); !isJSON { t.Fatalf("expected stream to be JSON") } else { b := make([]byte, 30) n, err := r.Read(b) if err != nil || n != 4 { t.Fatalf("unexpected body: %d / %v", n, err) } if string(b[:n]) != " \n{}" { t.Fatalf("unexpected body: %q", string(b[:n])) } } } func TestScanYAML(t *testing.T) { s := bufio.NewScanner(bytes.NewReader([]byte(`--- stuff: 1 --- `))) s.Split(splitYAMLDocument) if !s.Scan() { t.Fatalf("should have been able to scan") } t.Logf("scan: %s", s.Text()) if !s.Scan() { t.Fatalf("should have been able to scan") } t.Logf("scan: %s", s.Text()) if s.Scan() { t.Fatalf("scan should have been done") } if s.Err() != nil { t.Fatalf("err should have been nil: %v", s.Err()) } } func TestDecodeYAML(t *testing.T) { s := NewYAMLToJSONDecoder(bytes.NewReader([]byte(`--- stuff: 1 --- `))) obj := generic{} if err := s.Decode(&obj); err != nil { t.Fatalf("unexpected error: %v", err) } if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":1}` { t.Errorf("unexpected object: %#v", obj) } obj = generic{} if err := s.Decode(&obj); err != nil { t.Fatalf("unexpected error: %v", err) } if len(obj) != 0 { t.Fatalf("unexpected object: %#v", obj) } obj = generic{} if err := s.Decode(&obj); err != io.EOF { t.Fatalf("unexpected error: %v", err) } } func TestDecodeYAMLSeparatorValidation(t *testing.T) { s := NewYAMLToJSONDecoder(bytes.NewReader([]byte(`--- stuff: 1 --- # Make sure termination happen with inline comment stuff: 2 --- stuff: 3 --- Make sure uncommented content results YAMLSyntaxError `))) obj := generic{} if err := s.Decode(&obj); err != nil { t.Fatalf("unexpected error: %v", err) } if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":1}` { t.Errorf("unexpected object: %#v", obj) } obj = generic{} if err := s.Decode(&obj); err != nil { t.Fatalf("unexpected error: %v", err) } if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":2}` { t.Errorf("unexpected object: %#v", obj) } obj = generic{} err := s.Decode(&obj) if err == nil { t.Fatalf("expected YamlSyntaxError, got nil instead") } if _, ok := err.(YAMLSyntaxError); !ok { t.Fatalf("unexpected error: %v", err) } } func TestDecodeBrokenYAML(t *testing.T) { s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`--- stuff: 1 test-foo: 1 --- `)), 100) obj := generic{} err := s.Decode(&obj) if err == nil { t.Fatal("expected error with yaml: violate, got no error") } fmt.Printf("err: %s\n", err.Error()) if !strings.Contains(err.Error(), "yaml: line 3:") { t.Fatalf("expected %q to have 'yaml: line 3:' found a tab character", err.Error()) } } func TestDecodeBrokenJSON(t *testing.T) { s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`{ "foo": { "stuff": 1 "otherStuff": 2 } } `)), 100) obj := generic{} err := s.Decode(&obj) if err == nil { t.Fatal("expected error with json: prefix, got no error") } const msg = `json: offset 28: invalid character '"' after object key:value pair` if msg != err.Error() { t.Fatalf("expected %q, got %q", msg, err.Error()) } } type generic map[string]interface{} func TestYAMLOrJSONDecoder(t *testing.T) { testCases := []struct { input string buffer int isJSON bool err bool out []generic }{ {` {"1":2}{"3":4}`, 2, true, false, []generic{ {"1": 2}, {"3": 4}, }}, {" \n{}", 3, true, false, []generic{ {}, }}, {" \na: b", 2, false, false, []generic{ {"a": "b"}, }}, {" \n{\"a\": \"b\"}", 2, false, true, []generic{ {"a": "b"}, }}, {" \n{\"a\": \"b\"}", 3, true, false, []generic{ {"a": "b"}, }}, {` {"a":"b"}`, 100, true, false, []generic{ {"a": "b"}, }}, {"", 1, false, false, []generic{}}, {"foo: bar\n---\nbaz: biz", 100, false, false, []generic{ {"foo": "bar"}, {"baz": "biz"}, }}, {"---\nfoo: bar\n--- # with Comment\nbaz: biz", 100, false, false, []generic{ {"foo": "bar"}, {"baz": "biz"}, }}, {"foo: bar\n---\n", 100, false, false, []generic{ {"foo": "bar"}, }}, {"foo: bar\n---", 100, false, false, []generic{ {"foo": "bar"}, }}, {"foo: bar\n--", 100, false, true, []generic{ {"foo": "bar"}, }}, {"foo: bar\n-", 100, false, true, []generic{ {"foo": "bar"}, }}, {"foo: bar\n", 100, false, false, []generic{ {"foo": "bar"}, }}, } for i, testCase := range testCases { decoder := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(testCase.input)), testCase.buffer) objs := []generic{} var err error for { out := make(generic) err = decoder.Decode(&out) if err != nil { break } objs = append(objs, out) } if err != io.EOF { switch { case testCase.err && err == nil: t.Errorf("%d: unexpected non-error", i) continue case !testCase.err && err != nil: t.Errorf("%d: unexpected error: %v", i, err) continue case err != nil: continue } } switch decoder.decoder.(type) { case *YAMLToJSONDecoder: if testCase.isJSON { t.Errorf("%d: expected JSON decoder, got YAML", i) } case *json.Decoder: if !testCase.isJSON { t.Errorf("%d: expected YAML decoder, got JSON", i) } } if fmt.Sprintf("%#v", testCase.out) != fmt.Sprintf("%#v", objs) { t.Errorf("%d: objects were not equal: \n%#v\n%#v", i, testCase.out, objs) } } } func TestReadSingleLongLine(t *testing.T) { testReadLines(t, []int{128 * 1024}) } func TestReadRandomLineLengths(t *testing.T) { minLength := 100 maxLength := 96 * 1024 maxLines := 100 lineLengths := make([]int, maxLines) for i := 0; i < maxLines; i++ { lineLengths[i] = rand.Intn(maxLength-minLength) + minLength } testReadLines(t, lineLengths) } func testReadLines(t *testing.T, lineLengths []int) { var ( lines [][]byte inputStream []byte ) for _, lineLength := range lineLengths { inputLine := make([]byte, lineLength+1) for i := 0; i < lineLength; i++ { char := rand.Intn('z'-'A') + 'A' inputLine[i] = byte(char) } inputLine[len(inputLine)-1] = '\n' lines = append(lines, inputLine) } for _, line := range lines { inputStream = append(inputStream, line...) } // init Reader reader := bufio.NewReader(bytes.NewReader(inputStream)) lineReader := &LineReader{reader: reader} // read lines var readLines [][]byte for range lines { bytes, err := lineReader.Read() if err != nil && err != io.EOF { t.Fatalf("failed to read lines: %v", err) } readLines = append(readLines, bytes) } // validate for i := range lines { if len(lines[i]) != len(readLines[i]) { t.Fatalf("expected line length: %d, but got %d", len(lines[i]), len(readLines[i])) } if !reflect.DeepEqual(lines[i], readLines[i]) { t.Fatalf("expected line: %v, but got %v", lines[i], readLines[i]) } } } func TestTypedJSONOrYamlErrors(t *testing.T) { s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`{ "foo": { "stuff": 1 "otherStuff": 2 } } `)), 100) obj := generic{} err := s.Decode(&obj) if err == nil { t.Fatal("expected error with json: prefix, got no error") } if _, ok := err.(JSONSyntaxError); !ok { t.Fatalf("expected %q to be of type JSONSyntaxError", err.Error()) } s = NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`--- stuff: 1 test-foo: 1 --- `)), 100) obj = generic{} err = s.Decode(&obj) if err == nil { t.Fatal("expected error with yaml: prefix, got no error") } if _, ok := err.(YAMLSyntaxError); !ok { t.Fatalf("expected %q to be of type YAMLSyntaxError", err.Error()) } } func TestUnmarshal(t *testing.T) { mapWithIntegerBytes := []byte(`replicas: 1`) mapWithInteger := make(map[string]interface{}) if err := Unmarshal(mapWithIntegerBytes, &mapWithInteger); err != nil { t.Fatalf("unexpected error unmarshaling yaml: %v", err) } if _, ok := mapWithInteger["replicas"].(int64); !ok { t.Fatalf(`Expected number in map to be int64 but got "%T"`, mapWithInteger["replicas"]) } sliceWithIntegerBytes := []byte(`- 1`) var sliceWithInteger []interface{} if err := Unmarshal(sliceWithIntegerBytes, &sliceWithInteger); err != nil { t.Fatalf("unexpected error unmarshaling yaml: %v", err) } if _, ok := sliceWithInteger[0].(int64); !ok { t.Fatalf(`Expected number in slice to be int64 but got "%T"`, sliceWithInteger[0]) } integerBytes := []byte(`1`) var integer interface{} if err := Unmarshal(integerBytes, &integer); err != nil { t.Fatalf("unexpected error unmarshaling yaml: %v", err) } if _, ok := integer.(int64); !ok { t.Fatalf(`Expected number to be int64 but got "%T"`, integer) } otherTypeBytes := []byte(`123: 2`) otherType := make(map[int]interface{}) if err := Unmarshal(otherTypeBytes, &otherType); err != nil { t.Fatalf("unexpected error unmarshaling yaml: %v", err) } if _, ok := otherType[123].(int64); ok { t.Fatalf(`Expected number not to be converted to int64`) } if _, ok := otherType[123].(float64); !ok { t.Fatalf(`Expected number to be float64 but got "%T"`, otherType[123]) } } golang-k8s-apimachinery-0.29.0/pkg/version/000077500000000000000000000000001453143165200204755ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/version/doc.go000066400000000000000000000013471453143165200215760ustar00rootroot00000000000000/* 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. */ // +k8s:openapi-gen=true // Package version supplies the type for version information collected at build time. package version // import "k8s.io/apimachinery/pkg/version" golang-k8s-apimachinery-0.29.0/pkg/version/helpers.go000066400000000000000000000045221453143165200224710ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package version import ( "regexp" "strconv" "strings" ) type versionType int const ( // Bigger the version type number, higher priority it is versionTypeAlpha versionType = iota versionTypeBeta versionTypeGA ) var kubeVersionRegex = regexp.MustCompile("^v([\\d]+)(?:(alpha|beta)([\\d]+))?$") func parseKubeVersion(v string) (majorVersion int, vType versionType, minorVersion int, ok bool) { var err error submatches := kubeVersionRegex.FindStringSubmatch(v) if len(submatches) != 4 { return 0, 0, 0, false } switch submatches[2] { case "alpha": vType = versionTypeAlpha case "beta": vType = versionTypeBeta case "": vType = versionTypeGA default: return 0, 0, 0, false } if majorVersion, err = strconv.Atoi(submatches[1]); err != nil { return 0, 0, 0, false } if vType != versionTypeGA { if minorVersion, err = strconv.Atoi(submatches[3]); err != nil { return 0, 0, 0, false } } return majorVersion, vType, minorVersion, true } // CompareKubeAwareVersionStrings compares two kube-like version strings. // Kube-like version strings are starting with a v, followed by a major version, optional "alpha" or "beta" strings // followed by a minor version (e.g. v1, v2beta1). Versions will be sorted based on GA/alpha/beta first and then major // and minor versions. e.g. v2, v1, v1beta2, v1beta1, v1alpha1. func CompareKubeAwareVersionStrings(v1, v2 string) int { if v1 == v2 { return 0 } v1major, v1type, v1minor, ok1 := parseKubeVersion(v1) v2major, v2type, v2minor, ok2 := parseKubeVersion(v2) switch { case !ok1 && !ok2: return strings.Compare(v2, v1) case !ok1 && ok2: return -1 case ok1 && !ok2: return 1 } if v1type != v2type { return int(v1type) - int(v2type) } if v1major != v2major { return v1major - v2major } return v1minor - v2minor } golang-k8s-apimachinery-0.29.0/pkg/version/helpers_test.go000066400000000000000000000064051453143165200235320ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package version import ( "testing" ) func TestCompareKubeAwareVersionStrings(t *testing.T) { tests := []*struct { v1, v2 string expectedGreater bool }{ {"v1", "v2", false}, {"v2", "v1", true}, {"v10", "v2", true}, {"v1", "v2alpha1", true}, {"v1", "v2beta1", true}, {"v1alpha2", "v1alpha1", true}, {"v1beta1", "v2alpha3", true}, {"v1alpha10", "v1alpha2", true}, {"v1beta10", "v1beta2", true}, {"foo", "v1beta2", false}, {"bar", "foo", true}, {"version1", "version2", true}, // Non kube-like versions are sorted alphabetically {"version1", "version10", true}, // Non kube-like versions are sorted alphabetically } for _, tc := range tests { if e, a := tc.expectedGreater, CompareKubeAwareVersionStrings(tc.v1, tc.v2) > 0; e != a { if e { t.Errorf("expected %s to be greater than %s", tc.v1, tc.v2) } else { t.Errorf("expected %s to be less than %s", tc.v1, tc.v2) } } } } func Test_parseKubeVersion(t *testing.T) { tests := []struct { name string v string wantMajorVersion int wantVType versionType wantMinorVersion int wantOk bool }{ { name: "invaild version for ga", v: "v1.1", wantMajorVersion: 0, wantVType: 0, wantMinorVersion: 0, wantOk: false, }, { name: "invaild version for alpha", v: "v1alpha1.1", wantMajorVersion: 0, wantVType: 0, wantMinorVersion: 0, wantOk: false, }, { name: "alpha version", v: "v1alpha1", wantMajorVersion: 1, wantVType: 0, wantMinorVersion: 1, wantOk: true, }, { name: "beta version", v: "v2beta10", wantMajorVersion: 2, wantVType: 1, wantMinorVersion: 10, wantOk: true, }, { name: "ga version", v: "v3", wantMajorVersion: 3, wantVType: 2, wantMinorVersion: 0, wantOk: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotMajorVersion, gotVType, gotMinorVersion, gotOk := parseKubeVersion(tt.v) if gotMajorVersion != tt.wantMajorVersion { t.Errorf("parseKubeVersion() gotMajorVersion = %v, want %v", gotMajorVersion, tt.wantMajorVersion) } if gotVType != tt.wantVType { t.Errorf("parseKubeVersion() gotVType = %v, want %v", gotVType, tt.wantVType) } if gotMinorVersion != tt.wantMinorVersion { t.Errorf("parseKubeVersion() gotMinorVersion = %v, want %v", gotMinorVersion, tt.wantMinorVersion) } if gotOk != tt.wantOk { t.Errorf("parseKubeVersion() gotOk = %v, want %v", gotOk, tt.wantOk) } }) } } golang-k8s-apimachinery-0.29.0/pkg/version/types.go000066400000000000000000000023331453143165200221710ustar00rootroot00000000000000/* 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 version // Info contains versioning information. // TODO: Add []string of api versions supported? It's still unclear // how we'll want to distribute that information. type Info struct { Major string `json:"major"` Minor string `json:"minor"` GitVersion string `json:"gitVersion"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitVersion } golang-k8s-apimachinery-0.29.0/pkg/watch/000077500000000000000000000000001453143165200201165ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/pkg/watch/doc.go000066400000000000000000000013521453143165200212130ustar00rootroot00000000000000/* 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 watch contains a generic watchable interface, and a fake for // testing code that uses the watch interface. package watch // import "k8s.io/apimachinery/pkg/watch" golang-k8s-apimachinery-0.29.0/pkg/watch/filter.go000066400000000000000000000054251453143165200217400ustar00rootroot00000000000000/* 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 watch import ( "sync" ) // FilterFunc should take an event, possibly modify it in some way, and return // the modified event. If the event should be ignored, then return keep=false. type FilterFunc func(in Event) (out Event, keep bool) // Filter passes all events through f before allowing them to pass on. // Putting a filter on a watch, as an unavoidable side-effect due to the way // go channels work, effectively causes the watch's event channel to have its // queue length increased by one. // // WARNING: filter has a fatal flaw, in that it can't properly update the // Type field (Add/Modified/Deleted) to reflect items beginning to pass the // filter when they previously didn't. func Filter(w Interface, f FilterFunc) Interface { fw := &filteredWatch{ incoming: w, result: make(chan Event), f: f, } go fw.loop() return fw } type filteredWatch struct { incoming Interface result chan Event f FilterFunc } // ResultChan returns a channel which will receive filtered events. func (fw *filteredWatch) ResultChan() <-chan Event { return fw.result } // Stop stops the upstream watch, which will eventually stop this watch. func (fw *filteredWatch) Stop() { fw.incoming.Stop() } // loop waits for new values, filters them, and resends them. func (fw *filteredWatch) loop() { defer close(fw.result) for event := range fw.incoming.ResultChan() { filtered, keep := fw.f(event) if keep { fw.result <- filtered } } } // Recorder records all events that are sent from the watch until it is closed. type Recorder struct { Interface lock sync.Mutex events []Event } var _ Interface = &Recorder{} // NewRecorder wraps an Interface and records any changes sent across it. func NewRecorder(w Interface) *Recorder { r := &Recorder{} r.Interface = Filter(w, r.record) return r } // record is a FilterFunc and tracks each received event. func (r *Recorder) record(in Event) (Event, bool) { r.lock.Lock() defer r.lock.Unlock() r.events = append(r.events, in) return in, true } // Events returns a copy of the events sent across this recorder. func (r *Recorder) Events() []Event { r.lock.Lock() defer r.lock.Unlock() copied := make([]Event, len(r.events)) copy(copied, r.events) return copied } golang-k8s-apimachinery-0.29.0/pkg/watch/filter_test.go000066400000000000000000000050101453143165200227650ustar00rootroot00000000000000/* 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 watch_test import ( "reflect" "testing" . "k8s.io/apimachinery/pkg/watch" ) func TestFilter(t *testing.T) { table := []Event{ {Type: Added, Object: testType("foo")}, {Type: Added, Object: testType("bar")}, {Type: Added, Object: testType("baz")}, {Type: Added, Object: testType("qux")}, {Type: Added, Object: testType("zoo")}, } source := NewFake() filtered := Filter(source, func(e Event) (Event, bool) { return e, e.Object.(testType)[0] != 'b' }) go func() { for _, item := range table { source.Action(item.Type, item.Object) } source.Stop() }() var got []string for { event, ok := <-filtered.ResultChan() if !ok { break } got = append(got, string(event.Object.(testType))) } if e, a := []string{"foo", "qux", "zoo"}, got; !reflect.DeepEqual(e, a) { t.Errorf("got %v, wanted %v", e, a) } } func TestFilterStop(t *testing.T) { source := NewFake() filtered := Filter(source, func(e Event) (Event, bool) { return e, e.Object.(testType)[0] != 'b' }) go func() { source.Add(testType("foo")) filtered.Stop() }() var got []string for { event, ok := <-filtered.ResultChan() if !ok { break } got = append(got, string(event.Object.(testType))) } if e, a := []string{"foo"}, got; !reflect.DeepEqual(e, a) { t.Errorf("got %v, wanted %v", e, a) } } func TestRecorder(t *testing.T) { events := []Event{ {Type: Added, Object: testType("foo")}, {Type: Added, Object: testType("bar")}, {Type: Added, Object: testType("baz")}, {Type: Added, Object: testType("qux")}, {Type: Added, Object: testType("zoo")}, } source := NewFake() go func() { for _, item := range events { source.Action(item.Type, item.Object) } source.Stop() }() recorder := NewRecorder(source) for { _, ok := <-recorder.Interface.ResultChan() if !ok { break } } recordedEvents := recorder.Events() if !reflect.DeepEqual(recordedEvents, events) { t.Errorf("got %v, expected %v", recordedEvents, events) } } golang-k8s-apimachinery-0.29.0/pkg/watch/mux.go000066400000000000000000000227141453143165200212640ustar00rootroot00000000000000/* 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 watch import ( "fmt" "sync" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // FullChannelBehavior controls how the Broadcaster reacts if a watcher's watch // channel is full. type FullChannelBehavior int const ( WaitIfChannelFull FullChannelBehavior = iota DropIfChannelFull ) // Buffer the incoming queue a little bit even though it should rarely ever accumulate // anything, just in case a few events are received in such a short window that // Broadcaster can't move them onto the watchers' queues fast enough. const incomingQueueLength = 25 // Broadcaster distributes event notifications among any number of watchers. Every event // is delivered to every watcher. type Broadcaster struct { watchers map[int64]*broadcasterWatcher nextWatcher int64 distributing sync.WaitGroup // incomingBlock allows us to ensure we don't race and end up sending events // to a closed channel following a broadcaster shutdown. incomingBlock sync.Mutex incoming chan Event stopped chan struct{} // How large to make watcher's channel. watchQueueLength int // If one of the watch channels is full, don't wait for it to become empty. // Instead just deliver it to the watchers that do have space in their // channels and move on to the next event. // It's more fair to do this on a per-watcher basis than to do it on the // "incoming" channel, which would allow one slow watcher to prevent all // other watchers from getting new events. fullChannelBehavior FullChannelBehavior } // NewBroadcaster creates a new Broadcaster. queueLength is the maximum number of events to queue per watcher. // It is guaranteed that events will be distributed in the order in which they occur, // but the order in which a single event is distributed among all of the watchers is unspecified. func NewBroadcaster(queueLength int, fullChannelBehavior FullChannelBehavior) *Broadcaster { m := &Broadcaster{ watchers: map[int64]*broadcasterWatcher{}, incoming: make(chan Event, incomingQueueLength), stopped: make(chan struct{}), watchQueueLength: queueLength, fullChannelBehavior: fullChannelBehavior, } m.distributing.Add(1) go m.loop() return m } // NewLongQueueBroadcaster functions nearly identically to NewBroadcaster, // except that the incoming queue is the same size as the outgoing queues // (specified by queueLength). func NewLongQueueBroadcaster(queueLength int, fullChannelBehavior FullChannelBehavior) *Broadcaster { m := &Broadcaster{ watchers: map[int64]*broadcasterWatcher{}, incoming: make(chan Event, queueLength), stopped: make(chan struct{}), watchQueueLength: queueLength, fullChannelBehavior: fullChannelBehavior, } m.distributing.Add(1) go m.loop() return m } const internalRunFunctionMarker = "internal-do-function" // a function type we can shoehorn into the queue. type functionFakeRuntimeObject func() func (obj functionFakeRuntimeObject) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj functionFakeRuntimeObject) DeepCopyObject() runtime.Object { if obj == nil { return nil } // funcs are immutable. Hence, just return the original func. return obj } // Execute f, blocking the incoming queue (and waiting for it to drain first). // The purpose of this terrible hack is so that watchers added after an event // won't ever see that event, and will always see any event after they are // added. func (m *Broadcaster) blockQueue(f func()) { m.incomingBlock.Lock() defer m.incomingBlock.Unlock() select { case <-m.stopped: return default: } var wg sync.WaitGroup wg.Add(1) m.incoming <- Event{ Type: internalRunFunctionMarker, Object: functionFakeRuntimeObject(func() { defer wg.Done() f() }), } wg.Wait() } // Watch adds a new watcher to the list and returns an Interface for it. // Note: new watchers will only receive new events. They won't get an entire history // of previous events. It will block until the watcher is actually added to the // broadcaster. func (m *Broadcaster) Watch() (Interface, error) { var w *broadcasterWatcher m.blockQueue(func() { id := m.nextWatcher m.nextWatcher++ w = &broadcasterWatcher{ result: make(chan Event, m.watchQueueLength), stopped: make(chan struct{}), id: id, m: m, } m.watchers[id] = w }) if w == nil { return nil, fmt.Errorf("broadcaster already stopped") } return w, nil } // WatchWithPrefix adds a new watcher to the list and returns an Interface for it. It sends // queuedEvents down the new watch before beginning to send ordinary events from Broadcaster. // The returned watch will have a queue length that is at least large enough to accommodate // all of the items in queuedEvents. It will block until the watcher is actually added to // the broadcaster. func (m *Broadcaster) WatchWithPrefix(queuedEvents []Event) (Interface, error) { var w *broadcasterWatcher m.blockQueue(func() { id := m.nextWatcher m.nextWatcher++ length := m.watchQueueLength if n := len(queuedEvents) + 1; n > length { length = n } w = &broadcasterWatcher{ result: make(chan Event, length), stopped: make(chan struct{}), id: id, m: m, } m.watchers[id] = w for _, e := range queuedEvents { w.result <- e } }) if w == nil { return nil, fmt.Errorf("broadcaster already stopped") } return w, nil } // stopWatching stops the given watcher and removes it from the list. func (m *Broadcaster) stopWatching(id int64) { m.blockQueue(func() { w, ok := m.watchers[id] if !ok { // No need to do anything, it's already been removed from the list. return } delete(m.watchers, id) close(w.result) }) } // closeAll disconnects all watchers (presumably in response to a Shutdown call). func (m *Broadcaster) closeAll() { for _, w := range m.watchers { close(w.result) } // Delete everything from the map, since presence/absence in the map is used // by stopWatching to avoid double-closing the channel. m.watchers = map[int64]*broadcasterWatcher{} } // Action distributes the given event among all watchers. func (m *Broadcaster) Action(action EventType, obj runtime.Object) error { m.incomingBlock.Lock() defer m.incomingBlock.Unlock() select { case <-m.stopped: return fmt.Errorf("broadcaster already stopped") default: } m.incoming <- Event{action, obj} return nil } // Action distributes the given event among all watchers, or drops it on the floor // if too many incoming actions are queued up. Returns true if the action was sent, // false if dropped. func (m *Broadcaster) ActionOrDrop(action EventType, obj runtime.Object) (bool, error) { m.incomingBlock.Lock() defer m.incomingBlock.Unlock() // Ensure that if the broadcaster is stopped we do not send events to it. select { case <-m.stopped: return false, fmt.Errorf("broadcaster already stopped") default: } select { case m.incoming <- Event{action, obj}: return true, nil default: return false, nil } } // Shutdown disconnects all watchers (but any queued events will still be distributed). // You must not call Action or Watch* after calling Shutdown. This call blocks // until all events have been distributed through the outbound channels. Note // that since they can be buffered, this means that the watchers might not // have received the data yet as it can remain sitting in the buffered // channel. It will block until the broadcaster stop request is actually executed func (m *Broadcaster) Shutdown() { m.blockQueue(func() { close(m.stopped) close(m.incoming) }) m.distributing.Wait() } // loop receives from m.incoming and distributes to all watchers. func (m *Broadcaster) loop() { // Deliberately not catching crashes here. Yes, bring down the process if there's a // bug in watch.Broadcaster. for event := range m.incoming { if event.Type == internalRunFunctionMarker { event.Object.(functionFakeRuntimeObject)() continue } m.distribute(event) } m.closeAll() m.distributing.Done() } // distribute sends event to all watchers. Blocking. func (m *Broadcaster) distribute(event Event) { if m.fullChannelBehavior == DropIfChannelFull { for _, w := range m.watchers { select { case w.result <- event: case <-w.stopped: default: // Don't block if the event can't be queued. } } } else { for _, w := range m.watchers { select { case w.result <- event: case <-w.stopped: } } } } // broadcasterWatcher handles a single watcher of a broadcaster type broadcasterWatcher struct { result chan Event stopped chan struct{} stop sync.Once id int64 m *Broadcaster } // ResultChan returns a channel to use for waiting on events. func (mw *broadcasterWatcher) ResultChan() <-chan Event { return mw.result } // Stop stops watching and removes mw from its list. // It will block until the watcher stop request is actually executed func (mw *broadcasterWatcher) Stop() { mw.stop.Do(func() { close(mw.stopped) mw.m.stopWatching(mw.id) }) } golang-k8s-apimachinery-0.29.0/pkg/watch/mux_test.go000066400000000000000000000200301453143165200223100ustar00rootroot00000000000000/* 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 watch import ( "reflect" "sync" "testing" "time" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" ) type myType struct { ID string Value string } func (obj *myType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj *myType) DeepCopyObject() runtime.Object { if obj == nil { return nil } clone := *obj return &clone } func TestBroadcaster(t *testing.T) { table := []Event{ {Type: Added, Object: &myType{"foo", "hello world 1"}}, {Type: Added, Object: &myType{"bar", "hello world 2"}}, {Type: Modified, Object: &myType{"foo", "goodbye world 3"}}, {Type: Deleted, Object: &myType{"bar", "hello world 4"}}, } // The broadcaster we're testing m := NewBroadcaster(0, WaitIfChannelFull) // Add a bunch of watchers const testWatchers = 2 wg := sync.WaitGroup{} wg.Add(testWatchers) for i := 0; i < testWatchers; i++ { w, err := m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } // Verify that each watcher gets the events in the correct order go func(watcher int, w Interface) { tableLine := 0 for { event, ok := <-w.ResultChan() if !ok { break } if e, a := table[tableLine], event; !reflect.DeepEqual(e, a) { t.Errorf("Watcher %v, line %v: Expected (%v, %#v), got (%v, %#v)", watcher, tableLine, e.Type, e.Object, a.Type, a.Object) } else { t.Logf("Got (%v, %#v)", event.Type, event.Object) } tableLine++ } wg.Done() }(i, w) } for i, item := range table { t.Logf("Sending %v", i) m.Action(item.Type, item.Object) } m.Shutdown() wg.Wait() } func TestBroadcasterWatcherClose(t *testing.T) { m := NewBroadcaster(0, WaitIfChannelFull) w, err := m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } w2, err := m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } w.Stop() m.Shutdown() if _, open := <-w.ResultChan(); open { t.Errorf("Stop didn't work?") } if _, open := <-w2.ResultChan(); open { t.Errorf("Shutdown didn't work?") } // Extra stops don't hurt things w.Stop() w2.Stop() } func TestBroadcasterWatcherStopDeadlock(t *testing.T) { done := make(chan bool) m := NewBroadcaster(0, WaitIfChannelFull) w, err := m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } w2, err := m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } go func(w0, w1 Interface) { // We know Broadcaster is in the distribute loop once one watcher receives // an event. Stop the other watcher while distribute is trying to // send to it. select { case <-w0.ResultChan(): w1.Stop() case <-w1.ResultChan(): w0.Stop() } close(done) }(w, w2) m.Action(Added, &myType{}) select { case <-time.After(wait.ForeverTestTimeout): t.Error("timeout: deadlocked") case <-done: } m.Shutdown() } func TestBroadcasterDropIfChannelFull(t *testing.T) { m := NewBroadcaster(1, DropIfChannelFull) event1 := Event{Type: Added, Object: &myType{"foo", "hello world 1"}} event2 := Event{Type: Added, Object: &myType{"bar", "hello world 2"}} // Add a couple watchers watches := make([]Interface, 2) var err error for i := range watches { watches[i], err = m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } } // Send a couple events before closing the broadcast channel. t.Log("Sending event 1") m.Action(event1.Type, event1.Object) t.Log("Sending event 2") m.Action(event2.Type, event2.Object) m.Shutdown() // Pull events from the queue. wg := sync.WaitGroup{} wg.Add(len(watches)) for i := range watches { // Verify that each watcher only gets the first event because its watch // queue of length one was full from the first one. go func(watcher int, w Interface) { defer wg.Done() e1, ok := <-w.ResultChan() if !ok { t.Errorf("Watcher %v failed to retrieve first event.", watcher) } if e, a := event1, e1; !reflect.DeepEqual(e, a) { t.Errorf("Watcher %v: Expected (%v, %#v), got (%v, %#v)", watcher, e.Type, e.Object, a.Type, a.Object) } t.Logf("Got (%v, %#v)", e1.Type, e1.Object) e2, ok := <-w.ResultChan() if ok { t.Errorf("Watcher %v received second event (%v, %#v) even though it shouldn't have.", watcher, e2.Type, e2.Object) } }(i, watches[i]) } wg.Wait() } func BenchmarkBroadCaster(b *testing.B) { event1 := Event{Type: Added, Object: &myType{"foo", "hello world 1"}} m := NewBroadcaster(0, WaitIfChannelFull) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { m.Action(event1.Type, event1.Object) } }) b.StopTimer() } func TestBroadcasterWatchAfterShutdown(t *testing.T) { event1 := Event{Type: Added, Object: &myType{"foo", "hello world 1"}} event2 := Event{Type: Added, Object: &myType{"bar", "hello world 2"}} m := NewBroadcaster(0, WaitIfChannelFull) m.Shutdown() _, err := m.Watch() assert.EqualError(t, err, "broadcaster already stopped", "Watch should report error id broadcaster is shutdown") _, err = m.WatchWithPrefix([]Event{event1, event2}) assert.EqualError(t, err, "broadcaster already stopped", "WatchWithPrefix should report error id broadcaster is shutdown") } func TestBroadcasterSendEventAfterShutdown(t *testing.T) { m := NewBroadcaster(1, DropIfChannelFull) event := Event{Type: Added, Object: &myType{"foo", "hello world"}} // Add a couple watchers watches := make([]Interface, 2) for i := range watches { watches[i], _ = m.Watch() } m.Shutdown() // Send a couple events after closing the broadcast channel. t.Log("Sending event") err := m.Action(event.Type, event.Object) assert.EqualError(t, err, "broadcaster already stopped", "ActionOrDrop should report error id broadcaster is shutdown") sendOnClosed, err := m.ActionOrDrop(event.Type, event.Object) assert.Equal(t, sendOnClosed, false, "ActionOrDrop should return false if broadcaster is already shutdown") assert.EqualError(t, err, "broadcaster already stopped", "ActionOrDrop should report error id broadcaster is shutdown") } // Test this since we see usage patterns where the broadcaster and watchers are // stopped simultaneously leading to races. func TestBroadcasterShutdownRace(t *testing.T) { m := NewBroadcaster(1, WaitIfChannelFull) stopCh := make(chan struct{}) // Add a bunch of watchers const testWatchers = 2 for i := 0; i < testWatchers; i++ { i := i _, err := m.Watch() if err != nil { t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) } // This is how we force the watchers to close down independently of the // eventbroadcaster, see real usage pattern in startRecordingEvents() go func() { <-stopCh t.Log("Stopping Watchers") m.stopWatching(int64(i)) }() } event := Event{Type: Added, Object: &myType{"foo", "hello world"}} err := m.Action(event.Type, event.Object) if err != nil { t.Fatalf("error sending event: %v", err) } // Manually simulate m.Shutdown() but change it to force a race scenario // 1. Close watcher stopchannel, so watchers are closed independently of the // eventBroadcaster // 2. Shutdown the m.incoming slightly Before m.stopped so that the watcher's // call of Blockqueue can pass the m.stopped check. m.blockQueue(func() { close(stopCh) close(m.incoming) time.Sleep(1 * time.Millisecond) close(m.stopped) }) m.distributing.Wait() } golang-k8s-apimachinery-0.29.0/pkg/watch/streamwatcher.go000066400000000000000000000075651453143165200233330ustar00rootroot00000000000000/* 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 watch import ( "fmt" "io" "sync" "k8s.io/klog/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/net" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // Decoder allows StreamWatcher to watch any stream for which a Decoder can be written. type Decoder interface { // Decode should return the type of event, the decoded object, or an error. // An error will cause StreamWatcher to call Close(). Decode should block until // it has data or an error occurs. Decode() (action EventType, object runtime.Object, err error) // Close should close the underlying io.Reader, signalling to the source of // the stream that it is no longer being watched. Close() must cause any // outstanding call to Decode() to return with an error of some sort. Close() } // Reporter hides the details of how an error is turned into a runtime.Object for // reporting on a watch stream since this package may not import a higher level report. type Reporter interface { // AsObject must convert err into a valid runtime.Object for the watch stream. AsObject(err error) runtime.Object } // StreamWatcher turns any stream for which you can write a Decoder interface // into a watch.Interface. type StreamWatcher struct { sync.Mutex source Decoder reporter Reporter result chan Event done chan struct{} } // NewStreamWatcher creates a StreamWatcher from the given decoder. func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher { sw := &StreamWatcher{ source: d, reporter: r, // It's easy for a consumer to add buffering via an extra // goroutine/channel, but impossible for them to remove it, // so nonbuffered is better. result: make(chan Event), // If the watcher is externally stopped there is no receiver anymore // and the send operations on the result channel, especially the // error reporting might block forever. // Therefore a dedicated stop channel is used to resolve this blocking. done: make(chan struct{}), } go sw.receive() return sw } // ResultChan implements Interface. func (sw *StreamWatcher) ResultChan() <-chan Event { return sw.result } // Stop implements Interface. func (sw *StreamWatcher) Stop() { // Call Close() exactly once by locking and setting a flag. sw.Lock() defer sw.Unlock() // closing a closed channel always panics, therefore check before closing select { case <-sw.done: default: close(sw.done) sw.source.Close() } } // receive reads result from the decoder in a loop and sends down the result channel. func (sw *StreamWatcher) receive() { defer utilruntime.HandleCrash() defer close(sw.result) defer sw.Stop() for { action, obj, err := sw.source.Decode() if err != nil { switch err { case io.EOF: // watch closed normally case io.ErrUnexpectedEOF: klog.V(1).Infof("Unexpected EOF during watch stream event decoding: %v", err) default: if net.IsProbableEOF(err) || net.IsTimeout(err) { klog.V(5).Infof("Unable to decode an event from the watch stream: %v", err) } else { select { case <-sw.done: case sw.result <- Event{ Type: Error, Object: sw.reporter.AsObject(fmt.Errorf("unable to decode an event from the watch stream: %v", err)), }: } } } return } select { case <-sw.done: return case sw.result <- Event{ Type: action, Object: obj, }: } } } golang-k8s-apimachinery-0.29.0/pkg/watch/streamwatcher_test.go000066400000000000000000000050211453143165200243530ustar00rootroot00000000000000/* 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 watch_test import ( "fmt" "io" "reflect" "testing" "time" "k8s.io/apimachinery/pkg/runtime" . "k8s.io/apimachinery/pkg/watch" ) type fakeDecoder struct { items chan Event err error } func (f fakeDecoder) Decode() (action EventType, object runtime.Object, err error) { if f.err != nil { return "", nil, f.err } item, open := <-f.items if !open { return action, nil, io.EOF } return item.Type, item.Object, nil } func (f fakeDecoder) Close() { if f.items != nil { close(f.items) } } type fakeReporter struct { err error } func (f *fakeReporter) AsObject(err error) runtime.Object { f.err = err return runtime.Unstructured(nil) } func TestStreamWatcher(t *testing.T) { table := []Event{ {Type: Added, Object: testType("foo")}, } fd := fakeDecoder{items: make(chan Event, 5)} sw := NewStreamWatcher(fd, nil) for _, item := range table { fd.items <- item got, open := <-sw.ResultChan() if !open { t.Errorf("unexpected early close") } if e, a := item, got; !reflect.DeepEqual(e, a) { t.Errorf("expected %v, got %v", e, a) } } sw.Stop() _, open := <-sw.ResultChan() if open { t.Errorf("Unexpected failure to close") } } func TestStreamWatcherError(t *testing.T) { fd := fakeDecoder{err: fmt.Errorf("test error")} fr := &fakeReporter{} sw := NewStreamWatcher(fd, fr) evt, ok := <-sw.ResultChan() if !ok { t.Fatalf("unexpected close") } if evt.Type != Error || evt.Object != runtime.Unstructured(nil) { t.Fatalf("unexpected object: %#v", evt) } _, ok = <-sw.ResultChan() if ok { t.Fatalf("unexpected open channel") } sw.Stop() _, ok = <-sw.ResultChan() if ok { t.Fatalf("unexpected open channel") } } func TestStreamWatcherRace(t *testing.T) { fd := fakeDecoder{err: fmt.Errorf("test error")} fr := &fakeReporter{} sw := NewStreamWatcher(fd, fr) time.Sleep(10 * time.Millisecond) sw.Stop() time.Sleep(10 * time.Millisecond) _, ok := <-sw.ResultChan() if ok { t.Fatalf("unexpected pending send") } } golang-k8s-apimachinery-0.29.0/pkg/watch/watch.go000066400000000000000000000162541453143165200215630ustar00rootroot00000000000000/* 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 watch import ( "fmt" "sync" "k8s.io/klog/v2" "k8s.io/apimachinery/pkg/runtime" ) // Interface can be implemented by anything that knows how to watch and report changes. type Interface interface { // Stop stops watching. Will close the channel returned by ResultChan(). Releases // any resources used by the watch. Stop() // ResultChan returns a chan which will receive all the events. If an error occurs // or Stop() is called, the implementation will close this channel and // release any resources used by the watch. ResultChan() <-chan Event } // EventType defines the possible types of events. type EventType string const ( Added EventType = "ADDED" Modified EventType = "MODIFIED" Deleted EventType = "DELETED" Bookmark EventType = "BOOKMARK" Error EventType = "ERROR" ) var ( DefaultChanSize int32 = 100 ) // Event represents a single event to a watched resource. // +k8s:deepcopy-gen=true type Event struct { Type EventType // Object is: // * If Type is Added or Modified: the new state of the object. // * If Type is Deleted: the state of the object immediately before deletion. // * If Type is Bookmark: the object (instance of a type being watched) where // only ResourceVersion field is set. On successful restart of watch from a // bookmark resourceVersion, client is guaranteed to not get repeat event // nor miss any events. // * If Type is Error: *api.Status is recommended; other types may make sense // depending on context. Object runtime.Object } type emptyWatch chan Event // NewEmptyWatch returns a watch interface that returns no results and is closed. // May be used in certain error conditions where no information is available but // an error is not warranted. func NewEmptyWatch() Interface { ch := make(chan Event) close(ch) return emptyWatch(ch) } // Stop implements Interface func (w emptyWatch) Stop() { } // ResultChan implements Interface func (w emptyWatch) ResultChan() <-chan Event { return chan Event(w) } // FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe. type FakeWatcher struct { result chan Event stopped bool sync.Mutex } func NewFake() *FakeWatcher { return &FakeWatcher{ result: make(chan Event), } } func NewFakeWithChanSize(size int, blocking bool) *FakeWatcher { return &FakeWatcher{ result: make(chan Event, size), } } // Stop implements Interface.Stop(). func (f *FakeWatcher) Stop() { f.Lock() defer f.Unlock() if !f.stopped { klog.V(4).Infof("Stopping fake watcher.") close(f.result) f.stopped = true } } func (f *FakeWatcher) IsStopped() bool { f.Lock() defer f.Unlock() return f.stopped } // Reset prepares the watcher to be reused. func (f *FakeWatcher) Reset() { f.Lock() defer f.Unlock() f.stopped = false f.result = make(chan Event) } func (f *FakeWatcher) ResultChan() <-chan Event { return f.result } // Add sends an add event. func (f *FakeWatcher) Add(obj runtime.Object) { f.result <- Event{Added, obj} } // Modify sends a modify event. func (f *FakeWatcher) Modify(obj runtime.Object) { f.result <- Event{Modified, obj} } // Delete sends a delete event. func (f *FakeWatcher) Delete(lastValue runtime.Object) { f.result <- Event{Deleted, lastValue} } // Error sends an Error event. func (f *FakeWatcher) Error(errValue runtime.Object) { f.result <- Event{Error, errValue} } // Action sends an event of the requested type, for table-based testing. func (f *FakeWatcher) Action(action EventType, obj runtime.Object) { f.result <- Event{action, obj} } // RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe. type RaceFreeFakeWatcher struct { result chan Event Stopped bool sync.Mutex } func NewRaceFreeFake() *RaceFreeFakeWatcher { return &RaceFreeFakeWatcher{ result: make(chan Event, DefaultChanSize), } } // Stop implements Interface.Stop(). func (f *RaceFreeFakeWatcher) Stop() { f.Lock() defer f.Unlock() if !f.Stopped { klog.V(4).Infof("Stopping fake watcher.") close(f.result) f.Stopped = true } } func (f *RaceFreeFakeWatcher) IsStopped() bool { f.Lock() defer f.Unlock() return f.Stopped } // Reset prepares the watcher to be reused. func (f *RaceFreeFakeWatcher) Reset() { f.Lock() defer f.Unlock() f.Stopped = false f.result = make(chan Event, DefaultChanSize) } func (f *RaceFreeFakeWatcher) ResultChan() <-chan Event { f.Lock() defer f.Unlock() return f.result } // Add sends an add event. func (f *RaceFreeFakeWatcher) Add(obj runtime.Object) { f.Lock() defer f.Unlock() if !f.Stopped { select { case f.result <- Event{Added, obj}: return default: panic(fmt.Errorf("channel full")) } } } // Modify sends a modify event. func (f *RaceFreeFakeWatcher) Modify(obj runtime.Object) { f.Lock() defer f.Unlock() if !f.Stopped { select { case f.result <- Event{Modified, obj}: return default: panic(fmt.Errorf("channel full")) } } } // Delete sends a delete event. func (f *RaceFreeFakeWatcher) Delete(lastValue runtime.Object) { f.Lock() defer f.Unlock() if !f.Stopped { select { case f.result <- Event{Deleted, lastValue}: return default: panic(fmt.Errorf("channel full")) } } } // Error sends an Error event. func (f *RaceFreeFakeWatcher) Error(errValue runtime.Object) { f.Lock() defer f.Unlock() if !f.Stopped { select { case f.result <- Event{Error, errValue}: return default: panic(fmt.Errorf("channel full")) } } } // Action sends an event of the requested type, for table-based testing. func (f *RaceFreeFakeWatcher) Action(action EventType, obj runtime.Object) { f.Lock() defer f.Unlock() if !f.Stopped { select { case f.result <- Event{action, obj}: return default: panic(fmt.Errorf("channel full")) } } } // ProxyWatcher lets you wrap your channel in watch Interface. threadsafe. type ProxyWatcher struct { result chan Event stopCh chan struct{} mutex sync.Mutex stopped bool } var _ Interface = &ProxyWatcher{} // NewProxyWatcher creates new ProxyWatcher by wrapping a channel func NewProxyWatcher(ch chan Event) *ProxyWatcher { return &ProxyWatcher{ result: ch, stopCh: make(chan struct{}), stopped: false, } } // Stop implements Interface func (pw *ProxyWatcher) Stop() { pw.mutex.Lock() defer pw.mutex.Unlock() if !pw.stopped { pw.stopped = true close(pw.stopCh) } } // Stopping returns true if Stop() has been called func (pw *ProxyWatcher) Stopping() bool { pw.mutex.Lock() defer pw.mutex.Unlock() return pw.stopped } // ResultChan implements Interface func (pw *ProxyWatcher) ResultChan() <-chan Event { return pw.result } // StopChan returns stop channel func (pw *ProxyWatcher) StopChan() <-chan struct{} { return pw.stopCh } golang-k8s-apimachinery-0.29.0/pkg/watch/watch_test.go000066400000000000000000000073111453143165200226140ustar00rootroot00000000000000/* 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 watch_test import ( "reflect" "testing" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" . "k8s.io/apimachinery/pkg/watch" ) type testType string func (obj testType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } func (obj testType) DeepCopyObject() runtime.Object { return obj } func TestFake(t *testing.T) { f := NewFake() table := []struct { t EventType s testType }{ {Added, testType("foo")}, {Modified, testType("qux")}, {Modified, testType("bar")}, {Deleted, testType("bar")}, {Error, testType("error: blah")}, } // Prove that f implements Interface by phrasing this as a function. consumer := func(w Interface) { for _, expect := range table { got, ok := <-w.ResultChan() if !ok { t.Fatalf("closed early") } if e, a := expect.t, got.Type; e != a { t.Fatalf("Expected %v, got %v", e, a) } if a, ok := got.Object.(testType); !ok || a != expect.s { t.Fatalf("Expected %v, got %v", expect.s, a) } } _, stillOpen := <-w.ResultChan() if stillOpen { t.Fatal("Never stopped") } } sender := func() { f.Add(testType("foo")) f.Action(Modified, testType("qux")) f.Modify(testType("bar")) f.Delete(testType("bar")) f.Error(testType("error: blah")) f.Stop() } go sender() consumer(f) } func TestRaceFreeFake(t *testing.T) { f := NewRaceFreeFake() table := []struct { t EventType s testType }{ {Added, testType("foo")}, {Modified, testType("qux")}, {Modified, testType("bar")}, {Deleted, testType("bar")}, {Error, testType("error: blah")}, } // Prove that f implements Interface by phrasing this as a function. consumer := func(w Interface) { for _, expect := range table { got, ok := <-w.ResultChan() if !ok { t.Fatalf("closed early") } if e, a := expect.t, got.Type; e != a { t.Fatalf("Expected %v, got %v", e, a) } if a, ok := got.Object.(testType); !ok || a != expect.s { t.Fatalf("Expected %v, got %v", expect.s, a) } } _, stillOpen := <-w.ResultChan() if stillOpen { t.Fatal("Never stopped") } } sender := func() { f.Add(testType("foo")) f.Action(Modified, testType("qux")) f.Modify(testType("bar")) f.Delete(testType("bar")) f.Error(testType("error: blah")) f.Stop() } go sender() consumer(f) } func TestEmpty(t *testing.T) { w := NewEmptyWatch() _, ok := <-w.ResultChan() if ok { t.Errorf("unexpected result channel result") } w.Stop() _, ok = <-w.ResultChan() if ok { t.Errorf("unexpected result channel result") } } func TestProxyWatcher(t *testing.T) { events := []Event{ {Added, testType("foo")}, {Modified, testType("qux")}, {Modified, testType("bar")}, {Deleted, testType("bar")}, {Error, testType("error: blah")}, } ch := make(chan Event, len(events)) w := NewProxyWatcher(ch) for _, e := range events { ch <- e } for _, e := range events { g := <-w.ResultChan() if !reflect.DeepEqual(e, g) { t.Errorf("Expected %#v, got %#v", e, g) continue } } w.Stop() select { // Closed channel always reads immediately case <-w.StopChan(): default: t.Error("Channel isn't closed") } // Test double close w.Stop() } golang-k8s-apimachinery-0.29.0/pkg/watch/zz_generated.deepcopy.go000066400000000000000000000022151453143165200247350ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package watch // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Event) DeepCopyInto(out *Event) { *out = *in if in.Object != nil { out.Object = in.Object.DeepCopyObject() } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event. func (in *Event) DeepCopy() *Event { if in == nil { return nil } out := new(Event) in.DeepCopyInto(out) return out } golang-k8s-apimachinery-0.29.0/third_party/000077500000000000000000000000001453143165200205605ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/third_party/forked/000077500000000000000000000000001453143165200220325ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/third_party/forked/golang/000077500000000000000000000000001453143165200233015ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/third_party/forked/golang/LICENSE000066400000000000000000000027071453143165200243140ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-k8s-apimachinery-0.29.0/third_party/forked/golang/PATENTS000066400000000000000000000024271453143165200243470ustar00rootroot00000000000000Additional IP Rights Grant (Patents) "This implementation" means the copyrightable works distributed by Google as part of the Go project. Google hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, transfer and otherwise run, modify and propagate the contents of this implementation of Go, where such license applies only to those patent claims, both currently owned or controlled by Google and acquired in the future, licensable by Google that are necessarily infringed by this implementation of Go. This grant does not include claims that would be infringed only as a consequence of further modification of this implementation. If you or your agent or exclusive licensee institute or order or agree to the institution of patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that this implementation of Go or any code incorporated within this implementation of Go constitutes direct or contributory patent infringement, or inducement of patent infringement, then any patent rights granted to you under this License for this implementation of Go shall terminate as of the date such litigation is filed. golang-k8s-apimachinery-0.29.0/third_party/forked/golang/json/000077500000000000000000000000001453143165200242525ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/third_party/forked/golang/json/OWNERS000066400000000000000000000001441453143165200252110ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners approvers: - pwittrock reviewers: - apelisse golang-k8s-apimachinery-0.29.0/third_party/forked/golang/json/fields.go000066400000000000000000000321741453143165200260560ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package json is forked from the Go standard library to enable us to find the // field of a struct that a given JSON key maps to. package json import ( "bytes" "fmt" "reflect" "sort" "strings" "sync" "unicode" "unicode/utf8" ) const ( patchStrategyTagKey = "patchStrategy" patchMergeKeyTagKey = "patchMergeKey" ) // Finds the patchStrategy and patchMergeKey struct tag fields on a given // struct field given the struct type and the JSON name of the field. // It returns field type, a slice of patch strategies, merge key and error. // TODO: fix the returned errors to be introspectable. func LookupPatchMetadataForStruct(t reflect.Type, jsonField string) ( elemType reflect.Type, patchStrategies []string, patchMergeKey string, e error) { if t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() != reflect.Struct { e = fmt.Errorf("merging an object in json but data type is not struct, instead is: %s", t.Kind().String()) return } jf := []byte(jsonField) // Find the field that the JSON library would use. var f *field fields := cachedTypeFields(t) for i := range fields { ff := &fields[i] if bytes.Equal(ff.nameBytes, jf) { f = ff break } // Do case-insensitive comparison. if f == nil && ff.equalFold(ff.nameBytes, jf) { f = ff } } if f != nil { // Find the reflect.Value of the most preferential struct field. tjf := t.Field(f.index[0]) // we must navigate down all the anonymously included structs in the chain for i := 1; i < len(f.index); i++ { tjf = tjf.Type.Field(f.index[i]) } patchStrategy := tjf.Tag.Get(patchStrategyTagKey) patchMergeKey = tjf.Tag.Get(patchMergeKeyTagKey) patchStrategies = strings.Split(patchStrategy, ",") elemType = tjf.Type return } e = fmt.Errorf("unable to find api field in struct %s for the json field %q", t.Name(), jsonField) return } // A field represents a single field found in a struct. type field struct { name string nameBytes []byte // []byte(name) equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent tag bool // index is the sequence of indexes from the containing type fields to this field. // it is a slice because anonymous structs will need multiple navigation steps to correctly // resolve the proper fields index []int typ reflect.Type omitEmpty bool quoted bool } func (f field) String() string { return fmt.Sprintf("{name: %s, type: %v, tag: %v, index: %v, omitEmpty: %v, quoted: %v}", f.name, f.typ, f.tag, f.index, f.omitEmpty, f.quoted) } func fillField(f field) field { f.nameBytes = []byte(f.name) f.equalFold = foldFunc(f.nameBytes) return f } // byName sorts field by name, breaking ties with depth, // then breaking ties with "name came from json tag", then // breaking ties with index sequence. type byName []field func (x byName) Len() int { return len(x) } func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } func (x byName) Less(i, j int) bool { if x[i].name != x[j].name { return x[i].name < x[j].name } if len(x[i].index) != len(x[j].index) { return len(x[i].index) < len(x[j].index) } if x[i].tag != x[j].tag { return x[i].tag } return byIndex(x).Less(i, j) } // byIndex sorts field by index sequence. type byIndex []field func (x byIndex) Len() int { return len(x) } func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } func (x byIndex) Less(i, j int) bool { for k, xik := range x[i].index { if k >= len(x[j].index) { return false } if xik != x[j].index[k] { return xik < x[j].index[k] } } return len(x[i].index) < len(x[j].index) } // typeFields returns a list of fields that JSON should recognize for the given type. // The algorithm is breadth-first search over the set of structs to include - the top struct // and then any reachable anonymous structs. func typeFields(t reflect.Type) []field { // Anonymous fields to explore at the current level and the next. current := []field{} next := []field{{typ: t}} // Count of queued names for current level and the next. count := map[reflect.Type]int{} nextCount := map[reflect.Type]int{} // Types already visited at an earlier level. visited := map[reflect.Type]bool{} // Fields found. var fields []field for len(next) > 0 { current, next = next, current[:0] count, nextCount = nextCount, map[reflect.Type]int{} for _, f := range current { if visited[f.typ] { continue } visited[f.typ] = true // Scan f.typ for fields to include. for i := 0; i < f.typ.NumField(); i++ { sf := f.typ.Field(i) if sf.PkgPath != "" { // unexported continue } tag := sf.Tag.Get("json") if tag == "-" { continue } name, opts := parseTag(tag) if !isValidTag(name) { name = "" } index := make([]int, len(f.index)+1) copy(index, f.index) index[len(f.index)] = i ft := sf.Type if ft.Name() == "" && ft.Kind() == reflect.Pointer { // Follow pointer. ft = ft.Elem() } // Record found field and index sequence. if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { tagged := name != "" if name == "" { name = sf.Name } fields = append(fields, fillField(field{ name: name, tag: tagged, index: index, typ: ft, omitEmpty: opts.Contains("omitempty"), quoted: opts.Contains("string"), })) if count[f.typ] > 1 { // If there were multiple instances, add a second, // so that the annihilation code will see a duplicate. // It only cares about the distinction between 1 or 2, // so don't bother generating any more copies. fields = append(fields, fields[len(fields)-1]) } continue } // Record new anonymous struct to explore in next round. nextCount[ft]++ if nextCount[ft] == 1 { next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) } } } } sort.Sort(byName(fields)) // Delete all fields that are hidden by the Go rules for embedded fields, // except that fields with JSON tags are promoted. // The fields are sorted in primary order of name, secondary order // of field index length. Loop over names; for each name, delete // hidden fields by choosing the one dominant field that survives. out := fields[:0] for advance, i := 0, 0; i < len(fields); i += advance { // One iteration per name. // Find the sequence of fields with the name of this first field. fi := fields[i] name := fi.name for advance = 1; i+advance < len(fields); advance++ { fj := fields[i+advance] if fj.name != name { break } } if advance == 1 { // Only one field with this name out = append(out, fi) continue } dominant, ok := dominantField(fields[i : i+advance]) if ok { out = append(out, dominant) } } fields = out sort.Sort(byIndex(fields)) return fields } // dominantField looks through the fields, all of which are known to // have the same name, to find the single field that dominates the // others using Go's embedding rules, modified by the presence of // JSON tags. If there are multiple top-level fields, the boolean // will be false: This condition is an error in Go and we skip all // the fields. func dominantField(fields []field) (field, bool) { // The fields are sorted in increasing index-length order. The winner // must therefore be one with the shortest index length. Drop all // longer entries, which is easy: just truncate the slice. length := len(fields[0].index) tagged := -1 // Index of first tagged field. for i, f := range fields { if len(f.index) > length { fields = fields[:i] break } if f.tag { if tagged >= 0 { // Multiple tagged fields at the same level: conflict. // Return no field. return field{}, false } tagged = i } } if tagged >= 0 { return fields[tagged], true } // All remaining fields have the same length. If there's more than one, // we have a conflict (two fields named "X" at the same level) and we // return no field. if len(fields) > 1 { return field{}, false } return fields[0], true } var fieldCache struct { sync.RWMutex m map[reflect.Type][]field } // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. func cachedTypeFields(t reflect.Type) []field { fieldCache.RLock() f := fieldCache.m[t] fieldCache.RUnlock() if f != nil { return f } // Compute fields without lock. // Might duplicate effort but won't hold other computations back. f = typeFields(t) if f == nil { f = []field{} } fieldCache.Lock() if fieldCache.m == nil { fieldCache.m = map[reflect.Type][]field{} } fieldCache.m[t] = f fieldCache.Unlock() return f } func isValidTag(s string) bool { if s == "" { return false } for _, c := range s { switch { case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): // Backslash and quote chars are reserved, but // otherwise any punctuation chars are allowed // in a tag name. default: if !unicode.IsLetter(c) && !unicode.IsDigit(c) { return false } } } return true } const ( caseMask = ^byte(0x20) // Mask to ignore case in ASCII. kelvin = '\u212a' smallLongEss = '\u017f' ) // foldFunc returns one of four different case folding equivalence // functions, from most general (and slow) to fastest: // // 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 // 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') // 3) asciiEqualFold, no special, but includes non-letters (including _) // 4) simpleLetterEqualFold, no specials, no non-letters. // // The letters S and K are special because they map to 3 runes, not just 2: // * S maps to s and to U+017F 'ſ' Latin small letter long s // * k maps to K and to U+212A 'K' Kelvin sign // See http://play.golang.org/p/tTxjOc0OGo // // The returned function is specialized for matching against s and // should only be given s. It's not curried for performance reasons. func foldFunc(s []byte) func(s, t []byte) bool { nonLetter := false special := false // special letter for _, b := range s { if b >= utf8.RuneSelf { return bytes.EqualFold } upper := b & caseMask if upper < 'A' || upper > 'Z' { nonLetter = true } else if upper == 'K' || upper == 'S' { // See above for why these letters are special. special = true } } if special { return equalFoldRight } if nonLetter { return asciiEqualFold } return simpleLetterEqualFold } // equalFoldRight is a specialization of bytes.EqualFold when s is // known to be all ASCII (including punctuation), but contains an 's', // 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. // See comments on foldFunc. func equalFoldRight(s, t []byte) bool { for _, sb := range s { if len(t) == 0 { return false } tb := t[0] if tb < utf8.RuneSelf { if sb != tb { sbUpper := sb & caseMask if 'A' <= sbUpper && sbUpper <= 'Z' { if sbUpper != tb&caseMask { return false } } else { return false } } t = t[1:] continue } // sb is ASCII and t is not. t must be either kelvin // sign or long s; sb must be s, S, k, or K. tr, size := utf8.DecodeRune(t) switch sb { case 's', 'S': if tr != smallLongEss { return false } case 'k', 'K': if tr != kelvin { return false } default: return false } t = t[size:] } if len(t) > 0 { return false } return true } // asciiEqualFold is a specialization of bytes.EqualFold for use when // s is all ASCII (but may contain non-letters) and contains no // special-folding letters. // See comments on foldFunc. func asciiEqualFold(s, t []byte) bool { if len(s) != len(t) { return false } for i, sb := range s { tb := t[i] if sb == tb { continue } if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { if sb&caseMask != tb&caseMask { return false } } else { return false } } return true } // simpleLetterEqualFold is a specialization of bytes.EqualFold for // use when s is all ASCII letters (no underscores, etc) and also // doesn't contain 'k', 'K', 's', or 'S'. // See comments on foldFunc. func simpleLetterEqualFold(s, t []byte) bool { if len(s) != len(t) { return false } for i, b := range s { if b&caseMask != t[i]&caseMask { return false } } return true } // tagOptions is the string following a comma in a struct field's "json" // tag, or the empty string. It does not include the leading comma. type tagOptions string // parseTag splits a struct field's json tag into its name and // comma-separated options. func parseTag(tag string) (string, tagOptions) { if idx := strings.Index(tag, ","); idx != -1 { return tag[:idx], tagOptions(tag[idx+1:]) } return tag, tagOptions("") } // Contains reports whether a comma-separated list of options // contains a particular substr flag. substr must be surrounded by a // string boundary or commas. func (o tagOptions) Contains(optionName string) bool { if len(o) == 0 { return false } s := string(o) for s != "" { var next string i := strings.Index(s, ",") if i >= 0 { s, next = s[:i], s[i+1:] } if s == optionName { return true } s = next } return false } golang-k8s-apimachinery-0.29.0/third_party/forked/golang/json/fields_test.go000066400000000000000000000014011453143165200271020ustar00rootroot00000000000000package json import ( "reflect" "testing" ) func TestLookupPtrToStruct(t *testing.T) { type Elem struct { Key string Value string } type Outer struct { Inner []Elem `json:"inner" patchStrategy:"merge" patchMergeKey:"key"` } outer := &Outer{} elemType, patchStrategies, patchMergeKey, err := LookupPatchMetadataForStruct(reflect.TypeOf(outer), "inner") if err != nil { t.Fatal(err) } if elemType != reflect.TypeOf([]Elem{}) { t.Errorf("elemType = %v, want: %v", elemType, reflect.TypeOf([]Elem{})) } if !reflect.DeepEqual(patchStrategies, []string{"merge"}) { t.Errorf("patchStrategies = %v, want: %v", patchStrategies, []string{"merge"}) } if patchMergeKey != "key" { t.Errorf("patchMergeKey = %v, want: %v", patchMergeKey, "key") } } golang-k8s-apimachinery-0.29.0/third_party/forked/golang/netutil/000077500000000000000000000000001453143165200247655ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/third_party/forked/golang/netutil/addr.go000066400000000000000000000013231453143165200262250ustar00rootroot00000000000000package netutil import ( "net/url" "strings" ) // FROM: http://golang.org/src/net/http/client.go // Given a string of the form "host", "host:port", or "[ipv6::address]:port", // return true if the string includes a port. func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } // FROM: http://golang.org/src/net/http/transport.go var portMap = map[string]string{ "http": "80", "https": "443", "socks5": "1080", } // FROM: http://golang.org/src/net/http/transport.go // canonicalAddr returns url.Host but always with a ":port" suffix func CanonicalAddr(url *url.URL) string { addr := url.Host if !hasPort(addr) { return addr + ":" + portMap[url.Scheme] } return addr } golang-k8s-apimachinery-0.29.0/third_party/forked/golang/reflect/000077500000000000000000000000001453143165200247255ustar00rootroot00000000000000golang-k8s-apimachinery-0.29.0/third_party/forked/golang/reflect/deep_equal.go000066400000000000000000000255301453143165200273650ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package reflect is a fork of go's standard library reflection package, which // allows for deep equal with equality functions defined. package reflect import ( "fmt" "reflect" "strings" ) // Equalities is a map from type to a function comparing two values of // that type. type Equalities map[reflect.Type]reflect.Value // For convenience, panics on errors func EqualitiesOrDie(funcs ...interface{}) Equalities { e := Equalities{} if err := e.AddFuncs(funcs...); err != nil { panic(err) } return e } // AddFuncs is a shortcut for multiple calls to AddFunc. func (e Equalities) AddFuncs(funcs ...interface{}) error { for _, f := range funcs { if err := e.AddFunc(f); err != nil { return err } } return nil } // AddFunc uses func as an equality function: it must take // two parameters of the same type, and return a boolean. func (e Equalities) AddFunc(eqFunc interface{}) error { fv := reflect.ValueOf(eqFunc) ft := fv.Type() if ft.Kind() != reflect.Func { return fmt.Errorf("expected func, got: %v", ft) } if ft.NumIn() != 2 { return fmt.Errorf("expected two 'in' params, got: %v", ft) } if ft.NumOut() != 1 { return fmt.Errorf("expected one 'out' param, got: %v", ft) } if ft.In(0) != ft.In(1) { return fmt.Errorf("expected arg 1 and 2 to have same type, but got %v", ft) } var forReturnType bool boolType := reflect.TypeOf(forReturnType) if ft.Out(0) != boolType { return fmt.Errorf("expected bool return, got: %v", ft) } e[ft.In(0)] = fv return nil } // Below here is forked from go's reflect/deepequal.go // During deepValueEqual, must keep track of checks that are // in progress. The comparison algorithm assumes that all // checks in progress are true when it reencounters them. // Visited comparisons are stored in a map indexed by visit. type visit struct { a1 uintptr a2 uintptr typ reflect.Type } // unexportedTypePanic is thrown when you use this DeepEqual on something that has an // unexported type. It indicates a programmer error, so should not occur at runtime, // which is why it's not public and thus impossible to catch. type unexportedTypePanic []reflect.Type func (u unexportedTypePanic) Error() string { return u.String() } func (u unexportedTypePanic) String() string { strs := make([]string, len(u)) for i, t := range u { strs[i] = fmt.Sprintf("%v", t) } return "an unexported field was encountered, nested like this: " + strings.Join(strs, " -> ") } func makeUsefulPanic(v reflect.Value) { if x := recover(); x != nil { if u, ok := x.(unexportedTypePanic); ok { u = append(unexportedTypePanic{v.Type()}, u...) x = u } panic(x) } } // Tests for deep equality using reflected types. The map argument tracks // comparisons that have already been seen, which allows short circuiting on // recursive types. // equateNilAndEmpty controls whether empty maps/slices are equivalent to nil func (e Equalities) deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, equateNilAndEmpty bool, depth int) bool { defer makeUsefulPanic(v1) if !v1.IsValid() || !v2.IsValid() { return v1.IsValid() == v2.IsValid() } if v1.Type() != v2.Type() { return false } if fv, ok := e[v1.Type()]; ok { return fv.Call([]reflect.Value{v1, v2})[0].Bool() } hard := func(k reflect.Kind) bool { switch k { case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: return true } return false } if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { addr1 := v1.UnsafeAddr() addr2 := v2.UnsafeAddr() if addr1 > addr2 { // Canonicalize order to reduce number of entries in visited. addr1, addr2 = addr2, addr1 } // Short circuit if references are identical ... if addr1 == addr2 { return true } // ... or already seen typ := v1.Type() v := visit{addr1, addr2, typ} if visited[v] { return true } // Remember for later. visited[v] = true } switch v1.Kind() { case reflect.Array: // We don't need to check length here because length is part of // an array's type, which has already been filtered for. for i := 0; i < v1.Len(); i++ { if !e.deepValueEqual(v1.Index(i), v2.Index(i), visited, equateNilAndEmpty, depth+1) { return false } } return true case reflect.Slice: if equateNilAndEmpty { if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) { return false } if v1.IsNil() || v1.Len() == 0 { return true } } else { if v1.IsNil() != v2.IsNil() { return false } // Optimize nil and empty cases // Two lists that are BOTH nil are equal // No need to check v2 is nil since v1.IsNil == v2.IsNil from above if v1.IsNil() { return true } // Two lists that are both empty and both non nil are equal if v1.Len() == 0 || v2.Len() == 0 { return true } } if v1.Len() != v2.Len() { return false } if v1.Pointer() == v2.Pointer() { return true } for i := 0; i < v1.Len(); i++ { if !e.deepValueEqual(v1.Index(i), v2.Index(i), visited, equateNilAndEmpty, depth+1) { return false } } return true case reflect.Interface: if v1.IsNil() || v2.IsNil() { return v1.IsNil() == v2.IsNil() } return e.deepValueEqual(v1.Elem(), v2.Elem(), visited, equateNilAndEmpty, depth+1) case reflect.Ptr: return e.deepValueEqual(v1.Elem(), v2.Elem(), visited, equateNilAndEmpty, depth+1) case reflect.Struct: for i, n := 0, v1.NumField(); i < n; i++ { if !e.deepValueEqual(v1.Field(i), v2.Field(i), visited, equateNilAndEmpty, depth+1) { return false } } return true case reflect.Map: if equateNilAndEmpty { if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) { return false } if v1.IsNil() || v1.Len() == 0 { return true } } else { if v1.IsNil() != v2.IsNil() { return false } // Optimize nil and empty cases // Two maps that are BOTH nil are equal // No need to check v2 is nil since v1.IsNil == v2.IsNil from above if v1.IsNil() { return true } // Two maps that are both empty and both non nil are equal if v1.Len() == 0 || v2.Len() == 0 { return true } } if v1.Len() != v2.Len() { return false } if v1.Pointer() == v2.Pointer() { return true } for _, k := range v1.MapKeys() { if !e.deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, equateNilAndEmpty, depth+1) { return false } } return true case reflect.Func: if v1.IsNil() && v2.IsNil() { return true } // Can't do better than this: return false default: // Normal equality suffices if !v1.CanInterface() || !v2.CanInterface() { panic(unexportedTypePanic{}) } return v1.Interface() == v2.Interface() } } // DeepEqual is like reflect.DeepEqual, but focused on semantic equality // instead of memory equality. // // It will use e's equality functions if it finds types that match. // // An empty slice *is* equal to a nil slice for our purposes; same for maps. // // Unexported field members cannot be compared and will cause an informative panic; you must add an Equality // function for these types. func (e Equalities) DeepEqual(a1, a2 interface{}) bool { return e.deepEqual(a1, a2, true) } func (e Equalities) DeepEqualWithNilDifferentFromEmpty(a1, a2 interface{}) bool { return e.deepEqual(a1, a2, false) } func (e Equalities) deepEqual(a1, a2 interface{}, equateNilAndEmpty bool) bool { if a1 == nil || a2 == nil { return a1 == a2 } v1 := reflect.ValueOf(a1) v2 := reflect.ValueOf(a2) if v1.Type() != v2.Type() { return false } return e.deepValueEqual(v1, v2, make(map[visit]bool), equateNilAndEmpty, 0) } func (e Equalities) deepValueDerive(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { defer makeUsefulPanic(v1) if !v1.IsValid() || !v2.IsValid() { return v1.IsValid() == v2.IsValid() } if v1.Type() != v2.Type() { return false } if fv, ok := e[v1.Type()]; ok { return fv.Call([]reflect.Value{v1, v2})[0].Bool() } hard := func(k reflect.Kind) bool { switch k { case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: return true } return false } if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { addr1 := v1.UnsafeAddr() addr2 := v2.UnsafeAddr() if addr1 > addr2 { // Canonicalize order to reduce number of entries in visited. addr1, addr2 = addr2, addr1 } // Short circuit if references are identical ... if addr1 == addr2 { return true } // ... or already seen typ := v1.Type() v := visit{addr1, addr2, typ} if visited[v] { return true } // Remember for later. visited[v] = true } switch v1.Kind() { case reflect.Array: // We don't need to check length here because length is part of // an array's type, which has already been filtered for. for i := 0; i < v1.Len(); i++ { if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) { return false } } return true case reflect.Slice: if v1.IsNil() || v1.Len() == 0 { return true } if v1.Len() > v2.Len() { return false } if v1.Pointer() == v2.Pointer() { return true } for i := 0; i < v1.Len(); i++ { if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) { return false } } return true case reflect.String: if v1.Len() == 0 { return true } if v1.Len() > v2.Len() { return false } return v1.String() == v2.String() case reflect.Interface: if v1.IsNil() { return true } return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1) case reflect.Pointer: if v1.IsNil() { return true } return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1) case reflect.Struct: for i, n := 0, v1.NumField(); i < n; i++ { if !e.deepValueDerive(v1.Field(i), v2.Field(i), visited, depth+1) { return false } } return true case reflect.Map: if v1.IsNil() || v1.Len() == 0 { return true } if v1.Len() > v2.Len() { return false } if v1.Pointer() == v2.Pointer() { return true } for _, k := range v1.MapKeys() { if !e.deepValueDerive(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) { return false } } return true case reflect.Func: if v1.IsNil() && v2.IsNil() { return true } // Can't do better than this: return false default: // Normal equality suffices if !v1.CanInterface() || !v2.CanInterface() { panic(unexportedTypePanic{}) } return v1.Interface() == v2.Interface() } } // DeepDerivative is similar to DeepEqual except that unset fields in a1 are // ignored (not compared). This allows us to focus on the fields that matter to // the semantic comparison. // // The unset fields include a nil pointer and an empty string. func (e Equalities) DeepDerivative(a1, a2 interface{}) bool { if a1 == nil { return true } v1 := reflect.ValueOf(a1) v2 := reflect.ValueOf(a2) if v1.Type() != v2.Type() { return false } return e.deepValueDerive(v1, v2, make(map[visit]bool), 0) } golang-k8s-apimachinery-0.29.0/third_party/forked/golang/reflect/deep_equal_test.go000066400000000000000000000106021453143165200304160ustar00rootroot00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package reflect import ( "testing" ) func TestEqualities(t *testing.T) { e := Equalities{} type Bar struct { X int } type Baz struct { Y Bar } type Zap struct { A []int B map[string][]int } err := e.AddFuncs( func(a, b int) bool { return a+1 == b }, func(a, b Bar) bool { return a.X*10 == b.X }, ) if err != nil { t.Fatalf("Unexpected: %v", err) } type Foo struct { X int } type Case struct { a, b interface{} equal bool } table := []Case{ {1, 2, true}, {2, 1, false}, {"foo", "fo", false}, {"foo", "foo", true}, {"foo", "foobar", false}, {Foo{1}, Foo{2}, true}, {Foo{2}, Foo{1}, false}, {Bar{1}, Bar{10}, true}, {&Bar{1}, &Bar{10}, true}, {Baz{Bar{1}}, Baz{Bar{10}}, true}, {[...]string{}, [...]string{"1", "2", "3"}, false}, {[...]string{"1"}, [...]string{"1", "2", "3"}, false}, {[...]string{"1", "2", "3"}, [...]string{}, false}, {[...]string{"1", "2", "3"}, [...]string{"1", "2", "3"}, true}, {map[string]int{"foo": 1}, map[string]int{}, false}, {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true}, {map[string]int{"foo": 2}, map[string]int{"foo": 1}, false}, {map[string]int{"foo": 1}, map[string]int{"foo": 2, "bar": 6}, false}, {map[string]int{"foo": 1, "bar": 6}, map[string]int{"foo": 2}, false}, {map[string]int{}, map[string]int(nil), true}, {[]string(nil), []string(nil), true}, {[]string{}, []string(nil), true}, {[]string(nil), []string{}, true}, {[]string{"1"}, []string(nil), false}, {[]string{}, []string{"1", "2", "3"}, false}, {[]string{"1"}, []string{"1", "2", "3"}, false}, {[]string{"1", "2", "3"}, []string{}, false}, } for _, item := range table { if e, a := item.equal, e.DeepEqual(item.a, item.b); e != a { t.Errorf("Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) } } // Cases which hinge upon implicit nil/empty map/slice equality implicitTable := []Case{ {map[string][]int{}, map[string][]int(nil), true}, {[]int{}, []int(nil), true}, {map[string][]int{"foo": nil}, map[string][]int{"foo": {}}, true}, {Zap{A: nil, B: map[string][]int{"foo": nil}}, Zap{A: []int{}, B: map[string][]int{"foo": {}}}, true}, } for _, item := range implicitTable { if e, a := item.equal, e.DeepEqual(item.a, item.b); e != a { t.Errorf("Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) } } for _, item := range implicitTable { if e, a := !item.equal, e.DeepEqualWithNilDifferentFromEmpty(item.a, item.b); e != a { t.Errorf("Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) } } } func TestDerivatives(t *testing.T) { e := Equalities{} type Bar struct { X int } type Baz struct { Y Bar } err := e.AddFuncs( func(a, b int) bool { return a+1 == b }, func(a, b Bar) bool { return a.X*10 == b.X }, ) if err != nil { t.Fatalf("Unexpected: %v", err) } type Foo struct { X int } table := []struct { a, b interface{} equal bool }{ {1, 2, true}, {2, 1, false}, {"foo", "fo", false}, {"foo", "foo", true}, {"foo", "foobar", false}, {Foo{1}, Foo{2}, true}, {Foo{2}, Foo{1}, false}, {Bar{1}, Bar{10}, true}, {&Bar{1}, &Bar{10}, true}, {Baz{Bar{1}}, Baz{Bar{10}}, true}, {[...]string{}, [...]string{"1", "2", "3"}, false}, {[...]string{"1"}, [...]string{"1", "2", "3"}, false}, {[...]string{"1", "2", "3"}, [...]string{}, false}, {[...]string{"1", "2", "3"}, [...]string{"1", "2", "3"}, true}, {map[string]int{"foo": 1}, map[string]int{}, false}, {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true}, {map[string]int{"foo": 2}, map[string]int{"foo": 1}, false}, {map[string]int{"foo": 1}, map[string]int{"foo": 2, "bar": 6}, true}, {map[string]int{"foo": 1, "bar": 6}, map[string]int{"foo": 2}, false}, {map[string]int{}, map[string]int(nil), true}, {[]string(nil), []string(nil), true}, {[]string{}, []string(nil), true}, {[]string(nil), []string{}, true}, {[]string{"1"}, []string(nil), false}, {[]string{}, []string{"1", "2", "3"}, true}, {[]string{"1"}, []string{"1", "2", "3"}, true}, {[]string{"1", "2", "3"}, []string{}, false}, } for _, item := range table { if e, a := item.equal, e.DeepDerivative(item.a, item.b); e != a { t.Errorf("Expected (%+v ~ %+v) == %v, but got %v", item.a, item.b, e, a) } } }