Compare commits

...

145 Commits

Author SHA1 Message Date
Kubernetes Publisher
554656fd9d Fix dependencies to point to kubernetes-1.16.0-alpha.2 tag 2019-07-30 18:32:29 +00:00
Peter Swica
579ad46bdc Kubectl user exec should accept zero-length environment values #652 (#78875)
* Kubectl user exec should accept zero-length environment values #652

* Changing TestValidateAuthInfoExecInvalidEnv to allow for empty strings as Exec values

Kubernetes-commit: f30af9dd6da46f0f01e38b477d455907da9f1b6c
2019-07-25 19:01:41 -04:00
Kubernetes Publisher
a9c895e7f2 Merge pull request #77355 from apelisse/test-new-protoc
Use new reverse protobuf marshalling

Kubernetes-commit: a172e197b165f60c215b4467175bdd61d8a15e0e
2019-07-26 02:31:11 +00:00
Antoine Pelisse
3e0ec8b7fd Regenerate
Kubernetes-commit: 6568325ca2bef519e5c8228cd33887660b5ed7b0
2019-07-24 15:21:55 -07:00
Kubernetes Publisher
f16ff99a1d Merge pull request #77553 from s-urbaniak/fix-76956
pkg/util/workqueue/prometheus: fix double registration

Kubernetes-commit: f101466d2e4d96854c80f58203de2cc6b5aaeb6a
2019-07-23 02:31:03 +00:00
Kubernetes Publisher
8720706276 Merge pull request #80402 from mjudeikis/generic.retry
make client-go/util/retry more generic

Kubernetes-commit: 0d2b85878d417daf98ea59e8dc7a264a5533ae5a
2019-07-22 18:31:02 +00:00
Kubernetes Publisher
cea58bd85b Merge pull request #59416 from verb/debug-api
Add Ephemeral Containers to the Kubernetes core API

Kubernetes-commit: 7e6b70fbb5e86706f3fb9b9be0dc718028330d81
2019-07-22 14:31:04 +00:00
Lee Verberne
830fc9beb5 Generated code for ephemeral containers API
Kubernetes-commit: 4a753c7a44b5f92e52d4119530ac76e38b837336
2019-07-22 11:18:01 +00:00
Mangirdas
ee2735b48e make util/retry more generic
Kubernetes-commit: 2a0856a4d86600f3382dcdad26a0dd06428f951a
2019-07-21 19:00:40 +01:00
Kubernetes Publisher
0c47f9da00 Merge pull request #80123 from sttts/sttts-bump-kube-openapi2
Bump kube-openapi and structured-merge-diff

Kubernetes-commit: eee3a3749add92cd67b4c6f80567cd438a6c24e6
2019-07-17 02:31:32 +00:00
Kubernetes Publisher
81763ea0de Merge pull request #80200 from Huang-Wei/tolerant-event-related
Tolerate the case if `related` event cannot be found

Kubernetes-commit: 0791f5bc21e7f270e2be84e1db52166636d142d0
2019-07-16 07:27:25 -07:00
Wei Huang
43e18f2627 Tolerate the case if related event is nil
Kubernetes-commit: 187fc29e3efa915265f31991fe923ab46f0113ce
2019-07-15 23:19:04 -07:00
Kubernetes Publisher
1c70f03105 Merge pull request #73923 from liggitt/prefer-writeable-windows-homedir
Improve windows home directory selection

Kubernetes-commit: 98c68df6069dc8f81c1f983609be60b2045861cb
2019-07-16 06:30:11 +00:00
Dr. Stefan Schimanski
2e9049d47b Run update-vendor.sh
Kubernetes-commit: 91a3704938070b8b18dc39774d5acd8234c0c55c
2019-07-13 10:07:03 +02:00
Kubernetes Publisher
611184f7c4 Merge pull request #79786 from wojtek-t/watch_bookmarks_beta
Promote WatchBookmarks to beta

Kubernetes-commit: a46eed0ca3988a8d1fba78e2b8f882df1f7406f4
2019-07-12 10:29:59 +00:00
wojtekt
a553c2c2cc Enable watch bookmarks in reflector
Kubernetes-commit: 175c07f4e6d3a59bb6d9870c4a4a78c41940b2dd
2019-07-04 13:12:08 +02:00
Kubernetes Publisher
e8d4ff64b7 Merge pull request #79549 from liggitt/admission-webhooks-v1
Promote admission webhook API to v1

Kubernetes-commit: 3dd8add5d25d29358fb239ac23461643f1d8effc
2019-07-12 02:29:59 +00:00
Kubernetes Publisher
3d21683b87 Merge pull request #79077 from yuyulei/remove-ratelimiter
add "burst" validation check to code-generator

Kubernetes-commit: c302da4f429f80b9857261bedc17a62069e5b09e
2019-07-12 02:29:57 +00:00
Kubernetes Publisher
deaa3604a6 Merge pull request #79566 from sukeesh/auditlog
Cleanup: Audit log and error capitalization

Kubernetes-commit: 1c99825adfb30f6abb177e85ddb31f2fdef011e1
2019-07-11 22:30:15 +00:00
Kubernetes Publisher
0cb32da4ad Merge pull request #78742 from smarterclayton/client_gc
The garbage collector and quota counter should use the metadata client and protobuf to access resources

Kubernetes-commit: 91b26341f47088896ad373f4df8c7bdf5b249bcc
2019-07-11 14:33:33 -07:00
yaqi
885c525d76 execute hack/update-codegen.sh, sync updates
Kubernetes-commit: aa71695be1006c843de836104006a724451fa9a0
2019-07-11 20:34:59 +08:00
Clayton Coleman
0f7aa3096b Rename metadata.NewConfigOrDie to be consistent
Updated name to match dynamic client

Kubernetes-commit: 98d87a4f03e22bb8e4d22460855913d23930685a
2019-07-10 18:35:45 -04:00
Himanshu Pandey
79ec8b81e2 Added custom error message when wrong file is provided with KUBECONFIG (#78185)
* Added custom error message when wrong file is provided with KUBECONFIG

* Modified test case

* Updated the code to warn the missing files

* Renamed the variable

Kubernetes-commit: a5eedcde611658c220c56d2819bf0420aded4ed6
2019-07-10 20:24:54 -07:00
Kubernetes Publisher
2cca84cf52 Merge pull request #78744 from smarterclayton/client_ns
Update namespace controller to use the metadata client

Kubernetes-commit: ca6cc69bd4e1e5b72b94863729a5a10936684867
2019-07-11 10:39:05 +00:00
Kubernetes Publisher
890a5924da Merge pull request #79998 from yastij/owners-events
add wojtek-t and yastij as approvers/reviewers for events package

Kubernetes-commit: 7b9afe00f1ac5cb40764db08ab3ff068abf1c64d
2019-07-10 16:25:18 -07:00
Yassine TIJANI
2c14e0f870 add wojtek-t and yastij as approvers/reviewers for events package
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: a2994a296e3a509b40e74d77ae3f42d25a4fb1a5
2019-07-10 21:41:04 +02:00
Kubernetes Publisher
4a0861cac5 Merge pull request #78470 from nikhita/security-contacts-psc
Update SECURITY_CONTACTS with current PSC

Kubernetes-commit: 69c94671242c3d17bb32dcdc7ceb5d35535a8f34
2019-07-11 10:39:03 +00:00
Kubernetes Publisher
d5b3f640e9 Merge pull request #79724 from deads2k/update-raw
make kubectl --raw consistent for create, update, get, delete

Kubernetes-commit: b130ff450232eaf9100548aa08295168871a4ee9
2019-07-11 10:39:01 +00:00
Kubernetes Publisher
2abfa8322d Merge pull request #79817 from tianyuansun/fix/golint-error
fix: golint error of clientgo

Kubernetes-commit: 5b5301e9df68ee50e43ccc16d6dfd74e491c94a5
2019-07-11 10:39:00 +00:00
Kubernetes Publisher
39956190ba Merge pull request #79729 from nikhita/client-go-install
client-go: update installation instructions to reflect v12.0.0 release

Kubernetes-commit: 2c64ef5b7113d0c7b7782f8471cf541c6edf939f
2019-07-11 10:38:59 +00:00
Kubernetes Publisher
4ed54556a1 Merge pull request #79837 from yastij/add-eventexpension
add fakes for events package, add startEventWatcher to event interface

Kubernetes-commit: e8cc9ea02ead506c8b7f478210e3368f13c925b6
2019-07-06 00:55:06 +00:00
Yassine TIJANI
36ad1c08c8 add fakes for events package, add startEventWatcher to event interface
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: bfa7de0165c911178daaac1991c08f7424b0048f
2019-07-05 14:18:02 +02:00
suntianyuan
39b8e819a8 fix: golint error of clientgo
Kubernetes-commit: 853cf2d8969e49ffb5fe42c0d867edd2396e7a68
2019-07-05 11:37:02 +08:00
Kubernetes Publisher
07281898b0 Merge pull request #79637 from toliu/toliu/update-gophercloud
Update gophercloud vendor dependency to v0.1.0

Kubernetes-commit: 886c5d261f67d74a9d4d5e5f2f31c88dd5c2666f
2019-07-04 04:55:12 +00:00
Kubernetes Publisher
5aceab6588 Merge pull request #79306 from vllry/gogo-dep-tag
Updated github.com/gogo/protobuf from v0.5 SHA to v1.0.0 tag

Kubernetes-commit: 03aeab967abccc1da80ff2340f3bccf2131f63d8
2019-07-03 20:57:46 +00:00
Kubernetes Publisher
ab25eb98f4 Merge pull request #77819 from smarterclayton/client
Add a metadata client to client-go that can read PartialObjectMetadata

Kubernetes-commit: 024c7bd84f1e9a386778e14068f57c3b1e193565
2019-07-03 20:57:44 +00:00
Kubernetes Publisher
ac1a9799d0 Merge pull request #79045 from odinuge/file-perms
Fix file permissions for non executable files

Kubernetes-commit: 3c38115a326a229274661d4d0ad60b77342ab26c
2019-07-03 16:55:02 +00:00
Nikhita Raghunath
6b2766cb39 client-go: update installation instructions to reflect v12.0.0 release
Kubernetes-commit: d55b8791781ea2f9560561d4680f547f43ce4ddb
2019-07-03 20:09:14 +05:30
Kubernetes Prow Robot
50dafea438 Merge pull request #625 from nikhita/release-v.12
Update README to reflect v12.0.0 release
2019-07-03 07:38:32 -07:00
David Eads
4747a4bccd make kubectl --raw consistent for create, update, get, delete
Kubernetes-commit: 09c55bd1172e9b044762957efeae2c34de145bef
2019-07-03 08:27:19 -04:00
Kubernetes Publisher
b3101450e2 Merge pull request #73977 from khenidak/ipv6dualstack
ipv6 dual stack (Phase 1 - ALPHA)

Kubernetes-commit: a33840e0232aaf2b5c14c1c754d9189d7e841ec1
2019-07-03 04:55:05 +00:00
Kubernetes Publisher
d1d42e472f Merge pull request #79480 from wojtek-t/fix_clientgo_benchmark
Fix client-go request benchmark

Kubernetes-commit: 4b3987d0c390fbac65df5c0a1cf7f89d6ddfc6ce
2019-07-03 04:55:00 +00:00
Kubernetes Publisher
bf9edae4fc Merge pull request #79646 from tedyu/backoff-rw
Utilize RWMutex for efficient backoff operations

Kubernetes-commit: a57af6dadd493b3c629c5c2c636c03e8371f95c3
2019-07-03 04:54:59 +00:00
Vallery Lancey
73102250a8 Updated github.com/gogo/protobuf from SHA to nearest-pinnable tag (v1.0.0), as part of dependency management cleanup: #79234
Kubernetes-commit: fe59ee8aaf8c7399476d286349caca9e3c05c522
2019-07-02 21:44:06 -07:00
Nikhita Raghunath
418369312e Update README to reflect v12.0.0 release 2019-07-02 21:04:06 +05:30
Ted Yu
e12fc43899 Utilize RWMutex for efficient backoff operations
Kubernetes-commit: 9e73e69f4947c88e0d9b6e35ea115086d917a2a2
2019-07-02 16:12:43 +08:00
Kubernetes Publisher
c597d11714 Merge pull request #71170 from atlassian/rand-cleanup2
Cleanup math/rand package usage

Kubernetes-commit: cdddcf9b4856a7252fb47349c0f1aaf8eca05866
2019-07-02 04:54:42 +00:00
Kubernetes Publisher
e1c806028d Merge pull request #78820 from haosdent/fix_typos
Fix typos.

Kubernetes-commit: da0f51ffedcd32d1af31c7b6315e14e5a047abde
2019-07-02 00:54:47 +00:00
liushi
e926b6fbfd Update gophercloud vendor dependency to v0.1.0
Kubernetes-commit: 094fed6598b5c5731dd936bb80a85816a24bda53
2019-07-01 02:22:54 -07:00
Sukeesh
94daee0164 Cleanup: Audit log and error capitalization
Kubernetes-commit: 346fdbccf0ac06ab3fa0e51ab3b92fdc041bb4cc
2019-06-30 11:56:27 +09:00
Kubernetes Publisher
98902b2ea1 Merge pull request #79367 from sukeesh/sukeesh/updatedocsgo
Update doc.go in staging/src/k8s.io/

Kubernetes-commit: 583d83a13557fc4ceb68355fa590c194f23a1721
2019-06-29 12:54:32 +00:00
Jordan Liggitt
1f68422b5b Generated
Kubernetes-commit: b15aed640995630d4a87e3c0deb138f6a2109722
2019-06-28 15:45:50 -07:00
Khaled Henidak(Kal)
11b26abdb1 vendor updates
Kubernetes-commit: 2b776677183502cd2bcba530dbf03bff7d39c057
2019-06-28 20:46:52 +00:00
Kubernetes Publisher
8873628f23 Merge pull request #79375 from answer1991/feature/flow-control-context-aware
RateLimiter context aware and fix request may hang issue

Kubernetes-commit: 46667d0bbbcee0d8435feed3fe2fcb8c8977f9ab
2019-06-28 00:54:42 +00:00
Kubernetes Publisher
11059204e0 Merge pull request #76124 from tossmilestone/fix-crd-validate-items
Fix CRD validation error on 'items' field

Kubernetes-commit: 1a80962db6fc7138f4d2925a82d7e74cd5ec4fcd
2019-06-27 20:54:36 +00:00
wojtekt
deab738de6 Fix client-go request benchmark
Kubernetes-commit: 85763127c0b9399e6c48ec144c4716543a6cbe2d
2019-06-27 17:18:56 +02:00
He Xiaoxi
8079e84782 Bump gopkg.in/check to v1.0.0-20180628173108-788fd7840127
Signed-off-by: He Xiaoxi <xxhe@alauda.io>

Kubernetes-commit: 17ff2eda575686d054ac38b4670be4b4d5a474a0
2019-06-27 11:40:31 +08:00
He Xiaoxi
a691cd0d29 Bump objx to v0.2.0
Signed-off-by: He Xiaoxi <xxhe@alauda.io>

Kubernetes-commit: fb03e0c46120e5f7505c9bcc2999f39a415ea167
2019-06-27 11:33:27 +08:00
He Xiaoxi
bc1d7464f6 Bump go-openapi dependencies to preferred version
Signed-off-by: He Xiaoxi <xxhe@alauda.io>

Kubernetes-commit: eb2a1c10fad8d1ec0db03283bb156a0e3232607f
2019-06-27 10:42:28 +08:00
He Xiaoxi
66b01d91cd Fix CRD validation error for 'items' field
Signed-off-by: He Xiaoxi <xxhe@alauda.io>

Kubernetes-commit: 2e37a3bebe8c4a628e973e231d95c4ce5261c15f
2019-06-26 15:30:49 +08:00
Mike Spreitzer
1ec4b74c7b Fixed and clarified comments and parameter names in index.go (#77633)
* Fixed and clarified comments and parameter names in index.go

Fixed the comment on IndexFunc to say that it returns multiple indexed values.

Clarified the comments and parameter names in the Indexer interface to
consistently use a dichotomy between "storage keys" and "indexed
values".

* Updated comments in index.go based on review by Liggitt

Kubernetes-commit: 4160909495626262ae514bab30fe81a3ff869d39
2019-06-26 04:54:20 +00:00
Sukeesh
c8a491caea Update doc.go in staging/src/k8s.io/
Kubernetes-commit: c8bd20d1b289eadb6abc20f0868f4a5a6a668035
2019-06-25 22:43:44 +09:00
Kubernetes Publisher
2c6e35a5b9 Merge pull request #79303 from liggitt/preferred-deps
Use preferred dependency versions

Kubernetes-commit: dc764030c2cef7aced8dd1a9e3b5ca389223b8b6
2019-06-24 08:53:56 +00:00
Jordan Liggitt
d126bd6059 github.com/google/uuid v1.1.1
Kubernetes-commit: 29853a1976d4306593159d369cd8487eeed2b3d6
2019-06-22 11:40:22 -07:00
chenjun.cj
8f99f83432 flowcontrol context aware and fix request may hang issue
Kubernetes-commit: ce8805f95fcf6540397eaa60b8d84db752f05eea
2019-06-21 12:17:46 +08:00
Kubernetes Publisher
585a16d2e7 Merge pull request #78187 from dims/update-vendored-dependencies-to-released-versions-2
Update vendored dependencies to released version

Kubernetes-commit: 00e13dbc12612bd838bc5b9000a64c08f6f8a828
2019-06-20 07:40:45 +00:00
Kubernetes Publisher
7de88b14dc Merge pull request #78991 from sallyom/reflector-too-old-warning-info
Reflector watchHandler: make 'The resourceVersion for ... watch is too old' log Info not Warning

Kubernetes-commit: 1a15ce14473fb60bb817d1f29147ae7b70a0987e
2019-06-15 12:59:33 +00:00
Kubernetes Publisher
8352f269cd Merge pull request #78869 from enj/enj/i/better_cache_mutation_diff
cache mutation detector: use correct diff function

Kubernetes-commit: e52dc689e1bc28bbb589528182971ef19a94ba45
2019-06-15 08:59:36 +00:00
Kubernetes Publisher
5759fedc8d Merge pull request #78743 from smarterclayton/patch_object
Calling PatchAction on typed objects should work correctly

Kubernetes-commit: f09c828496a2010866a30dbb7cdf5b46a22bfa98
2019-06-15 00:59:33 +00:00
Odin Ugedal
2ee65257e5 Fix file permissions for non executable files
Normal files should have permissions 644 by default,
and does not require the last bit to be
executable

Signed-off-by: Odin Ugedal <odin@ugedal.com>

Kubernetes-commit: 35cb87f9cf71776e99a970dfff751cd29ba7ebfb
2019-06-14 21:04:39 +02:00
Davanum Srinivas
132e540b8d updating gopkg.in/yaml.v2 to v2.2.2
Kubernetes-commit: 1c7bfba9160de8e2449d410bda6f289907136a71
2019-06-14 11:46:38 -04:00
Davanum Srinivas
56cdfd719e updating github.com/stretchr/testify to v1.3.0
Kubernetes-commit: 21fc7d283d900dda76d7f98c6a4b3632659ea6d7
2019-06-14 11:41:28 -04:00
Davanum Srinivas
dc4444a75a updating github.com/spf13/pflag to v1.0.3
Kubernetes-commit: 99d5aa759e409864652d11d5a603b4cd2d95ffc2
2019-06-14 11:38:01 -04:00
Davanum Srinivas
e65eb0b5fe updating github.com/spf13/cobra to v0.0.4
Kubernetes-commit: 1b401bdab7c2d9de86363adf6a490e35d2f3c832
2019-06-14 11:34:37 -04:00
Davanum Srinivas
dc39ccf7e4 updating github.com/onsi/gomega to v1.5.0
Kubernetes-commit: 37479f975ea0383b771f99c308a762d2ded4b166
2019-06-14 11:20:57 -04:00
Davanum Srinivas
ef4592e4bb updating github.com/onsi/ginkgo to v1.8.0
Kubernetes-commit: 8629f7fa255d2c7c2d6de2a107a0fd1115b0c9fa
2019-06-14 11:19:15 -04:00
Davanum Srinivas
efbc7af4f2 updating github.com/mholt/caddy to v1.0.0
Kubernetes-commit: a7c552be862234c55721d4a00e76b39500831986
2019-06-14 11:10:45 -04:00
Davanum Srinivas
d422db9ef0 updating github.com/json-iterator/go to v1.1.6
Kubernetes-commit: c6b2b45fa98a230d0be5c96270db7058cfaa3b59
2019-06-14 11:04:03 -04:00
Davanum Srinivas
3c1534c2eb updating github.com/google/gofuzz to v1.0.0
Kubernetes-commit: d04014a2087f52d5263dfb7e48a4e223744e813d
2019-06-14 11:00:40 -04:00
Davanum Srinivas
47059beb8d updating github.com/evanphx/json-patch to v4.2.0+incompatible
Kubernetes-commit: ad2fc6903ae93f77d7c96fc5bc7e7151e0757a2b
2019-06-14 10:51:52 -04:00
Kubernetes Publisher
9f52f725d4 Merge pull request #76513 from vladimirvivien/create-update-delete-dynamic-example
Create-update-delete-deployment example using dynamic package

Kubernetes-commit: eae93f9c7acaca32a9a82a7de77b9f3135b055c0
2019-06-14 12:59:42 +00:00
Kubernetes Publisher
f1a699f1fd Merge pull request #74018 from JoeWrightss/patch-1
remove redundant words 'the' in comment

Kubernetes-commit: a52e08a2df1391798e1c620513f23009c0ec0264
2019-06-14 04:59:34 +00:00
Sally O'Malley
23e6ea699e Modify klog Warning 'The resourceVersion for the provided watch is too old' to Info.
This warning comes from Reflector watchHandler, from the apiserver error that
indicates a watch was restarted.  This happens when etcd drops the connection
and resources are relisted.  This informs the user that the watchers are
operating properly, so should be logged as Info rather than Warning.

Kubernetes-commit: a953d1542c06456c6fe97121abd99acd352545df
2019-06-13 16:36:06 -04:00
Kubernetes Publisher
5c45477a8a Merge pull request #78931 from krzysied/revert-78465-bump-klog
Revert "Bump klog to v0.3.2"

Kubernetes-commit: 9b15a5b0700724d2ea1021ceafb72c378b638a0b
2019-06-12 12:59:19 +00:00
Krzysztof Siedlecki
f139db1a80 Revert "Bump klog to v0.3.2"
Kubernetes-commit: 7dcec919a2e5e8e23da3d0dd61276d86c9b0e4b6
2019-06-12 10:27:41 +02:00
Monis Khan
55984cdeff cache mutation detector: use correct diff function
The cache mutation detector must use a diff function that is
designed to show differences that cause reflect.DeepEqual to return
false.

Signed-off-by: Monis Khan <mkhan@redhat.com>

Kubernetes-commit: dd4f53cd45499687e768de46a15038d61935ab96
2019-06-10 15:47:51 -04:00
Haosdent Huang
465553e287 Fix typos.
Kubernetes-commit: 7ce6e7189166e8b284ab44ae1dccd4df1719ab3e
2019-06-09 15:34:53 +08:00
Clayton Coleman
cade5c0473 Add fake client, informer factory, and lister to metadata client
These will be used by the garbage collector controller and others that
use higher level primitives.

Kubernetes-commit: bc89c37f32aa6cfd0f9ca975d9221d0a89320623
2019-06-05 14:28:51 -04:00
Clayton Coleman
58c2617e28 Calling PatchAction on typed objects should work correctly
Only Unstructured objects worked (because unstructured implicitly
clears the .Object map when Unmarshal is called). We must reset
obj before we attempt to unmarshal into it.

Kubernetes-commit: 3b599b383d32d80188fdc73adae1613cc167cbdd
2019-06-05 14:27:59 -04:00
Kubernetes Publisher
e65ca70987 Merge pull request #78520 from yastij/event-set-source
set deprecatedEventSource to be backward compatible

Kubernetes-commit: 766737ed8602ebd4a5e9c3e7cc454c00d08fd48c
2019-06-02 13:00:07 +00:00
Yassine TIJANI
540d4de249 set deprecatedEventSource to be backward compatible
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: 41e384397cf8505f226dc18f4250b297f98937ab
2019-05-30 13:03:20 +00:00
Kubernetes Publisher
88ff0afc48 Merge pull request #78482 from tedyu/evt-expansion
Check namespaces match in UpdateWithEventNamespace

Kubernetes-commit: b3981a2f9acce4ee57f55131873ceaccf1707598
2019-05-31 13:24:39 +00:00
Kubernetes Publisher
d58e65e5f4 Merge pull request #78465 from yuwenma/bump-klog
Bump klog to v0.3.2

Kubernetes-commit: b094dd9bc3a4617b587b04993931a6110691ddc0
2019-05-31 13:24:38 +00:00
Kubernetes Publisher
5cefd29505 Merge pull request #78037 from yastij/fix-event-cleanup
clean singleton event when calling finishSeries

Kubernetes-commit: b0a81349effc68e19a6e063c8b7ebe0a4c8774e3
2019-05-31 13:24:37 +00:00
Kubernetes Publisher
5ecd7315c7 Merge pull request #78176 from yastij/event-expansion-client
add event_expansion for v1beta1 Events

Kubernetes-commit: 3978efc2167d9b3bcf266b7c2f3e7fb579f04e20
2019-05-31 13:24:36 +00:00
Kubernetes Publisher
d2583122ce Merge pull request #77434 from mikedanese/watching
NewIndexerInformerWatcher: fix goroutine leak

Kubernetes-commit: 4fed75302a869c1c633e482af5e9d5fa3966fba6
2019-05-31 13:24:35 +00:00
Ted Yu
898c0bb9ba Check namespaces match in UpdateWithEventNamespace
Kubernetes-commit: 5990a7d5070400616192c09e3d68f5cebcf9db2f
2019-05-29 08:40:02 -07:00
Nikhita Raghunath
bae599d048 Update SECURITY_CONTACTS with current PSC
Kubernetes-commit: 42ea492bfdab9fcfd67dd4ea621b8a1a40da550d
2019-05-29 15:22:35 +05:30
yuwenma
339a6d47a3 Bump klog to v0.3.2
Kubernetes-commit: 5cef37433e55827226f20981598ddfa2c6511809
2019-05-28 22:45:19 -07:00
Kubernetes Publisher
79226fe194 Merge pull request #77793 from SataQiu/fix-golint-client-go-20190513
Fix golint failures of client-go/tools/auth client-go/tools/portforward

Kubernetes-commit: 5666982b2745e88d2009e4155d4a22f6c01061df
2019-05-28 15:47:35 +00:00
Kubernetes Publisher
df931a0dfc Merge pull request #78216 from mtaufen/update-klog
Update klog to v0.3.1

Kubernetes-commit: 25b0d2dbf4521bd1e7bae471d85c369eb89f9555
2019-05-28 15:47:35 +00:00
Michael Taufen
ecfd343fcc Update klog to v0.3.1
Includes recent fixes, notably https://github.com/kubernetes/klog/pull/66

Kubernetes-commit: ee7bcc53a206f669b057e38a477b51b3477aab23
2019-05-22 10:51:33 -07:00
Kubernetes Publisher
cbb2f3806a Merge pull request #78019 from mrkm4ntr/use-constant
Change to use a constant

Kubernetes-commit: 15d88d19d33ffd50bc54bcb7f95946ecedb4e42b
2019-05-28 15:47:33 +00:00
Kubernetes Publisher
ba257ce558 Merge pull request #77991 from mikedanese/ledoc
cleanup some leader election client doc

Kubernetes-commit: 69c90d8cca62d4dbb1acb966608d5100bada5500
2019-05-28 15:47:33 +00:00
Kubernetes Publisher
09484b1d58 Merge pull request #78063 from tedyu/evt-lock
Change lock type to write lock in eventBroadcasterImpl

Kubernetes-commit: 667c3ed94cdf52fb7dd26cfa9de7759df639c7d2
2019-05-28 15:47:32 +00:00
Kubernetes Publisher
2830425c91 Merge pull request #77170 from smarterclayton/delay_queue_reentrant
DelayingQueue.ShutDown() should be reentrant

Kubernetes-commit: 4891eaa3adbfafb61d2bd264d2f0daef124ee8b9
2019-05-28 15:47:31 +00:00
Kubernetes Publisher
206dbbdfd5 Merge pull request #78041 from yastij/fix-lastTimeObserved
update LastObservedTime instead of eventTime

Kubernetes-commit: b6f51d16d8a26558356b2eb876f7f3fdb0265aa9
2019-05-28 15:47:30 +00:00
Ted Yu
3ebee8cab0 Change lock type to write lock in eventBroadcasterImpl
Kubernetes-commit: 94af465819bd41a97b00acf997d5a7e077cbb654
2019-05-17 19:19:42 -07:00
Kubernetes Publisher
4645e65539 Merge pull request #78040 from tedyu/evt-broadcast
Use lock in eventBroadcasterImpl#refreshExistingEventSeries

Kubernetes-commit: 7809732e307e429b718a7675c08e19c1675789a9
2019-05-17 18:57:42 -07:00
Yassine TIJANI
1a6ae704e2 update LastObservedTime instead of eventTime
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: c6b224b16cff3c69b7a0d5b98b02c89777277971
2019-05-17 18:56:38 +02:00
Ted Yu
c3d4ca8db6 Use lock in eventBroadcasterImpl#refreshExistingEventSeries
Kubernetes-commit: 32241b0751c9b9bd5d061eae9a42bd88970d8478
2019-05-17 09:30:54 -07:00
Kubernetes Publisher
7fc601088d Merge pull request #65782 from yastij/eventv2-eventf
Implementing logic for v1beta1.Event API

Kubernetes-commit: e67c266a72bf7b379a2b283a9989804f2282bac5
2019-05-28 15:47:28 +00:00
Kubernetes Publisher
f027d0f9e7 Merge pull request #77945 from michaelfig/client-go-namespaced-dynamicinformer
Honour NewFilteredDynamicSharedInformerFactory namespace argument

Kubernetes-commit: e0f28e5d0f03392e2970742426438c05a1778cb4
2019-05-28 15:47:27 +00:00
Kubernetes Publisher
4ec78d810e Merge pull request #77936 from liggitt/shorten-cert-wait
Interrupt WaitForCertificate if desired kubelet serving cert changes

Kubernetes-commit: a6b546eb72481165bc4d799d74c2ee8ae903e254
2019-05-28 15:47:26 +00:00
Kubernetes Publisher
864b54ac36 Merge pull request #77925 from liggitt/icc-tokenfile
honor overridden tokenfile, add InClusterConfig override tests

Kubernetes-commit: 56683a2f1f4a16f592634306633a6bb3e252f051
2019-05-28 15:47:25 +00:00
Kubernetes Publisher
19c6c11b98 Merge pull request #77874 from yuchengwu/fix-CVE-2019-11244
fix CVE-2019-11244: `kubectl --http-cache=<world-accessible dir>` cre…

Kubernetes-commit: 730bc968b95842022c4c81d607bf6411b459a675
2019-05-28 15:47:24 +00:00
Kubernetes Publisher
e10a9b6eaa Merge pull request #77585 from andyxning/fix_leader_election_start
enhance leader election doc

Kubernetes-commit: c2847e8b689c92d5cc4f1bb00d96df9dcc2cebfe
2019-05-28 15:47:24 +00:00
Kubernetes Publisher
ed5ad93c9c Merge pull request #76442 from viegasdom/fix-golint-utils-bandwith
Fix golint failures of util/bandwith/*.go

Kubernetes-commit: 37281a400d29ec4f515fb8a590d4f012689d89d7
2019-05-28 15:47:23 +00:00
viegasdom
56a6b7d222 sync: squashed up to merge 69bd30507559be3dea905686b46bc3295c951f45 in 37281a400d29ec4f515fb8a590d4f012689d89d7 2019-05-28 15:47:22 +00:00
Yassine TIJANI
0dc1c77c9f clean singleton event when calling finishSeries
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: b6663e8d4fceb6f7de7267149341d51966276df8
2019-05-17 16:55:45 +02:00
Yassine TIJANI
f6c046f83a add event_expansion for v1beta1 Events
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: e29cb0fb7fbf5470e7caf2b3f1fc3d039bdda4ca
2019-05-17 12:12:19 +02:00
Shintaro Murakami
b37f4f2534 Change to use a constant
Kubernetes-commit: 2fcb248354ed5a3bf3154b1a8a0aafa237b2b37c
2019-05-17 18:40:03 +09:00
Mike Danese
e4a7e70861 cleanup some leader election client doc
Kubernetes-commit: 5df7f529f063ab354b050418b40c1f1dd2600175
2019-05-16 12:54:50 -07:00
Michael FIG
c80077227f Honour NewFilteredDynamicSharedInformerFactory namespace argument
Kubernetes-commit: 2ed3272a5e428690c066a7c09e5c6a2fad3d74b6
2019-05-15 11:48:17 -06:00
Jordan Liggitt
987d27d1af Interrupt WaitForCertificate if desired kubelet serving cert changes
Kubernetes-commit: 739a75fc32c5337ddbd13691e9bf6648fb13ff0d
2019-05-15 11:47:23 -04:00
Jordan Liggitt
9ae57bd75e honor overridden tokenfile, add InClusterConfig override tests
Kubernetes-commit: 7306fb7a89739a2fb48bfeb74595a5daa25060bd
2019-05-15 08:15:02 -04:00
Kubernetes Publisher
7b18d6600f Merge pull request #77613 from mikedanese/fixinclusterconfig
BoundServiceAccountTokenVolume: fix InClusterConfig

Kubernetes-commit: 5c4b6528c2e9fa989bb6af9dea15d28ca6ac4ef3
2019-05-15 06:37:10 +00:00
Kubernetes Publisher
78e94f51a0 Merge pull request #70929 from mikedanese/cmp
migrate everything to unify diff.Diff method using cmp

Kubernetes-commit: 1ae09a371faccfc193f9b8d82a9ef77bf3e7fb14
2019-05-15 02:37:09 +00:00
Mike Danese
459cb72bc6 BoundServiceAccountTokenVolume: fix InClusterConfig
Kubernetes-commit: 4198f28855cbda6dac61408fcba6f2d576a9347c
2019-05-14 09:29:16 -07:00
Yucheng Wu
790a4f6363 fix CVE-2019-11244: kubectl --http-cache=<world-accessible dir> creates world-writeable cached schema files
Kubernetes-commit: f228ae3364729caed59087e23c42868454bc3ff4
2019-05-14 14:49:38 +08:00
Andy Xie
f2156b6dde enhance leader election doc
Kubernetes-commit: 95f33ce39957e86cc3e81f4c8f325ecd133cbe9d
2019-05-14 10:36:22 +08:00
Sergiusz Urbaniak
ee078c72e2 pkg/util/workqueue: delete deprecated metrics
This deletes deprecated metrics and simplifies registration.

Kubernetes-commit: 4532cfd85c00da6f64e03fcf05f5636adc1151c7
2019-05-13 13:22:08 +02:00
SataQiu
185f79a1f6 fix golint failures of client-go/tools/auth client-go/tools/portforward
Kubernetes-commit: a89d75e3b822a13377373b0ef1c1969c69c616ec
2019-05-13 11:13:38 +08:00
Mike Danese
ba0da5aa4d NewIndexerInformerWatcher: fix goroutine leak
There was some weird queuing going on. The queue was implement by
spawning goroutines that would block on the "ticketer" until it was
their turn to write to the output channel. If N events where in the
watch when the consumer of the watch stopped reading events, N
goroutines would leak. In unit tests of the certificate manager, this
was causing ~10k goroutines to leak.

Fix it with a buffering event processor that uses only one routine and
cancels correctly.

Kubernetes-commit: cafc640bfa0f7362b178b1b896085962d018afe3
2019-05-03 23:50:53 -04:00
Clayton Coleman
bdead75b4b DelayingQueue.ShutDown() should be reentrant
All queue ShutDown() calls should be able to be invoked multiple times.

```
Observed a panic: "close of closed channel" (close of closed channel)
/go/src/github.com/openshift/cluster-version-operator/vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go:76
/go/src/github.com/openshift/cluster-version-operator/vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go:65
/go/src/github.com/openshift/cluster-version-operator/vendor/k8s.io/apimachinery/pkg/util/runtime/runtime.go:51
/usr/local/go/src/runtime/asm_amd64.s:573
/usr/local/go/src/runtime/panic.go:502
/usr/local/go/src/runtime/chan.go:333
/go/src/github.com/openshift/cluster-version-operator/vendor/k8s.io/client-go/util/workqueue/delaying_queue.go:137
```

Use sync.Once to guarantee a single close.

Kubernetes-commit: d2f7eb5235a93556261c8947e7a87342aeeaee2b
2019-04-27 16:16:55 -04:00
Mike Danese
366dcbb3df vendor github.com/google/go-cmp
Kubernetes-commit: 76f683a8f3dc2977846e16b2ea14208a51c2cb6b
2019-04-22 21:41:46 -07:00
Kubernetes Publisher
36e0c54762 Merge pull request #77456 from MikeSpreitzer/fix-run-comment
Fix comment on SharedInformer.Run

Kubernetes-commit: e5fec6507bf1642917c34d84ef453ec53cd47dbc
2019-05-14 02:37:11 +00:00
Vladimir Vivien
abf396d787 Create-update-delete-deployment example using dynamic package
Kubernetes-commit: 103c71a2b576cf2557418df3f235744b1d7f1e4e
2019-04-12 13:37:18 -04:00
Clayton Coleman
b83dc9a7d9 Fake ObjectReaction should handle PartialObjectMetadata special
When a client requests a PartialObjectMetadata returned from the
ObjectReaction type, if the object has a GVK set use that instead
of what the schema returns, since the majority of clients getting
partial object metadata will be doing so using the metadata client
or server side conversion.

Kubernetes-commit: baf091e9dbad00db39e246815f9d7a21d148044f
2019-04-03 12:12:11 -04:00
Clayton Coleman
0eaec69666 Add a metadata client to client-go that can read PartialObjectMetadata
This client exposes operations on generic metadata (get, list, watch, delete)
and allows patch operations. The client always uses protobuf and requests
the server transform the response into the appropriate object. Using this
client simplifies the work of generic controllers by allowing them to treat
all objects the same, and also improves performance both in the amount of
data sent as well as allowing protobuf on CRD resources.

Kubernetes-commit: 21f5e643d9dbe6b65d21713dc16ab8888de5423e
2019-04-02 15:45:07 -04:00
zhoulin xie
4e51191303 remove redundant words 'the' in comment
Signed-off-by: zhoulin xie <zhoulin.xie@daocloud.io>

Kubernetes-commit: 77ba845e6d2f0cba81993621531ae5915e7c072b
2019-02-13 22:40:34 +08:00
Jordan Liggitt
6d7018244d Improve windows home directory selection
Kubernetes-commit: 448e3f792a2993ae3b3133740904ed87c4515fd0
2019-02-11 12:17:06 -05:00
Mikhail Mazurskiy
458bdf6a67 Add missing TestMain() functions
Kubernetes-commit: ca38fba0af59e1193625a18002d0bf1408c4106c
2019-01-30 15:27:39 +11:00
Mikhail Mazurskiy
c90a87409a Cleanup math/rand package usage
Kubernetes-commit: 4ce1fb7d40beb9010e56d60792c4da25e8d86ed0
2018-11-17 16:45:36 +11:00
Yassine TIJANI
231e48e9f3 Implementing logic for v1beta1.Event API
Signed-off-by: Yassine TIJANI <ytijani@vmware.com>

Kubernetes-commit: 464a994a10b71c45583f3426fd970291f8a5b756
2018-07-03 22:25:17 +02:00
102 changed files with 5552 additions and 505 deletions

118
Godeps/Godeps.json generated
View File

@@ -14,6 +14,18 @@
"ImportPath": "github.com/Azure/go-autorest",
"Rev": "v11.1.2"
},
{
"ImportPath": "github.com/NYTimes/gziphandler",
"Rev": "56545f4a5d46"
},
{
"ImportPath": "github.com/PuerkitoBio/purell",
"Rev": "v1.0.0"
},
{
"ImportPath": "github.com/PuerkitoBio/urlesc",
"Rev": "5bd2802263f2"
},
{
"ImportPath": "github.com/davecgh/go-spew",
"Rev": "v1.1.1"
@@ -30,17 +42,41 @@
"ImportPath": "github.com/elazarl/goproxy",
"Rev": "c4fc26588b6e"
},
{
"ImportPath": "github.com/emicklei/go-restful",
"Rev": "ff4f55a20633"
},
{
"ImportPath": "github.com/evanphx/json-patch",
"Rev": "5858425f7550"
"Rev": "v4.2.0"
},
{
"ImportPath": "github.com/fsnotify/fsnotify",
"Rev": "v1.4.7"
},
{
"ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680"
},
{
"ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b1"
},
{
"ImportPath": "github.com/go-openapi/jsonreference",
"Rev": "13c6e3589ad9"
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87"
},
{
"ImportPath": "github.com/gogo/protobuf",
"Rev": "342cbe0a0415"
"Rev": "v0.0.0-20190410021324-65acae22fc9"
},
{
"ImportPath": "github.com/golang/groupcache",
@@ -54,13 +90,17 @@
"ImportPath": "github.com/google/btree",
"Rev": "7d79101e329e"
},
{
"ImportPath": "github.com/google/go-cmp",
"Rev": "v0.3.0"
},
{
"ImportPath": "github.com/google/gofuzz",
"Rev": "24818f796faf"
"Rev": "v1.0.0"
},
{
"ImportPath": "github.com/google/uuid",
"Rev": "v1.0.0"
"Rev": "v1.1.1"
},
{
"ImportPath": "github.com/googleapis/gnostic",
@@ -68,7 +108,7 @@
},
{
"ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "c818fa66e4c8"
"Rev": "v0.1.0"
},
{
"ImportPath": "github.com/gregjones/httpcache",
@@ -88,7 +128,31 @@
},
{
"ImportPath": "github.com/json-iterator/go",
"Rev": "ab8a2e0c74be"
"Rev": "v1.1.6"
},
{
"ImportPath": "github.com/kisielk/errcheck",
"Rev": "v1.2.0"
},
{
"ImportPath": "github.com/kisielk/gotool",
"Rev": "v1.0.0"
},
{
"ImportPath": "github.com/kr/pretty",
"Rev": "v0.1.0"
},
{
"ImportPath": "github.com/kr/pty",
"Rev": "v1.1.1"
},
{
"ImportPath": "github.com/kr/text",
"Rev": "v0.1.0"
},
{
"ImportPath": "github.com/mailru/easyjson",
"Rev": "d5b7844b561a"
},
{
"ImportPath": "github.com/modern-go/concurrent",
@@ -98,17 +162,21 @@
"ImportPath": "github.com/modern-go/reflect2",
"Rev": "v1.0.1"
},
{
"ImportPath": "github.com/munnerz/goautoneg",
"Rev": "a547fc61f48d"
},
{
"ImportPath": "github.com/mxk/go-flowrate",
"Rev": "cca7078d478f"
},
{
"ImportPath": "github.com/onsi/ginkgo",
"Rev": "v1.6.0"
"Rev": "v1.8.0"
},
{
"ImportPath": "github.com/onsi/gomega",
"Rev": "5533ce8a0da3"
"Rev": "v1.5.0"
},
{
"ImportPath": "github.com/peterbourgon/diskv",
@@ -118,13 +186,21 @@
"ImportPath": "github.com/pmezard/go-difflib",
"Rev": "v1.0.0"
},
{
"ImportPath": "github.com/spf13/afero",
"Rev": "v1.2.2"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "v1.0.1"
"Rev": "v1.0.3"
},
{
"ImportPath": "github.com/stretchr/objx",
"Rev": "v0.1.0"
},
{
"ImportPath": "github.com/stretchr/testify",
"Rev": "v1.2.2"
"Rev": "v1.3.0"
},
{
"ImportPath": "golang.org/x/crypto",
@@ -164,7 +240,7 @@
},
{
"ImportPath": "gopkg.in/check.v1",
"Rev": "20d25e280405"
"Rev": "788fd7840127"
},
{
"ImportPath": "gopkg.in/fsnotify.v1",
@@ -180,27 +256,35 @@
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "v2.2.1"
"Rev": "v2.2.2"
},
{
"ImportPath": "k8s.io/api",
"Rev": "e63b5755afac"
"Rev": "42d28c278e58"
},
{
"ImportPath": "k8s.io/apimachinery",
"Rev": "ad85901afca0"
"Rev": "f97a4e5b4abc"
},
{
"ImportPath": "k8s.io/gengo",
"Rev": "0689ccc1d7d6"
},
{
"ImportPath": "k8s.io/klog",
"Rev": "v0.3.0"
"Rev": "v0.3.1"
},
{
"ImportPath": "k8s.io/kube-openapi",
"Rev": "b3a7cee44a30"
"Rev": "33be087ad058"
},
{
"ImportPath": "k8s.io/utils",
"Rev": "c2654d5206da"
"Rev": "c55fbcfc754a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff",
"Rev": "15d366b2352e"
},
{
"ImportPath": "sigs.k8s.io/yaml",

View File

@@ -65,7 +65,7 @@ for each follows.
### Go modules
Dependency management tools are built into go 1.11+ in the form of [go modules](https://github.com/golang/go/wiki/Modules).
These are used by the main Kubernetes repo (>= 1.15) and `client-go` (on master, and v12.0.0+ once released) to manage dependencies.
These are used by the main Kubernetes repo (>= 1.15) and `client-go` (on master, and v12.0.0+) to manage dependencies.
When using `client-go` v12.0.0+ and go 1.11.4+, go modules are the recommended dependency management tool.
If you are using go 1.11 or 1.12 and are working with a project located within `$GOPATH`,
@@ -83,10 +83,10 @@ go mod init
```
Indicate which version of `client-go` your project requires.
For `client-go` on master (and once version v12.0.0 is released), this is a single step:
For `client-go` on v12.0.0 (and later), this is a single step:
```sh
go get k8s.io/client-go@master # or v12.0.0+ once released
go get k8s.io/client-go@v12.0.0
```
For `client-go` prior to v12.0.0, you also need to indicate the required versions of `k8s.io/api` and `k8s.io/apimachinery`:
@@ -119,7 +119,7 @@ your project:
package: ( your project's import path ) # e.g. github.com/foo/bar
import:
- package: k8s.io/client-go
version: v11.0.0 # replace v11.0.0 with the required version
version: v12.0.0 # replace v12.0.0 with the required version
```
Second, add a Go file that imports `client-go` somewhere in your project,
@@ -152,7 +152,7 @@ requests can override the version manually in `glide.yaml`. For example:
package: ( your project's import path ) # e.g. github.com/foo/bar
import:
- package: k8s.io/client-go
version: v11.0.0 # replace v11.0.0 with the required version
version: v12.0.0 # replace v12.0.0 with the required version
# Use a newer version of go-spew even though client-go wants an old one.
- package: github.com/davecgh/go-spew
version: v1.1.0

View File

@@ -2,7 +2,7 @@
Go clients for talking to a [kubernetes](http://kubernetes.io/) cluster.
We currently recommend using the v11.0.0 tag. See [INSTALL.md](/INSTALL.md) for
We currently recommend using the v12.0.0 tag. See [INSTALL.md](/INSTALL.md) for
detailed installation instructions. `go get k8s.io/client-go/...` works, but
will build `master`, which doesn't handle the dependencies well.
@@ -92,16 +92,16 @@ We will backport bugfixes--but not new features--into older versions of
#### Compatibility matrix
| | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 |
|---------------------|----------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
| client-go 5.0 | ✓ | +- | +- | +- | +- | +- | +- |
| client-go 6.0 | +- | ✓ | +- | +- | +- | +- | +- |
| client-go 7.0 | +- | +- | ✓ | +- | +- | +- | +- |
| client-go 8.0 | +- | +- | +- | ✓ | +- | +- | +- |
| client-go 9.0 | +- | +- | +- | +- | ✓ | +- | +- |
| client-go 10.0 | +- | +- | +- | +- | +- | ✓ | +- |
| client-go 11.0 | +- | +- | +- | +- | +- | +- | ✓ |
| client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
| | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 | Kubernetes 1.14 | Kubernetes 1.15 |
|---------------------|----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
| client-go 6.0 | ✓ | +- | +- | +- | +- | +- | +- |
| client-go 7.0 | +- | ✓ | +- | +- | +- | +- | +- |
| client-go 8.0 | +- | +- | ✓ | +- | +- | +- | +- |
| client-go 9.0 | +- | +- | +- | ✓ | +- | +- | +- |
| client-go 10.0 | +- | +- | +- | +- | ✓ | +- | +- |
| client-go 11.0 | +- | +- | +- | +- | +- | ✓ | +- |
| client-go 12.0 | +- | +- | +- | +- | +- | +- | ✓ |
| client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
Key:
@@ -131,9 +131,10 @@ between client-go versions.
| client-go 6.0 | Kubernetes main repo, 1.9 branch | = - |
| client-go 7.0 | Kubernetes main repo, 1.10 branch | = - |
| client-go 8.0 | Kubernetes main repo, 1.11 branch | =- |
| client-go 9.0 | Kubernetes main repo, 1.12 branch | |
| client-go 9.0 | Kubernetes main repo, 1.12 branch | =- |
| client-go 10.0 | Kubernetes main repo, 1.13 branch | ✓ |
| client-go 11.0 | Kubernetes main repo, 1.14 branch | ✓ |
| client-go 12.0 | Kubernetes main repo, 1.15 branch | ✓ |
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
Key:

View File

@@ -11,7 +11,7 @@
# INSTRUCTIONS AT https://kubernetes.io/security/
cjcullen
jessfraz
joelsmith
liggitt
philips
tallclair

View File

@@ -172,7 +172,7 @@ func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
}
func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(filename), 0750); err != nil {
return err
}
@@ -191,7 +191,7 @@ func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Obj
return err
}
err = os.Chmod(f.Name(), 0755)
err = os.Chmod(f.Name(), 0660)
if err != nil {
return err
}

View File

@@ -19,6 +19,7 @@ package disk
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
@@ -96,6 +97,32 @@ func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
assert.Equal(c.groupCalls, 2)
}
func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
assert.NoError(err)
os.RemoveAll(d)
defer os.RemoveAll(d)
c := fakeDiscoveryClient{}
cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
cdc.ServerGroups()
err = filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
assert.Equal(os.FileMode(0750), info.Mode().Perm())
} else {
assert.Equal(os.FileMode(0660), info.Mode().Perm())
}
return nil
})
assert.NoError(err)
}
type fakeDiscoveryClient struct {
groupCalls int
resourceCalls int

View File

@@ -18,6 +18,7 @@ package disk
import (
"net/http"
"os"
"path/filepath"
"github.com/gregjones/httpcache"
@@ -35,6 +36,8 @@ type cacheRoundTripper struct {
// corresponding requests.
func newCacheRoundTripper(cacheDir string, rt http.RoundTripper) http.RoundTripper {
d := diskv.New(diskv.Options{
PathPerm: os.FileMode(0750),
FilePerm: os.FileMode(0660),
BasePath: cacheDir,
TempDir: filepath.Join(cacheDir, ".diskv-temp"),
})

View File

@@ -22,7 +22,10 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
// copied from k8s.io/client-go/transport/round_trippers_test.go
@@ -93,3 +96,52 @@ func TestCacheRoundTripper(t *testing.T) {
t.Errorf("Invalid content read from cache %q", string(content))
}
}
func TestCacheRoundTripperPathPerm(t *testing.T) {
assert := assert.New(t)
rt := &testRoundTripper{}
cacheDir, err := ioutil.TempDir("", "cache-rt")
os.RemoveAll(cacheDir)
defer os.RemoveAll(cacheDir)
if err != nil {
t.Fatal(err)
}
cache := newCacheRoundTripper(cacheDir, rt)
// First call, caches the response
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{Host: "localhost"},
}
rt.Response = &http.Response{
Header: http.Header{"ETag": []string{`"123456"`}},
Body: ioutil.NopCloser(bytes.NewReader([]byte("Content"))),
StatusCode: http.StatusOK,
}
resp, err := cache.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if string(content) != "Content" {
t.Errorf(`Expected Body to be "Content", got %q`, string(content))
}
err = filepath.Walk(cacheDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
assert.Equal(os.FileMode(0750), info.Mode().Perm())
} else {
assert.Equal(os.FileMode(0660), info.Mode().Perm())
}
return nil
})
assert.NoError(err)
}

View File

@@ -16,4 +16,4 @@ limitations under the License.
// Package discovery provides ways to discover server-supported
// API groups, versions and resources.
package discovery
package discovery // import "k8s.io/client-go/discovery"

View File

@@ -42,7 +42,7 @@ func NewFilteredDynamicSharedInformerFactory(client dynamic.Interface, defaultRe
return &dynamicSharedInformerFactory{
client: client,
defaultResync: defaultResync,
namespace: metav1.NamespaceAll,
namespace: namespace,
informers: map[schema.GroupVersionResource]informers.GenericInformer{},
startedInformers: make(map[schema.GroupVersionResource]bool),
tweakListOptions: tweakListOptions,

View File

@@ -0,0 +1,93 @@
# Create, Update & Delete Deployment with the Dynamic Package
This example program demonstrates the fundamental operations for managing on
[Deployment][1] resources, such as `Create`, `List`, `Update` and `Delete` using client-go's `dynamic` package.
## Typed Vs. Dynamic
The code in this directory is based on a similar [example that uses Kubernetes typed client sets][2]. The typed client sets make it simple to communicate with the API server using pre-generated local API objects to achieve an RPC-like programming experience. Typed clients uses program compilations to enforce data safety and some validation. However, when using typed clients, programs are forced to be tightly coupled with the version and the types used.
The `dynamic` package on the other hand, uses a simple type, `unstructured.Unstructured`, to represent all object values from the API server. Type `Unstructured` uses a collection of nested `map[string]interface{}` values to create an internal structure that closely resemble the REST payload from the server.
The dynamic package defers all data bindings until runtime. This means programs that use the dynamic client will not get any of the benefits of type validation until the program is running. This may be a problem for certain types of applications that require strong data type check and validation.
Being loosely coupled, however, means that programs that uses the `dynamic` package do not require recompilation when the client API changes. The client program has more flexibility in handling updates to the API surface without knowing ahead of time what those changes are.
## Running this example
Make sure you have a Kubernetes cluster and `kubectl` is configured:
```
kubectl get nodes
```
Compile this example on your workstation:
```
cd dynamic-create-update-delete-deployment
go build -o ./app
```
Now, run this application on your workstation with your local kubeconfig file:
```
./app
# or specify a kubeconfig file with flag
./app -kubeconfig=$HOME/.kube/config
```
Running this command will execute the following operations on your cluster:
1. **Create Deployment:** This will create a 2 replica Deployment. Verify with
`kubectl get pods`.
2. **Update Deployment:** This will update the Deployment resource created in
previous step by setting the replica count to 1 and changing the container
image to `nginx:1.13`. You are encouraged to inspect the retry loop that
handles conflicts. Verify the new replica count and container image with
`kubectl describe deployment demo`.
3. **List Deployments:** This will retrieve Deployments in the `default`
namespace and print their names and replica counts.
4. **Delete Deployment:** This will delete the Deployment object and its
dependent ReplicaSet resource. Verify with `kubectl get deployments`.
Each step is separated by an interactive prompt. You must hit the
<kbd>Return</kbd> key to proceed to the next step. You can use these prompts as
a break to take time to run `kubectl` and inspect the result of the operations
executed.
You should see an output like the following:
```
Creating deployment...
Created deployment "demo-deployment".
-> Press Return key to continue.
Updating deployment...
Updated deployment...
-> Press Return key to continue.
Listing deployments in namespace "default":
* demo-deployment (1 replicas)
-> Press Return key to continue.
Deleting deployment...
Deleted deployment.
```
## Cleanup
Successfully running this program will clean the created artifacts. If you
terminate the program without completing, you can clean up the created
deployment with:
kubectl delete deploy demo-deployment
## Troubleshooting
If you are getting the following error, make sure Kubernetes version of your
cluster is v1.13 or higher in `kubectl version`:
panic: the server could not find the requested resource
[1]: https://kubernetes.io/docs/user-guide/deployments/
[2]: ../create-update-delete-deployment

View File

@@ -0,0 +1,208 @@
/*
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.
*/
// Note: the example only works with the code within the same release/branch.
package main
import (
"bufio"
"flag"
"fmt"
"os"
"path/filepath"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/client-go/util/retry"
//
// Uncomment to load all auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth
//
// Or uncomment to load specific auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)
func main() {
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
namespace := "default"
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
}
client, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
deployment := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "demo-deployment",
},
"spec": map[string]interface{}{
"replicas": 2,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "demo",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "demo",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
{
"name": "web",
"image": "nginx:1.12",
"ports": []map[string]interface{}{
{
"name": "http",
"protocol": "TCP",
"containerPort": 80,
},
},
},
},
},
},
},
},
}
// Create Deployment
fmt.Println("Creating deployment...")
result, err := client.Resource(deploymentRes).Namespace(namespace).Create(deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}
fmt.Printf("Created deployment %q.\n", result.GetName())
// Update Deployment
prompt()
fmt.Println("Updating deployment...")
// You have two options to Update() this Deployment:
//
// 1. Modify the "deployment" variable and call: Update(deployment).
// This works like the "kubectl replace" command and it overwrites/loses changes
// made by other clients between you Create() and Update() the object.
// 2. Modify the "result" returned by Get() and retry Update(result) until
// you no longer get a conflict error. This way, you can preserve changes made
// by other clients between Create() and Update(). This is implemented below
// using the retry utility package included with client-go. (RECOMMENDED)
//
// More Info:
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
// Retrieve the latest version of Deployment before attempting update
// RetryOnConflict uses exponential backoff to avoid exhausting the apiserver
result, getErr := client.Resource(deploymentRes).Namespace(namespace).Get("demo-deployment", metav1.GetOptions{})
if getErr != nil {
panic(fmt.Errorf("failed to get latest version of Deployment: %v", getErr))
}
// update replicas to 1
if err := unstructured.SetNestedField(result.Object, int64(1), "spec", "replicas"); err != nil {
panic(fmt.Errorf("failed to set replica value: %v", err))
}
// extract spec containers
containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers")
if err != nil || !found || containers == nil {
panic(fmt.Errorf("deployment containers not found or error in spec: %v", err))
}
// update container[0] image
if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), "nginx:1.13", "image"); err != nil {
panic(err)
}
if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil {
panic(err)
}
_, updateErr := client.Resource(deploymentRes).Namespace(namespace).Update(result, metav1.UpdateOptions{})
return updateErr
})
if retryErr != nil {
panic(fmt.Errorf("update failed: %v", retryErr))
}
fmt.Println("Updated deployment...")
// List Deployments
prompt()
fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
list, err := client.Resource(deploymentRes).Namespace(namespace).List(metav1.ListOptions{})
if err != nil {
panic(err)
}
for _, d := range list.Items {
replicas, found, err := unstructured.NestedInt64(d.Object, "spec", "replicas")
if err != nil || !found {
fmt.Printf("Replicas not found for deployment %s: error=%s", d.GetName(), err)
continue
}
fmt.Printf(" * %s (%d replicas)\n", d.GetName(), replicas)
}
// Delete Deployment
prompt()
fmt.Println("Deleting deployment...")
deletePolicy := metav1.DeletePropagationForeground
deleteOptions := &metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
if err := client.Resource(deploymentRes).Namespace(namespace).Delete("demo-deployment", deleteOptions); err != nil {
panic(err)
}
fmt.Println("Deleted deployment.")
}
func prompt() {
fmt.Printf("-> Press Return key to continue.")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
break
}
if err := scanner.Err(); err != nil {
panic(err)
}
fmt.Println()
}

31
go.mod
View File

@@ -8,35 +8,38 @@ require (
github.com/Azure/go-autorest v11.1.2+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda // indirect
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415
github.com/evanphx/json-patch v4.2.0+incompatible
github.com/gogo/protobuf v0.0.0-20190410021324-65acae22fc9
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903
github.com/golang/protobuf v1.2.0
github.com/google/btree v0.0.0-20160524151835-7d79101e329e // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
github.com/google/gofuzz v1.0.0
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8
github.com/gophercloud/gophercloud v0.1.0
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7
github.com/imdario/mergo v0.3.5
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/spf13/pflag v1.0.1
github.com/stretchr/testify v1.2.2
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d
google.golang.org/appengine v1.5.0 // indirect
k8s.io/api v0.0.0-20190511023547-e63b5755afac
k8s.io/apimachinery v0.0.0-20190511023455-ad85901afca0
k8s.io/klog v0.3.0
k8s.io/utils v0.0.0-20190221042446-c2654d5206da
k8s.io/api v0.0.0-20190730183008-42d28c278e58
k8s.io/apimachinery v0.0.0-20190727130956-f97a4e5b4abc
k8s.io/klog v0.3.1
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a
sigs.k8s.io/yaml v1.1.0
)
replace (
golang.org/x/crypto => golang.org/x/crypto v0.0.0-20181025213731-e84da0312774
golang.org/x/net => golang.org/x/net v0.0.0-20190206173232-65e2d4e15006
golang.org/x/sync => golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
golang.org/x/sys => golang.org/x/sys v0.0.0-20190209173611-3b5209105503
golang.org/x/text => golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db
golang.org/x/tools => golang.org/x/tools v0.0.0-20190313210603-aa82965741a9
k8s.io/api => k8s.io/api v0.0.0-20190511023547-e63b5755afac
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190511023455-ad85901afca0
k8s.io/api => k8s.io/api v0.0.0-20190730183008-42d28c278e58
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190727130956-f97a4e5b4abc
)

96
go.sum
View File

@@ -2,6 +2,11 @@ cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA=
github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY=
@@ -10,25 +15,36 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QL
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550 h1:mV9jbLoSW/8m4VK16ZkHTozJa8sesK5u5kTMFysTYac=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/gogo/protobuf v0.0.0-20190410021324-65acae22fc9 h1:Kt4R9nQn1c+x/o63vCZuxo3WjBc8EnSfnguI4ELkdoo=
github.com/gogo/protobuf v0.0.0-20190410021324-65acae22fc9/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e h1:JHB7F/4TJCrYBW8+GZO8VkWDj1jxcWuCl6uxKODiyi4=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8 h1:L9JPKrtsHMQ4VCRQfHvbbHBfB2Urn8xf6QZeXZ+OrN4=
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
@@ -37,30 +53,47 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
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 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3 h1:EooPXg51Tn+xmWPXJUGCnJhJSpeuMlBmfJVcqIRmmv8=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774 h1:a4tQYYYuK9QdeO/+kEvNYyuR21S+7ve5EANok6hABhI=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6UaXCMt3vhYJ1l4FQ80=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
@@ -69,7 +102,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTm
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
@@ -80,6 +112,8 @@ google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
@@ -88,13 +122,19 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20190511023547-e63b5755afac/go.mod h1:f265Ep4XvHtyggfaaNhtP0AtAFilBaXq2fPjm5kYxwI=
k8s.io/apimachinery v0.0.0-20190511023455-ad85901afca0/go.mod h1:5CBnzrKYGHzv9ZsSKmQ8wHt4XI4/TUBPDwYM9FlZMyw=
k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20190730183008-42d28c278e58/go.mod h1:gGyif5Gpa+QY8+cR+LX2vnDTcfZpBYFmztKX91bN9IM=
k8s.io/apimachinery v0.0.0-20190727130956-f97a4e5b4abc/go.mod h1:eXR4ljjmbwK6Ng0PKsXRySPXnTUy/qBUa6kPDeckhQ0=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058 h1:di3XCwddOR9cWBNpfgXaskhh6cgJuwcK54rvtwUaC10=
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -19,12 +19,15 @@ limitations under the License.
package admissionregistration
import (
v1 "k8s.io/client-go/informers/admissionregistration/v1"
v1beta1 "k8s.io/client-go/informers/admissionregistration/v1beta1"
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1 provides access to shared informers for resources in V1.
V1() v1.Interface
// V1beta1 provides access to shared informers for resources in V1beta1.
V1beta1() v1beta1.Interface
}
@@ -40,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1 returns a new v1.Interface.
func (g *group) V1() v1.Interface {
return v1.New(g.factory, g.namespace, g.tweakListOptions)
}
// V1beta1 returns a new v1beta1.Interface.
func (g *group) V1beta1() v1beta1.Interface {
return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)

View File

@@ -0,0 +1,52 @@
/*
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 informer-gen. DO NOT EDIT.
package v1
import (
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// MutatingWebhookConfigurations returns a MutatingWebhookConfigurationInformer.
MutatingWebhookConfigurations() MutatingWebhookConfigurationInformer
// ValidatingWebhookConfigurations returns a ValidatingWebhookConfigurationInformer.
ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInformer
}
type version struct {
factory internalinterfaces.SharedInformerFactory
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// New returns a new Interface.
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// MutatingWebhookConfigurations returns a MutatingWebhookConfigurationInformer.
func (v *version) MutatingWebhookConfigurations() MutatingWebhookConfigurationInformer {
return &mutatingWebhookConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}
// ValidatingWebhookConfigurations returns a ValidatingWebhookConfigurationInformer.
func (v *version) ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInformer {
return &validatingWebhookConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}

View File

@@ -0,0 +1,88 @@
/*
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 informer-gen. DO NOT EDIT.
package v1
import (
time "time"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
kubernetes "k8s.io/client-go/kubernetes"
v1 "k8s.io/client-go/listers/admissionregistration/v1"
cache "k8s.io/client-go/tools/cache"
)
// MutatingWebhookConfigurationInformer provides access to a shared informer and lister for
// MutatingWebhookConfigurations.
type MutatingWebhookConfigurationInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.MutatingWebhookConfigurationLister
}
type mutatingWebhookConfigurationInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewMutatingWebhookConfigurationInformer constructs a new informer for MutatingWebhookConfiguration type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewMutatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredMutatingWebhookConfigurationInformer constructs a new informer for MutatingWebhookConfiguration type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredMutatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AdmissionregistrationV1().MutatingWebhookConfigurations().Watch(options)
},
},
&admissionregistrationv1.MutatingWebhookConfiguration{},
resyncPeriod,
indexers,
)
}
func (f *mutatingWebhookConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredMutatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *mutatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&admissionregistrationv1.MutatingWebhookConfiguration{}, f.defaultInformer)
}
func (f *mutatingWebhookConfigurationInformer) Lister() v1.MutatingWebhookConfigurationLister {
return v1.NewMutatingWebhookConfigurationLister(f.Informer().GetIndexer())
}

View File

@@ -0,0 +1,88 @@
/*
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 informer-gen. DO NOT EDIT.
package v1
import (
time "time"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
kubernetes "k8s.io/client-go/kubernetes"
v1 "k8s.io/client-go/listers/admissionregistration/v1"
cache "k8s.io/client-go/tools/cache"
)
// ValidatingWebhookConfigurationInformer provides access to a shared informer and lister for
// ValidatingWebhookConfigurations.
type ValidatingWebhookConfigurationInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.ValidatingWebhookConfigurationLister
}
type validatingWebhookConfigurationInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewValidatingWebhookConfigurationInformer constructs a new informer for ValidatingWebhookConfiguration type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewValidatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredValidatingWebhookConfigurationInformer constructs a new informer for ValidatingWebhookConfiguration type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredValidatingWebhookConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Watch(options)
},
},
&admissionregistrationv1.ValidatingWebhookConfiguration{},
resyncPeriod,
indexers,
)
}
func (f *validatingWebhookConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredValidatingWebhookConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *validatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&admissionregistrationv1.ValidatingWebhookConfiguration{}, f.defaultInformer)
}
func (f *validatingWebhookConfigurationInformer) Lister() v1.ValidatingWebhookConfigurationLister {
return v1.NewValidatingWebhookConfigurationLister(f.Informer().GetIndexer())
}

View File

@@ -21,8 +21,9 @@ package informers
import (
"fmt"
v1 "k8s.io/api/admissionregistration/v1"
v1beta1 "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
v1beta2 "k8s.io/api/apps/v1beta2"
v1alpha1 "k8s.io/api/auditregistration/v1alpha1"
@@ -83,22 +84,28 @@ func (f *genericInformer) Lister() cache.GenericLister {
// TODO extend this to unknown resources with a client pool
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
// Group=admissionregistration.k8s.io, Version=v1beta1
// Group=admissionregistration.k8s.io, Version=v1
case v1.SchemeGroupVersion.WithResource("mutatingwebhookconfigurations"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1().MutatingWebhookConfigurations().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("validatingwebhookconfigurations"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1().ValidatingWebhookConfigurations().Informer()}, nil
// Group=admissionregistration.k8s.io, Version=v1beta1
case v1beta1.SchemeGroupVersion.WithResource("mutatingwebhookconfigurations"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1beta1().MutatingWebhookConfigurations().Informer()}, nil
case v1beta1.SchemeGroupVersion.WithResource("validatingwebhookconfigurations"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Admissionregistration().V1beta1().ValidatingWebhookConfigurations().Informer()}, nil
// Group=apps, Version=v1
case v1.SchemeGroupVersion.WithResource("controllerrevisions"):
case appsv1.SchemeGroupVersion.WithResource("controllerrevisions"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1().ControllerRevisions().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("daemonsets"):
case appsv1.SchemeGroupVersion.WithResource("daemonsets"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1().DaemonSets().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("deployments"):
case appsv1.SchemeGroupVersion.WithResource("deployments"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1().Deployments().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("replicasets"):
case appsv1.SchemeGroupVersion.WithResource("replicasets"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1().ReplicaSets().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("statefulsets"):
case appsv1.SchemeGroupVersion.WithResource("statefulsets"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1().StatefulSets().Informer()}, nil
// Group=apps, Version=v1beta1

View File

@@ -19,7 +19,10 @@ limitations under the License.
package kubernetes
import (
"fmt"
discovery "k8s.io/client-go/discovery"
admissionregistrationv1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1"
admissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
appsv1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1"
@@ -62,6 +65,7 @@ import (
type Interface interface {
Discovery() discovery.DiscoveryInterface
AdmissionregistrationV1() admissionregistrationv1.AdmissionregistrationV1Interface
AdmissionregistrationV1beta1() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface
AppsV1() appsv1.AppsV1Interface
AppsV1beta1() appsv1beta1.AppsV1beta1Interface
@@ -104,6 +108,7 @@ type Interface interface {
// version included in a Clientset.
type Clientset struct {
*discovery.DiscoveryClient
admissionregistrationV1 *admissionregistrationv1.AdmissionregistrationV1Client
admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
appsV1 *appsv1.AppsV1Client
appsV1beta1 *appsv1beta1.AppsV1beta1Client
@@ -142,6 +147,11 @@ type Clientset struct {
storageV1alpha1 *storagev1alpha1.StorageV1alpha1Client
}
// AdmissionregistrationV1 retrieves the AdmissionregistrationV1Client
func (c *Clientset) AdmissionregistrationV1() admissionregistrationv1.AdmissionregistrationV1Interface {
return c.admissionregistrationV1
}
// AdmissionregistrationV1beta1 retrieves the AdmissionregistrationV1beta1Client
func (c *Clientset) AdmissionregistrationV1beta1() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface {
return c.admissionregistrationV1beta1
@@ -331,13 +341,22 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface {
}
// NewForConfig creates a new Clientset for the given config.
// If config's RateLimiter is not set and QPS and Burst are acceptable,
// NewForConfig will generate a rate-limiter in configShallowCopy.
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
if configShallowCopy.Burst <= 0 {
return nil, fmt.Errorf("Burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
}
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
}
var cs Clientset
var err error
cs.admissionregistrationV1, err = admissionregistrationv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.admissionregistrationV1beta1, err = admissionregistrationv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
@@ -494,6 +513,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *Clientset {
var cs Clientset
cs.admissionregistrationV1 = admissionregistrationv1.NewForConfigOrDie(c)
cs.admissionregistrationV1beta1 = admissionregistrationv1beta1.NewForConfigOrDie(c)
cs.appsV1 = appsv1.NewForConfigOrDie(c)
cs.appsV1beta1 = appsv1beta1.NewForConfigOrDie(c)
@@ -538,6 +558,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
// New creates a new Clientset for the given RESTClient.
func New(c rest.Interface) *Clientset {
var cs Clientset
cs.admissionregistrationV1 = admissionregistrationv1.New(c)
cs.admissionregistrationV1beta1 = admissionregistrationv1beta1.New(c)
cs.appsV1 = appsv1.New(c)
cs.appsV1beta1 = appsv1beta1.New(c)

View File

@@ -24,6 +24,8 @@ import (
"k8s.io/client-go/discovery"
fakediscovery "k8s.io/client-go/discovery/fake"
clientset "k8s.io/client-go/kubernetes"
admissionregistrationv1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1"
fakeadmissionregistrationv1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake"
admissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
fakeadmissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1/fake"
appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
@@ -146,6 +148,11 @@ func (c *Clientset) Tracker() testing.ObjectTracker {
var _ clientset.Interface = &Clientset{}
// AdmissionregistrationV1 retrieves the AdmissionregistrationV1Client
func (c *Clientset) AdmissionregistrationV1() admissionregistrationv1.AdmissionregistrationV1Interface {
return &fakeadmissionregistrationv1.FakeAdmissionregistrationV1{Fake: &c.Fake}
}
// AdmissionregistrationV1beta1 retrieves the AdmissionregistrationV1beta1Client
func (c *Clientset) AdmissionregistrationV1beta1() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface {
return &fakeadmissionregistrationv1beta1.FakeAdmissionregistrationV1beta1{Fake: &c.Fake}

View File

@@ -19,6 +19,7 @@ limitations under the License.
package fake
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
@@ -66,6 +67,7 @@ var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
var parameterCodec = runtime.NewParameterCodec(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
admissionregistrationv1.AddToScheme,
admissionregistrationv1beta1.AddToScheme,
appsv1.AddToScheme,
appsv1beta1.AddToScheme,

View File

@@ -19,6 +19,7 @@ limitations under the License.
package scheme
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
@@ -66,6 +67,7 @@ var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
admissionregistrationv1.AddToScheme,
admissionregistrationv1beta1.AddToScheme,
appsv1.AddToScheme,
appsv1beta1.AddToScheme,

View File

@@ -0,0 +1,94 @@
/*
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 client-gen. DO NOT EDIT.
package v1
import (
v1 "k8s.io/api/admissionregistration/v1"
"k8s.io/client-go/kubernetes/scheme"
rest "k8s.io/client-go/rest"
)
type AdmissionregistrationV1Interface interface {
RESTClient() rest.Interface
MutatingWebhookConfigurationsGetter
ValidatingWebhookConfigurationsGetter
}
// AdmissionregistrationV1Client is used to interact with features provided by the admissionregistration.k8s.io group.
type AdmissionregistrationV1Client struct {
restClient rest.Interface
}
func (c *AdmissionregistrationV1Client) MutatingWebhookConfigurations() MutatingWebhookConfigurationInterface {
return newMutatingWebhookConfigurations(c)
}
func (c *AdmissionregistrationV1Client) ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInterface {
return newValidatingWebhookConfigurations(c)
}
// NewForConfig creates a new AdmissionregistrationV1Client for the given config.
func NewForConfig(c *rest.Config) (*AdmissionregistrationV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &AdmissionregistrationV1Client{client}, nil
}
// NewForConfigOrDie creates a new AdmissionregistrationV1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *AdmissionregistrationV1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new AdmissionregistrationV1Client for the given RESTClient.
func New(c rest.Interface) *AdmissionregistrationV1Client {
return &AdmissionregistrationV1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *AdmissionregistrationV1Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}

View File

@@ -0,0 +1,20 @@
/*
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 client-gen. DO NOT EDIT.
// This package has the automatically generated typed clients.
package v1

View File

@@ -0,0 +1,20 @@
/*
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 client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake

View File

@@ -0,0 +1,44 @@
/*
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 client-gen. DO NOT EDIT.
package fake
import (
v1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeAdmissionregistrationV1 struct {
*testing.Fake
}
func (c *FakeAdmissionregistrationV1) MutatingWebhookConfigurations() v1.MutatingWebhookConfigurationInterface {
return &FakeMutatingWebhookConfigurations{c}
}
func (c *FakeAdmissionregistrationV1) ValidatingWebhookConfigurations() v1.ValidatingWebhookConfigurationInterface {
return &FakeValidatingWebhookConfigurations{c}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeAdmissionregistrationV1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}

View File

@@ -0,0 +1,120 @@
/*
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 client-gen. DO NOT EDIT.
package fake
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeMutatingWebhookConfigurations implements MutatingWebhookConfigurationInterface
type FakeMutatingWebhookConfigurations struct {
Fake *FakeAdmissionregistrationV1
}
var mutatingwebhookconfigurationsResource = schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "mutatingwebhookconfigurations"}
var mutatingwebhookconfigurationsKind = schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingWebhookConfiguration"}
// Get takes name of the mutatingWebhookConfiguration, and returns the corresponding mutatingWebhookConfiguration object, and an error if there is any.
func (c *FakeMutatingWebhookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistrationv1.MutatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootGetAction(mutatingwebhookconfigurationsResource, name), &admissionregistrationv1.MutatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.MutatingWebhookConfiguration), err
}
// List takes label and field selectors, and returns the list of MutatingWebhookConfigurations that match those selectors.
func (c *FakeMutatingWebhookConfigurations) List(opts v1.ListOptions) (result *admissionregistrationv1.MutatingWebhookConfigurationList, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootListAction(mutatingwebhookconfigurationsResource, mutatingwebhookconfigurationsKind, opts), &admissionregistrationv1.MutatingWebhookConfigurationList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &admissionregistrationv1.MutatingWebhookConfigurationList{ListMeta: obj.(*admissionregistrationv1.MutatingWebhookConfigurationList).ListMeta}
for _, item := range obj.(*admissionregistrationv1.MutatingWebhookConfigurationList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested mutatingWebhookConfigurations.
func (c *FakeMutatingWebhookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchAction(mutatingwebhookconfigurationsResource, opts))
}
// Create takes the representation of a mutatingWebhookConfiguration and creates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any.
func (c *FakeMutatingWebhookConfigurations) Create(mutatingWebhookConfiguration *admissionregistrationv1.MutatingWebhookConfiguration) (result *admissionregistrationv1.MutatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(mutatingwebhookconfigurationsResource, mutatingWebhookConfiguration), &admissionregistrationv1.MutatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.MutatingWebhookConfiguration), err
}
// Update takes the representation of a mutatingWebhookConfiguration and updates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any.
func (c *FakeMutatingWebhookConfigurations) Update(mutatingWebhookConfiguration *admissionregistrationv1.MutatingWebhookConfiguration) (result *admissionregistrationv1.MutatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootUpdateAction(mutatingwebhookconfigurationsResource, mutatingWebhookConfiguration), &admissionregistrationv1.MutatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.MutatingWebhookConfiguration), err
}
// Delete takes name of the mutatingWebhookConfiguration and deletes it. Returns an error if one occurs.
func (c *FakeMutatingWebhookConfigurations) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteAction(mutatingwebhookconfigurationsResource, name), &admissionregistrationv1.MutatingWebhookConfiguration{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeMutatingWebhookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewRootDeleteCollectionAction(mutatingwebhookconfigurationsResource, listOptions)
_, err := c.Fake.Invokes(action, &admissionregistrationv1.MutatingWebhookConfigurationList{})
return err
}
// Patch applies the patch and returns the patched mutatingWebhookConfiguration.
func (c *FakeMutatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistrationv1.MutatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceAction(mutatingwebhookconfigurationsResource, name, pt, data, subresources...), &admissionregistrationv1.MutatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.MutatingWebhookConfiguration), err
}

View File

@@ -0,0 +1,120 @@
/*
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 client-gen. DO NOT EDIT.
package fake
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeValidatingWebhookConfigurations implements ValidatingWebhookConfigurationInterface
type FakeValidatingWebhookConfigurations struct {
Fake *FakeAdmissionregistrationV1
}
var validatingwebhookconfigurationsResource = schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1", Resource: "validatingwebhookconfigurations"}
var validatingwebhookconfigurationsKind = schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1", Kind: "ValidatingWebhookConfiguration"}
// Get takes name of the validatingWebhookConfiguration, and returns the corresponding validatingWebhookConfiguration object, and an error if there is any.
func (c *FakeValidatingWebhookConfigurations) Get(name string, options v1.GetOptions) (result *admissionregistrationv1.ValidatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootGetAction(validatingwebhookconfigurationsResource, name), &admissionregistrationv1.ValidatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.ValidatingWebhookConfiguration), err
}
// List takes label and field selectors, and returns the list of ValidatingWebhookConfigurations that match those selectors.
func (c *FakeValidatingWebhookConfigurations) List(opts v1.ListOptions) (result *admissionregistrationv1.ValidatingWebhookConfigurationList, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootListAction(validatingwebhookconfigurationsResource, validatingwebhookconfigurationsKind, opts), &admissionregistrationv1.ValidatingWebhookConfigurationList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &admissionregistrationv1.ValidatingWebhookConfigurationList{ListMeta: obj.(*admissionregistrationv1.ValidatingWebhookConfigurationList).ListMeta}
for _, item := range obj.(*admissionregistrationv1.ValidatingWebhookConfigurationList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested validatingWebhookConfigurations.
func (c *FakeValidatingWebhookConfigurations) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchAction(validatingwebhookconfigurationsResource, opts))
}
// Create takes the representation of a validatingWebhookConfiguration and creates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any.
func (c *FakeValidatingWebhookConfigurations) Create(validatingWebhookConfiguration *admissionregistrationv1.ValidatingWebhookConfiguration) (result *admissionregistrationv1.ValidatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(validatingwebhookconfigurationsResource, validatingWebhookConfiguration), &admissionregistrationv1.ValidatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.ValidatingWebhookConfiguration), err
}
// Update takes the representation of a validatingWebhookConfiguration and updates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any.
func (c *FakeValidatingWebhookConfigurations) Update(validatingWebhookConfiguration *admissionregistrationv1.ValidatingWebhookConfiguration) (result *admissionregistrationv1.ValidatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootUpdateAction(validatingwebhookconfigurationsResource, validatingWebhookConfiguration), &admissionregistrationv1.ValidatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.ValidatingWebhookConfiguration), err
}
// Delete takes name of the validatingWebhookConfiguration and deletes it. Returns an error if one occurs.
func (c *FakeValidatingWebhookConfigurations) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteAction(validatingwebhookconfigurationsResource, name), &admissionregistrationv1.ValidatingWebhookConfiguration{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeValidatingWebhookConfigurations) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewRootDeleteCollectionAction(validatingwebhookconfigurationsResource, listOptions)
_, err := c.Fake.Invokes(action, &admissionregistrationv1.ValidatingWebhookConfigurationList{})
return err
}
// Patch applies the patch and returns the patched validatingWebhookConfiguration.
func (c *FakeValidatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *admissionregistrationv1.ValidatingWebhookConfiguration, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceAction(validatingwebhookconfigurationsResource, name, pt, data, subresources...), &admissionregistrationv1.ValidatingWebhookConfiguration{})
if obj == nil {
return nil, err
}
return obj.(*admissionregistrationv1.ValidatingWebhookConfiguration), err
}

View File

@@ -0,0 +1,23 @@
/*
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 client-gen. DO NOT EDIT.
package v1
type MutatingWebhookConfigurationExpansion interface{}
type ValidatingWebhookConfigurationExpansion interface{}

View File

@@ -0,0 +1,164 @@
/*
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 client-gen. DO NOT EDIT.
package v1
import (
"time"
v1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
scheme "k8s.io/client-go/kubernetes/scheme"
rest "k8s.io/client-go/rest"
)
// MutatingWebhookConfigurationsGetter has a method to return a MutatingWebhookConfigurationInterface.
// A group's client should implement this interface.
type MutatingWebhookConfigurationsGetter interface {
MutatingWebhookConfigurations() MutatingWebhookConfigurationInterface
}
// MutatingWebhookConfigurationInterface has methods to work with MutatingWebhookConfiguration resources.
type MutatingWebhookConfigurationInterface interface {
Create(*v1.MutatingWebhookConfiguration) (*v1.MutatingWebhookConfiguration, error)
Update(*v1.MutatingWebhookConfiguration) (*v1.MutatingWebhookConfiguration, error)
Delete(name string, options *metav1.DeleteOptions) error
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(name string, options metav1.GetOptions) (*v1.MutatingWebhookConfiguration, error)
List(opts metav1.ListOptions) (*v1.MutatingWebhookConfigurationList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.MutatingWebhookConfiguration, err error)
MutatingWebhookConfigurationExpansion
}
// mutatingWebhookConfigurations implements MutatingWebhookConfigurationInterface
type mutatingWebhookConfigurations struct {
client rest.Interface
}
// newMutatingWebhookConfigurations returns a MutatingWebhookConfigurations
func newMutatingWebhookConfigurations(c *AdmissionregistrationV1Client) *mutatingWebhookConfigurations {
return &mutatingWebhookConfigurations{
client: c.RESTClient(),
}
}
// Get takes name of the mutatingWebhookConfiguration, and returns the corresponding mutatingWebhookConfiguration object, and an error if there is any.
func (c *mutatingWebhookConfigurations) Get(name string, options metav1.GetOptions) (result *v1.MutatingWebhookConfiguration, err error) {
result = &v1.MutatingWebhookConfiguration{}
err = c.client.Get().
Resource("mutatingwebhookconfigurations").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of MutatingWebhookConfigurations that match those selectors.
func (c *mutatingWebhookConfigurations) List(opts metav1.ListOptions) (result *v1.MutatingWebhookConfigurationList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.MutatingWebhookConfigurationList{}
err = c.client.Get().
Resource("mutatingwebhookconfigurations").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested mutatingWebhookConfigurations.
func (c *mutatingWebhookConfigurations) Watch(opts metav1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Resource("mutatingwebhookconfigurations").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a mutatingWebhookConfiguration and creates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any.
func (c *mutatingWebhookConfigurations) Create(mutatingWebhookConfiguration *v1.MutatingWebhookConfiguration) (result *v1.MutatingWebhookConfiguration, err error) {
result = &v1.MutatingWebhookConfiguration{}
err = c.client.Post().
Resource("mutatingwebhookconfigurations").
Body(mutatingWebhookConfiguration).
Do().
Into(result)
return
}
// Update takes the representation of a mutatingWebhookConfiguration and updates it. Returns the server's representation of the mutatingWebhookConfiguration, and an error, if there is any.
func (c *mutatingWebhookConfigurations) Update(mutatingWebhookConfiguration *v1.MutatingWebhookConfiguration) (result *v1.MutatingWebhookConfiguration, err error) {
result = &v1.MutatingWebhookConfiguration{}
err = c.client.Put().
Resource("mutatingwebhookconfigurations").
Name(mutatingWebhookConfiguration.Name).
Body(mutatingWebhookConfiguration).
Do().
Into(result)
return
}
// Delete takes name of the mutatingWebhookConfiguration and deletes it. Returns an error if one occurs.
func (c *mutatingWebhookConfigurations) Delete(name string, options *metav1.DeleteOptions) error {
return c.client.Delete().
Resource("mutatingwebhookconfigurations").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *mutatingWebhookConfigurations) DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Resource("mutatingwebhookconfigurations").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched mutatingWebhookConfiguration.
func (c *mutatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.MutatingWebhookConfiguration, err error) {
result = &v1.MutatingWebhookConfiguration{}
err = c.client.Patch(pt).
Resource("mutatingwebhookconfigurations").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -0,0 +1,164 @@
/*
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 client-gen. DO NOT EDIT.
package v1
import (
"time"
v1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
scheme "k8s.io/client-go/kubernetes/scheme"
rest "k8s.io/client-go/rest"
)
// ValidatingWebhookConfigurationsGetter has a method to return a ValidatingWebhookConfigurationInterface.
// A group's client should implement this interface.
type ValidatingWebhookConfigurationsGetter interface {
ValidatingWebhookConfigurations() ValidatingWebhookConfigurationInterface
}
// ValidatingWebhookConfigurationInterface has methods to work with ValidatingWebhookConfiguration resources.
type ValidatingWebhookConfigurationInterface interface {
Create(*v1.ValidatingWebhookConfiguration) (*v1.ValidatingWebhookConfiguration, error)
Update(*v1.ValidatingWebhookConfiguration) (*v1.ValidatingWebhookConfiguration, error)
Delete(name string, options *metav1.DeleteOptions) error
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(name string, options metav1.GetOptions) (*v1.ValidatingWebhookConfiguration, error)
List(opts metav1.ListOptions) (*v1.ValidatingWebhookConfigurationList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ValidatingWebhookConfiguration, err error)
ValidatingWebhookConfigurationExpansion
}
// validatingWebhookConfigurations implements ValidatingWebhookConfigurationInterface
type validatingWebhookConfigurations struct {
client rest.Interface
}
// newValidatingWebhookConfigurations returns a ValidatingWebhookConfigurations
func newValidatingWebhookConfigurations(c *AdmissionregistrationV1Client) *validatingWebhookConfigurations {
return &validatingWebhookConfigurations{
client: c.RESTClient(),
}
}
// Get takes name of the validatingWebhookConfiguration, and returns the corresponding validatingWebhookConfiguration object, and an error if there is any.
func (c *validatingWebhookConfigurations) Get(name string, options metav1.GetOptions) (result *v1.ValidatingWebhookConfiguration, err error) {
result = &v1.ValidatingWebhookConfiguration{}
err = c.client.Get().
Resource("validatingwebhookconfigurations").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of ValidatingWebhookConfigurations that match those selectors.
func (c *validatingWebhookConfigurations) List(opts metav1.ListOptions) (result *v1.ValidatingWebhookConfigurationList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.ValidatingWebhookConfigurationList{}
err = c.client.Get().
Resource("validatingwebhookconfigurations").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested validatingWebhookConfigurations.
func (c *validatingWebhookConfigurations) Watch(opts metav1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Resource("validatingwebhookconfigurations").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a validatingWebhookConfiguration and creates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any.
func (c *validatingWebhookConfigurations) Create(validatingWebhookConfiguration *v1.ValidatingWebhookConfiguration) (result *v1.ValidatingWebhookConfiguration, err error) {
result = &v1.ValidatingWebhookConfiguration{}
err = c.client.Post().
Resource("validatingwebhookconfigurations").
Body(validatingWebhookConfiguration).
Do().
Into(result)
return
}
// Update takes the representation of a validatingWebhookConfiguration and updates it. Returns the server's representation of the validatingWebhookConfiguration, and an error, if there is any.
func (c *validatingWebhookConfigurations) Update(validatingWebhookConfiguration *v1.ValidatingWebhookConfiguration) (result *v1.ValidatingWebhookConfiguration, err error) {
result = &v1.ValidatingWebhookConfiguration{}
err = c.client.Put().
Resource("validatingwebhookconfigurations").
Name(validatingWebhookConfiguration.Name).
Body(validatingWebhookConfiguration).
Do().
Into(result)
return
}
// Delete takes name of the validatingWebhookConfiguration and deletes it. Returns an error if one occurs.
func (c *validatingWebhookConfigurations) Delete(name string, options *metav1.DeleteOptions) error {
return c.client.Delete().
Resource("validatingwebhookconfigurations").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *validatingWebhookConfigurations) DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Resource("validatingwebhookconfigurations").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched validatingWebhookConfiguration.
func (c *validatingWebhookConfigurations) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ValidatingWebhookConfiguration, err error) {
result = &v1.ValidatingWebhookConfiguration{}
err = c.client.Patch(pt).
Resource("validatingwebhookconfigurations").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -138,3 +138,25 @@ func (c *FakePods) Patch(name string, pt types.PatchType, data []byte, subresour
}
return obj.(*corev1.Pod), err
}
// GetEphemeralContainers takes name of the pod, and returns the corresponding ephemeralContainers object, and an error if there is any.
func (c *FakePods) GetEphemeralContainers(podName string, options v1.GetOptions) (result *corev1.EphemeralContainers, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetSubresourceAction(podsResource, c.ns, "ephemeralcontainers", podName), &corev1.EphemeralContainers{})
if obj == nil {
return nil, err
}
return obj.(*corev1.EphemeralContainers), err
}
// UpdateEphemeralContainers takes the representation of a ephemeralContainers and updates it. Returns the server's representation of the ephemeralContainers, and an error, if there is any.
func (c *FakePods) UpdateEphemeralContainers(podName string, ephemeralContainers *corev1.EphemeralContainers) (result *corev1.EphemeralContainers, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(podsResource, "ephemeralcontainers", c.ns, ephemeralContainers), &corev1.EphemeralContainers{})
if obj == nil {
return nil, err
}
return obj.(*corev1.EphemeralContainers), err
}

View File

@@ -46,6 +46,9 @@ type PodInterface interface {
List(opts metav1.ListOptions) (*v1.PodList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error)
GetEphemeralContainers(podName string, options metav1.GetOptions) (*v1.EphemeralContainers, error)
UpdateEphemeralContainers(podName string, ephemeralContainers *v1.EphemeralContainers) (*v1.EphemeralContainers, error)
PodExpansion
}
@@ -189,3 +192,31 @@ func (c *pods) Patch(name string, pt types.PatchType, data []byte, subresources
Into(result)
return
}
// GetEphemeralContainers takes name of the pod, and returns the corresponding v1.EphemeralContainers object, and an error if there is any.
func (c *pods) GetEphemeralContainers(podName string, options metav1.GetOptions) (result *v1.EphemeralContainers, err error) {
result = &v1.EphemeralContainers{}
err = c.client.Get().
Namespace(c.ns).
Resource("pods").
Name(podName).
SubResource("ephemeralcontainers").
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// UpdateEphemeralContainers takes the top resource name and the representation of a ephemeralContainers and updates it. Returns the server's representation of the ephemeralContainers, and an error, if there is any.
func (c *pods) UpdateEphemeralContainers(podName string, ephemeralContainers *v1.EphemeralContainers) (result *v1.EphemeralContainers, err error) {
result = &v1.EphemeralContainers{}
err = c.client.Put().
Namespace(c.ns).
Resource("pods").
Name(podName).
SubResource("ephemeralcontainers").
Body(ephemeralContainers).
Do().
Into(result)
return
}

View File

@@ -0,0 +1,98 @@
/*
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 v1beta1
import (
"fmt"
"k8s.io/api/events/v1beta1"
"k8s.io/apimachinery/pkg/types"
)
// The EventExpansion interface allows manually adding extra methods to the EventInterface.
// TODO: Add querying functions to the event expansion
type EventExpansion interface {
// CreateWithEventNamespace is the same as a Create
// except that it sends the request to the event.Namespace.
CreateWithEventNamespace(event *v1beta1.Event) (*v1beta1.Event, error)
// UpdateWithEventNamespace is the same as a Update
// except that it sends the request to the event.Namespace.
UpdateWithEventNamespace(event *v1beta1.Event) (*v1beta1.Event, error)
// PatchWithEventNamespace is the same as an Update
// except that it sends the request to the event.Namespace.
PatchWithEventNamespace(event *v1beta1.Event, data []byte) (*v1beta1.Event, error)
}
// CreateWithEventNamespace makes a new event.
// Returns the copy of the event the server returns, or an error.
// The namespace to create the event within is deduced from the event.
// it must either match this event client's namespace, or this event client must
// have been created with the "" namespace.
func (e *events) CreateWithEventNamespace(event *v1beta1.Event) (*v1beta1.Event, error) {
if e.ns != "" && event.Namespace != e.ns {
return nil, fmt.Errorf("can't create an event with namespace '%v' in namespace '%v'", event.Namespace, e.ns)
}
result := &v1beta1.Event{}
err := e.client.Post().
NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0).
Resource("events").
Body(event).
Do().
Into(result)
return result, err
}
// UpdateWithEventNamespace modifies an existing event.
// It returns the copy of the event that the server returns, or an error.
// The namespace and key to update the event within is deduced from the event.
// The namespace must either match this event client's namespace, or this event client must have been
// created with the "" namespace.
// Update also requires the ResourceVersion to be set in the event object.
func (e *events) UpdateWithEventNamespace(event *v1beta1.Event) (*v1beta1.Event, error) {
if e.ns != "" && event.Namespace != e.ns {
return nil, fmt.Errorf("can't update an event with namespace '%v' in namespace '%v'", event.Namespace, e.ns)
}
result := &v1beta1.Event{}
err := e.client.Put().
NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0).
Resource("events").
Name(event.Name).
Body(event).
Do().
Into(result)
return result, err
}
// PatchWithEventNamespace modifies an existing event.
// It returns the copy of the event that the server returns, or an error.
// The namespace and name of the target event is deduced from the event.
// The namespace must either match this event client's namespace, or this event client must
// have been created with the "" namespace.
func (e *events) PatchWithEventNamespace(event *v1beta1.Event, data []byte) (*v1beta1.Event, error) {
if e.ns != "" && event.Namespace != e.ns {
return nil, fmt.Errorf("can't patch an event with namespace '%v' in namespace '%v'", event.Namespace, e.ns)
}
result := &v1beta1.Event{}
err := e.client.Patch(types.StrategicMergePatchType).
NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0).
Resource("events").
Name(event.Name).
Body(data).
Do().
Into(result)
return result, err
}

View File

@@ -0,0 +1,66 @@
/*
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 fake
import (
v1beta1 "k8s.io/api/events/v1beta1"
types "k8s.io/apimachinery/pkg/types"
core "k8s.io/client-go/testing"
)
// CreateWithEventNamespace creats a new event. Returns the copy of the event the server returns, or an error.
func (c *FakeEvents) CreateWithEventNamespace(event *v1beta1.Event) (*v1beta1.Event, error) {
action := core.NewRootCreateAction(eventsResource, event)
if c.ns != "" {
action = core.NewCreateAction(eventsResource, c.ns, event)
}
obj, err := c.Fake.Invokes(action, event)
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Event), err
}
// UpdateWithEventNamespace replaces an existing event. Returns the copy of the event the server returns, or an error.
func (c *FakeEvents) UpdateWithEventNamespace(event *v1beta1.Event) (*v1beta1.Event, error) {
action := core.NewRootUpdateAction(eventsResource, event)
if c.ns != "" {
action = core.NewUpdateAction(eventsResource, c.ns, event)
}
obj, err := c.Fake.Invokes(action, event)
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Event), err
}
// PatchWithEventNamespace patches an existing event. Returns the copy of the event the server returns, or an error.
func (c *FakeEvents) PatchWithEventNamespace(event *v1beta1.Event, data []byte) (*v1beta1.Event, error) {
pt := types.StrategicMergePatchType
action := core.NewRootPatchAction(eventsResource, event.Name, pt, data)
if c.ns != "" {
action = core.NewPatchAction(eventsResource, c.ns, event.Name, pt, data)
}
obj, err := c.Fake.Invokes(action, event)
if obj == nil {
return nil, err
}
return obj.(*v1beta1.Event), err
}

View File

@@ -17,5 +17,3 @@ limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package v1beta1
type EventExpansion interface{}

View File

@@ -0,0 +1,27 @@
/*
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 lister-gen. DO NOT EDIT.
package v1
// MutatingWebhookConfigurationListerExpansion allows custom methods to be added to
// MutatingWebhookConfigurationLister.
type MutatingWebhookConfigurationListerExpansion interface{}
// ValidatingWebhookConfigurationListerExpansion allows custom methods to be added to
// ValidatingWebhookConfigurationLister.
type ValidatingWebhookConfigurationListerExpansion interface{}

View File

@@ -0,0 +1,65 @@
/*
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 lister-gen. DO NOT EDIT.
package v1
import (
v1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// MutatingWebhookConfigurationLister helps list MutatingWebhookConfigurations.
type MutatingWebhookConfigurationLister interface {
// List lists all MutatingWebhookConfigurations in the indexer.
List(selector labels.Selector) (ret []*v1.MutatingWebhookConfiguration, err error)
// Get retrieves the MutatingWebhookConfiguration from the index for a given name.
Get(name string) (*v1.MutatingWebhookConfiguration, error)
MutatingWebhookConfigurationListerExpansion
}
// mutatingWebhookConfigurationLister implements the MutatingWebhookConfigurationLister interface.
type mutatingWebhookConfigurationLister struct {
indexer cache.Indexer
}
// NewMutatingWebhookConfigurationLister returns a new MutatingWebhookConfigurationLister.
func NewMutatingWebhookConfigurationLister(indexer cache.Indexer) MutatingWebhookConfigurationLister {
return &mutatingWebhookConfigurationLister{indexer: indexer}
}
// List lists all MutatingWebhookConfigurations in the indexer.
func (s *mutatingWebhookConfigurationLister) List(selector labels.Selector) (ret []*v1.MutatingWebhookConfiguration, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.MutatingWebhookConfiguration))
})
return ret, err
}
// Get retrieves the MutatingWebhookConfiguration from the index for a given name.
func (s *mutatingWebhookConfigurationLister) Get(name string) (*v1.MutatingWebhookConfiguration, error) {
obj, exists, err := s.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("mutatingwebhookconfiguration"), name)
}
return obj.(*v1.MutatingWebhookConfiguration), nil
}

View File

@@ -0,0 +1,65 @@
/*
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 lister-gen. DO NOT EDIT.
package v1
import (
v1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ValidatingWebhookConfigurationLister helps list ValidatingWebhookConfigurations.
type ValidatingWebhookConfigurationLister interface {
// List lists all ValidatingWebhookConfigurations in the indexer.
List(selector labels.Selector) (ret []*v1.ValidatingWebhookConfiguration, err error)
// Get retrieves the ValidatingWebhookConfiguration from the index for a given name.
Get(name string) (*v1.ValidatingWebhookConfiguration, error)
ValidatingWebhookConfigurationListerExpansion
}
// validatingWebhookConfigurationLister implements the ValidatingWebhookConfigurationLister interface.
type validatingWebhookConfigurationLister struct {
indexer cache.Indexer
}
// NewValidatingWebhookConfigurationLister returns a new ValidatingWebhookConfigurationLister.
func NewValidatingWebhookConfigurationLister(indexer cache.Indexer) ValidatingWebhookConfigurationLister {
return &validatingWebhookConfigurationLister{indexer: indexer}
}
// List lists all ValidatingWebhookConfigurations in the indexer.
func (s *validatingWebhookConfigurationLister) List(selector labels.Selector) (ret []*v1.ValidatingWebhookConfiguration, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.ValidatingWebhookConfiguration))
})
return ret, err
}
// Get retrieves the ValidatingWebhookConfiguration from the index for a given name.
func (s *validatingWebhookConfigurationLister) Get(name string) (*v1.ValidatingWebhookConfiguration, error) {
obj, exists, err := s.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("validatingwebhookconfiguration"), name)
}
return obj.(*v1.ValidatingWebhookConfiguration), nil
}

390
metadata/fake/simple.go Normal file
View File

@@ -0,0 +1,390 @@
/*
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 fake
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/metadata"
"k8s.io/client-go/testing"
)
// MetadataClient assists in creating fake objects for use when testing, since metadata.Getter
// does not expose create
type MetadataClient interface {
metadata.Getter
CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
}
// NewSimpleMetadataClient creates a new client that will use the provided scheme and respond with the
// provided objects when requests are made. It will track actions made to the client which can be checked
// with GetActions().
func NewSimpleMetadataClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeMetadataClient {
gvkFakeList := schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "List"}
if !scheme.Recognizes(gvkFakeList) {
// In order to use List with this client, you have to have the v1.List registered in your scheme, since this is a test
// type we modify the input scheme
scheme.AddKnownTypeWithName(gvkFakeList, &metav1.List{})
}
codecs := serializer.NewCodecFactory(scheme)
o := testing.NewObjectTracker(scheme, codecs.UniversalDeserializer())
for _, obj := range objects {
if err := o.Add(obj); err != nil {
panic(err)
}
}
cs := &FakeMetadataClient{scheme: scheme}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource()
ns := action.GetNamespace()
watch, err := o.Watch(gvr, ns)
if err != nil {
return false, nil, err
}
return true, watch, nil
})
return cs
}
// FakeMetadataClient implements clientset.Interface. Meant to be embedded into a
// struct to get a default implementation. This makes faking out just the method
// you want to test easier.
type FakeMetadataClient struct {
testing.Fake
scheme *runtime.Scheme
}
type metadataResourceClient struct {
client *FakeMetadataClient
namespace string
resource schema.GroupVersionResource
}
var _ metadata.Interface = &FakeMetadataClient{}
// Resource returns an interface for accessing the provided resource.
func (c *FakeMetadataClient) Resource(resource schema.GroupVersionResource) metadata.Getter {
return &metadataResourceClient{client: c, resource: resource}
}
// Namespace returns an interface for accessing the current resource in the specified
// namespace.
func (c *metadataResourceClient) Namespace(ns string) metadata.ResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
// CreateFake records the object creation and processes it via the reactor.
func (c *metadataResourceClient) CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
case len(c.namespace) == 0 && len(subresources) > 0:
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
case len(c.namespace) > 0 && len(subresources) > 0:
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
uncastRet, err = c.client.Fake.
Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
}
if err != nil {
return nil, err
}
if uncastRet == nil {
return nil, err
}
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
}
return ret, err
}
// UpdateFake records the object update and processes it via the reactor.
func (c *metadataResourceClient) UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
}
if err != nil {
return nil, err
}
if uncastRet == nil {
return nil, err
}
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
}
return ret, err
}
// UpdateStatus records the object status update and processes it via the reactor.
func (c *metadataResourceClient) UpdateStatus(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions) (*metav1.PartialObjectMetadata, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
case len(c.namespace) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
}
if err != nil {
return nil, err
}
if uncastRet == nil {
return nil, err
}
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
}
return ret, err
}
// Delete records the object deletion and processes it via the reactor.
func (c *metadataResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
_, err = c.client.Fake.
Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "metadata delete fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
_, err = c.client.Fake.
Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata delete fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
_, err = c.client.Fake.
Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata delete fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
_, err = c.client.Fake.
Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "metadata delete fail"})
}
return err
}
// DeleteCollection records the object collection deletion and processes it via the reactor.
func (c *metadataResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
var err error
switch {
case len(c.namespace) == 0:
action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"})
case len(c.namespace) > 0:
action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"})
}
return err
}
// Get records the object retrieval and processes it via the reactor.
func (c *metadataResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "metadata get fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata get fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"})
}
if err != nil {
return nil, err
}
if uncastRet == nil {
return nil, err
}
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
}
return ret, err
}
// List records the object deletion and processes it via the reactor.
func (c *metadataResourceClient) List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
var obj runtime.Object
var err error
switch {
case len(c.namespace) == 0:
obj, err = c.client.Fake.
Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "metadata list fail"})
case len(c.namespace) > 0:
obj, err = c.client.Fake.
Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "metadata list fail"})
}
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
inputList, ok := obj.(*metav1.List)
if !ok {
return nil, fmt.Errorf("incoming object is incorrect type %T", obj)
}
fmt.Printf("DEBUG: %#v\n", inputList)
list := &metav1.PartialObjectMetadataList{
ListMeta: inputList.ListMeta,
}
for i := range inputList.Items {
item, ok := inputList.Items[i].Object.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("item %d in list %T is %T", i, inputList, inputList.Items[i].Object)
}
metadata, err := meta.Accessor(item)
if err != nil {
return nil, err
}
if label.Matches(labels.Set(metadata.GetLabels())) {
list.Items = append(list.Items, *item)
}
}
return list, nil
}
func (c *metadataResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
switch {
case len(c.namespace) == 0:
return c.client.Fake.
InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
case len(c.namespace) > 0:
return c.client.Fake.
InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
}
panic("math broke")
}
// Patch records the object patch and processes it via the reactor.
func (c *metadataResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "metadata patch fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "metadata patch fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"})
}
if err != nil {
return nil, err
}
if uncastRet == nil {
return nil, err
}
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
}
return ret, err
}

View File

@@ -0,0 +1,201 @@
/*
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 fake
import (
"fmt"
"testing"
"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"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
)
const (
testGroup = "testgroup"
testVersion = "testversion"
testResource = "testkinds"
testNamespace = "testns"
testName = "testname"
testKind = "TestKind"
testAPIVersion = "testgroup/testversion"
)
var scheme *runtime.Scheme
func init() {
scheme = runtime.NewScheme()
metav1.AddMetaToScheme(scheme)
}
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
return &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
}
func newPartialObjectMetadataWithAnnotations(annotations map[string]string) *metav1.PartialObjectMetadata {
u := newPartialObjectMetadata(testAPIVersion, testKind, testNamespace, testName)
u.Annotations = annotations
return u
}
func TestList(t *testing.T) {
client := NewSimpleMetadataClient(scheme,
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-baz"),
)
listFirst, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).List(metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
expected := []metav1.PartialObjectMetadata{
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
}
if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
}
}
type patchTestCase struct {
name string
object runtime.Object
patchType types.PatchType
patchBytes []byte
wantErrMsg string
expectedPatchedObject runtime.Object
}
func (tc *patchTestCase) runner(t *testing.T) {
client := NewSimpleMetadataClient(scheme, tc.object)
resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)
got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.PatchOptions{})
if err := tc.verifyErr(recErr); err != nil {
t.Error(err)
}
if err := tc.verifyResult(got); err != nil {
t.Error(err)
}
}
// verifyErr verifies that the given error returned from Patch is the error
// expected by the test case.
func (tc *patchTestCase) verifyErr(err error) error {
if tc.wantErrMsg != "" && err == nil {
return fmt.Errorf("want error, got nil")
}
if tc.wantErrMsg == "" && err != nil {
return fmt.Errorf("want no error, got %v", err)
}
if err != nil {
if want, got := tc.wantErrMsg, err.Error(); want != got {
return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
}
}
return nil
}
func (tc *patchTestCase) verifyResult(result *metav1.PartialObjectMetadata) error {
if tc.expectedPatchedObject == nil && result == nil {
return nil
}
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
}
return nil
}
func TestPatch(t *testing.T) {
testCases := []patchTestCase{
{
name: "jsonpatch fails with merge type",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
patchBytes: []byte(`[]`),
wantErrMsg: "invalid JSON document",
}, {
name: "jsonpatch works with empty patch",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
patchType: types.JSONPatchType,
// No-op
patchBytes: []byte(`[]`),
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
}, {
name: "jsonpatch works with simple change patch",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
patchType: types.JSONPatchType,
// change spec.foo from bar to foobar
patchBytes: []byte(`[{"op": "replace", "path": "/metadata/annotations/foo", "value": "foobar"}]`),
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "foobar"}),
}, {
name: "jsonpatch works with simple addition",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
patchType: types.JSONPatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "newvalue": "dummy"}),
}, {
name: "jsonpatch works with simple deletion",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "toremove": "shouldnotbehere"}),
patchType: types.JSONPatchType,
// remove spec.newvalue = dummy
patchBytes: []byte(`[{"op": "remove", "path": "/metadata/annotations/toremove"}]`),
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
}, {
name: "strategic merge patch fails with JSONPatch",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
wantErrMsg: "invalid JSON document",
}, {
name: "merge patch works with simple replacement",
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
patchType: types.MergePatchType,
patchBytes: []byte(`{ "metadata": {"annotations": { "foo": "baz" } } }`),
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "baz"}),
},
// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
// demonstrate expected use cases.
}
for _, tc := range testCases {
t.Run(tc.name, tc.runner)
}
}

47
metadata/interface.go Normal file
View File

@@ -0,0 +1,47 @@
/*
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 metadata
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
)
// Interface allows a caller to get the metadata (in the form of PartialObjectMetadata objects)
// from any Kubernetes compatible resource API.
type Interface interface {
Resource(resource schema.GroupVersionResource) Getter
}
// ResourceInterface contains the set of methods that may be invoked on objects by their metadata.
// Update is not supported by the server, but Patch can be used for the actions Update would handle.
type ResourceInterface interface {
Delete(name string, options *metav1.DeleteOptions, subresources ...string) error
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(name string, options metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
}
// Getter handles both namespaced and non-namespaced resource types consistently.
type Getter interface {
Namespace(string) ResourceInterface
ResourceInterface
}

312
metadata/metadata.go Normal file
View File

@@ -0,0 +1,312 @@
/*
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 metadata
import (
"encoding/json"
"fmt"
"time"
"k8s.io/klog"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
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"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
)
var deleteScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
var versionV1 = schema.GroupVersion{Version: "v1"}
func init() {
metav1.AddToGroupVersion(parameterScheme, versionV1)
metav1.AddToGroupVersion(deleteScheme, versionV1)
}
// Client allows callers to retrieve the object metadata for any
// Kubernetes-compatible API endpoint. The client uses the
// meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
// retrieve just the necessary metadata, but on older servers
// (Kubernetes 1.14 and before) will retrieve the object and then
// convert the metadata.
type Client struct {
client *rest.RESTClient
}
var _ Interface = &Client{}
// ConfigFor returns a copy of the provided config with the
// appropriate metadata client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
config := rest.CopyConfig(inConfig)
config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
config.ContentType = "application/vnd.kubernetes.protobuf"
config.NegotiatedSerializer = metainternalversion.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return config
}
// NewForConfigOrDie creates a new metadata client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
ret, err := NewForConfig(c)
if err != nil {
panic(err)
}
return ret
}
// NewForConfig creates a new metadata client that can retrieve object
// metadata details about any Kubernetes object (core, aggregated, or custom
// resource based) in the form of PartialObjectMetadata objects, or returns
// an error.
func NewForConfig(inConfig *rest.Config) (Interface, error) {
config := ConfigFor(inConfig)
// for serializing the options
config.GroupVersion = &schema.GroupVersion{}
config.APIPath = "/this-value-should-never-be-sent"
restClient, err := rest.RESTClientFor(config)
if err != nil {
return nil, err
}
return &Client{client: restClient}, nil
}
type client struct {
client *Client
namespace string
resource schema.GroupVersionResource
}
// Resource returns an interface that can access cluster or namespace
// scoped instances of resource.
func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
return &client{client: c, resource: resource}
}
// Namespace returns an interface that can access namespace-scoped instances of the
// provided resource.
func (c *client) Namespace(ns string) ResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
// Delete removes the provided resource from the server.
func (c *client) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
if len(name) == 0 {
return fmt.Errorf("name is required")
}
if opts == nil {
opts = &metav1.DeleteOptions{}
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
if err != nil {
return err
}
result := c.client.client.
Delete().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(deleteOptionsByte).
Do()
return result.Error()
}
// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
func (c *client) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
if opts == nil {
opts = &metav1.DeleteOptions{}
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
if err != nil {
return err
}
result := c.client.client.
Delete().
AbsPath(c.makeURLSegments("")...).
Body(deleteOptionsByte).
SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
Do()
return result.Error()
}
// Get returns the resource with name from the specified scope (namespace or cluster).
func (c *client) Get(name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
obj, err := result.Get()
if runtime.IsNotRegisteredError(err) {
klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
rawBytes, err := result.Raw()
if err != nil {
return nil, err
}
var partial metav1.PartialObjectMetadata
if err := json.Unmarshal(rawBytes, &partial); err != nil {
return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
}
if !isLikelyObjectMetadata(&partial) {
return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
}
partial.TypeMeta = metav1.TypeMeta{}
return &partial, nil
}
if err != nil {
return nil, err
}
partial, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
}
return partial, nil
}
// List returns all resources within the specified scope (namespace or cluster).
func (c *client) List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
obj, err := result.Get()
if runtime.IsNotRegisteredError(err) {
klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
rawBytes, err := result.Raw()
if err != nil {
return nil, err
}
var partial metav1.PartialObjectMetadataList
if err := json.Unmarshal(rawBytes, &partial); err != nil {
return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
}
partial.TypeMeta = metav1.TypeMeta{}
return &partial, nil
}
if err != nil {
return nil, err
}
partial, ok := obj.(*metav1.PartialObjectMetadataList)
if !ok {
return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
}
return partial, nil
}
// Watch finds all changes to the resources in the specified scope (namespace or cluster).
func (c *client) Watch(opts metav1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.client.Get().
AbsPath(c.makeURLSegments("")...).
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Timeout(timeout).
Watch()
}
// Patch modifies the named resource in the specified scope (namespace or cluster).
func (c *client) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.
Patch(pt).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(data).
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
obj, err := result.Get()
if runtime.IsNotRegisteredError(err) {
rawBytes, err := result.Raw()
if err != nil {
return nil, err
}
var partial metav1.PartialObjectMetadata
if err := json.Unmarshal(rawBytes, &partial); err != nil {
return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
}
if !isLikelyObjectMetadata(&partial) {
return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
}
partial.TypeMeta = metav1.TypeMeta{}
return &partial, nil
}
if err != nil {
return nil, err
}
partial, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
}
return partial, nil
}
func (c *client) makeURLSegments(name string) []string {
url := []string{}
if len(c.resource.Group) == 0 {
url = append(url, "api")
} else {
url = append(url, "apis", c.resource.Group)
}
url = append(url, c.resource.Version)
if len(c.namespace) > 0 {
url = append(url, "namespaces", c.namespace)
}
url = append(url, c.resource.Resource)
if len(name) > 0 {
url = append(url, name)
}
return url
}
func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
}

243
metadata/metadata_test.go Normal file
View File

@@ -0,0 +1,243 @@
/*
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 metadata
import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/rest"
)
func TestClient(t *testing.T) {
gvr := schema.GroupVersionResource{Group: "group", Version: "v1", Resource: "resource"}
writeJSON := func(t *testing.T, w http.ResponseWriter, obj runtime.Object) {
data, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(data); err != nil {
t.Fatal(err)
}
}
testCases := []struct {
name string
handler func(t *testing.T, w http.ResponseWriter, req *http.Request)
want func(t *testing.T, client *Client)
}{
{
name: "GET is able to convert a JSON object to PartialObjectMetadata",
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
t.Fatal(req.Header.Get("Accept"))
}
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
t.Fatal(req.URL.String())
}
writeJSON(t, w, &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "ns",
},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
expect := &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "ns",
},
}
if !reflect.DeepEqual(expect, obj) {
t.Fatal(diff.ObjectReflectDiff(expect, obj))
}
},
},
{
name: "LIST is able to convert a JSON object to PartialObjectMetadata",
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json" {
t.Fatal(req.Header.Get("Accept"))
}
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource" {
t.Fatal(req.URL.String())
}
writeJSON(t, w, &corev1.PodList{
TypeMeta: metav1.TypeMeta{
Kind: "PodList",
APIVersion: "v1",
},
ListMeta: metav1.ListMeta{
ResourceVersion: "253",
},
Items: []corev1.Pod{
{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "ns",
},
},
},
})
},
want: func(t *testing.T, client *Client) {
objs, err := client.Resource(gvr).Namespace("ns").List(metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
if objs.GetResourceVersion() != "253" {
t.Fatal(objs)
}
expect := []metav1.PartialObjectMetadata{
{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "ns",
},
},
}
if !reflect.DeepEqual(expect, objs.Items) {
t.Fatal(diff.ObjectReflectDiff(expect, objs.Items))
}
},
},
{
name: "GET fails if the object is JSON and has no kind",
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
t.Fatal(req.Header.Get("Accept"))
}
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
t.Fatal(req.URL.String())
}
writeJSON(t, w, &corev1.Pod{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
UID: "123",
},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
if err == nil || !runtime.IsMissingKind(err) {
t.Fatal(err)
}
if obj != nil {
t.Fatal(obj)
}
},
},
{
name: "GET fails if the object is JSON and has no apiVersion",
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
t.Fatal(req.Header.Get("Accept"))
}
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
t.Fatal(req.URL.String())
}
writeJSON(t, w, &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
UID: "123",
},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
if err == nil || !runtime.IsMissingVersion(err) {
t.Fatal(err)
}
if obj != nil {
t.Fatal(obj)
}
},
},
{
name: "GET fails if the object is JSON and not clearly metadata",
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
t.Fatal(req.Header.Get("Accept"))
}
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
t.Fatal(req.URL.String())
}
writeJSON(t, w, &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{},
})
},
want: func(t *testing.T, client *Client) {
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
if err == nil || !strings.Contains(err.Error(), "object does not appear to match the ObjectMeta schema") {
t.Fatal(err)
}
if obj != nil {
t.Fatal(obj)
}
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { tt.handler(t, w, req) }))
defer s.Close()
cfg := ConfigFor(&rest.Config{Host: s.URL})
client := NewForConfigOrDie(cfg).(*Client)
tt.want(t, client)
})
}
}

View File

@@ -0,0 +1,156 @@
/*
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 metadatainformer
import (
"sync"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers"
"k8s.io/client-go/metadata"
"k8s.io/client-go/metadata/metadatalister"
"k8s.io/client-go/tools/cache"
)
// NewSharedInformerFactory constructs a new instance of metadataSharedInformerFactory for all namespaces.
func NewSharedInformerFactory(client metadata.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewFilteredSharedInformerFactory(client, defaultResync, metav1.NamespaceAll, nil)
}
// NewFilteredSharedInformerFactory constructs a new instance of metadataSharedInformerFactory.
// Listers obtained via this factory will be subject to the same filters as specified here.
func NewFilteredSharedInformerFactory(client metadata.Interface, defaultResync time.Duration, namespace string, tweakListOptions TweakListOptionsFunc) SharedInformerFactory {
return &metadataSharedInformerFactory{
client: client,
defaultResync: defaultResync,
namespace: namespace,
informers: map[schema.GroupVersionResource]informers.GenericInformer{},
startedInformers: make(map[schema.GroupVersionResource]bool),
tweakListOptions: tweakListOptions,
}
}
type metadataSharedInformerFactory struct {
client metadata.Interface
defaultResync time.Duration
namespace string
lock sync.Mutex
informers map[schema.GroupVersionResource]informers.GenericInformer
// startedInformers is used for tracking which informers have been started.
// This allows Start() to be called multiple times safely.
startedInformers map[schema.GroupVersionResource]bool
tweakListOptions TweakListOptionsFunc
}
var _ SharedInformerFactory = &metadataSharedInformerFactory{}
func (f *metadataSharedInformerFactory) ForResource(gvr schema.GroupVersionResource) informers.GenericInformer {
f.lock.Lock()
defer f.lock.Unlock()
key := gvr
informer, exists := f.informers[key]
if exists {
return informer
}
informer = NewFilteredMetadataInformer(f.client, gvr, f.namespace, f.defaultResync, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
f.informers[key] = informer
return informer
}
// Start initializes all requested informers.
func (f *metadataSharedInformerFactory) Start(stopCh <-chan struct{}) {
f.lock.Lock()
defer f.lock.Unlock()
for informerType, informer := range f.informers {
if !f.startedInformers[informerType] {
go informer.Informer().Run(stopCh)
f.startedInformers[informerType] = true
}
}
}
// WaitForCacheSync waits for all started informers' cache were synced.
func (f *metadataSharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool {
informers := func() map[schema.GroupVersionResource]cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
informers := map[schema.GroupVersionResource]cache.SharedIndexInformer{}
for informerType, informer := range f.informers {
if f.startedInformers[informerType] {
informers[informerType] = informer.Informer()
}
}
return informers
}()
res := map[schema.GroupVersionResource]bool{}
for informType, informer := range informers {
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
}
return res
}
// NewFilteredMetadataInformer constructs a new informer for a metadata type.
func NewFilteredMetadataInformer(client metadata.Interface, gvr schema.GroupVersionResource, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions TweakListOptionsFunc) informers.GenericInformer {
return &metadataInformer{
gvr: gvr,
informer: cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.Resource(gvr).Namespace(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.Resource(gvr).Namespace(namespace).Watch(options)
},
},
&metav1.PartialObjectMetadata{},
resyncPeriod,
indexers,
),
}
}
type metadataInformer struct {
informer cache.SharedIndexInformer
gvr schema.GroupVersionResource
}
var _ informers.GenericInformer = &metadataInformer{}
func (d *metadataInformer) Informer() cache.SharedIndexInformer {
return d.informer
}
func (d *metadataInformer) Lister() cache.GenericLister {
return metadatalister.NewRuntimeObjectShim(metadatalister.New(d.informer.GetIndexer(), d.gvr))
}

View File

@@ -0,0 +1,170 @@
/*
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 metadatainformer
import (
"context"
"flag"
"testing"
"time"
"k8s.io/klog"
"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"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/metadata/fake"
"k8s.io/client-go/tools/cache"
)
func init() {
klog.InitFlags(flag.CommandLine)
flag.CommandLine.Lookup("v").Value.Set("5")
flag.CommandLine.Lookup("alsologtostderr").Value.Set("true")
}
func TestMetadataSharedInformerFactory(t *testing.T) {
scenarios := []struct {
name string
existingObj *metav1.PartialObjectMetadata
gvr schema.GroupVersionResource
ns string
trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata
handler func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs
}{
// scenario 1
{
name: "scenario 1: test if adding an object triggers AddFunc",
ns: "ns-foo",
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, _ *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
testObject := newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo")
createdObj, err := fakeClient.Resource(gvr).Namespace(ns).(fake.MetadataClient).CreateFake(testObject, metav1.CreateOptions{})
if err != nil {
t.Error(err)
}
return createdObj
},
handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
return &cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
rcvCh <- obj.(*metav1.PartialObjectMetadata)
},
}
},
},
// scenario 2
{
name: "scenario 2: tests if updating an object triggers UpdateFunc",
ns: "ns-foo",
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
existingObj: newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
if testObject.Annotations == nil {
testObject.Annotations = make(map[string]string)
}
testObject.Annotations["test"] = "updatedName"
updatedObj, err := fakeClient.Resource(gvr).Namespace(ns).(fake.MetadataClient).UpdateFake(testObject, metav1.UpdateOptions{})
if err != nil {
t.Error(err)
}
return updatedObj
},
handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
return &cache.ResourceEventHandlerFuncs{
UpdateFunc: func(old, updated interface{}) {
rcvCh <- updated.(*metav1.PartialObjectMetadata)
},
}
},
},
// scenario 3
{
name: "scenario 3: test if deleting an object triggers DeleteFunc",
ns: "ns-foo",
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
existingObj: newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
err := fakeClient.Resource(gvr).Namespace(ns).Delete(testObject.GetName(), &metav1.DeleteOptions{})
if err != nil {
t.Error(err)
}
return testObject
},
handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
return &cache.ResourceEventHandlerFuncs{
DeleteFunc: func(obj interface{}) {
rcvCh <- obj.(*metav1.PartialObjectMetadata)
},
}
},
},
}
for _, ts := range scenarios {
t.Run(ts.name, func(t *testing.T) {
// test data
timeout := time.Duration(3 * time.Second)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
scheme := runtime.NewScheme()
metav1.AddMetaToScheme(scheme)
informerReciveObjectCh := make(chan *metav1.PartialObjectMetadata, 1)
objs := []runtime.Object{}
if ts.existingObj != nil {
objs = append(objs, ts.existingObj)
}
fakeClient := fake.NewSimpleMetadataClient(scheme, objs...)
target := NewSharedInformerFactory(fakeClient, 0)
// act
informerListerForGvr := target.ForResource(ts.gvr)
informerListerForGvr.Informer().AddEventHandler(ts.handler(informerReciveObjectCh))
target.Start(ctx.Done())
if synced := target.WaitForCacheSync(ctx.Done()); !synced[ts.gvr] {
t.Fatalf("informer for %s hasn't synced", ts.gvr)
}
testObject := ts.trigger(ts.gvr, ts.ns, fakeClient, ts.existingObj)
select {
case objFromInformer := <-informerReciveObjectCh:
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
}
case <-ctx.Done():
t.Errorf("tested informer haven't received an object, waited %v", timeout)
}
})
}
}
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
return &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
}

View File

@@ -0,0 +1,34 @@
/*
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 metadatainformer
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
)
// SharedInformerFactory provides access to a shared informer and lister for dynamic client
type SharedInformerFactory interface {
Start(stopCh <-chan struct{})
ForResource(gvr schema.GroupVersionResource) informers.GenericInformer
WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool
}
// TweakListOptionsFunc defines the signature of a helper function
// that wants to provide more listing options to API
type TweakListOptionsFunc func(*metav1.ListOptions)

View File

@@ -0,0 +1,40 @@
/*
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 metadatalister
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
// Lister helps list resources.
type Lister interface {
// List lists all resources in the indexer.
List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error)
// Get retrieves a resource from the indexer with the given name
Get(name string) (*metav1.PartialObjectMetadata, error)
// Namespace returns an object that can list and get resources in a given namespace.
Namespace(namespace string) NamespaceLister
}
// NamespaceLister helps list and get resources.
type NamespaceLister interface {
// List lists all resources in the indexer for a given namespace.
List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error)
// Get retrieves a resource from the indexer for a given namespace and name.
Get(name string) (*metav1.PartialObjectMetadata, error)
}

View File

@@ -0,0 +1,91 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metadatalister
import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
)
var _ Lister = &metadataLister{}
var _ NamespaceLister = &metadataNamespaceLister{}
// metadataLister implements the Lister interface.
type metadataLister struct {
indexer cache.Indexer
gvr schema.GroupVersionResource
}
// New returns a new Lister.
func New(indexer cache.Indexer, gvr schema.GroupVersionResource) Lister {
return &metadataLister{indexer: indexer, gvr: gvr}
}
// List lists all resources in the indexer.
func (l *metadataLister) List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error) {
err = cache.ListAll(l.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*metav1.PartialObjectMetadata))
})
return ret, err
}
// Get retrieves a resource from the indexer with the given name
func (l *metadataLister) Get(name string) (*metav1.PartialObjectMetadata, error) {
obj, exists, err := l.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
}
return obj.(*metav1.PartialObjectMetadata), nil
}
// Namespace returns an object that can list and get resources from a given namespace.
func (l *metadataLister) Namespace(namespace string) NamespaceLister {
return &metadataNamespaceLister{indexer: l.indexer, namespace: namespace, gvr: l.gvr}
}
// metadataNamespaceLister implements the NamespaceLister interface.
type metadataNamespaceLister struct {
indexer cache.Indexer
namespace string
gvr schema.GroupVersionResource
}
// List lists all resources in the indexer for a given namespace.
func (l *metadataNamespaceLister) List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error) {
err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*metav1.PartialObjectMetadata))
})
return ret, err
}
// Get retrieves a resource from the indexer for a given namespace and name.
func (l *metadataNamespaceLister) Get(name string) (*metav1.PartialObjectMetadata, error) {
obj, exists, err := l.indexer.GetByKey(l.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
}
return obj.(*metav1.PartialObjectMetadata), nil
}

View File

@@ -0,0 +1,256 @@
/*
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 metadatalister
import (
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/tools/cache"
)
func TestNamespaceGetMethod(t *testing.T) {
tests := []struct {
name string
existingObjects []runtime.Object
namespaceToSync string
gvrToSync schema.GroupVersionResource
objectToGet string
expectedObject *metav1.PartialObjectMetadata
expectError bool
}{
{
name: "scenario 1: gets name-foo1 resource from the indexer from ns-foo namespace",
existingObjects: []runtime.Object{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "ns-foo",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo1",
expectedObject: newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
},
{
name: "scenario 2: gets name-foo-non-existing resource from the indexer from ns-foo namespace",
existingObjects: []runtime.Object{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "ns-foo",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo-non-existing",
expectError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// test data
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range test.existingObjects {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
// act
target := New(indexer, test.gvrToSync).Namespace(test.namespaceToSync)
actualObject, err := target.Get(test.objectToGet)
// validate
if test.expectError {
if err == nil {
t.Fatal("expected to get an error but non was returned")
}
return
}
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(test.expectedObject, actualObject) {
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
}
})
}
}
func TestNamespaceListMethod(t *testing.T) {
// test data
objs := []runtime.Object{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
}
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range objs {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
expectedOutput := []*metav1.PartialObjectMetadata{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
}
namespaceToList := "ns-foo"
// act
target := New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"}).Namespace(namespaceToList)
actualOutput, err := target.List(labels.Everything())
// validate
if err != nil {
t.Fatal(err)
}
assertListOrDie(expectedOutput, actualOutput, t)
}
func TestListerGetMethod(t *testing.T) {
tests := []struct {
name string
existingObjects []runtime.Object
namespaceToSync string
gvrToSync schema.GroupVersionResource
objectToGet string
expectedObject *metav1.PartialObjectMetadata
expectError bool
}{
{
name: "scenario 1: gets name-foo1 resource from the indexer",
existingObjects: []runtime.Object{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "", "name-foo1"),
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo1",
expectedObject: newPartialObjectMetadata("group/version", "TheKind", "", "name-foo1"),
},
{
name: "scenario 2: doesn't get name-foo resource from the indexer from ns-foo namespace",
existingObjects: []runtime.Object{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
},
namespaceToSync: "ns-foo",
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
objectToGet: "name-foo",
expectError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// test data
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range test.existingObjects {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
// act
target := New(indexer, test.gvrToSync)
actualObject, err := target.Get(test.objectToGet)
// validate
if test.expectError {
if err == nil {
t.Fatal("expected to get an error but non was returned")
}
return
}
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(test.expectedObject, actualObject) {
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
}
})
}
}
func TestListerListMethod(t *testing.T) {
// test data
objs := []runtime.Object{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
}
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, obj := range objs {
err := indexer.Add(obj)
if err != nil {
t.Fatal(err)
}
}
expectedOutput := []*metav1.PartialObjectMetadata{
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
}
// act
target := New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"})
actualOutput, err := target.List(labels.Everything())
// validate
if err != nil {
t.Fatal(err)
}
assertListOrDie(expectedOutput, actualOutput, t)
}
func assertListOrDie(expected, actual []*metav1.PartialObjectMetadata, t *testing.T) {
if len(actual) != len(expected) {
t.Fatalf("unexpected number of items returned, expected = %d, actual = %d", len(expected), len(actual))
}
for _, expectedObject := range expected {
found := false
for _, actualObject := range actual {
if actualObject.GetName() == expectedObject.GetName() {
if !reflect.DeepEqual(expectedObject, actualObject) {
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", expectedObject, actualObject, diff.ObjectDiff(expectedObject, actualObject))
}
found = true
}
}
if !found {
t.Fatalf("the resource with the name = %s was not found in the returned output", expectedObject.GetName())
}
}
}
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
return &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
}

View File

@@ -0,0 +1,87 @@
/*
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 metadatalister
import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
)
var _ cache.GenericLister = &metadataListerShim{}
var _ cache.GenericNamespaceLister = &metadataNamespaceListerShim{}
// metadataListerShim implements the cache.GenericLister interface.
type metadataListerShim struct {
lister Lister
}
// NewRuntimeObjectShim returns a new shim for Lister.
// It wraps Lister so that it implements cache.GenericLister interface
func NewRuntimeObjectShim(lister Lister) cache.GenericLister {
return &metadataListerShim{lister: lister}
}
// List will return all objects across namespaces
func (s *metadataListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
objs, err := s.lister.List(selector)
if err != nil {
return nil, err
}
ret = make([]runtime.Object, len(objs))
for index, obj := range objs {
ret[index] = obj
}
return ret, err
}
// Get will attempt to retrieve assuming that name==key
func (s *metadataListerShim) Get(name string) (runtime.Object, error) {
return s.lister.Get(name)
}
func (s *metadataListerShim) ByNamespace(namespace string) cache.GenericNamespaceLister {
return &metadataNamespaceListerShim{
namespaceLister: s.lister.Namespace(namespace),
}
}
// metadataNamespaceListerShim implements the NamespaceLister interface.
// It wraps NamespaceLister so that it implements cache.GenericNamespaceLister interface
type metadataNamespaceListerShim struct {
namespaceLister NamespaceLister
}
// List will return all objects in this namespace
func (ns *metadataNamespaceListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
objs, err := ns.namespaceLister.List(selector)
if err != nil {
return nil, err
}
ret = make([]runtime.Object, len(objs))
for index, obj := range objs {
ret[index] = obj
}
return ret, err
}
// Get will attempt to retrieve by namespace and name
func (ns *metadataNamespaceListerShim) Get(name string) (runtime.Object, error) {
return ns.namespaceLister.Get(name)
}

View File

@@ -172,6 +172,10 @@ func (t *fakeLimiter) QPS() float32 {
return t.FakeQPS
}
func (t *fakeLimiter) Wait(ctx context.Context) error {
return nil
}
func (t *fakeLimiter) Stop() {}
func (t *fakeLimiter) Accept() {}

View File

@@ -55,7 +55,7 @@ func RegisterAuthProviderPlugin(name string, plugin Factory) error {
pluginsLock.Lock()
defer pluginsLock.Unlock()
if _, found := plugins[name]; found {
return fmt.Errorf("Auth Provider Plugin %q was registered twice", name)
return fmt.Errorf("auth Provider Plugin %q was registered twice", name)
}
klog.V(4).Infof("Registered Auth Provider Plugin %q", name)
plugins[name] = plugin
@@ -67,7 +67,7 @@ func GetAuthProvider(clusterAddress string, apc *clientcmdapi.AuthProviderConfig
defer pluginsLock.Unlock()
p, ok := plugins[apc.Name]
if !ok {
return nil, fmt.Errorf("No Auth Provider found for name %q", apc.Name)
return nil, fmt.Errorf("no Auth Provider found for name %q", apc.Name)
}
return p(clusterAddress, apc.Config, persister)
}

View File

@@ -521,14 +521,24 @@ func (r Request) finalURLTemplate() url.URL {
return *url
}
func (r *Request) tryThrottle() {
func (r *Request) tryThrottle() error {
if r.throttle == nil {
return nil
}
now := time.Now()
if r.throttle != nil {
var err error
if r.ctx != nil {
err = r.throttle.Wait(r.ctx)
} else {
r.throttle.Accept()
}
if latency := time.Since(now); latency > longThrottleLatency {
klog.V(4).Infof("Throttling request took %v, request: %s:%s", latency, r.verb, r.URL().String())
}
return err
}
// Watch attempts to begin watching the requested location.
@@ -630,13 +640,18 @@ func (r *Request) Stream() (io.ReadCloser, error) {
return nil, r.err
}
r.tryThrottle()
if err := r.tryThrottle(); err != nil {
return nil, err
}
url := r.URL().String()
req, err := http.NewRequest(r.verb, url, nil)
if err != nil {
return nil, err
}
if r.body != nil {
req.Body = ioutil.NopCloser(r.body)
}
if r.ctx != nil {
req = req.WithContext(r.ctx)
}
@@ -732,7 +747,9 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
// We are retrying the request that we already send to apiserver
// at least once before.
// This request should also be throttled with the client-internal throttler.
r.tryThrottle()
if err := r.tryThrottle(); err != nil {
return err
}
}
resp, err := client.Do(req)
updateURLMetrics(r, resp, err)
@@ -803,7 +820,9 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
// * If the server responds with a status: *errors.StatusError or *errors.UnexpectedObjectError
// * http.Client.Do errors are returned directly.
func (r *Request) Do() Result {
r.tryThrottle()
if err := r.tryThrottle(); err != nil {
return Result{err: err}
}
var result Result
err := r.request(func(req *http.Request, resp *http.Response) {
@@ -817,7 +836,9 @@ func (r *Request) Do() Result {
// DoRaw executes the request but does not process the response body.
func (r *Request) DoRaw() ([]byte, error) {
r.tryThrottle()
if err := r.tryThrottle(); err != nil {
return nil, err
}
var result Result
err := r.request(func(req *http.Request, resp *http.Response) {
@@ -850,13 +871,13 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
// 3. Apiserver closes connection.
// 4. client-go should catch this and return an error.
klog.V(2).Infof("Stream error %#v when reading response body, may be caused by closed connection.", err)
streamErr := fmt.Errorf("Stream error when reading response body, may be caused by closed connection. Please retry. Original error: %v", err)
streamErr := fmt.Errorf("stream error when reading response body, may be caused by closed connection. Please retry. Original error: %v", err)
return Result{
err: streamErr,
}
default:
klog.Errorf("Unexpected error when reading response body: %v", err)
unexpectedErr := fmt.Errorf("Unexpected error when reading response body. Please retry. Original error: %v", err)
unexpectedErr := fmt.Errorf("unexpected error when reading response body. Please retry. Original error: %v", err)
return Result{
err: unexpectedErr,
}

19
rest/request_test.go Executable file → Normal file
View File

@@ -1440,15 +1440,20 @@ func BenchmarkCheckRetryClosesBody(b *testing.B) {
defer testServer.Close()
c := testRESTClient(b, testServer)
r := c.Verb("POST").
Prefix("foo", "bar").
Suffix("baz").
Timeout(time.Second).
Body([]byte(strings.Repeat("abcd", 1000)))
requests := make([]*Request, 0, b.N)
for i := 0; i < b.N; i++ {
if _, err := r.DoRaw(); err != nil {
b.Fatalf("Unexpected error: %v %#v", err, err)
requests = append(requests, c.Verb("POST").
Prefix("foo", "bar").
Suffix("baz").
Timeout(time.Second).
Body([]byte(strings.Repeat("abcd", 1000))))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := requests[i].DoRaw(); err != nil {
b.Fatalf("Unexpected error (%d/%d): %v", i, b.N, err)
}
}
}

View File

@@ -74,9 +74,10 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
KeyFile: c.KeyFile,
KeyData: c.KeyData,
},
Username: c.Username,
Password: c.Password,
BearerToken: c.BearerToken,
Username: c.Username,
Password: c.Password,
BearerToken: c.BearerToken,
BearerTokenFile: c.BearerTokenFile,
Impersonate: transport.ImpersonationConfig{
UserName: c.Impersonate.UserName,
Groups: c.Impersonate.Groups,

View File

@@ -18,4 +18,4 @@ limitations under the License.
// and updating Scale for any resource which implements the `scale` subresource,
// as long as that subresource operates on a version of scale convertable to
// autoscaling.Scale.
package scale
package scale // import "k8s.io/client-go/scale"

View File

@@ -18,9 +18,11 @@ package testing
import (
"fmt"
"reflect"
"sync"
jsonpatch "github.com/evanphx/json-patch"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -139,6 +141,11 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc {
return true, nil, err
}
// reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields
// in obj that are removed by patch are cleared
value := reflect.ValueOf(obj)
value.Elem().Set(reflect.New(value.Type().Elem()).Elem())
switch action.GetPatchType() {
case types.JSONPatchType:
patch, err := jsonpatch.DecodePatch(action.GetPatch())
@@ -149,6 +156,7 @@ func ObjectReaction(tracker ObjectTracker) ReactionFunc {
if err != nil {
return true, nil, err
}
if err = json.Unmarshal(modified, obj); err != nil {
return true, nil, err
}
@@ -310,6 +318,11 @@ func (t *tracker) Add(obj runtime.Object) error {
if err != nil {
return err
}
if partial, ok := obj.(*metav1.PartialObjectMetadata); ok && len(partial.TypeMeta.APIVersion) > 0 {
gvks = []schema.GroupVersionKind{partial.TypeMeta.GroupVersionKind()}
}
if len(gvks) == 0 {
return fmt.Errorf("no registered kinds for %v", obj)
}

View File

@@ -105,7 +105,7 @@ func LoadFromFile(path string) (*Info, error) {
// The fields of client.Config with a corresponding field in the Info are set
// with the value from the Info.
func (info Info) MergeWithConfig(c restclient.Config) (restclient.Config, error) {
var config restclient.Config = c
var config = c
config.Username = info.User
config.Password = info.Password
config.CAFile = info.CAFile
@@ -118,6 +118,7 @@ func (info Info) MergeWithConfig(c restclient.Config) (restclient.Config, error)
return config, nil
}
// Complete returns true if the Kubernetes API authorization info is complete.
func (info Info) Complete() bool {
return len(info.User) > 0 ||
len(info.CertFile) > 0 ||

View File

@@ -242,16 +242,15 @@ func TestHammerController(t *testing.T) {
currentNames := sets.String{}
rs := rand.NewSource(rand.Int63())
f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs)
r := rand.New(rs) // Mustn't use r and f concurrently!
for i := 0; i < 100; i++ {
var name string
var isNew bool
if currentNames.Len() == 0 || r.Intn(3) == 1 {
if currentNames.Len() == 0 || rand.Intn(3) == 1 {
f.Fuzz(&name)
isNew = true
} else {
l := currentNames.List()
name = l[r.Intn(len(l))]
name = l[rand.Intn(len(l))]
}
pod := &v1.Pod{}
@@ -266,7 +265,7 @@ func TestHammerController(t *testing.T) {
source.Add(pod)
continue
}
switch r.Intn(2) {
switch rand.Intn(2) {
case 0:
currentNames.Insert(name)
source.Modify(pod)

View File

@@ -25,7 +25,7 @@ type FakeCustomStore struct {
ListKeysFunc func() []string
GetFunc func(obj interface{}) (item interface{}, exists bool, err error)
GetByKeyFunc func(key string) (item interface{}, exists bool, err error)
ReplaceFunc func(list []interface{}, resourceVerion string) error
ReplaceFunc func(list []interface{}, resourceVersion string) error
ResyncFunc func() error
}

26
tools/cache/index.go vendored
View File

@@ -23,17 +23,27 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
)
// Indexer is a storage interface that lets you list objects using multiple indexing functions
// Indexer is a storage interface that lets you list objects using multiple indexing functions.
// There are three kinds of strings here.
// One is a storage key, as defined in the Store interface.
// Another kind is a name of an index.
// The third kind of string is an "indexed value", which is produced by an
// IndexFunc and can be a field value or any other string computed from the object.
type Indexer interface {
Store
// Retrieve list of objects that match on the named indexing function
// Index returns the stored objects whose set of indexed values
// intersects the set of indexed values of the given object, for
// the named index
Index(indexName string, obj interface{}) ([]interface{}, error)
// IndexKeys returns the set of keys that match on the named indexing function.
IndexKeys(indexName, indexKey string) ([]string, error)
// ListIndexFuncValues returns the list of generated values of an Index func
// IndexKeys returns the storage keys of the stored objects whose
// set of indexed values for the named index includes the given
// indexed value
IndexKeys(indexName, indexedValue string) ([]string, error)
// ListIndexFuncValues returns all the indexed values of the given index
ListIndexFuncValues(indexName string) []string
// ByIndex lists object that match on the named indexing function with the exact key
ByIndex(indexName, indexKey string) ([]interface{}, error)
// ByIndex returns the stored objects whose set of indexed values
// for the named index includes the given indexed value
ByIndex(indexName, indexedValue string) ([]interface{}, error)
// GetIndexer return the indexers
GetIndexers() Indexers
@@ -42,7 +52,7 @@ type Indexer interface {
AddIndexers(newIndexers Indexers) error
}
// IndexFunc knows how to provide an indexed value for an object.
// IndexFunc knows how to compute the set of indexed values for an object.
type IndexFunc func(obj interface{}) ([]string, error)
// IndexFuncToKeyFuncAdapter adapts an indexFunc to a keyFunc. This is only useful if your index function returns

29
tools/cache/main_test.go vendored Normal file
View File

@@ -0,0 +1,29 @@
/*
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 cache
import (
"math/rand"
"os"
"testing"
"time"
)
func TestMain(m *testing.M) {
rand.Seed(time.Now().UnixNano())
os.Exit(m.Run())
}

View File

@@ -114,7 +114,7 @@ func (d *defaultCacheMutationDetector) CompareObjects() {
altered := false
for i, obj := range d.cachedObjs {
if !reflect.DeepEqual(obj.cached, obj.copied) {
fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectDiff(obj.cached, obj.copied))
fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectGoPrintSideBySide(obj.cached, obj.copied))
altered = true
}
}

View File

@@ -83,7 +83,7 @@ var (
// NewNamespaceKeyedIndexerAndReflector creates an Indexer and a Reflector
// The indexer is configured to key on namespace
func NewNamespaceKeyedIndexerAndReflector(lw ListerWatcher, expectedType interface{}, resyncPeriod time.Duration) (indexer Indexer, reflector *Reflector) {
indexer = NewIndexer(MetaNamespaceKeyFunc, Indexers{"namespace": MetaNamespaceIndexFunc})
indexer = NewIndexer(MetaNamespaceKeyFunc, Indexers{NamespaceIndex: MetaNamespaceIndexFunc})
reflector = NewReflector(lw, expectedType, indexer, resyncPeriod)
return indexer, reflector
}
@@ -268,8 +268,7 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
// To reduce load on kube-apiserver on watch restarts, you may enable watch bookmarks.
// Reflector doesn't assume bookmarks are returned at all (if the server do not support
// watch bookmarks, it will ignore this field).
// Disabled in Alpha release of watch bookmarks feature.
AllowWatchBookmarks: false,
AllowWatchBookmarks: true,
}
w, err := r.listerWatcher.Watch(options)
@@ -299,7 +298,12 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil {
if err != errorStopRequested {
klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err)
switch {
case apierrs.IsResourceExpired(err):
klog.V(4).Infof("%s: watch of %v ended with: %v", r.name, r.expectedType, err)
default:
klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err)
}
}
return nil
}

0
tools/cache/store.go vendored Executable file → Normal file
View File

View File

@@ -228,6 +228,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
// blindly overwrite existing values based on precedence
if len(configAuthInfo.Token) > 0 {
mergedConfig.BearerToken = configAuthInfo.Token
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
} else if len(configAuthInfo.TokenFile) > 0 {
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
if err != nil {
@@ -489,8 +490,9 @@ func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error)
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
icc.Host = server
}
if token := config.overrides.AuthInfo.Token; len(token) > 0 {
icc.BearerToken = token
if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
icc.BearerToken = config.overrides.AuthInfo.Token
icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
}
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
icc.TLSClientConfig.CAFile = certificateAuthorityFile

View File

@@ -548,6 +548,30 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
},
},
},
{
overrides: &ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
Server: "https://host-from-overrides.com",
CertificateAuthority: "/path/to/ca-from-overrides.crt",
},
AuthInfo: clientcmdapi.AuthInfo{
Token: "token-from-override",
TokenFile: "tokenfile-from-override",
},
},
},
{
overrides: &ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
Server: "https://host-from-overrides.com",
CertificateAuthority: "/path/to/ca-from-overrides.crt",
},
AuthInfo: clientcmdapi.AuthInfo{
Token: "",
TokenFile: "tokenfile-from-override",
},
},
},
{
overrides: &ConfigOverrides{},
},
@@ -556,13 +580,15 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
for _, tc := range tt {
expectedServer := "https://host-from-cluster.com"
expectedToken := "token-from-cluster"
expectedTokenFile := "tokenfile-from-cluster"
expectedCAFile := "/path/to/ca-from-cluster.crt"
icc := &inClusterClientConfig{
inClusterConfigProvider: func() (*restclient.Config, error) {
return &restclient.Config{
Host: expectedServer,
BearerToken: expectedToken,
Host: expectedServer,
BearerToken: expectedToken,
BearerTokenFile: expectedTokenFile,
TLSClientConfig: restclient.TLSClientConfig{
CAFile: expectedCAFile,
},
@@ -579,8 +605,9 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
if overridenServer := tc.overrides.ClusterInfo.Server; len(overridenServer) > 0 {
expectedServer = overridenServer
}
if overridenToken := tc.overrides.AuthInfo.Token; len(overridenToken) > 0 {
expectedToken = overridenToken
if len(tc.overrides.AuthInfo.Token) > 0 || len(tc.overrides.AuthInfo.TokenFile) > 0 {
expectedToken = tc.overrides.AuthInfo.Token
expectedTokenFile = tc.overrides.AuthInfo.TokenFile
}
if overridenCAFile := tc.overrides.ClusterInfo.CertificateAuthority; len(overridenCAFile) > 0 {
expectedCAFile = overridenCAFile
@@ -592,6 +619,9 @@ func TestInClusterClientConfigPrecedence(t *testing.T) {
if clientConfig.BearerToken != expectedToken {
t.Errorf("Expected token %v, got %v", expectedToken, clientConfig.BearerToken)
}
if clientConfig.BearerTokenFile != expectedTokenFile {
t.Errorf("Expected tokenfile %v, got %v", expectedTokenFile, clientConfig.BearerTokenFile)
}
if clientConfig.TLSClientConfig.CAFile != expectedCAFile {
t.Errorf("Expected Certificate Authority %v, got %v", expectedCAFile, clientConfig.TLSClientConfig.CAFile)
}

View File

@@ -127,6 +127,10 @@ type ClientConfigLoadingRules struct {
// DefaultClientConfig is an optional field indicating what rules to use to calculate a default configuration.
// This should match the overrides passed in to ClientConfig loader.
DefaultClientConfig ClientConfig
// WarnIfAllMissing indicates whether the configuration files pointed by KUBECONFIG environment variable are present or not.
// In case of missing files, it warns the user about the missing files.
WarnIfAllMissing bool
}
// ClientConfigLoadingRules implements the ClientConfigLoader interface.
@@ -136,20 +140,23 @@ var _ ClientConfigLoader = &ClientConfigLoadingRules{}
// use this constructor
func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
chain := []string{}
warnIfAllMissing := false
envVarFiles := os.Getenv(RecommendedConfigPathEnvVar)
if len(envVarFiles) != 0 {
fileList := filepath.SplitList(envVarFiles)
// prevent the same path load multiple times
chain = append(chain, deduplicate(fileList)...)
warnIfAllMissing = true
} else {
chain = append(chain, RecommendedHomeFile)
}
return &ClientConfigLoadingRules{
Precedence: chain,
MigrationRules: currentMigrationRules(),
Precedence: chain,
MigrationRules: currentMigrationRules(),
WarnIfAllMissing: warnIfAllMissing,
}
}
@@ -172,6 +179,7 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
}
errlist := []error{}
missingList := []string{}
kubeConfigFiles := []string{}
@@ -195,18 +203,26 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
}
config, err := LoadFromFile(filename)
if os.IsNotExist(err) {
// skip missing files
// Add to the missing list to produce a warning
missingList = append(missingList, filename)
continue
}
if err != nil {
errlist = append(errlist, fmt.Errorf("Error loading config file \"%s\": %v", filename, err))
errlist = append(errlist, fmt.Errorf("error loading config file \"%s\": %v", filename, err))
continue
}
kubeconfigs = append(kubeconfigs, config)
}
if rules.WarnIfAllMissing && len(missingList) > 0 && len(kubeconfigs) == 0 {
klog.Warningf("Config not found: %s", strings.Join(missingList, ", "))
}
// first merge all of our maps
mapConfig := clientcmdapi.NewConfig()
@@ -467,7 +483,7 @@ func ResolveLocalPaths(config *clientcmdapi.Config) error {
}
base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
if err != nil {
return fmt.Errorf("Could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
}
if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
@@ -480,7 +496,7 @@ func ResolveLocalPaths(config *clientcmdapi.Config) error {
}
base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
if err != nil {
return fmt.Errorf("Could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
}
if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {

View File

@@ -250,8 +250,6 @@ func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []err
for _, v := range authInfo.Exec.Env {
if len(v.Name) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("env variable name must be specified for %v to use exec authentication plugin", authInfoName))
} else if len(v.Value) == 0 {
validationErrors = append(validationErrors, fmt.Errorf("env variable %s value must be specified for %v to use exec authentication plugin", v.Name, authInfoName))
}
}
}

View File

@@ -443,22 +443,19 @@ func TestValidateAuthInfoExecWithAuthProvider(t *testing.T) {
test.testConfig(t)
}
func TestValidateAuthInfoExecInvalidEnv(t *testing.T) {
func TestValidateAuthInfoExecNoEnv(t *testing.T) {
config := clientcmdapi.NewConfig()
config.AuthInfos["user"] = &clientcmdapi.AuthInfo{
Exec: &clientcmdapi.ExecConfig{
Command: "/bin/example",
APIVersion: "clientauthentication.k8s.io/v1alpha1",
Env: []clientcmdapi.ExecEnvVar{
{Name: "foo"}, // No value
{Name: "foo", Value: ""},
},
},
}
test := configValidationTest{
config: config,
expectedErrorSubstring: []string{
"env variable foo value must be specified for user to use exec authentication plugin",
},
}
test.testAuthInfo("user", t)

8
tools/events/OWNERS Normal file
View File

@@ -0,0 +1,8 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- yastij
- wojtek-t
reviewers:
- yastij
- wojtek-t

View File

@@ -0,0 +1,312 @@
/*
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 events
import (
"os"
"sync"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/clock"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest"
"k8s.io/api/events/v1beta1"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
typedv1beta1 "k8s.io/client-go/kubernetes/typed/events/v1beta1"
"k8s.io/client-go/tools/record/util"
"k8s.io/klog"
)
const (
maxTriesPerEvent = 12
finishTime = 6 * time.Minute
refreshTime = 30 * time.Minute
maxQueuedEvents = 1000
)
var defaultSleepDuration = 10 * time.Second
// TODO: validate impact of copying and investigate hashing
type eventKey struct {
action string
reason string
reportingController string
reportingInstance string
regarding corev1.ObjectReference
related corev1.ObjectReference
}
type eventBroadcasterImpl struct {
*watch.Broadcaster
mu sync.Mutex
eventCache map[eventKey]*v1beta1.Event
sleepDuration time.Duration
sink EventSink
}
// EventSinkImpl wraps EventInterface to implement EventSink.
// TODO: this makes it easier for testing purpose and masks the logic of performing API calls.
// Note that rollbacking to raw clientset should also be transparent.
type EventSinkImpl struct {
Interface typedv1beta1.EventInterface
}
// Create is the same as CreateWithEventNamespace of the EventExpansion
func (e *EventSinkImpl) Create(event *v1beta1.Event) (*v1beta1.Event, error) {
return e.Interface.CreateWithEventNamespace(event)
}
// Update is the same as UpdateithEventNamespace of the EventExpansion
func (e *EventSinkImpl) Update(event *v1beta1.Event) (*v1beta1.Event, error) {
return e.Interface.UpdateWithEventNamespace(event)
}
// Patch is the same as PatchWithEventNamespace of the EventExpansion
func (e *EventSinkImpl) Patch(event *v1beta1.Event, data []byte) (*v1beta1.Event, error) {
return e.Interface.PatchWithEventNamespace(event, data)
}
// NewBroadcaster Creates a new event broadcaster.
func NewBroadcaster(sink EventSink) EventBroadcaster {
return newBroadcaster(sink, defaultSleepDuration, map[eventKey]*v1beta1.Event{})
}
// NewBroadcasterForTest Creates a new event broadcaster for test purposes.
func newBroadcaster(sink EventSink, sleepDuration time.Duration, eventCache map[eventKey]*v1beta1.Event) EventBroadcaster {
return &eventBroadcasterImpl{
Broadcaster: watch.NewBroadcaster(maxQueuedEvents, watch.DropIfChannelFull),
eventCache: eventCache,
sleepDuration: sleepDuration,
sink: sink,
}
}
// refreshExistingEventSeries refresh events TTL
func (e *eventBroadcasterImpl) refreshExistingEventSeries() {
// TODO: Investigate whether lock contention won't be a problem
e.mu.Lock()
defer e.mu.Unlock()
for isomorphicKey, event := range e.eventCache {
if event.Series != nil {
if recordedEvent, retry := recordEvent(e.sink, event); !retry {
e.eventCache[isomorphicKey] = recordedEvent
}
}
}
}
// finishSeries checks if a series has ended and either:
// - write final count to the apiserver
// - delete a singleton event (i.e. series field is nil) from the cache
func (e *eventBroadcasterImpl) finishSeries() {
// TODO: Investigate whether lock contention won't be a problem
e.mu.Lock()
defer e.mu.Unlock()
for isomorphicKey, event := range e.eventCache {
eventSerie := event.Series
if eventSerie != nil {
if eventSerie.LastObservedTime.Time.Before(time.Now().Add(-finishTime)) {
if _, retry := recordEvent(e.sink, event); !retry {
delete(e.eventCache, isomorphicKey)
}
}
} else if event.EventTime.Time.Before(time.Now().Add(-finishTime)) {
delete(e.eventCache, isomorphicKey)
}
}
}
// NewRecorder returns an EventRecorder that records events with the given event source.
func (e *eventBroadcasterImpl) NewRecorder(scheme *runtime.Scheme, reportingController string) EventRecorder {
hostname, _ := os.Hostname()
reportingInstance := reportingController + "-" + hostname
return &recorderImpl{scheme, reportingController, reportingInstance, e.Broadcaster, clock.RealClock{}}
}
func (e *eventBroadcasterImpl) recordToSink(event *v1beta1.Event, clock clock.Clock) {
// Make a copy before modification, because there could be multiple listeners.
eventCopy := event.DeepCopy()
go func() {
evToRecord := func() *v1beta1.Event {
e.mu.Lock()
defer e.mu.Unlock()
eventKey := getKey(eventCopy)
isomorphicEvent, isIsomorphic := e.eventCache[eventKey]
if isIsomorphic {
if isomorphicEvent.Series != nil {
isomorphicEvent.Series.Count++
isomorphicEvent.Series.LastObservedTime = metav1.MicroTime{Time: clock.Now()}
return nil
}
isomorphicEvent.Series = &v1beta1.EventSeries{
Count: 1,
LastObservedTime: metav1.MicroTime{Time: clock.Now()},
}
return isomorphicEvent
}
e.eventCache[eventKey] = eventCopy
return eventCopy
}()
if evToRecord != nil {
recordedEvent := e.attemptRecording(evToRecord)
if recordedEvent != nil {
recordedEventKey := getKey(recordedEvent)
e.mu.Lock()
defer e.mu.Unlock()
e.eventCache[recordedEventKey] = recordedEvent
}
}
}()
}
func (e *eventBroadcasterImpl) attemptRecording(event *v1beta1.Event) *v1beta1.Event {
tries := 0
for {
if recordedEvent, retry := recordEvent(e.sink, event); !retry {
return recordedEvent
}
tries++
if tries >= maxTriesPerEvent {
klog.Errorf("Unable to write event '%#v' (retry limit exceeded!)", event)
return nil
}
// Randomize sleep so that various clients won't all be
// synced up if the master goes down.
time.Sleep(wait.Jitter(e.sleepDuration, 0.25))
}
}
func recordEvent(sink EventSink, event *v1beta1.Event) (*v1beta1.Event, bool) {
var newEvent *v1beta1.Event
var err error
isEventSeries := event.Series != nil
if isEventSeries {
patch, err := createPatchBytesForSeries(event)
if err != nil {
klog.Errorf("Unable to calculate diff, no merge is possible: %v", err)
return nil, false
}
newEvent, err = sink.Patch(event, patch)
}
// Update can fail because the event may have been removed and it no longer exists.
if !isEventSeries || (isEventSeries && util.IsKeyNotFoundError(err)) {
// Making sure that ResourceVersion is empty on creation
event.ResourceVersion = ""
newEvent, err = sink.Create(event)
}
if err == nil {
return newEvent, false
}
// If we can't contact the server, then hold everything while we keep trying.
// Otherwise, something about the event is malformed and we should abandon it.
switch err.(type) {
case *restclient.RequestConstructionError:
// We will construct the request the same next time, so don't keep trying.
klog.Errorf("Unable to construct event '%#v': '%v' (will not retry!)", event, err)
return nil, false
case *errors.StatusError:
if errors.IsAlreadyExists(err) {
klog.V(5).Infof("Server rejected event '%#v': '%v' (will not retry!)", event, err)
} else {
klog.Errorf("Server rejected event '%#v': '%v' (will not retry!)", event, err)
}
return nil, false
case *errors.UnexpectedObjectError:
// We don't expect this; it implies the server's response didn't match a
// known pattern. Go ahead and retry.
default:
// This case includes actual http transport errors. Go ahead and retry.
}
klog.Errorf("Unable to write event: '%v' (may retry after sleeping)", err)
return nil, true
}
func createPatchBytesForSeries(event *v1beta1.Event) ([]byte, error) {
oldEvent := event.DeepCopy()
oldEvent.Series = nil
oldData, err := json.Marshal(oldEvent)
if err != nil {
return nil, err
}
newData, err := json.Marshal(event)
if err != nil {
return nil, err
}
return strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1beta1.Event{})
}
func getKey(event *v1beta1.Event) eventKey {
key := eventKey{
action: event.Action,
reason: event.Reason,
reportingController: event.ReportingController,
reportingInstance: event.ReportingInstance,
regarding: event.Regarding,
}
if event.Related != nil {
key.related = *event.Related
}
return key
}
// StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function.
// The return value is used to stop recording
func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(event runtime.Object)) func() {
watcher := e.Watch()
go func() {
defer utilruntime.HandleCrash()
for {
watchEvent, ok := <-watcher.ResultChan()
if !ok {
return
}
eventHandler(watchEvent.Object)
}
}()
return watcher.Stop
}
// StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink.
func (e *eventBroadcasterImpl) StartRecordingToSink(stopCh <-chan struct{}) {
go wait.Until(func() {
e.refreshExistingEventSeries()
}, refreshTime, stopCh)
go wait.Until(func() {
e.finishSeries()
}, finishTime, stopCh)
eventHandler := func(obj runtime.Object) {
event, ok := obj.(*v1beta1.Event)
if !ok {
klog.Errorf("unexpected type, expected v1beta1.Event")
return
}
e.recordToSink(event, clock.RealClock{})
}
stopWatcher := e.StartEventWatcher(eventHandler)
go func() {
<-stopCh
stopWatcher()
}()
}

View File

@@ -0,0 +1,92 @@
/*
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 events
import (
"fmt"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/clock"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/reference"
"k8s.io/api/events/v1beta1"
"k8s.io/client-go/tools/record/util"
"k8s.io/klog"
)
type recorderImpl struct {
scheme *runtime.Scheme
reportingController string
reportingInstance string
*watch.Broadcaster
clock clock.Clock
}
func (recorder *recorderImpl) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
timestamp := metav1.MicroTime{time.Now()}
message := fmt.Sprintf(note, args...)
refRegarding, err := reference.GetReference(recorder.scheme, regarding)
if err != nil {
klog.Errorf("Could not construct reference to: '%#v' due to: '%v'. Will not report event: '%v' '%v' '%v'", regarding, err, eventtype, reason, message)
return
}
refRelated, err := reference.GetReference(recorder.scheme, related)
if err != nil {
klog.V(9).Infof("Could not construct reference to: '%#v' due to: '%v'.", related, err)
}
if !util.ValidateEventType(eventtype) {
klog.Errorf("Unsupported event type: '%v'", eventtype)
return
}
event := recorder.makeEvent(refRegarding, refRelated, timestamp, eventtype, reason, message, recorder.reportingController, recorder.reportingInstance, action)
go func() {
defer utilruntime.HandleCrash()
recorder.Action(watch.Added, event)
}()
}
func (recorder *recorderImpl) makeEvent(refRegarding *v1.ObjectReference, refRelated *v1.ObjectReference, timestamp metav1.MicroTime, eventtype, reason, message string, reportingController string, reportingInstance string, action string) *v1beta1.Event {
t := metav1.Time{Time: recorder.clock.Now()}
namespace := refRegarding.Namespace
if namespace == "" {
namespace = metav1.NamespaceSystem
}
return &v1beta1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v.%x", refRegarding.Name, t.UnixNano()),
Namespace: namespace,
},
EventTime: timestamp,
Series: nil,
ReportingController: reportingController,
ReportingInstance: reportingInstance,
Action: action,
Reason: reason,
Regarding: *refRegarding,
Related: refRelated,
Note: message,
Type: eventtype,
// TODO: remove this when we change conversion to convert eventSource
// to reportingController
DeprecatedSource: v1.EventSource{Component: reportingController},
}
}

View File

@@ -0,0 +1,349 @@
/*
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 events
import (
"strconv"
"testing"
"time"
"os"
"strings"
v1 "k8s.io/api/core/v1"
"k8s.io/api/events/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes/scheme"
ref "k8s.io/client-go/tools/reference"
)
type testEventSeriesSink struct {
OnCreate func(e *v1beta1.Event) (*v1beta1.Event, error)
OnUpdate func(e *v1beta1.Event) (*v1beta1.Event, error)
OnPatch func(e *v1beta1.Event, p []byte) (*v1beta1.Event, error)
}
// Create records the event for testing.
func (t *testEventSeriesSink) Create(e *v1beta1.Event) (*v1beta1.Event, error) {
if t.OnCreate != nil {
return t.OnCreate(e)
}
return e, nil
}
// Update records the event for testing.
func (t *testEventSeriesSink) Update(e *v1beta1.Event) (*v1beta1.Event, error) {
if t.OnUpdate != nil {
return t.OnUpdate(e)
}
return e, nil
}
// Patch records the event for testing.
func (t *testEventSeriesSink) Patch(e *v1beta1.Event, p []byte) (*v1beta1.Event, error) {
if t.OnPatch != nil {
return t.OnPatch(e, p)
}
return e, nil
}
func TestEventSeriesf(t *testing.T) {
hostname, _ := os.Hostname()
testPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
SelfLink: "/api/version/pods/foo",
Name: "foo",
Namespace: "baz",
UID: "bar",
},
}
regarding, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[1]")
if err != nil {
t.Fatal(err)
}
related, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[0]")
if err != nil {
t.Fatal(err)
}
expectedEvent := &v1beta1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "baz",
},
EventTime: metav1.MicroTime{time.Now()},
ReportingController: "eventTest",
ReportingInstance: "eventTest-" + hostname,
Action: "started",
Reason: "test",
Regarding: *regarding,
Related: related,
Note: "some verbose message: 1",
Type: v1.EventTypeNormal,
}
isomorphicEvent := expectedEvent.DeepCopy()
nonIsomorphicEvent := expectedEvent.DeepCopy()
nonIsomorphicEvent.Action = "stopped"
expectedEvent.Series = &v1beta1.EventSeries{Count: 1}
table := []struct {
regarding k8sruntime.Object
related k8sruntime.Object
actual *v1beta1.Event
elements []interface{}
expect *v1beta1.Event
expectUpdate bool
}{
{
regarding: regarding,
related: related,
actual: isomorphicEvent,
elements: []interface{}{1},
expect: expectedEvent,
expectUpdate: true,
},
{
regarding: regarding,
related: related,
actual: nonIsomorphicEvent,
elements: []interface{}{1},
expect: nonIsomorphicEvent,
expectUpdate: false,
},
}
stopCh := make(chan struct{})
createEvent := make(chan *v1beta1.Event)
updateEvent := make(chan *v1beta1.Event)
patchEvent := make(chan *v1beta1.Event)
testEvents := testEventSeriesSink{
OnCreate: func(event *v1beta1.Event) (*v1beta1.Event, error) {
createEvent <- event
return event, nil
},
OnUpdate: func(event *v1beta1.Event) (*v1beta1.Event, error) {
updateEvent <- event
return event, nil
},
OnPatch: func(event *v1beta1.Event, patch []byte) (*v1beta1.Event, error) {
// event we receive is already patched, usually the sink uses it only to retrieve the name and namespace, here
// we'll use it directly
patchEvent <- event
return event, nil
},
}
eventBroadcaster := newBroadcaster(&testEvents, 0, map[eventKey]*v1beta1.Event{})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "eventTest")
eventBroadcaster.StartRecordingToSink(stopCh)
recorder.Eventf(regarding, related, isomorphicEvent.Type, isomorphicEvent.Reason, isomorphicEvent.Action, isomorphicEvent.Note, []interface{}{1})
// read from the chan as this was needed only to populate the cache
<-createEvent
for index, item := range table {
actual := item.actual
recorder.Eventf(item.regarding, item.related, actual.Type, actual.Reason, actual.Action, actual.Note, item.elements)
// validate event
if item.expectUpdate {
actualEvent := <-patchEvent
t.Logf("%v - validating event affected by patch request", index)
validateEventSerie(strconv.Itoa(index), true, actualEvent, item.expect, t)
} else {
actualEvent := <-createEvent
t.Logf("%v - validating event affected by a create request", index)
validateEventSerie(strconv.Itoa(index), false, actualEvent, item.expect, t)
}
}
close(stopCh)
}
func validateEventSerie(messagePrefix string, expectedUpdate bool, actualEvent *v1beta1.Event, expectedEvent *v1beta1.Event, t *testing.T) {
recvEvent := *actualEvent
// Just check that the timestamp was set.
if recvEvent.EventTime.IsZero() {
t.Errorf("%v - timestamp wasn't set: %#v", messagePrefix, recvEvent)
}
if expectedUpdate {
if recvEvent.Series == nil {
t.Errorf("%v - Series was nil but expected: %#v", messagePrefix, recvEvent.Series)
} else {
if recvEvent.Series.Count != expectedEvent.Series.Count {
t.Errorf("%v - Series mismatch actual was: %#v but expected: %#v", messagePrefix, recvEvent.Series, expectedEvent.Series)
}
}
// Check that name has the right prefix.
if n, en := recvEvent.Name, expectedEvent.Name; !strings.HasPrefix(n, en) {
t.Errorf("%v - Name '%v' does not contain prefix '%v'", messagePrefix, n, en)
}
} else {
if recvEvent.Series != nil {
t.Errorf("%v - series was expected to be nil but was: %#v", messagePrefix, recvEvent.Series)
}
}
}
func TestFinishSeries(t *testing.T) {
hostname, _ := os.Hostname()
testPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
SelfLink: "/api/version/pods/foo",
Name: "foo",
Namespace: "baz",
UID: "bar",
},
}
regarding, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[1]")
if err != nil {
t.Fatal(err)
}
related, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[0]")
if err != nil {
t.Fatal(err)
}
LastObservedTime := metav1.MicroTime{Time: time.Now().Add(-9 * time.Minute)}
createEvent := make(chan *v1beta1.Event, 10)
updateEvent := make(chan *v1beta1.Event, 10)
patchEvent := make(chan *v1beta1.Event, 10)
testEvents := testEventSeriesSink{
OnCreate: func(event *v1beta1.Event) (*v1beta1.Event, error) {
createEvent <- event
return event, nil
},
OnUpdate: func(event *v1beta1.Event) (*v1beta1.Event, error) {
updateEvent <- event
return event, nil
},
OnPatch: func(event *v1beta1.Event, patch []byte) (*v1beta1.Event, error) {
// event we receive is already patched, usually the sink uses it
// only to retrieve the name and namespace, here we'll use it directly
patchEvent <- event
return event, nil
},
}
cache := map[eventKey]*v1beta1.Event{}
eventBroadcaster := newBroadcaster(&testEvents, 0, cache).(*eventBroadcasterImpl)
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "k8s.io/kube-foo").(*recorderImpl)
cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{time.Now()}, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
nonFinishedEvent := cachedEvent.DeepCopy()
nonFinishedEvent.ReportingController = "nonFinished-controller"
cachedEvent.Series = &v1beta1.EventSeries{
Count: 10,
LastObservedTime: LastObservedTime,
}
cache[getKey(cachedEvent)] = cachedEvent
cache[getKey(nonFinishedEvent)] = nonFinishedEvent
eventBroadcaster.finishSeries()
select {
case actualEvent := <-patchEvent:
t.Logf("validating event affected by patch request")
eventBroadcaster.mu.Lock()
defer eventBroadcaster.mu.Unlock()
if len(cache) != 1 {
t.Errorf("cache should be empty, but instead got a size of %v", len(cache))
}
if !actualEvent.Series.LastObservedTime.Equal(&cachedEvent.Series.LastObservedTime) {
t.Errorf("series was expected be seen with LastObservedTime %v, but instead got %v ", cachedEvent.Series.LastObservedTime, actualEvent.Series.LastObservedTime)
}
// check that we emitted only one event
if len(patchEvent) != 0 || len(createEvent) != 0 || len(updateEvent) != 0 {
t.Errorf("exactly one event should be emitted, but got %v", len(patchEvent))
}
case <-time.After(wait.ForeverTestTimeout):
t.Fatalf("timeout after %v", wait.ForeverTestTimeout)
}
}
func TestRefreshExistingEventSeries(t *testing.T) {
hostname, _ := os.Hostname()
testPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
SelfLink: "/api/version/pods/foo",
Name: "foo",
Namespace: "baz",
UID: "bar",
},
}
regarding, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[1]")
if err != nil {
t.Fatal(err)
}
related, err := ref.GetPartialReference(scheme.Scheme, testPod, ".spec.containers[0]")
if err != nil {
t.Fatal(err)
}
LastObservedTime := metav1.MicroTime{Time: time.Now().Add(-9 * time.Minute)}
createEvent := make(chan *v1beta1.Event, 10)
updateEvent := make(chan *v1beta1.Event, 10)
patchEvent := make(chan *v1beta1.Event, 10)
testEvents := testEventSeriesSink{
OnCreate: func(event *v1beta1.Event) (*v1beta1.Event, error) {
createEvent <- event
return event, nil
},
OnUpdate: func(event *v1beta1.Event) (*v1beta1.Event, error) {
updateEvent <- event
return event, nil
},
OnPatch: func(event *v1beta1.Event, patch []byte) (*v1beta1.Event, error) {
// event we receive is already patched, usually the sink uses it
//only to retrieve the name and namespace, here we'll use it directly.
patchEvent <- event
return event, nil
},
}
cache := map[eventKey]*v1beta1.Event{}
eventBroadcaster := newBroadcaster(&testEvents, 0, cache).(*eventBroadcasterImpl)
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "k8s.io/kube-foo").(*recorderImpl)
cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{time.Now()}, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
cachedEvent.Series = &v1beta1.EventSeries{
Count: 10,
LastObservedTime: LastObservedTime,
}
cacheKey := getKey(cachedEvent)
cache[cacheKey] = cachedEvent
eventBroadcaster.refreshExistingEventSeries()
select {
case <-patchEvent:
t.Logf("validating event affected by patch request")
eventBroadcaster.mu.Lock()
defer eventBroadcaster.mu.Unlock()
if len(cache) != 1 {
t.Errorf("cache should be with same size, but instead got a size of %v", len(cache))
}
// check that we emitted only one event
if len(patchEvent) != 0 || len(createEvent) != 0 || len(updateEvent) != 0 {
t.Errorf("exactly one event should be emitted, but got %v", len(patchEvent))
}
case <-time.After(wait.ForeverTestTimeout):
t.Fatalf("timeout after %v", wait.ForeverTestTimeout)
}
}

45
tools/events/fake.go Normal file
View File

@@ -0,0 +1,45 @@
/*
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 events
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
)
// FakeRecorder is used as a fake during tests. It is thread safe. It is usable
// when created manually and not by NewFakeRecorder, however all events may be
// thrown away in this case.
type FakeRecorder struct {
Events chan string
}
// Eventf emits an event
func (f *FakeRecorder) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
if f.Events != nil {
f.Events <- fmt.Sprintf(eventtype+" "+reason+" "+note, args...)
}
}
// NewFakeRecorder creates new fake event recorder with event channel with
// buffer of given size.
func NewFakeRecorder(bufferSize int) *FakeRecorder {
return &FakeRecorder{
Events: make(chan string, bufferSize),
}
}

View File

@@ -0,0 +1,64 @@
/*
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 events
import (
"k8s.io/api/events/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
// EventRecorder knows how to record events on behalf of an EventSource.
type EventRecorder interface {
// Eventf constructs an event from the given information and puts it in the queue for sending.
// 'regarding' is the object this event is about. Event will make a reference-- or you may also
// pass a reference to the object directly.
// 'related' is the secondary object for more complex actions. E.g. when regarding object triggers
// a creation or deletion of related object.
// 'type' of this event, and can be one of Normal, Warning. New types could be added in future
// 'reason' is the reason this event is generated. 'reason' should be short and unique; it
// should be in UpperCamelCase format (starting with a capital letter). "reason" will be used
// to automate handling of events, so imagine people writing switch statements to handle them.
// You want to make that easy.
// 'note' is intended to be human readable.
Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{})
}
// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log.
type EventBroadcaster interface {
// StartRecordingToSink starts sending events received from the specified eventBroadcaster.
StartRecordingToSink(stopCh <-chan struct{})
// NewRecorder returns an EventRecorder that can be used to send events to this EventBroadcaster
// with the event source set to the given event source.
NewRecorder(scheme *runtime.Scheme, reportingController string) EventRecorder
// StartEventWatcher enables you to watch for emitted events without usage
// of StartRecordingToSink. This lets you also process events in a custom way (e.g. in tests).
// NOTE: events received on your eventHandler should be copied before being used.
// TODO: figure out if this can be removed.
StartEventWatcher(eventHandler func(event runtime.Object)) func()
}
// EventSink knows how to store events (client-go implements it.)
// EventSink must respect the namespace that will be embedded in 'event'.
// It is assumed that EventSink will return the same sorts of errors as
// client-go's REST client.
type EventSink interface {
Create(event *v1beta1.Event) (*v1beta1.Event, error)
Update(event *v1beta1.Event) (*v1beta1.Event, error)
Patch(oldEvent *v1beta1.Event, data []byte) (*v1beta1.Event, error)
}

View File

@@ -76,7 +76,7 @@ func TestLeaderElectionHealthChecker(t *testing.T) {
elector: nil,
},
{
description: "call check when the the lease is far expired",
description: "call check when the lease is far expired",
expected: fmt.Errorf("failed election to renew leadership on lease %s", "foo"),
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
@@ -93,7 +93,7 @@ func TestLeaderElectionHealthChecker(t *testing.T) {
},
},
{
description: "call check when the the lease is far expired but held by another server",
description: "call check when the lease is far expired but held by another server",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
@@ -110,7 +110,7 @@ func TestLeaderElectionHealthChecker(t *testing.T) {
},
},
{
description: "call check when the the lease is not expired",
description: "call check when the lease is not expired",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
@@ -127,7 +127,7 @@ func TestLeaderElectionHealthChecker(t *testing.T) {
},
},
{
description: "call check when the the lease is expired but inside the timeout",
description: "call check when the lease is expired but inside the timeout",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{

View File

@@ -16,12 +16,16 @@ limitations under the License.
// Package leaderelection implements leader election of a set of endpoints.
// It uses an annotation in the endpoints object to store the record of the
// election state.
// election state. This implementation does not guarantee that only one
// client is acting as a leader (a.k.a. fencing).
//
// This implementation does not guarantee that only one client is acting as a
// leader (a.k.a. fencing). A client observes timestamps captured locally to
// infer the state of the leader election. Thus the implementation is tolerant
// to arbitrary clock skew, but is not tolerant to arbitrary clock skew rate.
// A client only acts on timestamps captured locally to infer the state of the
// leader election. The client does not consider timestamps in the leader
// election record to be accurate because these timestamps may not have been
// produced by a local clock. The implemention does not depend on their
// accuracy and only uses their change to indicate that another client has
// renewed the leader lease. Thus the implementation is tolerant to arbitrary
// clock skew, but is not tolerant to arbitrary clock skew rate.
//
// However the level of tolerance to skew rate can be configured by setting
// RenewDeadline and LeaseDuration appropriately. The tolerance expressed as a
@@ -105,12 +109,26 @@ type LeaderElectionConfig struct {
// LeaseDuration is the duration that non-leader candidates will
// wait to force acquire leadership. This is measured against time of
// last observed ack.
//
// A client needs to wait a full LeaseDuration without observing a change to
// the record before it can attempt to take over. When all clients are
// shutdown and a new set of clients are started with different names against
// the same leader record, they must wait the full LeaseDuration before
// attempting to acquire the lease. Thus LeaseDuration should be as short as
// possible (within your tolerance for clock skew rate) to avoid a possible
// long waits in the scenario.
//
// Core clients default this value to 15 seconds.
LeaseDuration time.Duration
// RenewDeadline is the duration that the acting master will retry
// refreshing leadership before giving up.
//
// Core clients default this value to 10 seconds.
RenewDeadline time.Duration
// RetryPeriod is the duration the LeaderElector clients should wait
// between tries of actions.
//
// Core clients default this value to 2 seconds.
RetryPeriod time.Duration
// Callbacks are callbacks that are triggered during certain lifecycle

View File

@@ -33,8 +33,8 @@ import (
"k8s.io/apimachinery/pkg/util/runtime"
)
// PortForwardProtocolV1Name is the subprotocol used for port forwarding.
// TODO move to API machinery and re-unify with kubelet/server/portfoward
// The subprotocol "portforward.k8s.io" is used for port forwarding.
const PortForwardProtocolV1Name = "portforward.k8s.io"
// PortForwarder knows how to listen for local connections and forward them to
@@ -90,20 +90,20 @@ func parsePorts(ports []string) ([]ForwardedPort, error) {
}
remoteString = parts[1]
} else {
return nil, fmt.Errorf("Invalid port format '%s'", portString)
return nil, fmt.Errorf("invalid port format '%s'", portString)
}
localPort, err := strconv.ParseUint(localString, 10, 16)
if err != nil {
return nil, fmt.Errorf("Error parsing local port '%s': %s", localString, err)
return nil, fmt.Errorf("error parsing local port '%s': %s", localString, err)
}
remotePort, err := strconv.ParseUint(remoteString, 10, 16)
if err != nil {
return nil, fmt.Errorf("Error parsing remote port '%s': %s", remoteString, err)
return nil, fmt.Errorf("error parsing remote port '%s': %s", remoteString, err)
}
if remotePort == 0 {
return nil, fmt.Errorf("Remote port must be > 0")
return nil, fmt.Errorf("remote port must be > 0")
}
forwards = append(forwards, ForwardedPort{uint16(localPort), uint16(remotePort)})
@@ -159,14 +159,14 @@ func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, rea
// NewOnAddresses creates a new PortForwarder with custom listen addresses.
func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
if len(addresses) == 0 {
return nil, errors.New("You must specify at least 1 address")
return nil, errors.New("you must specify at least 1 address")
}
parsedAddresses, err := parseAddresses(addresses)
if err != nil {
return nil, err
}
if len(ports) == 0 {
return nil, errors.New("You must specify at least 1 port")
return nil, errors.New("you must specify at least 1 port")
}
parsedPorts, err := parsePorts(ports)
if err != nil {
@@ -219,7 +219,7 @@ func (pf *PortForwarder) forward() error {
}
if !listenSuccess {
return fmt.Errorf("Unable to listen on any of the requested ports: %v", pf.ports)
return fmt.Errorf("unable to listen on any of the requested ports: %v", pf.ports)
}
if pf.Ready != nil {
@@ -277,7 +277,7 @@ func (pf *PortForwarder) listenOnPortAndAddress(port *ForwardedPort, protocol st
func (pf *PortForwarder) getListener(protocol string, hostname string, port *ForwardedPort) (net.Listener, error) {
listener, err := net.Listen(protocol, net.JoinHostPort(hostname, strconv.Itoa(int(port.Local))))
if err != nil {
return nil, fmt.Errorf("Unable to create listener: Error %s", err)
return nil, fmt.Errorf("unable to create listener: Error %s", err)
}
listenerAddress := listener.Addr().String()
host, localPort, _ := net.SplitHostPort(listenerAddress)
@@ -285,7 +285,7 @@ func (pf *PortForwarder) getListener(protocol string, hostname string, port *For
if err != nil {
fmt.Fprintf(pf.out, "Failed to forward from %s:%d -> %d\n", hostname, localPortUInt, port.Remote)
return nil, fmt.Errorf("Error parsing local port: %s from %s (%s)", err, listenerAddress, host)
return nil, fmt.Errorf("error parsing local port: %s from %s (%s)", err, listenerAddress, host)
}
port.Local = uint16(localPortUInt)
if pf.out != nil {
@@ -303,7 +303,7 @@ func (pf *PortForwarder) waitForConnection(listener net.Listener, port Forwarded
if err != nil {
// TODO consider using something like https://github.com/hydrogen18/stoppableListener?
if !strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") {
runtime.HandleError(fmt.Errorf("Error accepting connection on port %d: %v", port.Local, err))
runtime.HandleError(fmt.Errorf("error accepting connection on port %d: %v", port.Local, err))
}
return
}
@@ -401,6 +401,7 @@ func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) {
}
}
// Close stops all listeners of PortForwarder.
func (pf *PortForwarder) Close() {
// stop all listeners
for _, l := range pf.listeners {

View File

@@ -162,17 +162,14 @@ type eventBroadcasterImpl struct {
// The return value can be ignored or used to stop recording, if desired.
// TODO: make me an object with parameterizable queue length and retry interval
func (eventBroadcaster *eventBroadcasterImpl) StartRecordingToSink(sink EventSink) watch.Interface {
// The default math/rand package functions aren't thread safe, so create a
// new Rand object for each StartRecording call.
randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
eventCorrelator := NewEventCorrelatorWithOptions(eventBroadcaster.options)
return eventBroadcaster.StartEventWatcher(
func(event *v1.Event) {
recordToSink(sink, event, eventCorrelator, randGen, eventBroadcaster.sleepDuration)
recordToSink(sink, event, eventCorrelator, eventBroadcaster.sleepDuration)
})
}
func recordToSink(sink EventSink, event *v1.Event, eventCorrelator *EventCorrelator, randGen *rand.Rand, sleepDuration time.Duration) {
func recordToSink(sink EventSink, event *v1.Event, eventCorrelator *EventCorrelator, sleepDuration time.Duration) {
// Make a copy before modification, because there could be multiple listeners.
// Events are safe to copy like this.
eventCopy := *event
@@ -197,7 +194,7 @@ func recordToSink(sink EventSink, event *v1.Event, eventCorrelator *EventCorrela
// Randomize the first sleep so that various clients won't all be
// synced up if the master goes down.
if tries == 1 {
time.Sleep(time.Duration(float64(sleepDuration) * randGen.Float64()))
time.Sleep(time.Duration(float64(sleepDuration) * rand.Float64()))
} else {
time.Sleep(sleepDuration)
}

View File

@@ -19,7 +19,6 @@ package record
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strconv"
"testing"
@@ -417,7 +416,6 @@ func TestWriteEventError(t *testing.T) {
clock := clock.IntervalClock{Time: time.Now(), Duration: time.Second}
eventCorrelator := NewEventCorrelator(&clock)
randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
for caseName, ent := range table {
attempts := 0
@@ -431,7 +429,7 @@ func TestWriteEventError(t *testing.T) {
},
}
ev := &v1.Event{}
recordToSink(sink, ev, eventCorrelator, randGen, 0)
recordToSink(sink, ev, eventCorrelator, 0)
if attempts != ent.attemptsWanted {
t.Errorf("case %v: wanted %d, got %d attempts", caseName, ent.attemptsWanted, attempts)
}
@@ -441,7 +439,6 @@ func TestWriteEventError(t *testing.T) {
func TestUpdateExpiredEvent(t *testing.T) {
clock := clock.IntervalClock{Time: time.Now(), Duration: time.Second}
eventCorrelator := NewEventCorrelator(&clock)
randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
var createdEvent *v1.Event
@@ -462,7 +459,7 @@ func TestUpdateExpiredEvent(t *testing.T) {
ev := &v1.Event{}
ev.ResourceVersion = "updated-resource-version"
ev.Count = 2
recordToSink(sink, ev, eventCorrelator, randGen, 0)
recordToSink(sink, ev, eventCorrelator, 0)
if createdEvent == nil {
t.Error("Event did not get created after patch failed")

29
tools/record/main_test.go Normal file
View File

@@ -0,0 +1,29 @@
/*
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 record
import (
"math/rand"
"os"
"testing"
"time"
)
func TestMain(m *testing.M) {
rand.Seed(time.Now().UnixNano())
os.Exit(m.Run())
}

View File

@@ -18,42 +18,86 @@ package watch
import (
"sync"
"sync/atomic"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
)
func newTicketer() *ticketer {
return &ticketer{
func newEventProcessor(out chan<- watch.Event) *eventProcessor {
return &eventProcessor{
out: out,
cond: sync.NewCond(&sync.Mutex{}),
done: make(chan struct{}),
}
}
type ticketer struct {
counter uint64
// eventProcessor buffers events and writes them to an out chan when a reader
// is waiting. Because of the requirement to buffer events, it synchronizes
// input with a condition, and synchronizes output with a channels. It needs to
// be able to yield while both waiting on an input condition and while blocked
// on writing to the output channel.
type eventProcessor struct {
out chan<- watch.Event
cond *sync.Cond
current uint64
cond *sync.Cond
buff []watch.Event
done chan struct{}
}
func (t *ticketer) GetTicket() uint64 {
// -1 to start from 0
return atomic.AddUint64(&t.counter, 1) - 1
func (e *eventProcessor) run() {
for {
batch := e.takeBatch()
e.writeBatch(batch)
if e.stopped() {
return
}
}
}
func (t *ticketer) WaitForTicket(ticket uint64, f func()) {
t.cond.L.Lock()
defer t.cond.L.Unlock()
for ticket != t.current {
t.cond.Wait()
func (e *eventProcessor) takeBatch() []watch.Event {
e.cond.L.Lock()
defer e.cond.L.Unlock()
for len(e.buff) == 0 && !e.stopped() {
e.cond.Wait()
}
f()
batch := e.buff
e.buff = nil
return batch
}
t.current++
t.cond.Broadcast()
func (e *eventProcessor) writeBatch(events []watch.Event) {
for _, event := range events {
select {
case e.out <- event:
case <-e.done:
return
}
}
}
func (e *eventProcessor) push(event watch.Event) {
e.cond.L.Lock()
defer e.cond.L.Unlock()
defer e.cond.Signal()
e.buff = append(e.buff, event)
}
func (e *eventProcessor) stopped() bool {
select {
case <-e.done:
return true
default:
return false
}
}
func (e *eventProcessor) stop() {
close(e.done)
e.cond.Signal()
}
// NewIndexerInformerWatcher will create an IndexerInformer and wrap it into watch.Interface
@@ -61,55 +105,44 @@ func (t *ticketer) WaitForTicket(ticket uint64, f func()) {
// it also returns a channel you can use to wait for the informers to fully shutdown.
func NewIndexerInformerWatcher(lw cache.ListerWatcher, objType runtime.Object) (cache.Indexer, cache.Controller, watch.Interface, <-chan struct{}) {
ch := make(chan watch.Event)
doneCh := make(chan struct{})
w := watch.NewProxyWatcher(ch)
t := newTicketer()
e := newEventProcessor(ch)
indexer, informer := cache.NewIndexerInformer(lw, objType, 0, cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
go t.WaitForTicket(t.GetTicket(), func() {
select {
case ch <- watch.Event{
Type: watch.Added,
Object: obj.(runtime.Object),
}:
case <-w.StopChan():
}
e.push(watch.Event{
Type: watch.Added,
Object: obj.(runtime.Object),
})
},
UpdateFunc: func(old, new interface{}) {
go t.WaitForTicket(t.GetTicket(), func() {
select {
case ch <- watch.Event{
Type: watch.Modified,
Object: new.(runtime.Object),
}:
case <-w.StopChan():
}
e.push(watch.Event{
Type: watch.Modified,
Object: new.(runtime.Object),
})
},
DeleteFunc: func(obj interface{}) {
go t.WaitForTicket(t.GetTicket(), func() {
staleObj, stale := obj.(cache.DeletedFinalStateUnknown)
if stale {
// We have no means of passing the additional information down using watch API based on watch.Event
// but the caller can filter such objects by checking if metadata.deletionTimestamp is set
obj = staleObj
}
staleObj, stale := obj.(cache.DeletedFinalStateUnknown)
if stale {
// We have no means of passing the additional information down using
// watch API based on watch.Event but the caller can filter such
// objects by checking if metadata.deletionTimestamp is set
obj = staleObj
}
select {
case ch <- watch.Event{
Type: watch.Deleted,
Object: obj.(runtime.Object),
}:
case <-w.StopChan():
}
e.push(watch.Event{
Type: watch.Deleted,
Object: obj.(runtime.Object),
})
},
}, cache.Indexers{})
go e.run()
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
defer e.stop()
informer.Run(w.StopChan())
}()

View File

@@ -17,8 +17,9 @@ limitations under the License.
package watch
import (
"math/rand"
"context"
"reflect"
goruntime "runtime"
"sort"
"testing"
"time"
@@ -28,6 +29,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/watch"
fakeclientset "k8s.io/client-go/kubernetes/fake"
@@ -35,6 +37,86 @@ import (
"k8s.io/client-go/tools/cache"
)
// TestEventProcessorExit is expected to timeout if the event processor fails
// to exit when stopped.
func TestEventProcessorExit(t *testing.T) {
event := watch.Event{}
tests := []struct {
name string
write func(e *eventProcessor)
}{
{
name: "exit on blocked read",
write: func(e *eventProcessor) {
e.push(event)
},
},
{
name: "exit on blocked write",
write: func(e *eventProcessor) {
e.push(event)
e.push(event)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
out := make(chan watch.Event)
e := newEventProcessor(out)
test.write(e)
exited := make(chan struct{})
go func() {
e.run()
close(exited)
}()
<-out
e.stop()
goruntime.Gosched()
<-exited
})
}
}
type apiInt int
func (apiInt) GetObjectKind() schema.ObjectKind { return nil }
func (apiInt) DeepCopyObject() runtime.Object { return nil }
func TestEventProcessorOrdersEvents(t *testing.T) {
out := make(chan watch.Event)
e := newEventProcessor(out)
go e.run()
numProcessed := 0
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
go func() {
for i := 0; i < 1000; i++ {
e := <-out
if got, want := int(e.Object.(apiInt)), i; got != want {
t.Errorf("unexpected event: got=%d, want=%d", got, want)
}
numProcessed++
}
cancel()
}()
for i := 0; i < 1000; i++ {
e.push(watch.Event{Object: apiInt(i)})
}
<-ctx.Done()
e.stop()
if numProcessed != 1000 {
t.Errorf("unexpected number of events processed: %d", numProcessed)
}
}
type byEventTypeAndName []watch.Event
func (a byEventTypeAndName) Len() int { return len(a) }
@@ -51,44 +133,6 @@ func (a byEventTypeAndName) Less(i, j int) bool {
return a[i].Object.(*corev1.Secret).Name < a[j].Object.(*corev1.Secret).Name
}
func TestTicketer(t *testing.T) {
tg := newTicketer()
const numTickets = 100 // current golang limit for race detector is 8192 simultaneously alive goroutines
var tickets []uint64
for i := 0; i < numTickets; i++ {
ticket := tg.GetTicket()
tickets = append(tickets, ticket)
exp, got := uint64(i), ticket
if got != exp {
t.Fatalf("expected ticket %d, got %d", exp, got)
}
}
// shuffle tickets
rand.Shuffle(len(tickets), func(i, j int) {
tickets[i], tickets[j] = tickets[j], tickets[i]
})
res := make(chan uint64, len(tickets))
for _, ticket := range tickets {
go func(ticket uint64) {
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
tg.WaitForTicket(ticket, func() {
res <- ticket
})
}(ticket)
}
for i := 0; i < numTickets; i++ {
exp, got := uint64(i), <-res
if got != exp {
t.Fatalf("expected ticket %d, got %d", exp, got)
}
}
}
func TestNewInformerWatcher(t *testing.T) {
// Make sure there are no 2 same types of events on a secret with the same name or that might be flaky.
tt := []struct {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package certificate
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
cryptorand "crypto/rand"
@@ -148,9 +149,13 @@ type CSRClientFunc func(current *tls.Certificate) (certificatesclient.Certificat
func (e *NoCertKeyError) Error() string { return string(*e) }
type manager struct {
getTemplate func() *x509.CertificateRequest
lastRequestLock sync.Mutex
lastRequest *x509.CertificateRequest
getTemplate func() *x509.CertificateRequest
// lastRequestLock guards lastRequestCancel and lastRequest
lastRequestLock sync.Mutex
lastRequestCancel context.CancelFunc
lastRequest *x509.CertificateRequest
dynamicTemplate bool
usages []certificates.KeyUsage
forceRotation bool
@@ -261,7 +266,8 @@ func (m *manager) Start() {
case <-timer.C:
// unblock when deadline expires
case <-templateChanged:
if reflect.DeepEqual(m.getLastRequest(), m.getTemplate()) {
_, lastRequestTemplate := m.getLastRequest()
if reflect.DeepEqual(lastRequestTemplate, m.getTemplate()) {
// if the template now matches what we last requested, restart the rotation deadline loop
return
}
@@ -289,10 +295,19 @@ func (m *manager) Start() {
if m.dynamicTemplate {
go wait.Until(func() {
// check if the current template matches what we last requested
if !m.certSatisfiesTemplate() && !reflect.DeepEqual(m.getLastRequest(), m.getTemplate()) {
lastRequestCancel, lastRequestTemplate := m.getLastRequest()
if !m.certSatisfiesTemplate() && !reflect.DeepEqual(lastRequestTemplate, m.getTemplate()) {
// if the template is different, queue up an interrupt of the rotation deadline loop.
// if we've requested a CSR that matches the new template by the time the interrupt is handled, the interrupt is disregarded.
templateChanged <- struct{}{}
if lastRequestCancel != nil {
// if we're currently waiting on a submitted request that no longer matches what we want, stop waiting
lastRequestCancel()
}
select {
case templateChanged <- struct{}{}:
case <-m.stopCh:
}
}
}, time.Second, m.stopCh)
}
@@ -386,14 +401,17 @@ func (m *manager) rotateCerts() (bool, error) {
return false, m.updateServerError(err)
}
ctx, cancel := context.WithTimeout(context.Background(), certificateWaitTimeout)
defer cancel()
// Once we've successfully submitted a CSR for this template, record that we did so
m.setLastRequest(template)
m.setLastRequest(cancel, template)
// Wait for the certificate to be signed. This interface and internal timout
// is a remainder after the old design using raw watch wrapped with backoff.
crtPEM, err := csr.WaitForCertificate(client, req, certificateWaitTimeout)
crtPEM, err := csr.WaitForCertificate(ctx, client, req)
if err != nil {
utilruntime.HandleError(fmt.Errorf("Certificate request was not signed: %v", err))
utilruntime.HandleError(fmt.Errorf("certificate request was not signed: %v", err))
return false, nil
}
@@ -561,14 +579,15 @@ func (m *manager) generateCSR() (template *x509.CertificateRequest, csrPEM []byt
return template, csrPEM, keyPEM, privateKey, nil
}
func (m *manager) getLastRequest() *x509.CertificateRequest {
func (m *manager) getLastRequest() (context.CancelFunc, *x509.CertificateRequest) {
m.lastRequestLock.Lock()
defer m.lastRequestLock.Unlock()
return m.lastRequest
return m.lastRequestCancel, m.lastRequest
}
func (m *manager) setLastRequest(r *x509.CertificateRequest) {
func (m *manager) setLastRequest(cancel context.CancelFunc, r *x509.CertificateRequest) {
m.lastRequestLock.Lock()
defer m.lastRequestLock.Unlock()
m.lastRequestCancel = cancel
m.lastRequest = r
}

View File

@@ -981,7 +981,7 @@ func (c fakeClient) Create(*certificates.CertificateSigningRequest) (*certificat
if c.err != nil {
return nil, c.err
}
return nil, fmt.Errorf("Create error")
return nil, fmt.Errorf("create error")
}
csrReply := certificates.CertificateSigningRequest{}
csrReply.UID = "fake-uid"
@@ -993,7 +993,7 @@ func (c fakeClient) Watch(opts v1.ListOptions) (watch.Interface, error) {
if c.err != nil {
return nil, c.err
}
return nil, fmt.Errorf("Watch error")
return nil, fmt.Errorf("watch error")
}
return &fakeWatch{
failureType: c.failureType,

View File

@@ -82,7 +82,7 @@ func RequestCertificate(client certificatesclient.CertificateSigningRequestInter
}
// WaitForCertificate waits for a certificate to be issued until timeout, or returns an error.
func WaitForCertificate(client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest, timeout time.Duration) (certData []byte, err error) {
func WaitForCertificate(ctx context.Context, client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest) (certData []byte, err error) {
fieldSelector := fields.OneTermEqualSelector("metadata.name", req.Name).String()
lw := &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
@@ -94,8 +94,6 @@ func WaitForCertificate(client certificatesclient.CertificateSigningRequestInter
return client.Watch(options)
},
}
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
defer cancel()
event, err := watchtools.UntilWithSync(
ctx,
lw,

View File

@@ -30,7 +30,7 @@ type backoffEntry struct {
}
type Backoff struct {
sync.Mutex
sync.RWMutex
Clock clock.Clock
defaultDuration time.Duration
maxDuration time.Duration
@@ -57,8 +57,8 @@ func NewBackOff(initial, max time.Duration) *Backoff {
// Get the current backoff Duration
func (p *Backoff) Get(id string) time.Duration {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
var delay time.Duration
entry, ok := p.perItemBackoff[id]
if ok {
@@ -90,8 +90,8 @@ func (p *Backoff) Reset(id string) {
// Returns True if the elapsed time since eventTime is smaller than the current backoff window
func (p *Backoff) IsInBackOffSince(id string, eventTime time.Time) bool {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
entry, ok := p.perItemBackoff[id]
if !ok {
return false
@@ -104,8 +104,8 @@ func (p *Backoff) IsInBackOffSince(id string, eventTime time.Time) bool {
// Returns True if time since lastupdate is less than the current backoff window.
func (p *Backoff) IsInBackOffSinceUpdate(id string, eventTime time.Time) bool {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
entry, ok := p.perItemBackoff[id]
if !ok {
return false

View File

@@ -17,6 +17,8 @@ limitations under the License.
package flowcontrol
import (
"context"
"errors"
"sync"
"time"
@@ -33,6 +35,8 @@ type RateLimiter interface {
Stop()
// QPS returns QPS of this rate limiter
QPS() float32
// Wait returns nil if a token is taken before the Context is done.
Wait(ctx context.Context) error
}
type tokenBucketRateLimiter struct {
@@ -98,6 +102,10 @@ func (t *tokenBucketRateLimiter) QPS() float32 {
return t.qps
}
func (t *tokenBucketRateLimiter) Wait(ctx context.Context) error {
return t.limiter.Wait(ctx)
}
type fakeAlwaysRateLimiter struct{}
func NewFakeAlwaysRateLimiter() RateLimiter {
@@ -116,6 +124,10 @@ func (t *fakeAlwaysRateLimiter) QPS() float32 {
return 1
}
func (t *fakeAlwaysRateLimiter) Wait(ctx context.Context) error {
return nil
}
type fakeNeverRateLimiter struct {
wg sync.WaitGroup
}
@@ -141,3 +153,7 @@ func (t *fakeNeverRateLimiter) Accept() {
func (t *fakeNeverRateLimiter) QPS() float32 {
return 1
}
func (t *fakeNeverRateLimiter) Wait(ctx context.Context) error {
return errors.New("can not be accept")
}

View File

@@ -17,6 +17,8 @@ limitations under the License.
package flowcontrol
import (
"context"
"fmt"
"sync"
"testing"
"time"
@@ -151,3 +153,21 @@ func TestNeverFake(t *testing.T) {
t.Error("Stop should make Accept unblock in NeverFake.")
}
}
func TestWait(t *testing.T) {
r := NewTokenBucketRateLimiter(0.0001, 1)
ctx, cancelFn := context.WithTimeout(context.Background(), time.Second)
defer cancelFn()
if err := r.Wait(ctx); err != nil {
t.Errorf("unexpected wait failed, err: %v", err)
}
ctx2, cancelFn2 := context.WithTimeout(context.Background(), time.Second)
defer cancelFn2()
if err := r.Wait(ctx2); err == nil {
t.Errorf("unexpected wait success")
} else {
t.Log(fmt.Sprintf("wait err: %v", err))
}
}

View File

@@ -18,30 +18,75 @@ package homedir
import (
"os"
"path/filepath"
"runtime"
)
// HomeDir returns the home directory for the current user
// HomeDir returns the home directory for the current user.
// On Windows:
// 1. the first of %HOME%, %HOMEDRIVE%%HOMEPATH%, %USERPROFILE% containing a `.kube\config` file is returned.
// 2. if none of those locations contain a `.kube\config` file, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists and is writeable is returned.
// 3. if none of those locations are writeable, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists is returned.
// 4. if none of those locations exists, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that is set is returned.
func HomeDir() string {
if runtime.GOOS == "windows" {
// First prefer the HOME environmental variable
if home := os.Getenv("HOME"); len(home) > 0 {
if _, err := os.Stat(home); err == nil {
return home
}
}
home := os.Getenv("HOME")
homeDriveHomePath := ""
if homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"); len(homeDrive) > 0 && len(homePath) > 0 {
homeDir := homeDrive + homePath
if _, err := os.Stat(homeDir); err == nil {
return homeDir
homeDriveHomePath = homeDrive + homePath
}
userProfile := os.Getenv("USERPROFILE")
// Return first of %HOME%, %HOMEDRIVE%/%HOMEPATH%, %USERPROFILE% that contains a `.kube\config` file.
// %HOMEDRIVE%/%HOMEPATH% is preferred over %USERPROFILE% for backwards-compatibility.
for _, p := range []string{home, homeDriveHomePath, userProfile} {
if len(p) == 0 {
continue
}
if _, err := os.Stat(filepath.Join(p, ".kube", "config")); err != nil {
continue
}
return p
}
firstSetPath := ""
firstExistingPath := ""
// Prefer %USERPROFILE% over %HOMEDRIVE%/%HOMEPATH% for compatibility with other auth-writing tools
for _, p := range []string{home, userProfile, homeDriveHomePath} {
if len(p) == 0 {
continue
}
if len(firstSetPath) == 0 {
// remember the first path that is set
firstSetPath = p
}
info, err := os.Stat(p)
if err != nil {
continue
}
if len(firstExistingPath) == 0 {
// remember the first path that exists
firstExistingPath = p
}
if info.IsDir() && info.Mode().Perm()&(1<<(uint(7))) != 0 {
// return first path that is writeable
return p
}
}
if userProfile := os.Getenv("USERPROFILE"); len(userProfile) > 0 {
if _, err := os.Stat(userProfile); err == nil {
return userProfile
}
// If none are writeable, return first location that exists
if len(firstExistingPath) > 0 {
return firstExistingPath
}
// If none exist, return first location that is set
if len(firstSetPath) > 0 {
return firstSetPath
}
// We've got nothing
return ""
}
return os.Getenv("HOME")
}

View File

@@ -42,12 +42,12 @@ var DefaultBackoff = wait.Backoff{
Jitter: 0.1,
}
// RetryConflict executes the provided function repeatedly, retrying if the server returns a conflicting
// write. Callers should preserve previous executions if they wish to retry changes. It performs an
// OnError executes the provided function repeatedly, retrying if the server returns a specified
// error. Callers should preserve previous executions if they wish to retry changes. It performs an
// exponential backoff.
//
// var pod *api.Pod
// err := RetryOnConflict(DefaultBackoff, func() (err error) {
// err := retry.OnError(DefaultBackoff, errors.IsConflict, func() (err error) {
// pod, err = c.Pods("mynamespace").UpdateStatus(podStatus)
// return
// })
@@ -58,14 +58,14 @@ var DefaultBackoff = wait.Backoff{
// ...
//
// TODO: Make Backoff an interface?
func RetryOnConflict(backoff wait.Backoff, fn func() error) error {
func OnError(backoff wait.Backoff, errorFunc func(error) bool, fn func() error) error {
var lastConflictErr error
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
err := fn()
switch {
case err == nil:
return true, nil
case errors.IsConflict(err):
case errorFunc(err):
lastConflictErr = err
return false, nil
default:
@@ -77,3 +77,8 @@ func RetryOnConflict(backoff wait.Backoff, fn func() error) error {
}
return err
}
// RetryOnConflict executes the function function repeatedly, retrying if the server returns a conflicting
func RetryOnConflict(backoff wait.Backoff, fn func() error) error {
return OnError(backoff, errors.IsConflict, fn)
}

View File

@@ -33,7 +33,7 @@ func MkTmpdir(prefix string) (string, error) {
return tmpDir, nil
}
// MkTmpdir does the same work as "MkTmpdir", except in case of
// MkTmpdirOrDie does the same work as "MkTmpdir", except in case of
// errors, it'll trigger a panic.
func MkTmpdirOrDie(prefix string) string {
tmpDir, err := MkTmpdir(prefix)

View File

@@ -18,6 +18,7 @@ package workqueue
import (
"container/heap"
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
@@ -43,13 +44,12 @@ func NewNamedDelayingQueue(name string) DelayingInterface {
func newDelayingQueue(clock clock.Clock, name string) DelayingInterface {
ret := &delayingType{
Interface: NewNamed(name),
clock: clock,
heartbeat: clock.NewTicker(maxWait),
stopCh: make(chan struct{}),
waitingForAddCh: make(chan *waitFor, 1000),
metrics: newRetryMetrics(name),
deprecatedMetrics: newDeprecatedRetryMetrics(name),
Interface: NewNamed(name),
clock: clock,
heartbeat: clock.NewTicker(maxWait),
stopCh: make(chan struct{}),
waitingForAddCh: make(chan *waitFor, 1000),
metrics: newRetryMetrics(name),
}
go ret.waitingLoop()
@@ -66,6 +66,8 @@ type delayingType struct {
// stopCh lets us signal a shutdown to the waiting loop
stopCh chan struct{}
// stopOnce guarantees we only signal shutdown a single time
stopOnce sync.Once
// heartbeat ensures we wait no more than maxWait before firing
heartbeat clock.Ticker
@@ -74,8 +76,7 @@ type delayingType struct {
waitingForAddCh chan *waitFor
// metrics counts the number of retries
metrics retryMetrics
deprecatedMetrics retryMetrics
metrics retryMetrics
}
// waitFor holds the data to add and the time it should be added
@@ -133,11 +134,14 @@ func (pq waitForPriorityQueue) Peek() interface{} {
return pq[0]
}
// ShutDown gives a way to shut off this queue
// ShutDown stops the queue. After the queue drains, the returned shutdown bool
// on Get() will be true. This method may be invoked more than once.
func (q *delayingType) ShutDown() {
q.Interface.ShutDown()
close(q.stopCh)
q.heartbeat.Stop()
q.stopOnce.Do(func() {
q.Interface.ShutDown()
close(q.stopCh)
q.heartbeat.Stop()
})
}
// AddAfter adds the given item to the work queue after the given delay
@@ -148,7 +152,6 @@ func (q *delayingType) AddAfter(item interface{}, duration time.Duration) {
}
q.metrics.retry()
q.deprecatedMetrics.retry()
// immediately add things with no delay
if duration <= 0 {

View File

@@ -216,15 +216,13 @@ func TestCopyShifting(t *testing.T) {
}
func BenchmarkDelayingQueue_AddAfter(b *testing.B) {
r := rand.New(rand.NewSource(time.Now().Unix()))
fakeClock := clock.NewFakeClock(time.Now())
q := newDelayingQueue(fakeClock, "")
// Add items
for n := 0; n < b.N; n++ {
data := fmt.Sprintf("%d", n)
q.AddAfter(data, time.Duration(r.Int63n(int64(10*time.Minute))))
q.AddAfter(data, time.Duration(rand.Int63n(int64(10*time.Minute))))
}
// Exercise item removal as well

View File

@@ -23,4 +23,4 @@ limitations under the License.
// * Multiple consumers and producers. In particular, it is allowed for an
// item to be reenqueued while it is being processed.
// * Shutdown notifications.
package workqueue
package workqueue // import "k8s.io/client-go/util/workqueue"

View File

@@ -0,0 +1,29 @@
/*
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 workqueue
import (
"math/rand"
"os"
"testing"
"time"
)
func TestMain(m *testing.M) {
rand.Seed(time.Now().UnixNano())
os.Exit(m.Run())
}

View File

@@ -87,14 +87,6 @@ type defaultQueueMetrics struct {
// how long have current threads been working?
unfinishedWorkSeconds SettableGaugeMetric
longestRunningProcessor SettableGaugeMetric
// TODO(danielqsj): Remove the following metrics, they are deprecated
deprecatedDepth GaugeMetric
deprecatedAdds CounterMetric
deprecatedLatency SummaryMetric
deprecatedWorkDuration SummaryMetric
deprecatedUnfinishedWorkSeconds SettableGaugeMetric
deprecatedLongestRunningProcessor SettableGaugeMetric
}
func (m *defaultQueueMetrics) add(item t) {
@@ -103,9 +95,7 @@ func (m *defaultQueueMetrics) add(item t) {
}
m.adds.Inc()
m.deprecatedAdds.Inc()
m.depth.Inc()
m.deprecatedDepth.Inc()
if _, exists := m.addTimes[item]; !exists {
m.addTimes[item] = m.clock.Now()
}
@@ -117,11 +107,9 @@ func (m *defaultQueueMetrics) get(item t) {
}
m.depth.Dec()
m.deprecatedDepth.Dec()
m.processingStartTimes[item] = m.clock.Now()
if startTime, exists := m.addTimes[item]; exists {
m.latency.Observe(m.sinceInSeconds(startTime))
m.deprecatedLatency.Observe(m.sinceInMicroseconds(startTime))
delete(m.addTimes, item)
}
}
@@ -133,7 +121,6 @@ func (m *defaultQueueMetrics) done(item t) {
if startTime, exists := m.processingStartTimes[item]; exists {
m.workDuration.Observe(m.sinceInSeconds(startTime))
m.deprecatedWorkDuration.Observe(m.sinceInMicroseconds(startTime))
delete(m.processingStartTimes, item)
}
}
@@ -153,9 +140,7 @@ func (m *defaultQueueMetrics) updateUnfinishedWork() {
// Convert to seconds; microseconds is unhelpfully granular for this.
total /= 1000000
m.unfinishedWorkSeconds.Set(total)
m.deprecatedUnfinishedWorkSeconds.Set(total)
m.longestRunningProcessor.Set(oldest / 1000000)
m.deprecatedLongestRunningProcessor.Set(oldest) // in microseconds.
}
type noMetrics struct{}
@@ -200,13 +185,6 @@ type MetricsProvider interface {
NewUnfinishedWorkSecondsMetric(name string) SettableGaugeMetric
NewLongestRunningProcessorSecondsMetric(name string) SettableGaugeMetric
NewRetriesMetric(name string) CounterMetric
NewDeprecatedDepthMetric(name string) GaugeMetric
NewDeprecatedAddsMetric(name string) CounterMetric
NewDeprecatedLatencyMetric(name string) SummaryMetric
NewDeprecatedWorkDurationMetric(name string) SummaryMetric
NewDeprecatedUnfinishedWorkSecondsMetric(name string) SettableGaugeMetric
NewDeprecatedLongestRunningProcessorMicrosecondsMetric(name string) SettableGaugeMetric
NewDeprecatedRetriesMetric(name string) CounterMetric
}
type noopMetricsProvider struct{}
@@ -239,34 +217,6 @@ func (_ noopMetricsProvider) NewRetriesMetric(name string) CounterMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedDepthMetric(name string) GaugeMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedAddsMetric(name string) CounterMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedLatencyMetric(name string) SummaryMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedWorkDurationMetric(name string) SummaryMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedUnfinishedWorkSecondsMetric(name string) SettableGaugeMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedLongestRunningProcessorMicrosecondsMetric(name string) SettableGaugeMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewDeprecatedRetriesMetric(name string) CounterMetric {
return noopMetric{}
}
var globalMetricsFactory = queueMetricsFactory{
metricsProvider: noopMetricsProvider{},
}
@@ -289,21 +239,15 @@ func (f *queueMetricsFactory) newQueueMetrics(name string, clock clock.Clock) qu
return noMetrics{}
}
return &defaultQueueMetrics{
clock: clock,
depth: mp.NewDepthMetric(name),
adds: mp.NewAddsMetric(name),
latency: mp.NewLatencyMetric(name),
workDuration: mp.NewWorkDurationMetric(name),
unfinishedWorkSeconds: mp.NewUnfinishedWorkSecondsMetric(name),
longestRunningProcessor: mp.NewLongestRunningProcessorSecondsMetric(name),
deprecatedDepth: mp.NewDeprecatedDepthMetric(name),
deprecatedAdds: mp.NewDeprecatedAddsMetric(name),
deprecatedLatency: mp.NewDeprecatedLatencyMetric(name),
deprecatedWorkDuration: mp.NewDeprecatedWorkDurationMetric(name),
deprecatedUnfinishedWorkSeconds: mp.NewDeprecatedUnfinishedWorkSecondsMetric(name),
deprecatedLongestRunningProcessor: mp.NewDeprecatedLongestRunningProcessorMicrosecondsMetric(name),
addTimes: map[t]time.Time{},
processingStartTimes: map[t]time.Time{},
clock: clock,
depth: mp.NewDepthMetric(name),
adds: mp.NewAddsMetric(name),
latency: mp.NewLatencyMetric(name),
workDuration: mp.NewWorkDurationMetric(name),
unfinishedWorkSeconds: mp.NewUnfinishedWorkSecondsMetric(name),
longestRunningProcessor: mp.NewLongestRunningProcessorSecondsMetric(name),
addTimes: map[t]time.Time{},
processingStartTimes: map[t]time.Time{},
}
}
@@ -317,16 +261,6 @@ func newRetryMetrics(name string) retryMetrics {
}
}
func newDeprecatedRetryMetrics(name string) retryMetrics {
var ret *defaultRetryMetrics
if len(name) == 0 {
return ret
}
return &defaultRetryMetrics{
retries: globalMetricsFactory.metricsProvider.NewDeprecatedRetriesMetric(name),
}
}
// SetProvider sets the metrics provider for all subsequently created work
// queues. Only the first call has an effect.
func SetProvider(metricsProvider MetricsProvider) {

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