Compare commits

...

89 Commits

Author SHA1 Message Date
Kubernetes Publisher
57716e046b Update dependencies to v0.26.0-alpha.1 tag 2022-09-20 22:44:07 +00:00
Kubernetes Publisher
f7c9c639d4 Merge pull request #112545 from dims/update-etcd-3.5.5-and-all-otel-related-to-latest
Update etcd 3.5.5 and all otel related libraries to newer versions

Kubernetes-commit: 6820a383be4a1bbb5cd526eb3aeb9e6ade351498
2022-09-20 02:44:03 +00:00
Kubernetes Publisher
b6e72dce28 Merge pull request #112226 from aojea/client_go_transport
client-go: test transport generation is goroutine safe

Kubernetes-commit: 5d45f766b5f1527cb54bbc9b625f90cc64b3b784
2022-09-19 18:47:00 +00:00
Kubernetes Publisher
acfaa39399 Merge pull request #112352 from pohly/e2e-ginkgo-progress
e2e: better ginkgo progress reports

Kubernetes-commit: 25b93a607a3edd28b0c720701ab7b6ebf1252148
2022-09-19 10:56:47 +00:00
Davanum Srinivas
6b5ecadcc5 updated etcd to v3.5.5 and newer otel libraries as well
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 5be80c05c423daf4bd2a4eb1cabf61fae9a03629
2022-09-17 14:04:16 -04:00
Kubernetes Publisher
037b5fd01d Merge pull request #112514 from markmc/patch-1
client-go: remove reference to TPR in examples

Kubernetes-commit: 86c46a4bcc2b5be6371422fd3130feb98c6697ab
2022-09-16 22:23:41 +00:00
Mark McLoughlin
3f66212017 client-go: remove reference to TPR in examples
5 years after third party resources were removed, we're probably just confusing readers with this reference.

Kubernetes-commit: d21fc6e762276747e3306cfaf27b0ce1201071c2
2022-09-16 15:14:13 +01:00
Kubernetes Publisher
86ffa32437 Merge pull request #112475 from vatsalparekh/fix-TestRESTClientLimiter
Fix Infelicities in TestRESTClientLimiter

Kubernetes-commit: 8b7840e393d8107647b1818e0a623018786f006f
2022-09-16 14:41:04 +00:00
Kubernetes Publisher
ece64627e4 Merge pull request #112476 from enj/enj/i/list_pager_flake
Check for context cancellation on each buffered chunk

Kubernetes-commit: be506dc46e4ba13c701bd8f228a4d1d1581873be
2022-09-16 14:41:03 +00:00
Kubernetes Publisher
58155b7df0 Merge pull request #112450 from enj/enj/i/exec_tls_cache_holder_cleanup
client-go/transport: drop Dial and GetCert fields in favor of Holders

Kubernetes-commit: 6824cd3addbb214e88e00f451babb8a44eb738d1
2022-09-16 14:41:02 +00:00
Kubernetes Publisher
eecd3e52a3 Merge pull request #112091 from xyz-li/master
kubectl: fix memory leaks in port forwarding client

Kubernetes-commit: 7a68c8a21a29787adf9e959271b8f955a68d3d82
2022-09-15 14:36:40 +00:00
Monis Khan
67030989c0 Check for context cancellation on each buffered chunk
TestListPager_EachListItem flakes without this change.

Signed-off-by: Monis Khan <mok@microsoft.com>

Kubernetes-commit: 7458ed01d6aa150b8a6046e0dbeba88d5d5ff1bb
2022-09-15 10:33:33 -04:00
Vatsal Parekh
bf2b395a89 Fix Infelicities in TestRESTClientLimiter
Signed-off-by: Vatsal Parekh <vatsalparekh@outlook.com>

Kubernetes-commit: c980cf489eeb39fd0fe0af7e80cfea2dbe2007db
2022-09-15 19:30:40 +05:30
Kubernetes Publisher
f6b8521807 Merge pull request #111333 from flant/selfsubjectattributesreviews
Add auth API to get self subject attributes

Kubernetes-commit: 4e8b11d4411cefbe1a32cf9e54810d9e0bd7378e
2022-09-14 22:43:31 +00:00
Patrick Ohly
ec6c80aa4d e2e: bump ginkgo to v2.2.0
The new release adds support for intermediate progress reports.

Kubernetes-commit: 1e4edaf2fe0a7bedd7e54725b489f62c20c92954
2022-09-09 15:47:46 +02:00
Monis Khan
5dab9a0b84 client-go/transport: drop Dial and GetCert fields in favor of Holders
Signed-off-by: Monis Khan <mok@microsoft.com>

Kubernetes-commit: 3313a70d5bcc40a39f99f482c18effc9de6072ba
2022-09-09 08:06:01 -04:00
Antonio Ojea
1bd914a925 client-go: test transport generation is goroutine safe
Kubernetes-commit: fe41181c3d470207772d3ac9a91d4ef2c2a6720d
2022-09-04 12:36:01 +02:00
LiHui
cc3cc93e6a kubectl: fix memory leaks in port forwarding client
Signed-off-by: LiHui <andrewli@kubesphere.io>

Kubernetes-commit: 1df24569a0bf62a528c49f73fdb236fd56eb05ee
2022-08-29 14:15:01 +08:00
m.nabokikh
b2b55e607d Add auth API to get self subject attributes
Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

Kubernetes-commit: 00dfba473b08528f0a21f7bd3b921f5dd35b013a
2022-07-22 04:01:52 +04:00
Kubernetes Publisher
18c3338d48 Merge pull request #112200 from pohly/client-go-shared-informer-factory-shutdown
client-go: support waiting for SharedInformerFactory shutdown

Kubernetes-commit: 084a412e03816ef3ea57da928ae8fd332d17ab59
2022-09-13 18:36:50 +00:00
Kubernetes Publisher
9dae6917fb Merge pull request #112309 from shyamjvs/disable-compression
Add a "DisableCompression" option to kubeconfig

Kubernetes-commit: ed520f3cac2243cd5f66967e2c590caf1e98b9e2
2022-09-12 18:36:27 +00:00
Kubernetes Publisher
ab826d2728 Merge pull request #112349 from pohly/klog-update
build: update to klog v2.80.1

Kubernetes-commit: b48b0eac6ad3556b343f54c26b49ed74cac276e3
2022-09-09 22:42:45 +00:00
Patrick Ohly
ab0bfda62e build: update to klog v2.80.1
The fix for https://github.com/kubernetes/klog/issues/348 is required before
https://github.com/kubernetes/kubernetes/pull/111998 can be merged because the
way how a unit test in that PR uses klog triggers the data race.

Kubernetes-commit: 60d92dd96a4ba3873dd9e061c3e332c16c213e30
2022-09-09 13:11:55 +02:00
Kubernetes Publisher
f32861cf25 Merge pull request #112341 from enj/enj/i/second_time_is_the_charm
Remove in-tree credential plugins (again)

Kubernetes-commit: d569886a234cb65cb1ec777b7ab2e5b9a35d7145
2022-09-09 06:35:35 +00:00
Monis Khan
7d208ba573 Remove in-tree credential plugins (again)
This change reverts three commits:

1. Revert "Revert "Remove gcp and azure auth plugins""
   This reverts commit 651b4f5b647a205d12fad4d0edc489d97109cccc.

2. Revert "update-gofmt"
   This reverts commit 79c09f0b310b8b66f8f14ad254f268788c42e076.

3. Revert "Update removal warnings to 1.26"
   This reverts commit 68758fc5c5099c83104a637e6d6ad3f4534f0d08.

Signed-off-by: Monis Khan <mok@microsoft.com>

Kubernetes-commit: 09cde58bbc13ff1229f61146221f56af4ff675dc
2022-09-08 19:11:03 -04:00
Kubernetes Publisher
e003fa92aa Merge pull request #112017 from enj/enj/i/exec_tls_cache
exec auth: support TLS config caching

Kubernetes-commit: 082da2f04e413932615bcc32cba83959e7d043f7
2022-09-09 02:35:35 +00:00
Shyam Jeedigunta
49ac40b489 Autogen code
Kubernetes-commit: f9c46a0e3361e19de93c02562fedfff7de448dc2
2022-09-07 18:05:42 -07:00
Shyam Jeedigunta
b8a8d94944 Add DisableCompression option to KubeConfig
Kubernetes-commit: 641fd5b482c6117d459a22d99f76aae086c3524f
2022-09-07 18:04:17 -07:00
Kubernetes Publisher
2698e8276e Merge pull request #111967 from alexzielenski/csa-to-ssa
[client-go] add function to upgrade managedfields CSA to SSA

Kubernetes-commit: 385cc255c77dd8c03bba6322946f7c97fdb33f53
2022-09-07 22:50:19 +00:00
Kubernetes Publisher
27c67e708a Merge pull request #111122 from alexzielenski/informer
support removal of event handlers from SharedIndexInformers

Kubernetes-commit: 2969000db3fae354b7b27bc79712d244248e640e
2022-09-07 22:50:16 +00:00
Kubernetes Publisher
d28c73639f Merge pull request #112022 from JackZxj/release-lock
update lock getter of leaderelection

Kubernetes-commit: 2b203348fbe7d05dec9e913e0fb50045a2474425
2022-09-07 18:39:46 +00:00
Alexander Zielenski
00d892f447 correct spacing
Kubernetes-commit: dbff8e117c8d4aeccac6ea211dfa334e30621727
2022-09-06 20:58:50 -07:00
Alexander Zielenski
2efbeaf56e add boilerplate
Kubernetes-commit: 3528fd7e47f585c144b13c0c2b75bc4e432987c6
2022-09-06 19:51:47 -07:00
Alexander Zielenski
dac0826f18 remove inaccurate comment
Kubernetes-commit: 52fcd20b578b67234a9ac84b5abf1cd2b3d06ea4
2022-09-02 17:42:08 -07:00
Alexander Zielenski
7634f2e002 make upgrade modify input instead of deep copying
Kubernetes-commit: adcb5ec3d43908a916fd1ae75af121568f26b80d
2022-09-02 17:40:34 -07:00
Alexander Zielenski
9aa7c11460 remove fieldsv1 from upgrade body
Kubernetes-commit: 5c9534b9cfdcfa9625153051f0859e101abb97a9
2022-09-02 17:38:29 -07:00
Alex Zielenski
703d15eff6 Update staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go
Co-authored-by: Antoine Pelisse <apelisse@gmail.com>

Kubernetes-commit: 5caffef694b50168f546d7758a26de832302b541
2022-09-02 17:34:07 -07:00
Kubernetes Publisher
b8b620636c Merge pull request #112199 from pohly/klog-update
dependencies: update to klog v2.80.0

Kubernetes-commit: bacd6029b3bac1dbc468b0cc2bc5770bef671af2
2022-09-02 18:36:24 +00:00
Patrick Ohly
5a2c3e9222 dependencies: update to klog v2.80.0
Contains one bug fix in the code path for formatting of objects that support
LogMarshal.

Kubernetes-commit: 1bc3bde128463cdc6d55d5d222f489e8eda5eeb7
2022-09-02 08:05:47 +02:00
Patrick Ohly
ec4fedd955 client-go: support waiting for SharedInformerFactory shutdown
SharedInformerFactory starts goroutines in Start and those can be stopped by
closing the stop channel. However, there was no API that waits for the
goroutines.

This is a problem for unit testing. A test has to return while the informers
are still running, which may get flagged by tools like
https://github.com/uber-go/goleak or by klog/ktesting when those informers
lead to log output.

While at it, more documentation gets added to address
https://github.com/kubernetes/kubernetes/issues/65036.

Kubernetes-commit: e89d1d47e8365cad31600b17dab662d3c8a359dd
2022-09-01 13:46:08 +02:00
Kubernetes Publisher
7ccf7b05af Merge pull request #112134 from apelisse/client-go-valid-segment
Validate segments with client-go/dynamic

Kubernetes-commit: b274815e200528c77b66f352e223cdaa987505f5
2022-09-01 02:35:55 +00:00
Kubernetes Publisher
d83ec9e727 Merge pull request #112129 from pohly/e2e-ginkgo-report-after-each
e2e: suppress too verbose output

Kubernetes-commit: d0e413e86d150e0d4638ce306ca3fd437fe0b763
2022-08-31 18:36:12 +00:00
Antoine Pelisse
a4b84d86ec Validate segments with client-go/dynamic
Kubernetes-commit: 7d64440dd1edf2acb8e71ecd6726904962c6bd02
2022-08-30 16:32:10 -07:00
Alexander Zielenski
675ca93180 refactor if statement
Kubernetes-commit: f7defeecce9c9e32d180deb2a2c67e063a10265f
2022-08-30 15:46:43 -07:00
Alexander Zielenski
a300ae0dfe return when test is done
Kubernetes-commit: cc0b9ffbd5a4edfe7ce01293c2bf963d4335d8d6
2022-08-30 14:38:21 -07:00
Alexander Zielenski
cfaca90a30 address comments
Kubernetes-commit: e8d8eb4381f96ec4923a801116050ee998de4887
2022-08-30 13:21:29 -07:00
Patrick Ohly
cac10a8427 dependencies: update to ginkgo v2.1.6 and gomega v1.20.1
Ginkgo v2.1.6 adds ginkgo.SuppressProgressReporting which is needed
to suppress too verbose output each time the ReportAfterEach of the custom
progress reporter is invoked.

Kubernetes-commit: 311144fb3e61dabdbcd48d164dc80716b5ba5a73
2022-08-30 19:33:50 +02:00
Alexander Zielenski
93e5e0e8a0 hold listener lock while waiting for goroutines to finish
Kubernetes-commit: 7ce19b75a8dbca12837ed9f4c5c2828c38b82a03
2022-08-29 12:34:35 -07:00
Alexander Zielenski
e11a988e1c simplify control flow
Kubernetes-commit: ee24648300f8575f503156f45b44034054c2e49d
2022-08-29 11:52:35 -07:00
jackzhang
d04c2ced1c update lock getter of leaderelection
The lock acquired by tryAcquireOrRenew is released when the leader ends
leadership. However, due to the cancellation of the context, the lock may
be set as an empty lock, so the Update cannot be run normally, resulting
in a failure to release the lock.

Signed-off-by: jackzhang <x_jackzhang@qq.com>

Kubernetes-commit: 8690ff6264cceb38bd81dec99bb8affcc40286a9
2022-08-29 09:58:13 +08:00
Kubernetes Publisher
46d4284b93 Merge pull request #111241 from Abirdcfly/fixtestorsource
make TestListPager_EachListItem rework

Kubernetes-commit: ce2c5875d83487eae67896b2ec099bbb9e40395f
2022-08-26 22:35:39 +00:00
Kubernetes Publisher
bdae576b94 Merge pull request #112068 from aojea/aojea_client_go
add aojea as client-go reviewer

Kubernetes-commit: 213d811fcb23d918ade553cbb74b174946768045
2022-08-26 11:30:26 -07:00
Antonio Ojea
fd22687940 add aojea as client-go reviewer
Kubernetes-commit: 7ddb0ef4642d62ac8bc5a58cf0370b14b8d44f01
2022-08-26 17:01:56 +02:00
Kubernetes Publisher
35ead05f62 Merge pull request #112052 from tosi3k/bump-client-golang
Bump prometheus/client_golang to v1.13.0

Kubernetes-commit: 8ccd03226f81573b91550f21c6b8d63f65e7c2f1
2022-08-26 10:35:28 +00:00
Antoni Zawodny
5291ca2900 Bump prometheus/client_golang to v1.13.0
Kubernetes-commit: f78e7a2b19fe275b7cb43c01099366c52545e4d1
2022-08-26 05:40:17 +02:00
Kubernetes Publisher
e6538dd42b Merge pull request #112024 from cndoit18/remove-redundant-judgment
style: remove redundant judgment

Kubernetes-commit: bc9f48b84187e6fd9ba5b828a449893d4569e4c6
2022-08-25 18:35:19 +00:00
Alexander Zielenski
089614c43e remove last applied configuration information
Kubernetes-commit: a7fe0f0283765e4970211f7227602e2caa4b3a57
2022-08-24 12:31:57 -07:00
Alexander Zielenski
efe378914a add more test cases
Kubernetes-commit: 42c960497a4737d8147d40d436a00be81ec4248d
2022-08-24 11:51:30 -07:00
Alexander Zielenski
46dc22f46a clean up test
Kubernetes-commit: 8f6d7f1fa13db3d7a3354a13f014e1ce7acabda0
2022-08-24 10:24:05 -07:00
Alexander Zielenski
ced85a8521 update godoc
Kubernetes-commit: f94ef92f2ac44fb07bc8d75d5f250b757b343c11
2022-08-24 10:12:41 -07:00
Alexander Zielenski
049ba69f2f expose FieldsToSet and SetToFields
Kubernetes-commit: a338fff435c35a4da2556a8f4c4ab059c98e909c
2022-08-24 10:03:16 -07:00
Monis Khan
6a008ec216 exec auth: support TLS config caching
This change updates the transport.Config .Dial and .TLS.GetCert fields
to use a struct wrapper.  This indirection via a pointer allows the
functions to be compared and thus makes them valid to use as map keys.
This change is then leveraged by the existing global exec auth and TLS
config caches to return the same authenticator and TLS config even when
distinct but identical rest configs were used to create distinct
clientsets.

Signed-off-by: Monis Khan <mok@microsoft.com>

Kubernetes-commit: 831d95b6a021c2767effe85e461309cb6a0fdcec
2022-08-24 16:04:19 +00:00
Alexander Zielenski
aa892ab1ac remove unused code
Kubernetes-commit: f495d7656ca79e2d5cae03b37977e75369352645
2022-08-23 16:17:27 -07:00
Alexander Zielenski
90ef078c30 dont expose internal methods in implementatoin
Kubernetes-commit: fe2b5d00f2c1f16636638acd10b8d640e6de22c9
2022-08-23 16:12:47 -07:00
Alexander Zielenski
ac7f6579ff fix spelling
Kubernetes-commit: 7685b393c2ee8f8548d62f9dd8485df764a0c8c3
2022-08-22 17:34:02 -07:00
Alexander Zielenski
c364b639fb add function to upgrade managedfields CSA to SSA
Kubernetes-commit: 27cd307e23df3a2d508d52ff10ac8f46bf3bcea3
2022-08-22 16:42:18 -07:00
Alexander Zielenski
0f4a6cf319 reset listenersStarted
for correctness. technically shouldnt be an issue since restarting a stopped processor is not supported

Kubernetes-commit: 3a81341cfa6f7e2ca1b9bfc195c567dcdfaa4dea
2022-08-08 14:19:37 -07:00
Alexander Zielenski
449817f7b5 add multithreaded test to shared informer
Kubernetes-commit: 8af0a31a15079725e5910d538a6ace03df2e382a
2022-08-08 13:39:18 -07:00
Alexander Zielenski
de0b7671e3 remove duplicate test
became a duplicate while refactoring original PR

Kubernetes-commit: df9a7afa63164035f1e94c3278d470b347b697b9
2022-08-08 11:46:05 -07:00
Alexander Zielenski
0565962dd0 address review comments
asd

Kubernetes-commit: 5f372e1867305679d8f9d8a013c5500763e5c875
2022-08-08 11:44:37 -07:00
cndoit18
bcd2e6c7da style: remove redundant judgment
Signed-off-by: cndoit18 <cndoit18@outlook.com>

Kubernetes-commit: ec43037d0f57fdfc2fdc4960fdb8a7e31ac79fae
2022-07-29 18:25:05 +08:00
Kubernetes Publisher
d5e58631fd Merge pull request #111752 from aanm/revert-final-url-template
Revert "client-go: remove no longer used finalURLTemplate"

Kubernetes-commit: dfd10d3133669d7c30b28edcf1024bafda1df766
2022-08-24 02:35:32 +00:00
Kubernetes Publisher
5feaced742 Merge pull request #67782 from dims/yank-in-tree-openstack-cloud-provider
Remove in-tree openstack cloud provider

Kubernetes-commit: 07bca2d7919c192435949c53f81acff58d6f39eb
2022-08-24 02:35:30 +00:00
Kubernetes Publisher
0fdc4f348a Merge pull request #111684 from 0xff-dev/master1
use constant NamespaceDefault instead of variable namespace

Kubernetes-commit: e7297321321a7cde918734402a8a999990045179
2022-08-24 02:35:29 +00:00
Kubernetes Publisher
4faffa8644 Merge pull request #111564 from inosato/remove-ioutil-from-cli-client-go
Remove ioutil from client-go

Kubernetes-commit: eb713b11867557c0bf829fc5cf919791db2a022b
2022-08-24 02:35:26 +00:00
Kubernetes Publisher
2e404084ad Merge pull request #111918 from liggitt/in-tree-auth
Restore in-tree credential plugins for 1.25

Kubernetes-commit: d581cc90adba6c84919738841fe3e07302d53e33
2022-08-18 22:38:37 +00:00
André Martins
e9d4627252 client-go/rest: check if url is nil to prevent nil pointer dereference
Signed-off-by: André Martins <aanm90@gmail.com>

Kubernetes-commit: 94e7b2b8fdb4028b8b9d10ce57b9b33df146beb7
2022-08-09 14:19:01 +02:00
André Martins
c501ee0eba Revert "client-go: remove no longer used finalURLTemplate"
The functionality provided by the finalURLTemplate is still used by
certain external projects to track the request latency for requests
performed to kube-apiserver.

Using a template of the URL, instead of the URL itself, prevents the
explosion of label cardinality in exposed metrics since it aggregates
the URLs in a way that common URLs requests are reported as being the
same.

This reverts commit bebf5a608f68523fc430a44f6db26b16022dc862.

Signed-off-by: André Martins <aanm90@gmail.com>

Kubernetes-commit: f8f190cdd2fa76296f8b1b019ac77128b5d40b79
2022-08-08 23:27:45 +02:00
Davanum Srinivas
98e81a7784 Remove references to openstack and cinder
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 9bbf01bae93600d3314bce0acbff1f3a0a42e74b
2022-08-08 16:01:59 -04:00
0xff-dev
c94a539bfb use constant NamespaceDefault instead of variable namespace
Kubernetes-commit: 4bdca5689f65597f28a99f3610dd4e43fbc9cd7c
2022-08-04 14:18:47 +08:00
inosato
27de641f75 Remove ioutil from client-go
Signed-off-by: inosato <si17_21@yahoo.co.jp>

Kubernetes-commit: 88dfa51b6003c90e8f0a0508939a1d79950a40df
2022-07-30 20:54:41 +09:00
Abirdcfly
9b300defa3 make TestListPager_EachListItem rework
Signed-off-by: Abirdcfly <fp544037857@gmail.com>

Kubernetes-commit: 5e84f6b6d7cd39b785eaca404c3f7e460f80f71b
2022-07-19 15:34:23 +08:00
Alexander Zielenski
5a25eb0de8 switch listeners to use a map, adapt tests
Kubernetes-commit: 063ef090e7fb5823ca18a10a83e8847eedac9599
2022-06-16 10:54:03 -07:00
Uwe Krueger
90c6a46b32 active remove/add tests for event handlers
Kubernetes-commit: e9cd17170b1646672796e713dee6c4fb0e6693bb
2021-10-06 12:40:46 +02:00
Uwe Krueger
de4dd3aaf3 tests for invalid registration removals
Kubernetes-commit: 152b6e11af4c8c50b768630bf7724e0bb3414e9c
2021-08-22 13:27:36 +02:00
Uwe Krueger
33eff64a05 apply desired changes for handler registration
Kubernetes-commit: 92f04baac98186673c684bba419087d6285807b1
2021-08-22 13:03:18 +02:00
Uwe Krueger
d73e40f207 rename handle to registration
Kubernetes-commit: 7054ac16d43a4a55d6e7b69943eb209f6495c4ce
2021-08-04 21:54:32 +02:00
Alexander Zielenski
b3a61c6731 remove informational informer methods again
Kubernetes-commit: f52f4a8e3045fd3eeba0315a347ed653012cd5c5
2022-06-16 00:09:20 -07:00
Alexander Zielenski
ecdc8bf729 support removal of event handlers from SharedIndexInformers
To be able to implement controllers that are dynamically deciding
on which resources to watch, it is required to get rid of
dedicated watches and event handlers again. This requires the
possibility to remove event handlers from SharedIndexInformers again.
Stopping an informer is not sufficient, because there might
be multiple controllers in a controller manager that independently
decide which resources to watch.

Unfortunately the ResourceEventHandler interface encourages to use
value objects for handlers (like the ResourceEventHandlerFuncs
struct, that uses value receivers to implement the interface).
Go does not support comparison of function pointers and therefore
the comparison of such structs is not possible, also. To be able
to remove all kinds of handlers and to solve the problem of
multi-registrations of handlers a registration handle is introduced.
It is returned when adding a handler and can later be used to remove
the registration again. This handle directly stores the created
listener to simplify the deletion.

Kubernetes-commit: 7436af3302088c979b431856c432b95dd230f847
2022-06-16 00:07:54 -07:00
105 changed files with 3338 additions and 2758 deletions

1
OWNERS
View File

@@ -9,6 +9,7 @@ approvers:
- sttts
- yliaog
reviewers:
- aojea
- caesarxuchao
- deads2k
- jpbetz

View File

@@ -18,7 +18,7 @@ package disk
import (
"errors"
"io/ioutil"
"io"
"net/http"
"os"
"path/filepath"
@@ -157,7 +157,7 @@ func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
}
// the cache is present and its valid. Try to read and use it.
cachedBytes, err := ioutil.ReadAll(file)
cachedBytes, err := io.ReadAll(file)
if err != nil {
return nil, err
}
@@ -179,7 +179,7 @@ func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Obj
return err
}
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".")
f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".")
if err != nil {
return err
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package disk
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -42,7 +41,7 @@ import (
func TestCachedDiscoveryClient_Fresh(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
d, err := os.MkdirTemp("", "")
assert.NoError(err)
defer os.RemoveAll(d)
@@ -86,7 +85,7 @@ func TestCachedDiscoveryClient_Fresh(t *testing.T) {
func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
d, err := os.MkdirTemp("", "")
assert.NoError(err)
defer os.RemoveAll(d)
@@ -104,7 +103,7 @@ func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
d, err := os.MkdirTemp("", "")
assert.NoError(err)
os.RemoveAll(d)
defer os.RemoveAll(d)
@@ -131,13 +130,13 @@ func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
// successive calls
func TestOpenAPIDiskCache(t *testing.T) {
// Create discovery cache dir (unused)
discoCache, err := ioutil.TempDir("", "")
discoCache, err := os.MkdirTemp("", "")
require.NoError(t, err)
os.RemoveAll(discoCache)
defer os.RemoveAll(discoCache)
// Create http cache dir
httpCache, err := ioutil.TempDir("", "")
httpCache, err := os.MkdirTemp("", "")
require.NoError(t, err)
os.RemoveAll(httpCache)
defer os.RemoveAll(httpCache)

View File

@@ -19,7 +19,7 @@ package disk
import (
"bytes"
"crypto/sha256"
"io/ioutil"
"io"
"net/http"
"net/url"
"os"
@@ -43,7 +43,7 @@ func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
}
func BenchmarkDiskCache(b *testing.B) {
cacheDir, err := ioutil.TempDir("", "cache-rt")
cacheDir, err := os.MkdirTemp("", "cache-rt")
if err != nil {
b.Fatal(err)
}
@@ -57,7 +57,7 @@ func BenchmarkDiskCache(b *testing.B) {
})
k := "localhost:8080/apis/batch/v1.json"
v, err := ioutil.ReadFile("../../testdata/apis/batch/v1.json")
v, err := os.ReadFile("../../testdata/apis/batch/v1.json")
if err != nil {
b.Fatal(err)
}
@@ -73,7 +73,7 @@ func BenchmarkDiskCache(b *testing.B) {
func TestCacheRoundTripper(t *testing.T) {
rt := &testRoundTripper{}
cacheDir, err := ioutil.TempDir("", "cache-rt")
cacheDir, err := os.MkdirTemp("", "cache-rt")
defer os.RemoveAll(cacheDir)
if err != nil {
t.Fatal(err)
@@ -87,14 +87,14 @@ func TestCacheRoundTripper(t *testing.T) {
}
rt.Response = &http.Response{
Header: http.Header{"ETag": []string{`"123456"`}},
Body: ioutil.NopCloser(bytes.NewReader([]byte("Content"))),
Body: io.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)
content, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
@@ -109,7 +109,7 @@ func TestCacheRoundTripper(t *testing.T) {
}
rt.Response = &http.Response{
StatusCode: http.StatusNotModified,
Body: ioutil.NopCloser(bytes.NewReader([]byte("Other Content"))),
Body: io.NopCloser(bytes.NewReader([]byte("Other Content"))),
}
resp, err = cache.RoundTrip(req)
@@ -118,7 +118,7 @@ func TestCacheRoundTripper(t *testing.T) {
}
// Read body and make sure we have the initial content
content, err = ioutil.ReadAll(resp.Body)
content, err = io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatal(err)
@@ -132,7 +132,7 @@ func TestCacheRoundTripperPathPerm(t *testing.T) {
assert := assert.New(t)
rt := &testRoundTripper{}
cacheDir, err := ioutil.TempDir("", "cache-rt")
cacheDir, err := os.MkdirTemp("", "cache-rt")
os.RemoveAll(cacheDir)
defer os.RemoveAll(cacheDir)
@@ -148,14 +148,14 @@ func TestCacheRoundTripperPathPerm(t *testing.T) {
}
rt.Response = &http.Response{
Header: http.Header{"ETag": []string{`"123456"`}},
Body: ioutil.NopCloser(bytes.NewReader([]byte("Content"))),
Body: io.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)
content, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
@@ -182,7 +182,7 @@ func TestSumDiskCache(t *testing.T) {
// Ensure that we'll return a cache miss if the backing file doesn't exist.
t.Run("NoSuchKey", func(t *testing.T) {
cacheDir, err := ioutil.TempDir("", "cache-test")
cacheDir, err := os.MkdirTemp("", "cache-test")
if err != nil {
t.Fatal(err)
}
@@ -199,7 +199,7 @@ func TestSumDiskCache(t *testing.T) {
// Ensure that we'll return a cache miss if the backing file is empty.
t.Run("EmptyFile", func(t *testing.T) {
cacheDir, err := ioutil.TempDir("", "cache-test")
cacheDir, err := os.MkdirTemp("", "cache-test")
if err != nil {
t.Fatal(err)
}
@@ -223,7 +223,7 @@ func TestSumDiskCache(t *testing.T) {
// Ensure that we'll return a cache miss if the backing has an invalid
// checksum.
t.Run("InvalidChecksum", func(t *testing.T) {
cacheDir, err := ioutil.TempDir("", "cache-test")
cacheDir, err := os.MkdirTemp("", "cache-test")
if err != nil {
t.Fatal(err)
}
@@ -258,7 +258,7 @@ func TestSumDiskCache(t *testing.T) {
// This should cause httpcache to fall back to its underlying transport and
// to subsequently cache the new value, overwriting the corrupt one.
t.Run("OverwriteExistingKey", func(t *testing.T) {
cacheDir, err := ioutil.TempDir("", "cache-test")
cacheDir, err := os.MkdirTemp("", "cache-test")
if err != nil {
t.Fatal(err)
}
@@ -290,7 +290,7 @@ func TestSumDiskCache(t *testing.T) {
// Ensure that deleting a key does in fact delete it.
t.Run("DeleteKey", func(t *testing.T) {
cacheDir, err := ioutil.TempDir("", "cache-test")
cacheDir, err := os.MkdirTemp("", "cache-test")
if err != nil {
t.Fatal(err)
}

View File

@@ -21,7 +21,6 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
@@ -41,7 +40,7 @@ func objBody(object interface{}) io.ReadCloser {
if err != nil {
panic(err)
}
return ioutil.NopCloser(bytes.NewReader([]byte(output)))
return io.NopCloser(bytes.NewReader([]byte(output)))
}
func TestServerSupportsVersion(t *testing.T) {

View File

@@ -20,10 +20,11 @@ import (
"bytes"
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -405,7 +406,7 @@ func TestCreate(t *testing.T) {
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
data, err := ioutil.ReadAll(r.Body)
data, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err)
w.WriteHeader(http.StatusInternalServerError)
@@ -487,7 +488,7 @@ func TestUpdate(t *testing.T) {
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
data, err := ioutil.ReadAll(r.Body)
data, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err)
w.WriteHeader(http.StatusInternalServerError)
@@ -645,7 +646,7 @@ func TestPatch(t *testing.T) {
t.Errorf("Patch(%q) got Content-Type %s. wanted %s", tc.name, content, types.StrategicMergePatchType)
}
data, err := ioutil.ReadAll(r.Body)
data, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("Patch(%q) unexpected error reading body: %v", tc.name, err)
w.WriteHeader(http.StatusInternalServerError)
@@ -672,3 +673,107 @@ func TestPatch(t *testing.T) {
}
}
}
func TestInvalidSegments(t *testing.T) {
name := "bad/name"
namespace := "bad/namespace"
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
obj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "vtest",
"kind": "vkind",
"metadata": map[string]interface{}{
"name": name,
},
},
}
cl, err := NewForConfig(&restclient.Config{
Host: "127.0.0.1",
})
if err != nil {
t.Fatalf("Failed to create config: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Update(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Update(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
err = cl.Resource(resource).Delete(context.TODO(), name, metav1.DeleteOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
err = cl.Resource(resource).Namespace(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
err = cl.Resource(resource).Namespace(namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Watch(context.TODO(), metav1.ListOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Patch(context.TODO(), name, types.StrategicMergePatchType, []byte("{}"), metav1.PatchOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, []byte("{}"), metav1.PatchOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Apply(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Apply(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).ApplyStatus(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).ApplyStatus(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
}

View File

@@ -120,6 +120,9 @@ func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Un
return nil, fmt.Errorf("name is required")
}
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
result := c.client.client.
Post().
@@ -152,6 +155,9 @@ func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Un
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
@@ -188,7 +194,9 @@ func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructu
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
@@ -220,6 +228,9 @@ func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts me
if len(name) == 0 {
return fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return err
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
if err != nil {
return err
@@ -235,6 +246,10 @@ func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts me
}
func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return err
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
if err != nil {
return err
@@ -254,6 +269,9 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
if err := result.Error(); err != nil {
return nil, err
@@ -270,6 +288,9 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
}
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return nil, err
}
result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
if err := result.Error(); err != nil {
return nil, err
@@ -295,6 +316,9 @@ func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOption
func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
opts.Watch = true
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return nil, err
}
return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Watch(ctx)
@@ -304,6 +328,9 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
result := c.client.client.
Patch(pt).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
@@ -328,6 +355,9 @@ func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *uns
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
@@ -366,6 +396,20 @@ func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, ob
return c.Apply(ctx, name, obj, opts, "status")
}
func validateNamespaceWithOptionalName(namespace string, name ...string) error {
if msgs := rest.IsValidPathSegmentName(namespace); len(msgs) != 0 {
return fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
}
if len(name) > 1 {
panic("Invalid number of names")
} else if len(name) == 1 {
if msgs := rest.IsValidPathSegmentName(name[0]); len(msgs) != 0 {
return fmt.Errorf("invalid resource name %q: %v", name[0], msgs)
}
}
return nil
}
func (c *dynamicResourceClient) makeURLSegments(name string) []string {
url := []string{}
if len(c.resource.Group) == 0 {

View File

@@ -19,7 +19,6 @@ Or you can load specific auth plugins:
import _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
import _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
import _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
import _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
```
### Configuration
@@ -38,7 +37,7 @@ import _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
- [**Work queues**](./workqueue): Create a hotloop-free controller with the
rate-limited workqueue and the [informer framework][informer].
- [**Custom Resource Definition (successor of TPR)**](https://git.k8s.io/apiextensions-apiserver/examples/client-go):
- [**Custom Resource Definition (CRD)**](https://git.k8s.io/apiextensions-apiserver/examples/client-go):
Register a custom resource type with the API, create/update/query this custom
type, and write a controller that drives the cluster state based on the changes to
the custom resources.

View File

@@ -40,7 +40,6 @@ import (
// _ "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() {

View File

@@ -41,7 +41,6 @@ import (
// _ "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() {
@@ -53,8 +52,6 @@ func main() {
}
flag.Parse()
namespace := "default"
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
@@ -109,7 +106,7 @@ func main() {
// Create Deployment
fmt.Println("Creating deployment...")
result, err := client.Resource(deploymentRes).Namespace(namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
result, err := client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}
@@ -134,7 +131,7 @@ func main() {
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(context.TODO(), "demo-deployment", metav1.GetOptions{})
result, getErr := client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).Get(context.TODO(), "demo-deployment", metav1.GetOptions{})
if getErr != nil {
panic(fmt.Errorf("failed to get latest version of Deployment: %v", getErr))
}
@@ -158,7 +155,7 @@ func main() {
panic(err)
}
_, updateErr := client.Resource(deploymentRes).Namespace(namespace).Update(context.TODO(), result, metav1.UpdateOptions{})
_, updateErr := client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).Update(context.TODO(), result, metav1.UpdateOptions{})
return updateErr
})
if retryErr != nil {
@@ -169,7 +166,7 @@ func main() {
// List Deployments
prompt()
fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
list, err := client.Resource(deploymentRes).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
list, err := client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err)
}
@@ -189,7 +186,7 @@ func main() {
deleteOptions := metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
if err := client.Resource(deploymentRes).Namespace(namespace).Delete(context.TODO(), "demo-deployment", deleteOptions); err != nil {
if err := client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).Delete(context.TODO(), "demo-deployment", deleteOptions); err != nil {
panic(err)
}

View File

@@ -34,7 +34,6 @@ import (
// _ "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() {

View File

@@ -37,7 +37,6 @@ import (
// _ "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() {

30
go.mod
View File

@@ -5,30 +5,28 @@ module k8s.io/client-go
go 1.19
require (
github.com/Azure/go-autorest/autorest v0.11.27
github.com/Azure/go-autorest/autorest/adal v0.9.20
github.com/davecgh/go-spew v1.1.1
github.com/evanphx/json-patch v4.12.0+incompatible
github.com/gogo/protobuf v1.3.2
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/golang/protobuf v1.5.2
github.com/google/gnostic v0.5.7-v3refs
github.com/google/go-cmp v0.5.6
github.com/google/go-cmp v0.5.9
github.com/google/gofuzz v1.1.0
github.com/google/uuid v1.1.2
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
github.com/imdario/mergo v0.3.6
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.0
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
google.golang.org/protobuf v1.28.0
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/klog/v2 v2.70.1
google.golang.org/protobuf v1.28.1
k8s.io/api v0.26.0-alpha.1
k8s.io/apimachinery v0.26.0-alpha.1
k8s.io/klog/v2 v2.80.1
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
sigs.k8s.io/structured-merge-diff/v4 v4.2.3
@@ -36,19 +34,13 @@ require (
)
require (
cloud.google.com/go v0.97.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@@ -59,7 +51,6 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
@@ -70,7 +61,6 @@ require (
)
replace (
k8s.io/api => ../api
k8s.io/apimachinery => ../apimachinery
k8s.io/client-go => ../client-go
k8s.io/api => k8s.io/api v0.26.0-alpha.1
k8s.io/apimachinery => k8s.io/apimachinery v0.26.0-alpha.1
)

224
go.sum
View File

@@ -13,19 +13,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -44,60 +31,33 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw=
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -115,9 +75,6 @@ github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5F
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -131,8 +88,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -146,12 +101,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
@@ -165,19 +117,14 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -185,27 +132,17 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -237,8 +174,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -246,42 +183,34 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -304,8 +233,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@@ -314,9 +241,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -345,15 +269,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -361,18 +277,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -382,8 +288,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -409,30 +313,11 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -440,9 +325,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -491,22 +373,10 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -524,18 +394,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -567,39 +425,13 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -612,20 +444,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -638,9 +456,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -650,7 +467,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -665,9 +481,13 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.26.0-alpha.1 h1:0uaX04eLS9dwIcuWgRvu+WoB63hXFdc9s8fSeR6C1PI=
k8s.io/api v0.26.0-alpha.1/go.mod h1:snuTxVDYyZ0s0Ftc/3Cvl8jZ9zLPDn7PQliEqn93Rrs=
k8s.io/apimachinery v0.26.0-alpha.1 h1:9ZD9i3tISdlxc18MpS3SGc3Hlsyqtkf8/FeRucTfUTI=
k8s.io/apimachinery v0.26.0-alpha.1/go.mod h1:YxcSfgHt+jqvurbA0MLOvpo1OlrnkzW3sZTxMu+hrgI=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA=
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=

View File

@@ -64,6 +64,11 @@ type sharedInformerFactory struct {
// startedInformers is used for tracking which informers have been started.
// This allows Start() to be called multiple times safely.
startedInformers map[reflect.Type]bool
// wg tracks how many goroutines were started.
wg sync.WaitGroup
// shuttingDown is true when Shutdown has been called. It may still be running
// because it needs to wait for goroutines.
shuttingDown bool
}
// WithCustomResyncConfig sets a custom resync period for the specified informer types.
@@ -124,20 +129,39 @@ func NewSharedInformerFactoryWithOptions(client kubernetes.Interface, defaultRes
return factory
}
// Start initializes all requested informers.
func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
f.lock.Lock()
defer f.lock.Unlock()
if f.shuttingDown {
return
}
for informerType, informer := range f.informers {
if !f.startedInformers[informerType] {
go informer.Run(stopCh)
f.wg.Add(1)
// We need a new variable in each loop iteration,
// otherwise the goroutine would use the loop variable
// and that keeps changing.
informer := informer
go func() {
defer f.wg.Done()
informer.Run(stopCh)
}()
f.startedInformers[informerType] = true
}
}
}
// WaitForCacheSync waits for all started informers' cache were synced.
func (f *sharedInformerFactory) Shutdown() {
f.lock.Lock()
f.shuttingDown = true
f.lock.Unlock()
// Will return immediately if there is nothing to wait for.
f.wg.Wait()
}
func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
informers := func() map[reflect.Type]cache.SharedIndexInformer {
f.lock.Lock()
@@ -184,11 +208,58 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal
// SharedInformerFactory provides shared informers for resources in all known
// API group versions.
//
// It is typically used like this:
//
// ctx, cancel := context.Background()
// defer cancel()
// factory := NewSharedInformerFactory(client, resyncPeriod)
// defer factory.WaitForStop() // Returns immediately if nothing was started.
// genericInformer := factory.ForResource(resource)
// typedInformer := factory.SomeAPIGroup().V1().SomeType()
// factory.Start(ctx.Done()) // Start processing these informers.
// synced := factory.WaitForCacheSync(ctx.Done())
// for v, ok := range synced {
// if !ok {
// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v)
// return
// }
// }
//
// // Creating informers can also be created after Start, but then
// // Start must be called again:
// anotherGenericInformer := factory.ForResource(resource)
// factory.Start(ctx.Done())
type SharedInformerFactory interface {
internalinterfaces.SharedInformerFactory
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
// Start initializes all requested informers. They are handled in goroutines
// which run until the stop channel gets closed.
Start(stopCh <-chan struct{})
// Shutdown marks a factory as shutting down. At that point no new
// informers can be started anymore and Start will return without
// doing anything.
//
// In addition, Shutdown blocks until all goroutines have terminated. For that
// to happen, the close channel(s) that they were started with must be closed,
// either before Shutdown gets called or while it is waiting.
//
// Shutdown may be called multiple times, even concurrently. All such calls will
// block until all goroutines have terminated.
Shutdown()
// WaitForCacheSync blocks until all started informers' caches were synced
// or the stop channel gets closed.
WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
// ForResource gives generic access to a shared informer of the matching type.
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
// client.
InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
Admissionregistration() admissionregistration.Interface
Internal() apiserverinternal.Interface
Apps() apps.Interface

View File

@@ -30,6 +30,7 @@ import (
appsv1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1"
appsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2"
authenticationv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
authenticationv1alpha1 "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
authorizationv1beta1 "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
@@ -82,6 +83,7 @@ type Interface interface {
AppsV1beta1() appsv1beta1.AppsV1beta1Interface
AppsV1beta2() appsv1beta2.AppsV1beta2Interface
AuthenticationV1() authenticationv1.AuthenticationV1Interface
AuthenticationV1alpha1() authenticationv1alpha1.AuthenticationV1alpha1Interface
AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface
AuthorizationV1() authorizationv1.AuthorizationV1Interface
AuthorizationV1beta1() authorizationv1beta1.AuthorizationV1beta1Interface
@@ -134,6 +136,7 @@ type Clientset struct {
appsV1beta1 *appsv1beta1.AppsV1beta1Client
appsV1beta2 *appsv1beta2.AppsV1beta2Client
authenticationV1 *authenticationv1.AuthenticationV1Client
authenticationV1alpha1 *authenticationv1alpha1.AuthenticationV1alpha1Client
authenticationV1beta1 *authenticationv1beta1.AuthenticationV1beta1Client
authorizationV1 *authorizationv1.AuthorizationV1Client
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
@@ -210,6 +213,11 @@ func (c *Clientset) AuthenticationV1() authenticationv1.AuthenticationV1Interfac
return c.authenticationV1
}
// AuthenticationV1alpha1 retrieves the AuthenticationV1alpha1Client
func (c *Clientset) AuthenticationV1alpha1() authenticationv1alpha1.AuthenticationV1alpha1Interface {
return c.authenticationV1alpha1
}
// AuthenticationV1beta1 retrieves the AuthenticationV1beta1Client
func (c *Clientset) AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface {
return c.authenticationV1beta1
@@ -477,6 +485,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
if err != nil {
return nil, err
}
cs.authenticationV1alpha1, err = authenticationv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.authenticationV1beta1, err = authenticationv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
@@ -661,6 +673,7 @@ func New(c rest.Interface) *Clientset {
cs.appsV1beta1 = appsv1beta1.New(c)
cs.appsV1beta2 = appsv1beta2.New(c)
cs.authenticationV1 = authenticationv1.New(c)
cs.authenticationV1alpha1 = authenticationv1alpha1.New(c)
cs.authenticationV1beta1 = authenticationv1beta1.New(c)
cs.authorizationV1 = authorizationv1.New(c)
cs.authorizationV1beta1 = authorizationv1beta1.New(c)

View File

@@ -38,6 +38,8 @@ import (
fakeappsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2/fake"
authenticationv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
fakeauthenticationv1 "k8s.io/client-go/kubernetes/typed/authentication/v1/fake"
authenticationv1alpha1 "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1"
fakeauthenticationv1alpha1 "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1/fake"
authenticationv1beta1 "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
fakeauthenticationv1beta1 "k8s.io/client-go/kubernetes/typed/authentication/v1beta1/fake"
authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
@@ -204,6 +206,11 @@ func (c *Clientset) AuthenticationV1() authenticationv1.AuthenticationV1Interfac
return &fakeauthenticationv1.FakeAuthenticationV1{Fake: &c.Fake}
}
// AuthenticationV1alpha1 retrieves the AuthenticationV1alpha1Client
func (c *Clientset) AuthenticationV1alpha1() authenticationv1alpha1.AuthenticationV1alpha1Interface {
return &fakeauthenticationv1alpha1.FakeAuthenticationV1alpha1{Fake: &c.Fake}
}
// AuthenticationV1beta1 retrieves the AuthenticationV1beta1Client
func (c *Clientset) AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface {
return &fakeauthenticationv1beta1.FakeAuthenticationV1beta1{Fake: &c.Fake}

View File

@@ -26,6 +26,7 @@ import (
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
authorizationv1 "k8s.io/api/authorization/v1"
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
@@ -83,6 +84,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
appsv1beta1.AddToScheme,
appsv1beta2.AddToScheme,
authenticationv1.AddToScheme,
authenticationv1alpha1.AddToScheme,
authenticationv1beta1.AddToScheme,
authorizationv1.AddToScheme,
authorizationv1beta1.AddToScheme,

View File

@@ -26,6 +26,7 @@ import (
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
authenticationv1 "k8s.io/api/authentication/v1"
authenticationv1alpha1 "k8s.io/api/authentication/v1alpha1"
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
authorizationv1 "k8s.io/api/authorization/v1"
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
@@ -83,6 +84,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
appsv1beta1.AddToScheme,
appsv1beta2.AddToScheme,
authenticationv1.AddToScheme,
authenticationv1alpha1.AddToScheme,
authenticationv1beta1.AddToScheme,
authorizationv1.AddToScheme,
authorizationv1beta1.AddToScheme,

View File

@@ -0,0 +1,107 @@
/*
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 v1alpha1
import (
"net/http"
v1alpha1 "k8s.io/api/authentication/v1alpha1"
"k8s.io/client-go/kubernetes/scheme"
rest "k8s.io/client-go/rest"
)
type AuthenticationV1alpha1Interface interface {
RESTClient() rest.Interface
SelfSubjectReviewsGetter
}
// AuthenticationV1alpha1Client is used to interact with features provided by the authentication.k8s.io group.
type AuthenticationV1alpha1Client struct {
restClient rest.Interface
}
func (c *AuthenticationV1alpha1Client) SelfSubjectReviews() SelfSubjectReviewInterface {
return newSelfSubjectReviews(c)
}
// NewForConfig creates a new AuthenticationV1alpha1Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*AuthenticationV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new AuthenticationV1alpha1Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AuthenticationV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &AuthenticationV1alpha1Client{client}, nil
}
// NewForConfigOrDie creates a new AuthenticationV1alpha1Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *AuthenticationV1alpha1Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new AuthenticationV1alpha1Client for the given RESTClient.
func New(c rest.Interface) *AuthenticationV1alpha1Client {
return &AuthenticationV1alpha1Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha1.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 *AuthenticationV1alpha1Client) 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 v1alpha1

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,40 @@
/*
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 (
v1alpha1 "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeAuthenticationV1alpha1 struct {
*testing.Fake
}
func (c *FakeAuthenticationV1alpha1) SelfSubjectReviews() v1alpha1.SelfSubjectReviewInterface {
return &FakeSelfSubjectReviews{c}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeAuthenticationV1alpha1) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}

View File

@@ -0,0 +1,47 @@
/*
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 (
"context"
v1alpha1 "k8s.io/api/authentication/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
schema "k8s.io/apimachinery/pkg/runtime/schema"
testing "k8s.io/client-go/testing"
)
// FakeSelfSubjectReviews implements SelfSubjectReviewInterface
type FakeSelfSubjectReviews struct {
Fake *FakeAuthenticationV1alpha1
}
var selfsubjectreviewsResource = schema.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1alpha1", Resource: "selfsubjectreviews"}
var selfsubjectreviewsKind = schema.GroupVersionKind{Group: "authentication.k8s.io", Version: "v1alpha1", Kind: "SelfSubjectReview"}
// Create takes the representation of a selfSubjectReview and creates it. Returns the server's representation of the selfSubjectReview, and an error, if there is any.
func (c *FakeSelfSubjectReviews) Create(ctx context.Context, selfSubjectReview *v1alpha1.SelfSubjectReview, opts v1.CreateOptions) (result *v1alpha1.SelfSubjectReview, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(selfsubjectreviewsResource, selfSubjectReview), &v1alpha1.SelfSubjectReview{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.SelfSubjectReview), err
}

View File

@@ -0,0 +1,21 @@
/*
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 v1alpha1
type SelfSubjectReviewExpansion interface{}

View File

@@ -0,0 +1,64 @@
/*
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 v1alpha1
import (
"context"
v1alpha1 "k8s.io/api/authentication/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
scheme "k8s.io/client-go/kubernetes/scheme"
rest "k8s.io/client-go/rest"
)
// SelfSubjectReviewsGetter has a method to return a SelfSubjectReviewInterface.
// A group's client should implement this interface.
type SelfSubjectReviewsGetter interface {
SelfSubjectReviews() SelfSubjectReviewInterface
}
// SelfSubjectReviewInterface has methods to work with SelfSubjectReview resources.
type SelfSubjectReviewInterface interface {
Create(ctx context.Context, selfSubjectReview *v1alpha1.SelfSubjectReview, opts v1.CreateOptions) (*v1alpha1.SelfSubjectReview, error)
SelfSubjectReviewExpansion
}
// selfSubjectReviews implements SelfSubjectReviewInterface
type selfSubjectReviews struct {
client rest.Interface
}
// newSelfSubjectReviews returns a SelfSubjectReviews
func newSelfSubjectReviews(c *AuthenticationV1alpha1Client) *selfSubjectReviews {
return &selfSubjectReviews{
client: c.RESTClient(),
}
}
// Create takes the representation of a selfSubjectReview and creates it. Returns the server's representation of the selfSubjectReview, and an error, if there is any.
func (c *selfSubjectReviews) Create(ctx context.Context, selfSubjectReview *v1alpha1.SelfSubjectReview, opts v1.CreateOptions) (result *v1alpha1.SelfSubjectReview, err error) {
result = &v1alpha1.SelfSubjectReview{}
err = c.client.Post().
Resource("selfsubjectreviews").
VersionedParams(&opts, scheme.ParameterCodec).
Body(selfSubjectReview).
Do(ctx).
Into(result)
return
}

View File

@@ -19,7 +19,7 @@ package fake
import (
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
@@ -68,7 +68,7 @@ func (c *FakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Requ
Client: fakerest.CreateHTTPClient(func(request *http.Request) (*http.Response, error) {
resp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader("fake logs")),
Body: io.NopCloser(strings.NewReader("fake logs")),
}
return resp, nil
}),

View File

@@ -19,7 +19,7 @@ package kubernetes_test
import (
"bytes"
"context"
"io/ioutil"
"io"
"net/http"
"testing"
@@ -41,7 +41,7 @@ func TestListTimeout(t *testing.T) {
if req.URL.Query().Get("timeout") != "21s" {
t.Fatal(spew.Sdump(req.URL.Query()))
}
return &http.Response{StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(&bytes.Buffer{})}, nil
return &http.Response{StatusCode: http.StatusNotFound, Body: io.NopCloser(&bytes.Buffer{})}, nil
}),
}
clientConfig := &rest.Config{

View File

@@ -19,7 +19,7 @@ package metadata
import (
"context"
"encoding/json"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"reflect"
@@ -245,7 +245,7 @@ func TestClient(t *testing.T) {
t.Fatal(req.URL.String())
}
defer req.Body.Close()
buf, err := ioutil.ReadAll(req.Body)
buf, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
@@ -272,7 +272,7 @@ func TestClient(t *testing.T) {
t.Fatal(req.URL.String())
}
defer req.Body.Close()
buf, err := ioutil.ReadAll(req.Body)
buf, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}

View File

@@ -98,6 +98,11 @@ type Cluster struct {
// cluster.
// +optional
ProxyURL string
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
// +optional
DisableCompression bool
// Config holds additional config data that is specific to the exec
// plugin with regards to the cluster being authenticated to.
//

View File

@@ -96,6 +96,11 @@ type Cluster struct {
// cluster.
// +optional
ProxyURL string `json:"proxy-url,omitempty"`
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
// +optional
DisableCompression bool `json:"disable-compression,omitempty"`
// Config holds additional config data that is specific to the exec
// plugin with regards to the cluster being authenticated to.
//

View File

@@ -86,6 +86,7 @@ func autoConvert_v1_Cluster_To_clientauthentication_Cluster(in *Cluster, out *cl
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
out.ProxyURL = in.ProxyURL
out.DisableCompression = in.DisableCompression
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Config, &out.Config, s); err != nil {
return err
}
@@ -103,6 +104,7 @@ func autoConvert_clientauthentication_Cluster_To_v1_Cluster(in *clientauthentica
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
out.ProxyURL = in.ProxyURL
out.DisableCompression = in.DisableCompression
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Config, &out.Config, s); err != nil {
return err
}

View File

@@ -96,6 +96,11 @@ type Cluster struct {
// cluster.
// +optional
ProxyURL string `json:"proxy-url,omitempty"`
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
// +optional
DisableCompression bool `json:"disable-compression,omitempty"`
// Config holds additional config data that is specific to the exec
// plugin with regards to the cluster being authenticated to.
//

View File

@@ -86,6 +86,7 @@ func autoConvert_v1beta1_Cluster_To_clientauthentication_Cluster(in *Cluster, ou
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
out.ProxyURL = in.ProxyURL
out.DisableCompression = in.DisableCompression
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Config, &out.Config, s); err != nil {
return err
}
@@ -103,6 +104,7 @@ func autoConvert_clientauthentication_Cluster_To_v1beta1_Cluster(in *clientauthe
out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
out.ProxyURL = in.ProxyURL
out.DisableCompression = in.DisableCompression
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Config, &out.Config, s); err != nil {
return err
}

View File

@@ -1,56 +0,0 @@
# Azure Active Directory plugin for client authentication
This plugin provides an integration with Azure Active Directory device flow. If no tokens are present in the kubectl configuration, it will prompt a device code which can be used to login in a browser. After login it will automatically fetch the tokens and store them in the kubectl configuration. In addition it will refresh and update the tokens in the configuration when expired.
## Usage
1. Create an Azure Active Directory *Web App / API* application for `apiserver` following these [instructions](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-app-registration). The callback URL does not matter (just cannot be empty).
2. Create a second Azure Active Directory native application for `kubectl`. The callback URL does not matter (just cannot be empty).
3. On `kubectl` application's configuration page in Azure portal grant permissions to `apiserver` application by clicking on *Required Permissions*, click the *Add* button and search for the apiserver application created in step 1. Select "Access apiserver" under the *DELEGATED PERMISSIONS*. Once added click the *Grant Permissions* button to apply the changes.
4. Configure the `apiserver` to use the Azure Active Directory as an OIDC provider with following options
```
--oidc-client-id="spn:APISERVER_APPLICATION_ID" \
--oidc-issuer-url="https://sts.windows.net/TENANT_ID/"
--oidc-username-claim="sub"
```
* Replace the `APISERVER_APPLICATION_ID` with the application ID of `apiserver` application
* Replace `TENANT_ID` with your tenant ID.
  * For a list of alternative username claims that are supported by the OIDC issuer check the JSON response at `https://sts.windows.net/TENANT_ID/.well-known/openid-configuration`.
5. Configure `kubectl` to use the `azure` authentication provider
```
kubectl config set-credentials "USER_NAME" --auth-provider=azure \
--auth-provider-arg=environment=AzurePublicCloud \
--auth-provider-arg=client-id=APPLICATION_ID \
--auth-provider-arg=tenant-id=TENANT_ID \
--auth-provider-arg=apiserver-id=APISERVER_APPLICATION_ID
```
* Supported environments: `AzurePublicCloud`, `AzureUSGovernmentCloud`, `AzureChinaCloud`, `AzureGermanCloud`
* Replace `USER_NAME` and `TENANT_ID` with your user name and tenant ID
* Replace `APPLICATION_ID` with the application ID of your`kubectl` application ID
* Replace `APISERVER_APPLICATION_ID` with the application ID of your `apiserver` application ID
* Be sure to also (create and) select a context that uses above user
6. (Optionally) the AAD token has `aud` claim with `spn:` prefix. To omit that, add following auth configuration:
```
--auth-provider-arg=config-mode="1"
```
7. The access token is acquired when first `kubectl` command is executed
```
kubectl get pods
To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code DEC7D48GA to authenticate.
```
* After signing in a web browser, the token is stored in the configuration, and it will be reused when executing further commands.
* The resulting username in Kubernetes depends on your [configuration of the `--oidc-username-claim` and `--oidc-username-prefix` flags on the API server](https://kubernetes.io/docs/admin/authentication/#configuring-the-api-server). If you are using any authorization method you need to give permissions to that user, e.g. by binding the user to a role in the case of RBAC.

View File

@@ -1,477 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"sync"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/util/net"
restclient "k8s.io/client-go/rest"
)
type configMode int
const (
azureTokenKey = "azureTokenKey"
tokenType = "Bearer"
authHeader = "Authorization"
cfgClientID = "client-id"
cfgTenantID = "tenant-id"
cfgAccessToken = "access-token"
cfgRefreshToken = "refresh-token"
cfgExpiresIn = "expires-in"
cfgExpiresOn = "expires-on"
cfgEnvironment = "environment"
cfgApiserverID = "apiserver-id"
cfgConfigMode = "config-mode"
configModeDefault configMode = 0
configModeOmitSPNPrefix configMode = 1
)
func init() {
if err := restclient.RegisterAuthProviderPlugin("azure", newAzureAuthProvider); err != nil {
klog.Fatalf("Failed to register azure auth plugin: %v", err)
}
}
var cache = newAzureTokenCache()
type azureTokenCache struct {
lock sync.Mutex
cache map[string]*azureToken
}
func newAzureTokenCache() *azureTokenCache {
return &azureTokenCache{cache: make(map[string]*azureToken)}
}
func (c *azureTokenCache) getToken(tokenKey string) *azureToken {
c.lock.Lock()
defer c.lock.Unlock()
return c.cache[tokenKey]
}
func (c *azureTokenCache) setToken(tokenKey string, token *azureToken) {
c.lock.Lock()
defer c.lock.Unlock()
c.cache[tokenKey] = token
}
var warnOnce sync.Once
func newAzureAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
// deprecated in v1.22, remove in v1.25
warnOnce.Do(func() {
klog.Warningf(`WARNING: the azure auth plugin is deprecated in v1.22+, unavailable in v1.26+; use https://github.com/Azure/kubelogin instead.
To learn more, consult https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins`)
})
var (
ts tokenSource
environment azure.Environment
err error
mode configMode
)
environment, err = azure.EnvironmentFromName(cfg[cfgEnvironment])
if err != nil {
environment = azure.PublicCloud
}
mode = configModeDefault
if cfg[cfgConfigMode] != "" {
configModeInt, err := strconv.Atoi(cfg[cfgConfigMode])
if err != nil {
return nil, fmt.Errorf("failed to parse %s, error: %s", cfgConfigMode, err)
}
mode = configMode(configModeInt)
switch mode {
case configModeOmitSPNPrefix:
case configModeDefault:
default:
return nil, fmt.Errorf("%s:%s is not a valid mode", cfgConfigMode, cfg[cfgConfigMode])
}
}
ts, err = newAzureTokenSourceDeviceCode(environment, cfg[cfgClientID], cfg[cfgTenantID], cfg[cfgApiserverID], mode)
if err != nil {
return nil, fmt.Errorf("creating a new azure token source for device code authentication: %v", err)
}
cacheSource := newAzureTokenSource(ts, cache, cfg, mode, persister)
return &azureAuthProvider{
tokenSource: cacheSource,
}, nil
}
type azureAuthProvider struct {
tokenSource tokenSource
}
func (p *azureAuthProvider) Login() error {
return errors.New("not yet implemented")
}
func (p *azureAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &azureRoundTripper{
tokenSource: p.tokenSource,
roundTripper: rt,
}
}
type azureRoundTripper struct {
tokenSource tokenSource
roundTripper http.RoundTripper
}
var _ net.RoundTripperWrapper = &azureRoundTripper{}
func (r *azureRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get(authHeader)) != 0 {
return r.roundTripper.RoundTrip(req)
}
token, err := r.tokenSource.Token()
if err != nil {
klog.Errorf("Failed to acquire a token: %v", err)
return nil, fmt.Errorf("acquiring a token for authorization header: %v", err)
}
// clone the request in order to avoid modifying the headers of the original request
req2 := new(http.Request)
*req2 = *req
req2.Header = make(http.Header, len(req.Header))
for k, s := range req.Header {
req2.Header[k] = append([]string(nil), s...)
}
req2.Header.Set(authHeader, fmt.Sprintf("%s %s", tokenType, token.token.AccessToken))
return r.roundTripper.RoundTrip(req2)
}
func (r *azureRoundTripper) WrappedRoundTripper() http.RoundTripper { return r.roundTripper }
type azureToken struct {
token adal.Token
environment string
clientID string
tenantID string
apiserverID string
}
type tokenSource interface {
Token() (*azureToken, error)
Refresh(*azureToken) (*azureToken, error)
}
type azureTokenSource struct {
source tokenSource
cache *azureTokenCache
lock sync.Mutex
configMode configMode
cfg map[string]string
persister restclient.AuthProviderConfigPersister
}
func newAzureTokenSource(source tokenSource, cache *azureTokenCache, cfg map[string]string, configMode configMode, persister restclient.AuthProviderConfigPersister) tokenSource {
return &azureTokenSource{
source: source,
cache: cache,
cfg: cfg,
persister: persister,
configMode: configMode,
}
}
// Token fetches a token from the cache of configuration if present otherwise
// acquires a new token from the configured source. Automatically refreshes
// the token if expired.
func (ts *azureTokenSource) Token() (*azureToken, error) {
ts.lock.Lock()
defer ts.lock.Unlock()
var err error
token := ts.cache.getToken(azureTokenKey)
if token != nil && !token.token.IsExpired() {
return token, nil
}
// retrieve from config if no cache
if token == nil {
tokenFromCfg, err := ts.retrieveTokenFromCfg()
if err == nil {
token = tokenFromCfg
}
}
if token != nil {
// cache and return if the token is as good
// avoids frequent persistor calls
if !token.token.IsExpired() {
ts.cache.setToken(azureTokenKey, token)
return token, nil
}
klog.V(4).Info("Refreshing token.")
tokenFromRefresh, err := ts.Refresh(token)
switch {
case err == nil:
token = tokenFromRefresh
case autorest.IsTokenRefreshError(err):
klog.V(4).Infof("Failed to refresh expired token, proceed to auth: %v", err)
// reset token to nil so that the token source will be used to acquire new
token = nil
default:
return nil, fmt.Errorf("unexpected error when refreshing token: %v", err)
}
}
if token == nil {
tokenFromSource, err := ts.source.Token()
if err != nil {
return nil, fmt.Errorf("failed acquiring new token: %v", err)
}
token = tokenFromSource
}
// sanity check
if token == nil {
return nil, fmt.Errorf("unable to acquire token")
}
// corner condition, newly got token is valid but expired
if token.token.IsExpired() {
return nil, fmt.Errorf("newly acquired token is expired")
}
err = ts.storeTokenInCfg(token)
if err != nil {
return nil, fmt.Errorf("storing the refreshed token in configuration: %v", err)
}
ts.cache.setToken(azureTokenKey, token)
return token, nil
}
func (ts *azureTokenSource) retrieveTokenFromCfg() (*azureToken, error) {
accessToken := ts.cfg[cfgAccessToken]
if accessToken == "" {
return nil, fmt.Errorf("no access token in cfg: %s", cfgAccessToken)
}
refreshToken := ts.cfg[cfgRefreshToken]
if refreshToken == "" {
return nil, fmt.Errorf("no refresh token in cfg: %s", cfgRefreshToken)
}
environment := ts.cfg[cfgEnvironment]
if environment == "" {
return nil, fmt.Errorf("no environment in cfg: %s", cfgEnvironment)
}
clientID := ts.cfg[cfgClientID]
if clientID == "" {
return nil, fmt.Errorf("no client ID in cfg: %s", cfgClientID)
}
tenantID := ts.cfg[cfgTenantID]
if tenantID == "" {
return nil, fmt.Errorf("no tenant ID in cfg: %s", cfgTenantID)
}
resourceID := ts.cfg[cfgApiserverID]
if resourceID == "" {
return nil, fmt.Errorf("no apiserver ID in cfg: %s", cfgApiserverID)
}
expiresIn := ts.cfg[cfgExpiresIn]
if expiresIn == "" {
return nil, fmt.Errorf("no expiresIn in cfg: %s", cfgExpiresIn)
}
expiresOn := ts.cfg[cfgExpiresOn]
if expiresOn == "" {
return nil, fmt.Errorf("no expiresOn in cfg: %s", cfgExpiresOn)
}
tokenAudience := resourceID
if ts.configMode == configModeDefault {
tokenAudience = fmt.Sprintf("spn:%s", resourceID)
}
return &azureToken{
token: adal.Token{
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: json.Number(expiresIn),
ExpiresOn: json.Number(expiresOn),
NotBefore: json.Number(expiresOn),
Resource: tokenAudience,
Type: tokenType,
},
environment: environment,
clientID: clientID,
tenantID: tenantID,
apiserverID: resourceID,
}, nil
}
func (ts *azureTokenSource) storeTokenInCfg(token *azureToken) error {
newCfg := make(map[string]string)
newCfg[cfgAccessToken] = token.token.AccessToken
newCfg[cfgRefreshToken] = token.token.RefreshToken
newCfg[cfgEnvironment] = token.environment
newCfg[cfgClientID] = token.clientID
newCfg[cfgTenantID] = token.tenantID
newCfg[cfgApiserverID] = token.apiserverID
newCfg[cfgExpiresIn] = string(token.token.ExpiresIn)
newCfg[cfgExpiresOn] = string(token.token.ExpiresOn)
newCfg[cfgConfigMode] = strconv.Itoa(int(ts.configMode))
err := ts.persister.Persist(newCfg)
if err != nil {
return fmt.Errorf("persisting the configuration: %v", err)
}
ts.cfg = newCfg
return nil
}
func (ts *azureTokenSource) Refresh(token *azureToken) (*azureToken, error) {
return ts.source.Refresh(token)
}
// refresh outdated token with adal.
func (ts *azureTokenSourceDeviceCode) Refresh(token *azureToken) (*azureToken, error) {
env, err := azure.EnvironmentFromName(token.environment)
if err != nil {
return nil, err
}
var oauthConfig *adal.OAuthConfig
if ts.configMode == configModeOmitSPNPrefix {
oauthConfig, err = adal.NewOAuthConfigWithAPIVersion(env.ActiveDirectoryEndpoint, token.tenantID, nil)
if err != nil {
return nil, fmt.Errorf("building the OAuth configuration without api-version for token refresh: %v", err)
}
} else {
oauthConfig, err = adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, token.tenantID)
if err != nil {
return nil, fmt.Errorf("building the OAuth configuration for token refresh: %v", err)
}
}
callback := func(t adal.Token) error {
return nil
}
spt, err := adal.NewServicePrincipalTokenFromManualToken(
*oauthConfig,
token.clientID,
token.apiserverID,
token.token,
callback)
if err != nil {
return nil, fmt.Errorf("creating new service principal for token refresh: %v", err)
}
if err := spt.Refresh(); err != nil {
// Caller expects IsTokenRefreshError(err) to trigger prompt.
return nil, fmt.Errorf("refreshing token: %w", err)
}
return &azureToken{
token: spt.Token(),
environment: token.environment,
clientID: token.clientID,
tenantID: token.tenantID,
apiserverID: token.apiserverID,
}, nil
}
type azureTokenSourceDeviceCode struct {
environment azure.Environment
clientID string
tenantID string
apiserverID string
configMode configMode
}
func newAzureTokenSourceDeviceCode(environment azure.Environment, clientID string, tenantID string, apiserverID string, configMode configMode) (tokenSource, error) {
if clientID == "" {
return nil, errors.New("client-id is empty")
}
if tenantID == "" {
return nil, errors.New("tenant-id is empty")
}
if apiserverID == "" {
return nil, errors.New("apiserver-id is empty")
}
return &azureTokenSourceDeviceCode{
environment: environment,
clientID: clientID,
tenantID: tenantID,
apiserverID: apiserverID,
configMode: configMode,
}, nil
}
func (ts *azureTokenSourceDeviceCode) Token() (*azureToken, error) {
var (
oauthConfig *adal.OAuthConfig
err error
)
if ts.configMode == configModeOmitSPNPrefix {
oauthConfig, err = adal.NewOAuthConfigWithAPIVersion(ts.environment.ActiveDirectoryEndpoint, ts.tenantID, nil)
if err != nil {
return nil, fmt.Errorf("building the OAuth configuration without api-version for device code authentication: %v", err)
}
} else {
oauthConfig, err = adal.NewOAuthConfig(ts.environment.ActiveDirectoryEndpoint, ts.tenantID)
if err != nil {
return nil, fmt.Errorf("building the OAuth configuration for device code authentication: %v", err)
}
}
client := &autorest.Client{}
deviceCode, err := adal.InitiateDeviceAuth(client, *oauthConfig, ts.clientID, ts.apiserverID)
if err != nil {
return nil, fmt.Errorf("initialing the device code authentication: %v", err)
}
_, err = fmt.Fprintln(os.Stderr, *deviceCode.Message)
if err != nil {
return nil, fmt.Errorf("prompting the device code message: %v", err)
}
token, err := adal.WaitForUserCompletion(client, deviceCode)
if err != nil {
return nil, fmt.Errorf("waiting for device code authentication to complete: %v", err)
}
return &azureToken{
token: *token,
environment: ts.environment.Name,
clientID: ts.clientID,
tenantID: ts.tenantID,
apiserverID: ts.apiserverID,
}, nil
}

View File

@@ -0,0 +1,36 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"errors"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
func init() {
if err := rest.RegisterAuthProviderPlugin("azure", newAzureAuthProvider); err != nil {
klog.Fatalf("Failed to register azure auth plugin: %v", err)
}
}
func newAzureAuthProvider(_ string, _ map[string]string, _ rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
return nil, errors.New(`The azure auth plugin has been removed.
Please use the https://github.com/Azure/kubelogin kubectl/client-go credential plugin instead.
See https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins for further details`)
}

View File

@@ -1,534 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
func TestAzureAuthProvider(t *testing.T) {
t.Run("validate against invalid configurations", func(t *testing.T) {
vectors := []struct {
cfg map[string]string
expectedError string
}{
{
cfg: map[string]string{
cfgClientID: "foo",
cfgApiserverID: "foo",
cfgTenantID: "foo",
cfgConfigMode: "-1",
},
expectedError: "config-mode:-1 is not a valid mode",
},
{
cfg: map[string]string{
cfgClientID: "foo",
cfgApiserverID: "foo",
cfgTenantID: "foo",
cfgConfigMode: "2",
},
expectedError: "config-mode:2 is not a valid mode",
},
{
cfg: map[string]string{
cfgClientID: "foo",
cfgApiserverID: "foo",
cfgTenantID: "foo",
cfgConfigMode: "foo",
},
expectedError: "failed to parse config-mode, error: strconv.Atoi: parsing \"foo\": invalid syntax",
},
}
for _, v := range vectors {
persister := &fakePersister{}
_, err := newAzureAuthProvider("", v.cfg, persister)
if !strings.Contains(err.Error(), v.expectedError) {
t.Errorf("cfg %v should fail with message containing '%s'. actual: '%s'", v.cfg, v.expectedError, err)
}
}
})
t.Run("it should return non-nil provider in happy cases", func(t *testing.T) {
vectors := []struct {
cfg map[string]string
expectedConfigMode configMode
}{
{
cfg: map[string]string{
cfgClientID: "foo",
cfgApiserverID: "foo",
cfgTenantID: "foo",
},
expectedConfigMode: configModeDefault,
},
{
cfg: map[string]string{
cfgClientID: "foo",
cfgApiserverID: "foo",
cfgTenantID: "foo",
cfgConfigMode: "0",
},
expectedConfigMode: configModeDefault,
},
{
cfg: map[string]string{
cfgClientID: "foo",
cfgApiserverID: "foo",
cfgTenantID: "foo",
cfgConfigMode: "1",
},
expectedConfigMode: configModeOmitSPNPrefix,
},
}
for _, v := range vectors {
persister := &fakePersister{}
provider, err := newAzureAuthProvider("", v.cfg, persister)
if err != nil {
t.Errorf("newAzureAuthProvider should not fail with '%s'", err)
}
if provider == nil {
t.Fatalf("newAzureAuthProvider should return non-nil provider")
}
azureProvider := provider.(*azureAuthProvider)
if azureProvider == nil {
t.Fatalf("newAzureAuthProvider should return an instance of type azureAuthProvider")
}
ts := azureProvider.tokenSource.(*azureTokenSource)
if ts == nil {
t.Fatalf("azureAuthProvider should be an instance of azureTokenSource")
}
if ts.configMode != v.expectedConfigMode {
t.Errorf("expected configMode: %d, actual: %d", v.expectedConfigMode, ts.configMode)
}
}
})
}
func TestTokenSourceDeviceCode(t *testing.T) {
var (
clientID = "clientID"
tenantID = "tenantID"
apiserverID = "apiserverID"
configMode = configModeDefault
azureEnv = azure.Environment{}
)
t.Run("validate to create azureTokenSourceDeviceCode", func(t *testing.T) {
if _, err := newAzureTokenSourceDeviceCode(azureEnv, clientID, tenantID, apiserverID, configModeDefault); err != nil {
t.Errorf("newAzureTokenSourceDeviceCode should not have failed. err: %s", err)
}
if _, err := newAzureTokenSourceDeviceCode(azureEnv, clientID, tenantID, apiserverID, configModeOmitSPNPrefix); err != nil {
t.Errorf("newAzureTokenSourceDeviceCode should not have failed. err: %s", err)
}
_, err := newAzureTokenSourceDeviceCode(azureEnv, "", tenantID, apiserverID, configMode)
actual := "client-id is empty"
if err.Error() != actual {
t.Errorf("newAzureTokenSourceDeviceCode should have failed. expected: %s, actual: %s", actual, err)
}
_, err = newAzureTokenSourceDeviceCode(azureEnv, clientID, "", apiserverID, configMode)
actual = "tenant-id is empty"
if err.Error() != actual {
t.Errorf("newAzureTokenSourceDeviceCode should have failed. expected: %s, actual: %s", actual, err)
}
_, err = newAzureTokenSourceDeviceCode(azureEnv, clientID, tenantID, "", configMode)
actual = "apiserver-id is empty"
if err.Error() != actual {
t.Errorf("newAzureTokenSourceDeviceCode should have failed. expected: %s, actual: %s", actual, err)
}
})
}
func TestAzureTokenSource(t *testing.T) {
configModes := []configMode{configModeOmitSPNPrefix, configModeDefault}
expectedConfigModes := []string{"1", "0"}
for i, configMode := range configModes {
t.Run(fmt.Sprintf("validate token from cfg with configMode %v", configMode), func(t *testing.T) {
const (
serverID = "fakeServerID"
clientID = "fakeClientID"
tenantID = "fakeTenantID"
accessToken = "fakeToken"
environment = "fakeEnvironment"
refreshToken = "fakeToken"
expiresIn = "foo"
expiresOn = "foo"
)
cfg := map[string]string{
cfgConfigMode: strconv.Itoa(int(configMode)),
cfgApiserverID: serverID,
cfgClientID: clientID,
cfgTenantID: tenantID,
cfgEnvironment: environment,
cfgAccessToken: accessToken,
cfgRefreshToken: refreshToken,
cfgExpiresIn: expiresIn,
cfgExpiresOn: expiresOn,
}
fakeSource := fakeTokenSource{token: newFakeAzureToken("fakeToken", time.Now().Add(3600*time.Second))}
persiter := &fakePersister{cache: make(map[string]string)}
tokenCache := newAzureTokenCache()
tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, persiter)
azTokenSource := tokenSource.(*azureTokenSource)
token, err := azTokenSource.retrieveTokenFromCfg()
if err != nil {
t.Errorf("failed to retrieve the token form cfg: %s", err)
}
if token.apiserverID != serverID {
t.Errorf("expecting token.apiserverID: %s, actual: %s", serverID, token.apiserverID)
}
if token.clientID != clientID {
t.Errorf("expecting token.clientID: %s, actual: %s", clientID, token.clientID)
}
if token.tenantID != tenantID {
t.Errorf("expecting token.tenantID: %s, actual: %s", tenantID, token.tenantID)
}
expectedAudience := serverID
if configMode == configModeDefault {
expectedAudience = fmt.Sprintf("spn:%s", serverID)
}
if token.token.Resource != expectedAudience {
t.Errorf("expecting adal token.Resource: %s, actual: %s", expectedAudience, token.token.Resource)
}
})
t.Run("validate token against cache", func(t *testing.T) {
fakeAccessToken := "fake token 1"
fakeSource := fakeTokenSource{token: newFakeAzureToken(fakeAccessToken, time.Now().Add(3600*time.Second))}
cfg := make(map[string]string)
persiter := &fakePersister{cache: make(map[string]string)}
tokenCache := newAzureTokenCache()
tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, persiter)
token, err := tokenSource.Token()
if err != nil {
t.Errorf("failed to retrieve the token form cache: %v", err)
}
wantCacheLen := 1
if len(tokenCache.cache) != wantCacheLen {
t.Errorf("Token() cache length error: got %v, want %v", len(tokenCache.cache), wantCacheLen)
}
if token != tokenCache.cache[azureTokenKey] {
t.Error("Token() returned token != cached token")
}
wantCfg := token2Cfg(token)
wantCfg[cfgConfigMode] = expectedConfigModes[i]
persistedCfg := persiter.Cache()
wantCfgLen := len(wantCfg)
persistedCfgLen := len(persistedCfg)
if wantCfgLen != persistedCfgLen {
t.Errorf("wantCfgLen and persistedCfgLen do not match, wantCfgLen=%v, persistedCfgLen=%v", wantCfgLen, persistedCfgLen)
}
for k, v := range persistedCfg {
if strings.Compare(v, wantCfg[k]) != 0 {
t.Errorf("Token() persisted cfg %s: got %v, want %v", k, v, wantCfg[k])
}
}
fakeSource.token = newFakeAzureToken("fake token 2", time.Now().Add(3600*time.Second))
token, err = tokenSource.Token()
if err != nil {
t.Errorf("failed to retrieve the cached token: %v", err)
}
if token.token.AccessToken != fakeAccessToken {
t.Errorf("Token() didn't return the cached token")
}
})
}
}
func TestAzureTokenSourceScenarios(t *testing.T) {
expiredToken := newFakeAzureToken("expired token", time.Now().Add(-time.Second))
extendedToken := newFakeAzureToken("extend token", time.Now().Add(1000*time.Second))
fakeToken := newFakeAzureToken("fake token", time.Now().Add(1000*time.Second))
wrongToken := newFakeAzureToken("wrong token", time.Now().Add(1000*time.Second))
tests := []struct {
name string
sourceToken *azureToken
refreshToken *azureToken
cachedToken *azureToken
configToken *azureToken
expectToken *azureToken
tokenErr error
refreshErr error
expectErr string
tokenCalls uint
refreshCalls uint
persistCalls uint
}{
{
name: "new config",
sourceToken: fakeToken,
expectToken: fakeToken,
tokenCalls: 1,
persistCalls: 1,
},
{
name: "load token from cache",
sourceToken: wrongToken,
cachedToken: fakeToken,
configToken: wrongToken,
expectToken: fakeToken,
},
{
name: "load token from config",
sourceToken: wrongToken,
configToken: fakeToken,
expectToken: fakeToken,
},
{
name: "cached token timeout, extend success, config token should never load",
cachedToken: expiredToken,
refreshToken: extendedToken,
configToken: wrongToken,
expectToken: extendedToken,
refreshCalls: 1,
persistCalls: 1,
},
{
name: "config token timeout, extend failure, acquire new token",
configToken: expiredToken,
refreshErr: fakeTokenRefreshError{message: "FakeError happened when refreshing"},
sourceToken: fakeToken,
expectToken: fakeToken,
refreshCalls: 1,
tokenCalls: 1,
persistCalls: 1,
},
{
name: "extend failure with fmt.Errorf nested tokenRefreshError",
configToken: expiredToken,
refreshErr: fmt.Errorf("refreshing token: %w", fakeTokenRefreshError{message: "nested FakeError happened when refreshing"}),
sourceToken: fakeToken,
expectToken: fakeToken,
refreshCalls: 1,
tokenCalls: 1,
persistCalls: 1,
},
{
name: "unexpected error when extend",
configToken: expiredToken,
refreshErr: errors.New("unexpected refresh error"),
sourceToken: fakeToken,
expectErr: "unexpected refresh error",
refreshCalls: 1,
},
{
name: "token error",
tokenErr: errors.New("tokenerr"),
expectErr: "tokenerr",
tokenCalls: 1,
},
{
name: "Token() got expired token",
sourceToken: expiredToken,
expectErr: "newly acquired token is expired",
tokenCalls: 1,
},
{
name: "Token() got nil but no error",
sourceToken: nil,
expectErr: "unable to acquire token",
tokenCalls: 1,
},
}
for _, tc := range tests {
configModes := []configMode{configModeOmitSPNPrefix, configModeDefault}
for _, configMode := range configModes {
t.Run(fmt.Sprintf("%s with configMode: %v", tc.name, configMode), func(t *testing.T) {
persister := newFakePersister()
cfg := map[string]string{
cfgConfigMode: strconv.Itoa(int(configMode)),
}
if tc.configToken != nil {
cfg = token2Cfg(tc.configToken)
}
tokenCache := newAzureTokenCache()
if tc.cachedToken != nil {
tokenCache.setToken(azureTokenKey, tc.cachedToken)
}
fakeSource := fakeTokenSource{
token: tc.sourceToken,
tokenErr: tc.tokenErr,
refreshToken: tc.refreshToken,
refreshErr: tc.refreshErr,
}
tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, &persister)
token, err := tokenSource.Token()
if token != nil && fakeSource.token != nil && token.apiserverID != fakeSource.token.apiserverID {
t.Errorf("expecting apiservierID: %s, got: %s", fakeSource.token.apiserverID, token.apiserverID)
}
if fakeSource.tokenCalls != tc.tokenCalls {
t.Errorf("expecting tokenCalls: %v, got: %v", tc.tokenCalls, fakeSource.tokenCalls)
}
if fakeSource.refreshCalls != tc.refreshCalls {
t.Errorf("expecting refreshCalls: %v, got: %v", tc.refreshCalls, fakeSource.refreshCalls)
}
if persister.calls != tc.persistCalls {
t.Errorf("expecting persister calls: %v, got: %v", tc.persistCalls, persister.calls)
}
if tc.expectErr != "" {
if !strings.Contains(err.Error(), tc.expectErr) {
t.Errorf("expecting error %v, got %v", tc.expectErr, err)
}
if token != nil {
t.Errorf("token should be nil in err situation, got %v", token)
}
} else {
if err != nil {
t.Fatalf("error should be nil, got %v", err)
}
if token.token.AccessToken != tc.expectToken.token.AccessToken {
t.Errorf("token should have accessToken %v, got %v", token.token.AccessToken, tc.expectToken.token.AccessToken)
}
}
})
}
}
}
type fakePersister struct {
lock sync.Mutex
cache map[string]string
calls uint
}
func newFakePersister() fakePersister {
return fakePersister{cache: make(map[string]string), calls: 0}
}
func (p *fakePersister) Persist(cache map[string]string) error {
p.lock.Lock()
defer p.lock.Unlock()
p.calls++
p.cache = map[string]string{}
for k, v := range cache {
p.cache[k] = v
}
return nil
}
func (p *fakePersister) Cache() map[string]string {
ret := map[string]string{}
p.lock.Lock()
defer p.lock.Unlock()
for k, v := range p.cache {
ret[k] = v
}
return ret
}
// a simple token source simply always returns the token property
type fakeTokenSource struct {
token *azureToken
tokenCalls uint
tokenErr error
refreshToken *azureToken
refreshCalls uint
refreshErr error
}
func (ts *fakeTokenSource) Token() (*azureToken, error) {
ts.tokenCalls++
return ts.token, ts.tokenErr
}
func (ts *fakeTokenSource) Refresh(*azureToken) (*azureToken, error) {
ts.refreshCalls++
return ts.refreshToken, ts.refreshErr
}
func token2Cfg(token *azureToken) map[string]string {
cfg := make(map[string]string)
cfg[cfgAccessToken] = token.token.AccessToken
cfg[cfgRefreshToken] = token.token.RefreshToken
cfg[cfgEnvironment] = token.environment
cfg[cfgClientID] = token.clientID
cfg[cfgTenantID] = token.tenantID
cfg[cfgApiserverID] = token.apiserverID
cfg[cfgExpiresIn] = string(token.token.ExpiresIn)
cfg[cfgExpiresOn] = string(token.token.ExpiresOn)
return cfg
}
func newFakeAzureToken(accessToken string, expiresOnTime time.Time) *azureToken {
return &azureToken{
token: newFakeADALToken(accessToken, strconv.FormatInt(expiresOnTime.Unix(), 10)),
environment: "testenv",
clientID: "fake",
tenantID: "fake",
apiserverID: "fake",
}
}
func newFakeADALToken(accessToken string, expiresOn string) adal.Token {
return adal.Token{
AccessToken: accessToken,
RefreshToken: "fake",
ExpiresIn: "3600",
ExpiresOn: json.Number(expiresOn),
NotBefore: json.Number(expiresOn),
Resource: "fake",
Type: "fake",
}
}
// copied from go-autorest/adal
type fakeTokenRefreshError struct {
message string
resp *http.Response
}
// Error implements the error interface which is part of the TokenRefreshError interface.
func (tre fakeTokenRefreshError) Error() string {
return tre.message
}
// Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation.
func (tre fakeTokenRefreshError) Response() *http.Response {
return tre.resp
}

View File

@@ -199,14 +199,18 @@ func newAuthenticator(c *cache, isTerminalFunc func(int) bool, config *api.ExecC
now: time.Now,
environ: os.Environ,
defaultDialer: defaultDialer,
connTracker: connTracker,
connTracker: connTracker,
}
for _, env := range config.Env {
a.env = append(a.env, env.Name+"="+env.Value)
}
// these functions are made comparable and stored in the cache so that repeated clientset
// construction with the same rest.Config results in a single TLS cache and Authenticator
a.getCert = &transport.GetCertHolder{GetCert: a.cert}
a.dial = &transport.DialHolder{Dial: defaultDialer.DialContext}
return c.put(key, a), nil
}
@@ -261,8 +265,6 @@ type Authenticator struct {
now func() time.Time
environ func() []string
// defaultDialer is used for clients which don't specify a custom dialer
defaultDialer *connrotation.Dialer
// connTracker tracks all connections opened that we need to close when rotating a client certificate
connTracker *connrotation.ConnectionTracker
@@ -273,6 +275,12 @@ type Authenticator struct {
mu sync.Mutex
cachedCreds *credentials
exp time.Time
// getCert makes Authenticator.cert comparable to support TLS config caching
getCert *transport.GetCertHolder
// dial is used for clients which do not specify a custom dialer
// it is comparable to support TLS config caching
dial *transport.DialHolder
}
type credentials struct {
@@ -300,18 +308,21 @@ func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error {
if c.HasCertCallback() {
return errors.New("can't add TLS certificate callback: transport.Config.TLS.GetCert already set")
}
c.TLS.GetCert = a.cert
c.TLS.GetCertHolder = a.getCert // comparable for TLS config caching
if c.DialHolder != nil {
if c.DialHolder.Dial == nil {
return errors.New("invalid transport.Config.DialHolder: wrapped Dial function is nil")
}
var d *connrotation.Dialer
if c.Dial != nil {
// if c has a custom dialer, we have to wrap it
d = connrotation.NewDialerWithTracker(c.Dial, a.connTracker)
// TLS config caching is not supported for this config
d := connrotation.NewDialerWithTracker(c.DialHolder.Dial, a.connTracker)
c.DialHolder = &transport.DialHolder{Dial: d.DialContext}
} else {
d = a.defaultDialer
c.DialHolder = a.dial // comparable for TLS config caching
}
c.Dial = d.DialContext
return nil
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package exec_test // separate package to prevent circular import
import (
"context"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilnet "k8s.io/apimachinery/pkg/util/net"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// TestExecTLSCache asserts the semantics of the TLS cache when exec auth is used.
//
// In particular, when:
// - multiple identical rest configs exist as distinct objects, and
// - these rest configs use exec auth, and
// - these rest configs are used to create distinct clientsets, then
//
// the underlying TLS config is shared between those clientsets.
func TestExecTLSCache(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)
config1 := &rest.Config{
Host: "https://localhost",
ExecProvider: &clientcmdapi.ExecConfig{
Command: "./testdata/test-plugin.sh",
APIVersion: "client.authentication.k8s.io/v1",
InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
},
}
client1 := clientset.NewForConfigOrDie(config1)
config2 := &rest.Config{
Host: "https://localhost",
ExecProvider: &clientcmdapi.ExecConfig{
Command: "./testdata/test-plugin.sh",
APIVersion: "client.authentication.k8s.io/v1",
InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
},
}
client2 := clientset.NewForConfigOrDie(config2)
config3 := &rest.Config{
Host: "https://localhost",
ExecProvider: &clientcmdapi.ExecConfig{
Command: "./testdata/test-plugin.sh",
Args: []string{"make this exec auth different"},
APIVersion: "client.authentication.k8s.io/v1",
InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
},
}
client3 := clientset.NewForConfigOrDie(config3)
_, _ = client1.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
_, _ = client2.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
_, _ = client3.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{})
rt1 := client1.RESTClient().(*rest.RESTClient).Client.Transport
rt2 := client2.RESTClient().(*rest.RESTClient).Client.Transport
rt3 := client3.RESTClient().(*rest.RESTClient).Client.Transport
tlsConfig1, err := utilnet.TLSClientConfig(rt1)
if err != nil {
t.Fatal(err)
}
tlsConfig2, err := utilnet.TLSClientConfig(rt2)
if err != nil {
t.Fatal(err)
}
tlsConfig3, err := utilnet.TLSClientConfig(rt3)
if err != nil {
t.Fatal(err)
}
if tlsConfig1 == nil || tlsConfig2 == nil || tlsConfig3 == nil {
t.Fatal("expected non-nil TLS configs")
}
if tlsConfig1 != tlsConfig2 {
t.Fatal("expected the same TLS config for matching exec config via rest config")
}
if tlsConfig1 == tlsConfig3 {
t.Fatal("expected different TLS config for non-matching exec config via rest config")
}
}

View File

@@ -27,7 +27,7 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"io"
"math/big"
"net/http"
"net/http/httptest"
@@ -888,7 +888,7 @@ func TestRoundTripper(t *testing.T) {
}
a.environ = environ
a.now = now
a.stderr = ioutil.Discard
a.stderr = io.Discard
tc := &transport.Config{}
if err := a.UpdateTransportConfig(tc); err != nil {
@@ -1005,7 +1005,7 @@ func TestAuthorizationHeaderPresentCancelsExecAction(t *testing.T) {
cert := func() (*tls.Certificate, error) {
return nil, nil
}
tc := &transport.Config{TLS: transport.TLSConfig{Insecure: true, GetCert: cert}}
tc := &transport.Config{TLS: transport.TLSConfig{Insecure: true, GetCertHolder: &transport.GetCertHolder{GetCert: cert}}}
test.setTransportConfig(tc)
if err := a.UpdateTransportConfig(tc); err != nil {
@@ -1051,7 +1051,7 @@ func TestTLSCredentials(t *testing.T) {
return []string{"TEST_OUTPUT=" + string(data)}
}
a.now = func() time.Time { return now }
a.stderr = ioutil.Discard
a.stderr = io.Discard
// We're not interested in server's cert, this test is about client cert.
tc := &transport.Config{TLS: transport.TLSConfig{Insecure: true}}
@@ -1134,7 +1134,7 @@ func TestConcurrentUpdateTransportConfig(t *testing.T) {
}
a.environ = environ
a.now = now
a.stderr = ioutil.Discard
a.stderr = io.Discard
stopCh := make(chan struct{})
defer close(stopCh)

View File

@@ -1,8 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- cjcullen
reviewers:
- cjcullen
emeritus_approvers:
- jlowdermilk

View File

@@ -1,389 +0,0 @@
/*
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 gcp
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os/exec"
"strings"
"sync"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/yaml"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/util/jsonpath"
"k8s.io/klog/v2"
)
func init() {
if err := restclient.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil {
klog.Fatalf("Failed to register gcp auth plugin: %v", err)
}
}
var (
// Stubbable for testing
execCommand = exec.Command
// defaultScopes:
// - cloud-platform is the base scope to authenticate to GCP.
// - userinfo.email is used to authenticate to GKE APIs with gserviceaccount
// email instead of numeric uniqueID.
defaultScopes = []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email"}
)
// gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide
// tokens for kubectl to authenticate itself to the apiserver. A sample json config
// is provided below with all recognized options described.
//
// {
// 'auth-provider': {
// # Required
// "name": "gcp",
//
// 'config': {
// # Authentication options
// # These options are used while getting a token.
//
// # comma-separated list of GCP API scopes. default value of this field
// # is "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email".
// # to override the API scopes, specify this field explicitly.
// "scopes": "https://www.googleapis.com/auth/cloud-platform"
//
// # Caching options
//
// # Raw string data representing cached access token.
// "access-token": "ya29.CjWdA4GiBPTt",
// # RFC3339Nano expiration timestamp for cached access token.
// "expiry": "2016-10-31 22:31:9.123",
//
// # Command execution options
// # These options direct the plugin to execute a specified command and parse
// # token and expiry time from the output of the command.
//
// # Command to execute for access token. Command output will be parsed as JSON.
// # If "cmd-args" is not present, this value will be split on whitespace, with
// # the first element interpreted as the command, remaining elements as args.
// "cmd-path": "/usr/bin/gcloud",
//
// # Arguments to pass to command to execute for access token.
// "cmd-args": "config config-helper --output=json"
//
// # JSONPath to the string field that represents the access token in
// # command output. If omitted, defaults to "{.access_token}".
// "token-key": "{.credential.access_token}",
//
// # JSONPath to the string field that represents expiration timestamp
// # of the access token in the command output. If omitted, defaults to
// # "{.token_expiry}"
// "expiry-key": ""{.credential.token_expiry}",
//
// # golang reference time in the format that the expiration timestamp uses.
// # If omitted, defaults to time.RFC3339Nano
// "time-fmt": "2006-01-02 15:04:05.999999999"
// }
// }
// }
type gcpAuthProvider struct {
tokenSource oauth2.TokenSource
persister restclient.AuthProviderConfigPersister
}
var warnOnce sync.Once
func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
warnOnce.Do(func() {
klog.Warningf(`WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.26+; use gcloud instead.
To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke`)
})
ts, err := tokenSource(isCmdTokenSource(gcpConfig), gcpConfig)
if err != nil {
return nil, err
}
cts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister, ts, gcpConfig)
if err != nil {
return nil, err
}
return &gcpAuthProvider{cts, persister}, nil
}
func isCmdTokenSource(gcpConfig map[string]string) bool {
_, ok := gcpConfig["cmd-path"]
return ok
}
func tokenSource(isCmd bool, gcpConfig map[string]string) (oauth2.TokenSource, error) {
// Command-based token source
if isCmd {
cmd := gcpConfig["cmd-path"]
if len(cmd) == 0 {
return nil, fmt.Errorf("missing access token cmd")
}
if gcpConfig["scopes"] != "" {
return nil, fmt.Errorf("scopes can only be used when kubectl is using a gcp service account key")
}
var args []string
if cmdArgs, ok := gcpConfig["cmd-args"]; ok {
args = strings.Fields(cmdArgs)
} else {
fields := strings.Fields(cmd)
cmd = fields[0]
args = fields[1:]
}
return newCmdTokenSource(cmd, args, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"]), nil
}
// Google Application Credentials-based token source
scopes := parseScopes(gcpConfig)
ts, err := google.DefaultTokenSource(context.Background(), scopes...)
if err != nil {
return nil, fmt.Errorf("cannot construct google default token source: %v", err)
}
return ts, nil
}
// parseScopes constructs a list of scopes that should be included in token source
// from the config map.
func parseScopes(gcpConfig map[string]string) []string {
scopes, ok := gcpConfig["scopes"]
if !ok {
return defaultScopes
}
if scopes == "" {
return []string{}
}
return strings.Split(gcpConfig["scopes"], ",")
}
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
var resetCache map[string]string
if cts, ok := g.tokenSource.(*cachedTokenSource); ok {
resetCache = cts.baseCache()
} else {
resetCache = make(map[string]string)
}
return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister, resetCache}
}
func (g *gcpAuthProvider) Login() error { return nil }
type cachedTokenSource struct {
lk sync.Mutex
source oauth2.TokenSource
accessToken string `datapolicy:"token"`
expiry time.Time
persister restclient.AuthProviderConfigPersister
cache map[string]string
}
func newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister, ts oauth2.TokenSource, cache map[string]string) (*cachedTokenSource, error) {
var expiryTime time.Time
if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil {
expiryTime = parsedTime
}
if cache == nil {
cache = make(map[string]string)
}
return &cachedTokenSource{
source: ts,
accessToken: accessToken,
expiry: expiryTime,
persister: persister,
cache: cache,
}, nil
}
func (t *cachedTokenSource) Token() (*oauth2.Token, error) {
tok := t.cachedToken()
if tok.Valid() && !tok.Expiry.IsZero() {
return tok, nil
}
tok, err := t.source.Token()
if err != nil {
return nil, err
}
cache := t.update(tok)
if t.persister != nil {
if err := t.persister.Persist(cache); err != nil {
klog.V(4).Infof("Failed to persist token: %v", err)
}
}
return tok, nil
}
func (t *cachedTokenSource) cachedToken() *oauth2.Token {
t.lk.Lock()
defer t.lk.Unlock()
return &oauth2.Token{
AccessToken: t.accessToken,
TokenType: "Bearer",
Expiry: t.expiry,
}
}
func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {
t.lk.Lock()
defer t.lk.Unlock()
t.accessToken = tok.AccessToken
t.expiry = tok.Expiry
ret := map[string]string{}
for k, v := range t.cache {
ret[k] = v
}
ret["access-token"] = t.accessToken
ret["expiry"] = t.expiry.Format(time.RFC3339Nano)
return ret
}
// baseCache is the base configuration value for this TokenSource, without any cached ephemeral tokens.
func (t *cachedTokenSource) baseCache() map[string]string {
t.lk.Lock()
defer t.lk.Unlock()
ret := map[string]string{}
for k, v := range t.cache {
ret[k] = v
}
delete(ret, "access-token")
delete(ret, "expiry")
return ret
}
type commandTokenSource struct {
cmd string
args []string
tokenKey string `datapolicy:"token"`
expiryKey string `datapolicy:"secret-key"`
timeFmt string
}
func newCmdTokenSource(cmd string, args []string, tokenKey, expiryKey, timeFmt string) *commandTokenSource {
if len(timeFmt) == 0 {
timeFmt = time.RFC3339Nano
}
if len(tokenKey) == 0 {
tokenKey = "{.access_token}"
}
if len(expiryKey) == 0 {
expiryKey = "{.token_expiry}"
}
return &commandTokenSource{
cmd: cmd,
args: args,
tokenKey: tokenKey,
expiryKey: expiryKey,
timeFmt: timeFmt,
}
}
func (c *commandTokenSource) Token() (*oauth2.Token, error) {
fullCmd := strings.Join(append([]string{c.cmd}, c.args...), " ")
cmd := execCommand(c.cmd, c.args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("error executing access token command %q: err=%v output=%s stderr=%s", fullCmd, err, output, string(stderr.Bytes()))
}
token, err := c.parseTokenCmdOutput(output)
if err != nil {
return nil, fmt.Errorf("error parsing output for access token command %q: %v", fullCmd, err)
}
return token, nil
}
func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token, error) {
output, err := yaml.ToJSON(output)
if err != nil {
return nil, err
}
var data interface{}
if err := json.Unmarshal(output, &data); err != nil {
return nil, err
}
accessToken, err := parseJSONPath(data, "token-key", c.tokenKey)
if err != nil {
return nil, fmt.Errorf("error parsing token-key %q from %q: %v", c.tokenKey, string(output), err)
}
expiryStr, err := parseJSONPath(data, "expiry-key", c.expiryKey)
if err != nil {
return nil, fmt.Errorf("error parsing expiry-key %q from %q: %v", c.expiryKey, string(output), err)
}
var expiry time.Time
if t, err := time.Parse(c.timeFmt, expiryStr); err != nil {
klog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err)
} else {
expiry = t
}
return &oauth2.Token{
AccessToken: accessToken,
TokenType: "Bearer",
Expiry: expiry,
}, nil
}
func parseJSONPath(input interface{}, name, template string) (string, error) {
j := jsonpath.New(name)
buf := new(bytes.Buffer)
if err := j.Parse(template); err != nil {
return "", err
}
if err := j.Execute(buf, input); err != nil {
return "", err
}
return buf.String(), nil
}
type conditionalTransport struct {
oauthTransport *oauth2.Transport
persister restclient.AuthProviderConfigPersister
resetCache map[string]string
}
var _ net.RoundTripperWrapper = &conditionalTransport{}
func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Authorization")) != 0 {
return t.oauthTransport.Base.RoundTrip(req)
}
res, err := t.oauthTransport.RoundTrip(req)
if err != nil {
return nil, err
}
if res.StatusCode == 401 {
klog.V(4).Infof("The credentials that were supplied are invalid for the target cluster")
t.persister.Persist(t.resetCache)
}
return res, nil
}
func (t *conditionalTransport) WrappedRoundTripper() http.RoundTripper { return t.oauthTransport.Base }

View File

@@ -0,0 +1,36 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gcp
import (
"errors"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
func init() {
if err := rest.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil {
klog.Fatalf("Failed to register gcp auth plugin: %v", err)
}
}
func newGCPAuthProvider(_ string, _ map[string]string, _ rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
return nil, errors.New(`The gcp auth plugin has been removed.
Please use the "gke-gcloud-auth-plugin" kubectl/client-go credential plugin instead.
See https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke for further details`)
}

View File

@@ -1,527 +0,0 @@
/*
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 gcp
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"reflect"
"strings"
"sync"
"testing"
"time"
"golang.org/x/oauth2"
)
type fakeOutput struct {
args []string
output string
}
var (
wantCmd []string
// Output for fakeExec, keyed by command
execOutputs = map[string]fakeOutput{
"/default/no/args": {
args: []string{},
output: `{
"access_token": "faketoken",
"token_expiry": "2016-10-31T22:31:09.123000000Z"
}`},
"/default/legacy/args": {
args: []string{"arg1", "arg2", "arg3"},
output: `{
"access_token": "faketoken",
"token_expiry": "2016-10-31T22:31:09.123000000Z"
}`},
"/space in path/customkeys": {
args: []string{"can", "haz", "auth"},
output: `{
"token": "faketoken",
"token_expiry": {
"datetime": "2016-10-31 22:31:09.123"
}
}`},
"missing/tokenkey/noargs": {
args: []string{},
output: `{
"broken": "faketoken",
"token_expiry": {
"datetime": "2016-10-31 22:31:09.123000000Z"
}
}`},
"missing/expirykey/legacyargs": {
args: []string{"split", "on", "whitespace"},
output: `{
"access_token": "faketoken",
"expires": "2016-10-31T22:31:09.123000000Z"
}`},
"invalid expiry/timestamp": {
args: []string{"foo", "--bar", "--baz=abc,def"},
output: `{
"access_token": "faketoken",
"token_expiry": "sometime soon, idk"
}`},
"badjson": {
args: []string{},
output: `{
"access_token": "faketoken",
"token_expiry": "sometime soon, idk"
------
`},
}
)
func fakeExec(command string, args ...string) *exec.Cmd {
cs := []string{"-test.run=TestHelperProcess", "--", command}
cs = append(cs, args...)
cmd := exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
return cmd
}
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
// Strip out the leading args used to exec into this function.
gotCmd := os.Args[3]
gotArgs := os.Args[4:]
output, ok := execOutputs[gotCmd]
if !ok {
fmt.Fprintf(os.Stdout, "unexpected call cmd=%q args=%v\n", gotCmd, gotArgs)
os.Exit(1)
} else if !reflect.DeepEqual(output.args, gotArgs) {
fmt.Fprintf(os.Stdout, "call cmd=%q got args %v, want: %v\n", gotCmd, gotArgs, output.args)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, output.output)
os.Exit(0)
}
func Test_isCmdTokenSource(t *testing.T) {
c1 := map[string]string{"cmd-path": "foo"}
if v := isCmdTokenSource(c1); !v {
t.Fatalf("cmd-path present in config (%+v), but got %v", c1, v)
}
c2 := map[string]string{"cmd-args": "foo bar"}
if v := isCmdTokenSource(c2); v {
t.Fatalf("cmd-path not present in config (%+v), but got %v", c2, v)
}
}
func Test_tokenSource_cmd(t *testing.T) {
if _, err := tokenSource(true, map[string]string{}); err == nil {
t.Fatalf("expected error, cmd-args not present in config")
}
c := map[string]string{
"cmd-path": "foo",
"cmd-args": "bar"}
ts, err := tokenSource(true, c)
if err != nil {
t.Fatalf("failed to return cmd token source: %+v", err)
}
if ts == nil {
t.Fatal("returned nil token source")
}
if _, ok := ts.(*commandTokenSource); !ok {
t.Fatalf("returned token source type:(%T) expected:(*commandTokenSource)", ts)
}
}
func Test_tokenSource_cmdCannotBeUsedWithScopes(t *testing.T) {
c := map[string]string{
"cmd-path": "foo",
"scopes": "A,B"}
if _, err := tokenSource(true, c); err == nil {
t.Fatal("expected error when scopes is used with cmd-path")
}
}
func Test_tokenSource_applicationDefaultCredentials_fails(t *testing.T) {
// try to use empty ADC file
fakeTokenFile, err := ioutil.TempFile("", "adctoken")
if err != nil {
t.Fatalf("failed to create fake token file: +%v", err)
}
fakeTokenFile.Close()
defer os.Remove(fakeTokenFile.Name())
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", fakeTokenFile.Name())
defer os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS")
if _, err := tokenSource(false, map[string]string{}); err == nil {
t.Fatalf("expected error because specified ADC token file is not a JSON")
}
}
func Test_tokenSource_applicationDefaultCredentials(t *testing.T) {
fakeTokenFile, err := ioutil.TempFile("", "adctoken")
if err != nil {
t.Fatalf("failed to create fake token file: +%v", err)
}
fakeTokenFile.Close()
defer os.Remove(fakeTokenFile.Name())
if err := ioutil.WriteFile(fakeTokenFile.Name(), []byte(`{"type":"service_account"}`), 0600); err != nil {
t.Fatalf("failed to write to fake token file: %+v", err)
}
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", fakeTokenFile.Name())
defer os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS")
ts, err := tokenSource(false, map[string]string{})
if err != nil {
t.Fatalf("failed to get a token source: %+v", err)
}
if ts == nil {
t.Fatal("returned nil token source")
}
}
func Test_parseScopes(t *testing.T) {
cases := []struct {
in map[string]string
out []string
}{
{
map[string]string{},
[]string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email"},
},
{
map[string]string{"scopes": ""},
[]string{},
},
{
map[string]string{"scopes": "A,B,C"},
[]string{"A", "B", "C"},
},
}
for _, c := range cases {
got := parseScopes(c.in)
if !reflect.DeepEqual(got, c.out) {
t.Errorf("expected=%v, got=%v", c.out, got)
}
}
}
func errEquiv(got, want error) bool {
if got == want {
return true
}
if got != nil && want != nil {
return strings.Contains(got.Error(), want.Error())
}
return false
}
func TestCmdTokenSource(t *testing.T) {
execCommand = fakeExec
fakeExpiry := time.Date(2016, 10, 31, 22, 31, 9, 123000000, time.UTC)
customFmt := "2006-01-02 15:04:05.999999999"
tests := []struct {
name string
gcpConfig map[string]string
tok *oauth2.Token
newErr, tokenErr error
}{
{
"default",
map[string]string{
"cmd-path": "/default/no/args",
},
&oauth2.Token{
AccessToken: "faketoken",
TokenType: "Bearer",
Expiry: fakeExpiry,
},
nil,
nil,
},
{
"default legacy args",
map[string]string{
"cmd-path": "/default/legacy/args arg1 arg2 arg3",
},
&oauth2.Token{
AccessToken: "faketoken",
TokenType: "Bearer",
Expiry: fakeExpiry,
},
nil,
nil,
},
{
"custom keys",
map[string]string{
"cmd-path": "/space in path/customkeys",
"cmd-args": "can haz auth",
"token-key": "{.token}",
"expiry-key": "{.token_expiry.datetime}",
"time-fmt": customFmt,
},
&oauth2.Token{
AccessToken: "faketoken",
TokenType: "Bearer",
Expiry: fakeExpiry,
},
nil,
nil,
},
{
"missing cmd",
map[string]string{
"cmd-path": "",
},
nil,
fmt.Errorf("missing access token cmd"),
nil,
},
{
"missing token-key",
map[string]string{
"cmd-path": "missing/tokenkey/noargs",
"token-key": "{.token}",
},
nil,
nil,
fmt.Errorf("error parsing token-key %q", "{.token}"),
},
{
"missing expiry-key",
map[string]string{
"cmd-path": "missing/expirykey/legacyargs split on whitespace",
"expiry-key": "{.expiry}",
},
nil,
nil,
fmt.Errorf("error parsing expiry-key %q", "{.expiry}"),
},
{
"invalid expiry timestamp",
map[string]string{
"cmd-path": "invalid expiry/timestamp",
"cmd-args": "foo --bar --baz=abc,def",
},
&oauth2.Token{
AccessToken: "faketoken",
TokenType: "Bearer",
Expiry: time.Time{},
},
nil,
nil,
},
{
"bad JSON",
map[string]string{
"cmd-path": "badjson",
},
nil,
nil,
fmt.Errorf("invalid character '-' after object key:value pair"),
},
}
for _, tc := range tests {
provider, err := newGCPAuthProvider("", tc.gcpConfig, nil /* persister */)
if !errEquiv(err, tc.newErr) {
t.Errorf("%q newGCPAuthProvider error: got %v, want %v", tc.name, err, tc.newErr)
continue
}
if err != nil {
continue
}
ts := provider.(*gcpAuthProvider).tokenSource.(*cachedTokenSource).source.(*commandTokenSource)
wantCmd = append([]string{ts.cmd}, ts.args...)
tok, err := ts.Token()
if !errEquiv(err, tc.tokenErr) {
t.Errorf("%q Token() error: got %v, want %v", tc.name, err, tc.tokenErr)
}
if !reflect.DeepEqual(tok, tc.tok) {
t.Errorf("%q Token() got %v, want %v", tc.name, tok, tc.tok)
}
}
}
type fakePersister struct {
lk sync.Mutex
cache map[string]string
}
func (f *fakePersister) Persist(cache map[string]string) error {
f.lk.Lock()
defer f.lk.Unlock()
f.cache = map[string]string{}
for k, v := range cache {
f.cache[k] = v
}
return nil
}
func (f *fakePersister) read() map[string]string {
ret := map[string]string{}
f.lk.Lock()
defer f.lk.Unlock()
for k, v := range f.cache {
ret[k] = v
}
return ret
}
type fakeTokenSource struct {
token *oauth2.Token
err error
}
func (f *fakeTokenSource) Token() (*oauth2.Token, error) {
return f.token, f.err
}
func TestCachedTokenSource(t *testing.T) {
tok := &oauth2.Token{AccessToken: "fakeaccesstoken"}
persister := &fakePersister{}
source := &fakeTokenSource{
token: tok,
err: nil,
}
cache := map[string]string{
"foo": "bar",
"baz": "bazinga",
}
ts, err := newCachedTokenSource("fakeaccesstoken", "", persister, source, cache)
if err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
_, err := ts.Token()
if err != nil {
t.Errorf("unexpected error: %s", err)
}
wg.Done()
}()
}
wg.Wait()
cache["access-token"] = "fakeaccesstoken"
cache["expiry"] = tok.Expiry.Format(time.RFC3339Nano)
if got := persister.read(); !reflect.DeepEqual(got, cache) {
t.Errorf("got cache %v, want %v", got, cache)
}
}
type MockTransport struct {
res *http.Response
}
func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.res, nil
}
func Test_cmdTokenSource_roundTrip(t *testing.T) {
accessToken := "fakeToken"
fakeExpiry := time.Now().Add(time.Hour)
fakeExpiryStr := fakeExpiry.Format(time.RFC3339Nano)
fs := &fakeTokenSource{
token: &oauth2.Token{
AccessToken: accessToken,
Expiry: fakeExpiry,
},
}
cmdCache := map[string]string{
"cmd-path": "/path/to/tokensource/cmd",
"cmd-args": "--output=json",
}
cmdCacheUpdated := map[string]string{
"cmd-path": "/path/to/tokensource/cmd",
"cmd-args": "--output=json",
"access-token": accessToken,
"expiry": fakeExpiryStr,
}
simpleCacheUpdated := map[string]string{
"access-token": accessToken,
"expiry": fakeExpiryStr,
}
tests := []struct {
name string
res http.Response
baseCache, expectedCache map[string]string
}{
{
"Unauthorized",
http.Response{StatusCode: http.StatusUnauthorized},
make(map[string]string),
make(map[string]string),
},
{
"Unauthorized, nonempty defaultCache",
http.Response{StatusCode: http.StatusUnauthorized},
cmdCache,
cmdCache,
},
{
"Authorized",
http.Response{StatusCode: http.StatusOK},
make(map[string]string),
simpleCacheUpdated,
},
{
"Authorized, nonempty defaultCache",
http.Response{StatusCode: http.StatusOK},
cmdCache,
cmdCacheUpdated,
},
}
persister := &fakePersister{}
req := http.Request{Header: http.Header{}}
for _, tc := range tests {
cts, err := newCachedTokenSource(accessToken, fakeExpiry.String(), persister, fs, tc.baseCache)
if err != nil {
t.Fatalf("unexpected error from newCachedTokenSource: %v", err)
}
authProvider := gcpAuthProvider{cts, persister}
fakeTransport := MockTransport{&tc.res}
transport := (authProvider.WrapTransport(&fakeTransport))
// call Token to persist/update cache
if _, err := cts.Token(); err != nil {
t.Fatalf("unexpected error from cachedTokenSource.Token(): %v", err)
}
transport.RoundTrip(&req)
if got := persister.read(); !reflect.DeepEqual(got, tc.expectedCache) {
t.Errorf("got cache %v, want %v", got, tc.expectedCache)
}
}
}

View File

@@ -22,7 +22,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"sync"
@@ -301,7 +301,7 @@ func tokenEndpoint(client *http.Client, issuer string) (string, error) {
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

View File

@@ -1,36 +0,0 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openstack
import (
"errors"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
func init() {
if err := rest.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil {
klog.Fatalf("Failed to register openstack auth plugin: %s", err)
}
}
func newOpenstackAuthProvider(_ string, _ map[string]string, _ rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
return nil, errors.New(`The openstack auth plugin has been removed.
Please use the "client-keystone-auth" kubectl/client-go credential plugin instead.
See https://github.com/kubernetes/cloud-provider-openstack/blob/master/docs/using-client-keystone-auth.md for further details`)
}

View File

@@ -23,5 +23,4 @@ import (
// Initialize client auth plugins for cloud providers.
_ "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/openstack"
)

View File

@@ -20,7 +20,6 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
@@ -519,7 +518,7 @@ func InClusterConfig() (*Config, error) {
return nil, ErrNotInCluster
}
token, err := ioutil.ReadFile(tokenFile)
token, err := os.ReadFile(tokenFile)
if err != nil {
return nil, err
}
@@ -572,10 +571,7 @@ func LoadTLSFiles(c *Config) error {
}
c.KeyData, err = dataFromSliceOrFile(c.KeyData, c.KeyFile)
if err != nil {
return err
}
return nil
return err
}
// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
@@ -585,7 +581,7 @@ func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
return data, nil
}
if len(file) > 0 {
fileData, err := ioutil.ReadFile(file)
fileData, err := os.ReadFile(file)
if err != nil {
return []byte{}, err
}

View File

@@ -163,18 +163,22 @@ func TestRESTClientLimiter(t *testing.T) {
Limiter flowcontrol.RateLimiter
}{
{
Name: "with no QPS",
Config: Config{},
Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10),
},
{
Name: "with QPS:10",
Config: Config{QPS: 10},
Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10),
},
{
Name: "with QPS:-1",
Config: Config{QPS: -1},
Limiter: nil,
},
{
Name: "with RateLimiter",
Config: Config{
RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
},
@@ -191,7 +195,7 @@ func TestRESTClientLimiter(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter)
t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name)
}
})
t.Run("Unversioned_"+testCase.Name, func(t *testing.T) {
@@ -203,7 +207,7 @@ func TestRESTClientLimiter(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter)
t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name)
}
})
}

View File

@@ -55,6 +55,7 @@ func ConfigToExecCluster(config *Config) (*clientauthenticationapi.Cluster, erro
InsecureSkipTLSVerify: config.Insecure,
CertificateAuthorityData: caData,
ProxyURL: proxyURL,
DisableCompression: config.DisableCompression,
Config: config.ExecProvider.Config,
}, nil
}
@@ -79,6 +80,7 @@ func ExecClusterToConfig(cluster *clientauthenticationapi.Cluster) (*Config, err
ServerName: cluster.TLSServerName,
CAData: cluster.CertificateAuthorityData,
},
Proxy: proxy,
Proxy: proxy,
DisableCompression: cluster.DisableCompression,
}, nil
}

View File

@@ -22,10 +22,10 @@ import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"path"
"reflect"
"strconv"
@@ -437,7 +437,7 @@ func (r *Request) Body(obj interface{}) *Request {
}
switch t := obj.(type) {
case string:
data, err := ioutil.ReadFile(t)
data, err := os.ReadFile(t)
if err != nil {
r.err = err
return r
@@ -508,6 +508,87 @@ func (r *Request) URL() *url.URL {
return finalURL
}
// finalURLTemplate is similar to URL(), but will make all specific parameter values equal
// - instead of name or namespace, "{name}" and "{namespace}" will be used, and all query
// parameters will be reset. This creates a copy of the url so as not to change the
// underlying object.
func (r Request) finalURLTemplate() url.URL {
newParams := url.Values{}
v := []string{"{value}"}
for k := range r.params {
newParams[k] = v
}
r.params = newParams
u := r.URL()
if u == nil {
return url.URL{}
}
segments := strings.Split(u.Path, "/")
groupIndex := 0
index := 0
trimmedBasePath := ""
if r.c.base != nil && strings.Contains(u.Path, r.c.base.Path) {
p := strings.TrimPrefix(u.Path, r.c.base.Path)
if !strings.HasPrefix(p, "/") {
p = "/" + p
}
// store the base path that we have trimmed so we can append it
// before returning the URL
trimmedBasePath = r.c.base.Path
segments = strings.Split(p, "/")
groupIndex = 1
}
if len(segments) <= 2 {
return *u
}
const CoreGroupPrefix = "api"
const NamedGroupPrefix = "apis"
isCoreGroup := segments[groupIndex] == CoreGroupPrefix
isNamedGroup := segments[groupIndex] == NamedGroupPrefix
if isCoreGroup {
// checking the case of core group with /api/v1/... format
index = groupIndex + 2
} else if isNamedGroup {
// checking the case of named group with /apis/apps/v1/... format
index = groupIndex + 3
} else {
// this should not happen that the only two possibilities are /api... and /apis..., just want to put an
// outlet here in case more API groups are added in future if ever possible:
// https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups
// if a wrong API groups name is encountered, return the {prefix} for url.Path
u.Path = "/{prefix}"
u.RawQuery = ""
return *u
}
// switch segLength := len(segments) - index; segLength {
switch {
// case len(segments) - index == 1:
// resource (with no name) do nothing
case len(segments)-index == 2:
// /$RESOURCE/$NAME: replace $NAME with {name}
segments[index+1] = "{name}"
case len(segments)-index == 3:
if segments[index+2] == "finalize" || segments[index+2] == "status" {
// /$RESOURCE/$NAME/$SUBRESOURCE: replace $NAME with {name}
segments[index+1] = "{name}"
} else {
// /namespace/$NAMESPACE/$RESOURCE: replace $NAMESPACE with {namespace}
segments[index+1] = "{namespace}"
}
case len(segments)-index >= 4:
segments[index+1] = "{namespace}"
// /namespace/$NAMESPACE/$RESOURCE/$NAME: replace $NAMESPACE with {namespace}, $NAME with {name}
if segments[index+3] != "finalize" && segments[index+3] != "status" {
// /$RESOURCE/$NAME/$SUBRESOURCE: replace $NAME with {name}
segments[index+3] = "{name}"
}
}
u.Path = path.Join(trimmedBasePath, path.Join(segments...))
return *u
}
func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) error {
if r.rateLimiter == nil {
return nil
@@ -537,7 +618,7 @@ func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) err
// but we use a throttled logger to prevent spamming.
globalThrottledLogger.Infof("%s", message)
}
metrics.RateLimiterLatency.Observe(ctx, r.verb, *r.URL(), latency)
metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency)
return err
}
@@ -745,7 +826,7 @@ func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
return nil, err
}
if r.body != nil {
req.Body = ioutil.NopCloser(r.body)
req.Body = io.NopCloser(r.body)
}
resp, err := client.Do(req)
updateURLMetrics(ctx, r, resp, err)
@@ -826,7 +907,7 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
// Metrics for total request latency
start := time.Now()
defer func() {
metrics.RequestLatency.Observe(ctx, r.verb, *r.URL(), time.Since(start))
metrics.RequestLatency.Observe(ctx, r.verb, r.finalURLTemplate(), time.Since(start))
}()
if r.err != nil {
@@ -937,7 +1018,7 @@ func (r *Request) Do(ctx context.Context) Result {
func (r *Request) DoRaw(ctx context.Context) ([]byte, error) {
var result Result
err := r.request(ctx, func(req *http.Request, resp *http.Response) {
result.body, result.err = ioutil.ReadAll(resp.Body)
result.body, result.err = io.ReadAll(resp.Body)
glogBody("Response Body", result.body)
if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent {
result.err = r.transformUnstructuredResponseError(resp, req, result.body)
@@ -956,7 +1037,7 @@ func (r *Request) DoRaw(ctx context.Context) ([]byte, error) {
func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result {
var body []byte
if resp.Body != nil {
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
switch err.(type) {
case nil:
body = data
@@ -1098,7 +1179,7 @@ const maxUnstructuredResponseTextBytes = 2048
// TODO: introduce transformation of generic http.Client.Do() errors that separates 4.
func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error {
if body == nil && resp.Body != nil {
if data, err := ioutil.ReadAll(&io.LimitedReader{R: resp.Body, N: maxUnstructuredResponseTextBytes}); err == nil {
if data, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: maxUnstructuredResponseTextBytes}); err == nil {
body = data
}
}

View File

@@ -23,7 +23,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
@@ -88,7 +87,7 @@ func TestRequestSetsHeaders(t *testing.T) {
}
return &http.Response{
StatusCode: http.StatusForbidden,
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
})
config := defaultContentConfig()
@@ -303,7 +302,7 @@ func TestRequestBody(t *testing.T) {
}
// test error set when failing to read file
f, err := ioutil.TempFile("", "test")
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatalf("unable to create temp file")
}
@@ -338,6 +337,206 @@ func TestResultIntoWithNoBodyReturnsErr(t *testing.T) {
}
}
func TestURLTemplate(t *testing.T) {
uri, _ := url.Parse("http://localhost/some/base/url/path")
uriSingleSlash, _ := url.Parse("http://localhost/")
testCases := []struct {
Request *Request
ExpectedFullURL string
ExpectedFinalURL string
}{
{
// non dynamic client
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").
Prefix("api", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/nm?p0=v0",
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D",
},
{
// non dynamic client with wrong api group
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").
Prefix("pre1", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
ExpectedFullURL: "http://localhost/some/base/url/path/pre1/v1/namespaces/ns/r1/nm?p0=v0",
ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
},
{
// dynamic client with core group + namespace + resourceResource (with name)
// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/api/v1/namespaces/ns/r1/name1"),
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/name1",
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
},
{
// dynamic client with named group + namespace + resourceResource (with name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/g1/v1/namespaces/ns/r1/name1"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1/name1",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
},
{
// dynamic client with core group + namespace + resourceResource (with NO name)
// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/api/v1/namespaces/ns/r1"),
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1",
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1",
},
{
// dynamic client with named group + namespace + resourceResource (with NO name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/g1/v1/namespaces/ns/r1"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1",
},
{
// dynamic client with core group + resourceResource (with name)
// /api/$RESOURCEVERSION/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/api/v1/r1/name1"),
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/r1/name1",
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/r1/%7Bname%7D",
},
{
// dynamic client with named group + resourceResource (with name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/g1/v1/r1/name1"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/name1",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/%7Bname%7D",
},
{
// dynamic client with named group + namespace + resourceResource (with name) + subresource
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D/finalize",
},
{
// dynamic client with named group + namespace + resourceResource (with name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D",
},
{
// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/finalize",
},
{
// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/status",
},
{
// dynamic client with named group + namespace + resourceResource (with no name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces",
},
{
// dynamic client with named group + resourceResource (with name) + subresource
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/finalize"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/finalize",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/finalize",
},
{
// dynamic client with named group + resourceResource (with name) + subresource
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces/status"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/status",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/status",
},
{
// dynamic client with named group + resourceResource (with name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces/namespaces"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D",
},
{
// dynamic client with named group + resourceResource (with no name)
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/apis/namespaces/namespaces/namespaces"),
ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
},
{
// dynamic client with wrong api group + namespace + resourceResource (with name) + subresource
// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
ExpectedFullURL: "http://localhost/some/base/url/path/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
},
{
// dynamic client with core group + namespace + resourceResource (with name) where baseURL is a single /
// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/api/v1/namespaces/ns/r2/name1"),
ExpectedFullURL: "http://localhost/api/v1/namespaces/ns/r2/name1",
ExpectedFinalURL: "http://localhost/api/v1/namespaces/%7Bnamespace%7D/r2/%7Bname%7D",
},
{
// dynamic client with core group + namespace + resourceResource (with name) where baseURL is 'some/base/url/path'
// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/api/v1/namespaces/ns/r3/name1"),
ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r3/name1",
ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r3/%7Bname%7D",
},
{
// dynamic client where baseURL is a single /
// /
Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/"),
ExpectedFullURL: "http://localhost/",
ExpectedFinalURL: "http://localhost/",
},
{
// dynamic client where baseURL is a single /
// /version
Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
Prefix("/version"),
ExpectedFullURL: "http://localhost/version",
ExpectedFinalURL: "http://localhost/version",
},
}
for i, testCase := range testCases {
r := testCase.Request
full := r.URL()
if full.String() != testCase.ExpectedFullURL {
t.Errorf("%d: unexpected initial URL: %s %s", i, full, testCase.ExpectedFullURL)
}
actualURL := r.finalURLTemplate()
actual := actualURL.String()
if actual != testCase.ExpectedFinalURL {
t.Errorf("%d: unexpected URL template: %s %s", i, actual, testCase.ExpectedFinalURL)
}
if r.URL().String() != full.String() {
t.Errorf("%d, creating URL template changed request: %s -> %s", i, full.String(), r.URL().String())
}
}
}
func TestTransformResponse(t *testing.T) {
invalid := []byte("aaaaa")
uri, _ := url.Parse("http://localhost")
@@ -360,7 +559,7 @@ func TestTransformResponse(t *testing.T) {
Response: &http.Response{
StatusCode: http.StatusUnauthorized,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Error: true,
ErrFn: func(err error) bool {
@@ -371,7 +570,7 @@ func TestTransformResponse(t *testing.T) {
Response: &http.Response{
StatusCode: http.StatusUnauthorized,
Header: http.Header{"Content-Type": []string{"text/any"}},
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Error: true,
ErrFn: func(err error) bool {
@@ -379,13 +578,13 @@ func TestTransformResponse(t *testing.T) {
},
},
{Response: &http.Response{StatusCode: http.StatusForbidden}, Error: true},
{Response: &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
{Response: &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
{Response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
{Response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
}
for i, test := range testCases {
r := NewRequestWithClient(uri, "", defaultContentConfig(), nil)
if test.Response.Body == nil {
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
test.Response.Body = io.NopCloser(bytes.NewReader([]byte{}))
}
result := r.transformResponse(test.Response, &http.Request{})
response, created, err := result.body, result.statusCode == http.StatusCreated, result.err
@@ -458,7 +657,7 @@ func TestTransformResponseNegotiate(t *testing.T) {
Response: &http.Response{
StatusCode: http.StatusUnauthorized,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Called: true,
ExpectContentType: "application/json",
@@ -472,7 +671,7 @@ func TestTransformResponseNegotiate(t *testing.T) {
Response: &http.Response{
StatusCode: http.StatusUnauthorized,
Header: http.Header{"Content-Type": []string{"application/protobuf"}},
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
@@ -502,7 +701,7 @@ func TestTransformResponseNegotiate(t *testing.T) {
Response: &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"text/any"}},
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
Called: true,
@@ -513,7 +712,7 @@ func TestTransformResponseNegotiate(t *testing.T) {
ContentType: "text/any",
Response: &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
Called: true,
@@ -525,7 +724,7 @@ func TestTransformResponseNegotiate(t *testing.T) {
Response: &http.Response{
StatusCode: http.StatusNotFound,
Header: http.Header{"Content-Type": []string{"application/unrecognized"}},
Body: ioutil.NopCloser(bytes.NewReader(invalid)),
Body: io.NopCloser(bytes.NewReader(invalid)),
},
Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
@@ -549,7 +748,7 @@ func TestTransformResponseNegotiate(t *testing.T) {
contentConfig.Negotiator = negotiator
r := NewRequestWithClient(uri, "", contentConfig, nil)
if test.Response.Body == nil {
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
test.Response.Body = io.NopCloser(bytes.NewReader([]byte{}))
}
result := r.transformResponse(test.Response, &http.Request{})
_, err := result.body, result.err
@@ -601,7 +800,7 @@ func TestTransformUnstructuredError(t *testing.T) {
},
Res: &http.Response{
StatusCode: http.StatusConflict,
Body: ioutil.NopCloser(bytes.NewReader(nil)),
Body: io.NopCloser(bytes.NewReader(nil)),
},
ErrFn: apierrors.IsAlreadyExists,
},
@@ -613,7 +812,7 @@ func TestTransformUnstructuredError(t *testing.T) {
},
Res: &http.Response{
StatusCode: http.StatusConflict,
Body: ioutil.NopCloser(bytes.NewReader(nil)),
Body: io.NopCloser(bytes.NewReader(nil)),
},
ErrFn: apierrors.IsConflict,
},
@@ -623,7 +822,7 @@ func TestTransformUnstructuredError(t *testing.T) {
Req: &http.Request{},
Res: &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader(nil)),
Body: io.NopCloser(bytes.NewReader(nil)),
},
ErrFn: apierrors.IsNotFound,
},
@@ -631,14 +830,14 @@ func TestTransformUnstructuredError(t *testing.T) {
Req: &http.Request{},
Res: &http.Response{
StatusCode: http.StatusBadRequest,
Body: ioutil.NopCloser(bytes.NewReader(nil)),
Body: io.NopCloser(bytes.NewReader(nil)),
},
ErrFn: apierrors.IsBadRequest,
},
{
// status in response overrides transformed result
Req: &http.Request{},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))},
ErrFn: apierrors.IsBadRequest,
Transformed: &apierrors.StatusError{
ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound},
@@ -647,20 +846,20 @@ func TestTransformUnstructuredError(t *testing.T) {
{
// successful status is ignored
Req: &http.Request{},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))},
ErrFn: apierrors.IsBadRequest,
},
{
// empty object does not change result
Req: &http.Request{},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`)))},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{}`)))},
ErrFn: apierrors.IsBadRequest,
},
{
// we default apiVersion for backwards compatibility with old clients
// TODO: potentially remove in 1.7
Req: &http.Request{},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))},
ErrFn: apierrors.IsBadRequest,
Transformed: &apierrors.StatusError{
ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound},
@@ -669,7 +868,7 @@ func TestTransformUnstructuredError(t *testing.T) {
{
// we do not default kind
Req: &http.Request{},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))},
Res: &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))},
ErrFn: apierrors.IsBadRequest,
},
}
@@ -773,7 +972,7 @@ func TestRequestWatch(t *testing.T) {
serverReturns: []responseErr{
{response: &http.Response{
StatusCode: http.StatusForbidden,
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, err: nil},
},
attemptsExpected: 1,
@@ -816,7 +1015,7 @@ func TestRequestWatch(t *testing.T) {
serverReturns: []responseErr{
{response: &http.Response{
StatusCode: http.StatusForbidden,
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, err: nil},
},
attemptsExpected: 1,
@@ -836,7 +1035,7 @@ func TestRequestWatch(t *testing.T) {
serverReturns: []responseErr{
{response: &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, err: nil},
},
attemptsExpected: 1,
@@ -856,7 +1055,7 @@ func TestRequestWatch(t *testing.T) {
serverReturns: []responseErr{
{response: &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
Status: metav1.StatusFailure,
Reason: metav1.StatusReasonUnauthorized,
})))),
@@ -1088,7 +1287,7 @@ func TestRequestStream(t *testing.T) {
serverReturns: []responseErr{
{response: &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
Status: metav1.StatusFailure,
Reason: metav1.StatusReasonUnauthorized,
})))),
@@ -1107,7 +1306,7 @@ func TestRequestStream(t *testing.T) {
serverReturns: []responseErr{
{response: &http.Response{
StatusCode: http.StatusBadRequest,
Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]","reason":"BadRequest","code":400}`))),
Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]","reason":"BadRequest","code":400}`))),
}, err: nil},
},
attemptsExpected: 1,
@@ -1188,7 +1387,7 @@ func TestRequestStream(t *testing.T) {
{response: retryAfterResponse(), err: nil},
{response: &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, err: nil},
},
},
@@ -1427,7 +1626,7 @@ func TestConnectionResetByPeerIsRetried(t *testing.T) {
if count >= 3 {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}, nil
}
return nil, &net.OpError{Err: syscall.ECONNRESET}
@@ -1455,7 +1654,7 @@ func TestCheckRetryHandles429And5xx(t *testing.T) {
count := 0
ch := make(chan struct{})
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
data, err := ioutil.ReadAll(req.Body)
data, err := io.ReadAll(req.Body)
if err != nil {
t.Fatalf("unable to read request body: %v", err)
}
@@ -1607,7 +1806,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
t.Errorf("unexpected error: %v", err)
}
file, err := ioutil.TempFile("", "foo")
file, err := os.CreateTemp("", "foo")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -1779,7 +1978,7 @@ func TestBody(t *testing.T) {
obj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
bodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), obj)
f, err := ioutil.TempFile("", "test_body")
f, err := os.CreateTemp("", "test_body")
if err != nil {
t.Fatalf("TempFile error: %v", err)
}
@@ -2449,7 +2648,7 @@ func TestRequestWithRetry(t *testing.T) {
}{
{
name: "server returns retry-after response, request body is not io.Seeker, retry goes ahead",
body: ioutil.NopCloser(bytes.NewReader([]byte{})),
body: io.NopCloser(bytes.NewReader([]byte{})),
serverReturns: responseErr{response: retryAfterResponse(), err: nil},
errExpected: nil,
transformFuncInvokedExpected: 1,
@@ -2473,7 +2672,7 @@ func TestRequestWithRetry(t *testing.T) {
},
{
name: "server returns retryable err, request body is not io.Seek, retry goes ahead",
body: ioutil.NopCloser(bytes.NewReader([]byte{})),
body: io.NopCloser(bytes.NewReader([]byte{})),
serverReturns: responseErr{response: nil, err: io.ErrUnexpectedEOF},
errExpected: io.ErrUnexpectedEOF,
transformFuncInvokedExpected: 0,
@@ -2773,7 +2972,7 @@ func testRequestWithRetry(t *testing.T, key string, doFunc func(ctx context.Cont
resp := test.serverReturns[attempts].response
if resp != nil {
responseRecorder.delegated = ioutil.NopCloser(bytes.NewReader([]byte{}))
responseRecorder.delegated = io.NopCloser(bytes.NewReader([]byte{}))
resp.Body = responseRecorder
}
return resp, test.serverReturns[attempts].err
@@ -3054,7 +3253,7 @@ func testRetryWithRateLimiterBackoffAndMetrics(t *testing.T, key string, doFunc
interceptor.Do()
resp := test.serverReturns[attempts].response
if resp != nil {
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
}
return resp, test.serverReturns[attempts].err
})
@@ -3190,7 +3389,7 @@ func testWithRetryInvokeOrder(t *testing.T, key string, doFunc func(ctx context.
interceptor.Do()
resp := test.serverReturns[attempts].response
if resp != nil {
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
}
return resp, test.serverReturns[attempts].err
})
@@ -3365,7 +3564,7 @@ func testWithWrapPreviousError(t *testing.T, doFunc func(ctx context.Context, r
resp := test.serverReturns[attempts].response
if resp != nil {
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
}
return resp, test.serverReturns[attempts].err
})
@@ -3665,12 +3864,12 @@ func TestRequestBodyResetOrder(t *testing.T) {
}()
// read the request body.
ioutil.ReadAll(req.Body)
io.ReadAll(req.Body)
// first attempt, we send a retry-after
if attempts == 0 {
resp := retryAfterResponse()
respBodyTracker.ReadCloser = ioutil.NopCloser(bytes.NewReader([]byte{}))
respBodyTracker.ReadCloser = io.NopCloser(bytes.NewReader([]byte{}))
resp.Body = respBodyTracker
return resp, nil
}

View File

@@ -108,10 +108,13 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
Groups: c.Impersonate.Groups,
Extra: c.Impersonate.Extra,
},
Dial: c.Dial,
Proxy: c.Proxy,
}
if c.Dial != nil {
conf.DialHolder = &transport.DialHolder{Dial: c.Dial}
}
if c.ExecProvider != nil && c.AuthProvider != nil {
return nil, errors.New("execProvider and authProvider cannot be used in combination")
}

149
rest/transport_test.go Normal file
View File

@@ -0,0 +1,149 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rest
import (
"context"
"net"
"strings"
"sync"
"testing"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// TestTransportForThreadSafe is meant to be run with the race detector
// to detect that the Transport generation for client-go is safe to
// call from multiple goroutines.
func TestTransportForThreadSafe(t *testing.T) {
const (
rootCACert = `-----BEGIN CERTIFICATE-----
MIIC4DCCAcqgAwIBAgIBATALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu
MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIxNTczN1oXDTE2MDExNTIxNTcz
OFowIzEhMB8GA1UEAwwYMTAuMTMuMTI5LjEwNkAxNDIxMzU5MDU4MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunDRXGwsiYWGFDlWH6kjGun+PshDGeZX
xtx9lUnL8pIRWH3wX6f13PO9sktaOWW0T0mlo6k2bMlSLlSZgG9H6og0W6gLS3vq
s4VavZ6DbXIwemZG2vbRwsvR+t4G6Nbwelm6F8RFnA1Fwt428pavmNQ/wgYzo+T1
1eS+HiN4ACnSoDSx3QRWcgBkB1g6VReofVjx63i0J+w8Q/41L9GUuLqquFxu6ZnH
60vTB55lHgFiDLjA1FkEz2dGvGh/wtnFlRvjaPC54JH2K1mPYAUXTreoeJtLJKX0
ycoiyB24+zGCniUmgIsmQWRPaOPircexCp1BOeze82BT1LCZNTVaxQIDAQABoyMw
ITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQsD
ggEBADMxsUuAFlsYDpF4fRCzXXwrhbtj4oQwcHpbu+rnOPHCZupiafzZpDu+rw4x
YGPnCb594bRTQn4pAu3Ac18NbLD5pV3uioAkv8oPkgr8aUhXqiv7KdDiaWm6sbAL
EHiXVBBAFvQws10HMqMoKtO8f1XDNAUkWduakR/U6yMgvOPwS7xl0eUTqyRB6zGb
K55q2dejiFWaFqB/y78txzvz6UlOZKE44g2JAVoJVM6kGaxh33q8/FmrL4kuN3ut
W+MmJCVDvd4eEqPwbp7146ZWTqpIJ8lvA6wuChtqV8lhAPka2hD/LMqY8iXNmfXD
uml0obOEy+ON91k+SWTJ3ggmF/U=
-----END CERTIFICATE-----`
authPluginName = "auth-plugin-tr"
)
err := RegisterAuthProviderPlugin(authPluginName, pluginAProvider)
// the plugins are stored in a global variable that fails if the test
// runs with the flag -count N, with N > 1
if err != nil && !strings.Contains(err.Error(), "was registered twice") {
t.Errorf("Unexpected error: failed to register pluginA: %v", err)
}
configurations := []struct {
name string
config *Config
}{
{
name: "simple config",
config: &Config{
Host: "localhost:8080",
Username: "gopher",
Password: "g0ph3r",
},
},
{
name: "not cacheable config",
config: &Config{
Host: "localhost:8080",
Username: "gopher",
Password: "g0ph3r",
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return nil, nil
},
},
},
{
name: "with TLS config",
config: &Config{
Host: "localhost:8080",
Username: "gopher",
Password: "g0ph3r",
TLSClientConfig: TLSClientConfig{
CAData: []byte(rootCACert),
},
},
},
{
name: "with auth provider",
config: &Config{
Host: "localhost:8080",
Username: "gopher",
Password: "g0ph3r",
TLSClientConfig: TLSClientConfig{
CAData: []byte(rootCACert),
},
AuthProvider: &clientcmdapi.AuthProviderConfig{
Name: authPluginName,
Config: map[string]string{"secret": "s3cr3t"},
},
},
},
{
name: "with exec provider",
config: &Config{
Host: "localhost:8080",
Username: "gopher",
Password: "g0ph3r",
TLSClientConfig: TLSClientConfig{
CAData: []byte(rootCACert),
},
ExecProvider: &clientcmdapi.ExecConfig{
Args: []string{"secret"},
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
APIVersion: "client.authentication.k8s.io/v1beta1",
},
},
},
}
var wg sync.WaitGroup
for _, tt := range configurations {
tt := tt
wg.Add(1)
go func() {
defer wg.Done()
for i := 1; i <= 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := TransportFor(tt.config)
if err != nil {
t.Errorf("Config: %s TransportFor() error = %v", tt.name, err)
}
}()
}
}()
}
wg.Wait()
}

View File

@@ -18,7 +18,7 @@ package versioned_test
import (
"bytes"
"io/ioutil"
"io"
"testing"
"k8s.io/api/core/v1"
@@ -70,7 +70,7 @@ func TestEncodeDecodeRoundTrip(t *testing.T) {
continue
}
rc := ioutil.NopCloser(buf)
rc := io.NopCloser(buf)
decoder := restclientwatch.NewDecoder(streaming.NewDecoder(rc, getDecoder()), getDecoder())
event, obj, err := decoder.Decode()
if err != nil {

View File

@@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
@@ -345,7 +344,7 @@ func readAndCloseResponseBody(resp *http.Response) {
defer resp.Body.Close()
if resp.ContentLength <= maxBodySlurpSize {
io.Copy(ioutil.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize})
io.Copy(io.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize})
}
}

View File

@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"testing"
@@ -46,7 +45,7 @@ import (
)
func bytesBody(bodyBytes []byte) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader(bodyBytes))
return io.NopCloser(bytes.NewReader(bodyBytes))
}
func defaultHeaders() http.Header {
@@ -184,7 +183,7 @@ func fakeScaleClient(t *testing.T) (ScalesGetter, []schema.GroupResource) {
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeaders(), Body: bytesBody(res)}, nil
case "PUT":
decoder := codecs.UniversalDeserializer()
body, err := ioutil.ReadAll(req.Body)
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
@@ -201,7 +200,7 @@ func fakeScaleClient(t *testing.T) (ScalesGetter, []schema.GroupResource) {
}
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeaders(), Body: bytesBody(res)}, nil
case "PATCH":
body, err := ioutil.ReadAll(req.Body)
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}

View File

@@ -65,7 +65,6 @@ package auth
// TODO: need a way to rotate Tokens. Therefore, need a way for client object to be reset when the authcfg is updated.
import (
"encoding/json"
"io/ioutil"
"os"
restclient "k8s.io/client-go/rest"
@@ -90,7 +89,7 @@ func LoadFromFile(path string) (*Info, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err
}
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package auth_test
import (
"io/ioutil"
"os"
"reflect"
"testing"
@@ -42,7 +41,7 @@ func TestLoadFromFile(t *testing.T) {
}
for _, loadAuthInfoTest := range loadAuthInfoTests {
tt := loadAuthInfoTest
aifile, err := ioutil.TempFile("", "testAuthInfo")
aifile, err := os.CreateTemp("", "testAuthInfo")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

View File

@@ -135,7 +135,9 @@ type SharedInformer interface {
// AddEventHandler adds an event handler to the shared informer using the shared informer's resync
// period. Events to a single handler are delivered sequentially, but there is no coordination
// between different handlers.
AddEventHandler(handler ResourceEventHandler)
// It returns a registration handle for the handler that can be used to remove
// the handler again.
AddEventHandler(handler ResourceEventHandler) (ResourceEventHandlerRegistration, error)
// AddEventHandlerWithResyncPeriod adds an event handler to the
// shared informer with the requested resync period; zero means
// this handler does not care about resyncs. The resync operation
@@ -150,7 +152,13 @@ type SharedInformer interface {
// between any two resyncs may be longer than the nominal period
// because the implementation takes time to do work and there may
// be competing load and scheduling noise.
AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration)
// It returns a registration handle for the handler that can be used to remove
// the handler again and an error if the handler cannot be added.
AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) (ResourceEventHandlerRegistration, error)
// RemoveEventHandler removes a formerly added event handler given by
// its registration handle.
// This function is guaranteed to be idempotent, and thread-safe.
RemoveEventHandler(handle ResourceEventHandlerRegistration) error
// GetStore returns the informer's local cache as a Store.
GetStore() Store
// GetController is deprecated, it does nothing useful
@@ -195,8 +203,18 @@ type SharedInformer interface {
// transform before mutating it at all and returning the copy to prevent
// data races.
SetTransform(handler TransformFunc) error
// IsStopped reports whether the informer has already been stopped.
// Adding event handlers to already stopped informers is not possible.
// An informer already stopped will never be started again.
IsStopped() bool
}
// Opaque interface representing the registration of ResourceEventHandler for
// a SharedInformer. Must be supplied back to the same SharedInformer's
// `RemoveEventHandler` to unregister the handlers.
type ResourceEventHandlerRegistration interface{}
// SharedIndexInformer provides add and get Indexers ability based on SharedInformer.
type SharedIndexInformer interface {
SharedInformer
@@ -492,8 +510,8 @@ func (s *sharedIndexInformer) GetController() Controller {
return &dummyController{informer: s}
}
func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) {
s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)
func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) (ResourceEventHandlerRegistration, error) {
return s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)
}
func determineResyncPeriod(desired, check time.Duration) time.Duration {
@@ -513,13 +531,12 @@ func determineResyncPeriod(desired, check time.Duration) time.Duration {
const minimumResyncPeriod = 1 * time.Second
func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) {
func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) (ResourceEventHandlerRegistration, error) {
s.startedLock.Lock()
defer s.startedLock.Unlock()
if s.stopped {
klog.V(2).Infof("Handler %v was not added to shared informer because it has stopped already", handler)
return
return nil, fmt.Errorf("handler %v was not added to shared informer because it has stopped already", handler)
}
if resyncPeriod > 0 {
@@ -545,8 +562,7 @@ func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEv
listener := newProcessListener(handler, resyncPeriod, determineResyncPeriod(resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize)
if !s.started {
s.processor.addListener(listener)
return
return s.processor.addListener(listener), nil
}
// in order to safely join, we have to
@@ -557,10 +573,11 @@ func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEv
s.blockDeltas.Lock()
defer s.blockDeltas.Unlock()
s.processor.addListener(listener)
handle := s.processor.addListener(listener)
for _, item := range s.indexer.List() {
listener.add(addNotification{newObj: item})
}
return handle, nil
}
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
@@ -610,6 +627,26 @@ func (s *sharedIndexInformer) OnDelete(old interface{}) {
s.processor.distribute(deleteNotification{oldObj: old}, false)
}
// IsStopped reports whether the informer has already been stopped
func (s *sharedIndexInformer) IsStopped() bool {
s.startedLock.Lock()
defer s.startedLock.Unlock()
return s.stopped
}
func (s *sharedIndexInformer) RemoveEventHandler(handle ResourceEventHandlerRegistration) error {
s.startedLock.Lock()
defer s.startedLock.Unlock()
// in order to safely remove, we have to
// 1. stop sending add/update/delete notifications
// 2. remove and stop listener
// 3. unblock
s.blockDeltas.Lock()
defer s.blockDeltas.Unlock()
return s.processor.removeListener(handle)
}
// sharedProcessor has a collection of processorListener and can
// distribute a notification object to its listeners. There are two
// kinds of distribute operations. The sync distributions go to a
@@ -619,39 +656,85 @@ func (s *sharedIndexInformer) OnDelete(old interface{}) {
type sharedProcessor struct {
listenersStarted bool
listenersLock sync.RWMutex
listeners []*processorListener
syncingListeners []*processorListener
clock clock.Clock
wg wait.Group
// Map from listeners to whether or not they are currently syncing
listeners map[*processorListener]bool
clock clock.Clock
wg wait.Group
}
func (p *sharedProcessor) addListener(listener *processorListener) {
func (p *sharedProcessor) getListener(registration ResourceEventHandlerRegistration) *processorListener {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
if p.listeners == nil {
return nil
}
if result, ok := registration.(*processorListener); ok {
if _, exists := p.listeners[result]; exists {
return result
}
}
return nil
}
func (p *sharedProcessor) addListener(listener *processorListener) ResourceEventHandlerRegistration {
p.listenersLock.Lock()
defer p.listenersLock.Unlock()
p.addListenerLocked(listener)
if p.listeners == nil {
p.listeners = make(map[*processorListener]bool)
}
p.listeners[listener] = true
if p.listenersStarted {
p.wg.Start(listener.run)
p.wg.Start(listener.pop)
}
return listener
}
func (p *sharedProcessor) addListenerLocked(listener *processorListener) {
p.listeners = append(p.listeners, listener)
p.syncingListeners = append(p.syncingListeners, listener)
func (p *sharedProcessor) removeListener(handle ResourceEventHandlerRegistration) error {
p.listenersLock.Lock()
defer p.listenersLock.Unlock()
listener, ok := handle.(*processorListener)
if !ok {
return fmt.Errorf("invalid key type %t", handle)
} else if p.listeners == nil {
// No listeners are registered, do nothing
return nil
} else if _, exists := p.listeners[listener]; !exists {
// Listener is not registered, just do nothing
return nil
}
delete(p.listeners, listener)
if p.listenersStarted {
close(listener.addCh)
}
return nil
}
func (p *sharedProcessor) distribute(obj interface{}, sync bool) {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
if sync {
for _, listener := range p.syncingListeners {
for listener, isSyncing := range p.listeners {
switch {
case !sync:
// non-sync messages are delivered to every listener
listener.add(obj)
}
} else {
for _, listener := range p.listeners {
case isSyncing:
// sync messages are delivered to every syncing listenter
listener.add(obj)
default:
// skipping a sync obj for a non-syncing listener
}
}
}
@@ -660,18 +743,27 @@ func (p *sharedProcessor) run(stopCh <-chan struct{}) {
func() {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for _, listener := range p.listeners {
for listener := range p.listeners {
p.wg.Start(listener.run)
p.wg.Start(listener.pop)
}
p.listenersStarted = true
}()
<-stopCh
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for _, listener := range p.listeners {
p.listenersLock.Lock()
defer p.listenersLock.Unlock()
for listener := range p.listeners {
close(listener.addCh) // Tell .pop() to stop. .pop() will tell .run() to stop
}
// Wipe out list of listeners since they are now closed
// (processorListener cannot be re-used)
p.listeners = nil
// Reset to false since no listeners are running
p.listenersStarted = false
p.wg.Wait() // Wait for all .pop() and .run() to stop
}
@@ -681,16 +773,16 @@ func (p *sharedProcessor) shouldResync() bool {
p.listenersLock.Lock()
defer p.listenersLock.Unlock()
p.syncingListeners = []*processorListener{}
resyncNeeded := false
now := p.clock.Now()
for _, listener := range p.listeners {
for listener := range p.listeners {
// need to loop through all the listeners to see if they need to resync so we can prepare any
// listeners that are going to be resyncing.
if listener.shouldResync(now) {
shouldResync := listener.shouldResync(now)
p.listeners[listener] = shouldResync
if shouldResync {
resyncNeeded = true
p.syncingListeners = append(p.syncingListeners, listener)
listener.determineNextResync(now)
}
}
@@ -701,8 +793,9 @@ func (p *sharedProcessor) resyncCheckPeriodChanged(resyncCheckPeriod time.Durati
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for _, listener := range p.listeners {
resyncPeriod := determineResyncPeriod(listener.requestedResyncPeriod, resyncCheckPeriod)
for listener := range p.listeners {
resyncPeriod := determineResyncPeriod(
listener.requestedResyncPeriod, resyncCheckPeriod)
listener.setResyncPeriod(resyncPeriod)
}
}

View File

@@ -17,7 +17,10 @@ limitations under the License.
package cache
import (
"context"
"fmt"
"math/rand"
"strconv"
"strings"
"sync"
"testing"
@@ -96,6 +99,25 @@ func (l *testListener) satisfiedExpectations() bool {
return sets.NewString(l.receivedItemNames...).Equal(l.expectedItemNames)
}
func eventHandlerCount(i SharedInformer) int {
s := i.(*sharedIndexInformer)
s.startedLock.Lock()
defer s.startedLock.Unlock()
return len(s.processor.listeners)
}
func isStarted(i SharedInformer) bool {
s := i.(*sharedIndexInformer)
s.startedLock.Lock()
defer s.startedLock.Unlock()
return s.started
}
func isRegistered(i SharedInformer, h ResourceEventHandlerRegistration) bool {
s := i.(*sharedIndexInformer)
return s.processor.getListener(h) != nil
}
func TestListenerResyncPeriods(t *testing.T) {
// source simulates an apiserver object endpoint.
source := fcache.NewFakeControllerSource()
@@ -189,6 +211,7 @@ func TestResyncCheckPeriod(t *testing.T) {
// create the shared informer and resync every 12 hours
informer := NewSharedInformer(source, &v1.Pod{}, 12*time.Hour).(*sharedIndexInformer)
gl := informer.processor.getListener
clock := testingclock.NewFakeClock(time.Now())
informer.clock = clock
@@ -196,59 +219,60 @@ func TestResyncCheckPeriod(t *testing.T) {
// listener 1, never resync
listener1 := newTestListener("listener1", 0)
informer.AddEventHandlerWithResyncPeriod(listener1, listener1.resyncPeriod)
handler1, _ := informer.AddEventHandlerWithResyncPeriod(listener1, listener1.resyncPeriod)
if e, a := 12*time.Hour, informer.resyncCheckPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := time.Duration(0), informer.processor.listeners[0].resyncPeriod; e != a {
if e, a := time.Duration(0), gl(handler1).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
// listener 2, resync every minute
listener2 := newTestListener("listener2", 1*time.Minute)
informer.AddEventHandlerWithResyncPeriod(listener2, listener2.resyncPeriod)
handler2, _ := informer.AddEventHandlerWithResyncPeriod(listener2, listener2.resyncPeriod)
if e, a := 1*time.Minute, informer.resyncCheckPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := time.Duration(0), informer.processor.listeners[0].resyncPeriod; e != a {
if e, a := time.Duration(0), gl(handler1).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := 1*time.Minute, informer.processor.listeners[1].resyncPeriod; e != a {
if e, a := 1*time.Minute, gl(handler2).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
// listener 3, resync every 55 seconds
listener3 := newTestListener("listener3", 55*time.Second)
informer.AddEventHandlerWithResyncPeriod(listener3, listener3.resyncPeriod)
handler3, _ := informer.AddEventHandlerWithResyncPeriod(listener3, listener3.resyncPeriod)
if e, a := 55*time.Second, informer.resyncCheckPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := time.Duration(0), informer.processor.listeners[0].resyncPeriod; e != a {
if e, a := time.Duration(0), gl(handler1).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := 1*time.Minute, informer.processor.listeners[1].resyncPeriod; e != a {
if e, a := 1*time.Minute, gl(handler2).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := 55*time.Second, informer.processor.listeners[2].resyncPeriod; e != a {
if e, a := 55*time.Second, gl(handler3).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
// listener 4, resync every 5 seconds
listener4 := newTestListener("listener4", 5*time.Second)
informer.AddEventHandlerWithResyncPeriod(listener4, listener4.resyncPeriod)
handler4, _ := informer.AddEventHandlerWithResyncPeriod(listener4, listener4.resyncPeriod)
if e, a := 5*time.Second, informer.resyncCheckPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := time.Duration(0), informer.processor.listeners[0].resyncPeriod; e != a {
if e, a := time.Duration(0), gl(handler1).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := 1*time.Minute, informer.processor.listeners[1].resyncPeriod; e != a {
if e, a := 1*time.Minute, gl(handler2).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := 55*time.Second, informer.processor.listeners[2].resyncPeriod; e != a {
if e, a := 55*time.Second, gl(handler3).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
if e, a := 5*time.Second, informer.processor.listeners[3].resyncPeriod; e != a {
if e, a := 5*time.Second, gl(handler4).resyncPeriod; e != a {
t.Errorf("expected %d, got %d", e, a)
}
}
@@ -390,3 +414,528 @@ func TestSharedInformerTransformer(t *testing.T) {
t.Errorf("%s: expected %v, got %v", listenerTransformer.name, listenerTransformer.expectedItemNames, listenerTransformer.receivedItemNames)
}
}
func TestSharedInformerRemoveHandler(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second)
handler1 := &ResourceEventHandlerFuncs{}
handle1, err := informer.AddEventHandler(handler1)
if err != nil {
t.Errorf("informer did not add handler1: %s", err)
return
}
handler2 := &ResourceEventHandlerFuncs{}
handle2, err := informer.AddEventHandler(handler2)
if err != nil {
t.Errorf("informer did not add handler2: %s", err)
return
}
if eventHandlerCount(informer) != 2 {
t.Errorf("informer has %d registered handler, instead of 2", eventHandlerCount(informer))
}
if err := informer.RemoveEventHandler(handle2); err != nil {
t.Errorf("removing of second pointer handler failed: %s", err)
}
if eventHandlerCount(informer) != 1 {
t.Errorf("after removing handler informer has %d registered handler(s), instead of 1", eventHandlerCount(informer))
}
if err := informer.RemoveEventHandler(handle1); err != nil {
t.Errorf("removing of first pointer handler failed: %s", err)
}
if eventHandlerCount(informer) != 0 {
t.Errorf("informer still has registered handlers after removing both handlers")
}
}
func TestSharedInformerRemoveForeignHandler(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
source2 := fcache.NewFakeControllerSource()
source2.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer2 := NewSharedInformer(source2, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
handler1 := &ResourceEventHandlerFuncs{}
handle1, err := informer.AddEventHandler(handler1)
if err != nil {
t.Errorf("informer did not add handler1: %s", err)
return
}
handler2 := &ResourceEventHandlerFuncs{}
handle2, err := informer.AddEventHandler(handler2)
if err != nil {
t.Errorf("informer did not add handler2: %s", err)
return
}
if eventHandlerCount(informer) != 2 {
t.Errorf("informer has %d registered handler, instead of 2", eventHandlerCount(informer))
}
if eventHandlerCount(informer2) != 0 {
t.Errorf("informer2 has %d registered handler, instead of 0", eventHandlerCount(informer2))
}
// remove handle at foreign informer
if isRegistered(informer2, handle1) {
t.Errorf("handle1 registered for informer2")
}
if isRegistered(informer2, handle2) {
t.Errorf("handle2 registered for informer2")
}
if err := informer2.RemoveEventHandler(handle1); err != nil {
t.Errorf("removing of second pointer handler failed: %s", err)
}
if eventHandlerCount(informer) != 2 {
t.Errorf("informer has %d registered handler, instead of 2", eventHandlerCount(informer))
}
if eventHandlerCount(informer2) != 0 {
t.Errorf("informer2 has %d registered handler, instead of 0", eventHandlerCount(informer2))
}
if !isRegistered(informer, handle1) {
t.Errorf("handle1 not registered anymore for informer")
}
if !isRegistered(informer, handle2) {
t.Errorf("handle2 not registered anymore for informer")
}
if eventHandlerCount(informer) != 2 {
t.Errorf("informer has %d registered handler, instead of 2", eventHandlerCount(informer))
}
if eventHandlerCount(informer2) != 0 {
t.Errorf("informer2 has %d registered handler, instead of 0", eventHandlerCount(informer2))
}
if !isRegistered(informer, handle1) {
t.Errorf("handle1 not registered anymore for informer")
}
if !isRegistered(informer, handle2) {
t.Errorf("handle2 not registered anymore for informer")
}
if err := informer.RemoveEventHandler(handle2); err != nil {
t.Errorf("removing of second pointer handler failed: %s", err)
}
if eventHandlerCount(informer) != 1 {
t.Errorf("after removing handler informer has %d registered handler(s), instead of 1", eventHandlerCount(informer))
}
if err := informer.RemoveEventHandler(handle1); err != nil {
t.Errorf("removing of first pointer handler failed: %s", err)
}
if eventHandlerCount(informer) != 0 {
t.Errorf("informer still has registered handlers after removing both handlers")
}
}
func TestSharedInformerMultipleRegistration(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
handler1 := &ResourceEventHandlerFuncs{}
reg1, err := informer.AddEventHandler(handler1)
if err != nil {
t.Errorf("informer did not add handler for the first time: %s", err)
return
}
if !isRegistered(informer, reg1) {
t.Errorf("handle1 is not active after successful registration")
return
}
reg2, err := informer.AddEventHandler(handler1)
if err != nil {
t.Errorf("informer did not add handler for the second: %s", err)
return
}
if !isRegistered(informer, reg2) {
t.Errorf("handle2 is not active after successful registration")
return
}
if eventHandlerCount(informer) != 2 {
t.Errorf("informer has %d registered handler(s), instead of 2", eventHandlerCount(informer))
}
if err := informer.RemoveEventHandler(reg1); err != nil {
t.Errorf("removing of duplicate handler registration failed: %s", err)
}
if isRegistered(informer, reg1) {
t.Errorf("handle1 is still active after successful remove")
return
}
if !isRegistered(informer, reg2) {
t.Errorf("handle2 is not active after removing handle1")
return
}
if eventHandlerCount(informer) != 1 {
if eventHandlerCount(informer) == 0 {
t.Errorf("informer has no registered handler anymore after removal of duplicate registrations")
} else {
t.Errorf("informer has unexpected number (%d) of handlers after removal of duplicate handler registration", eventHandlerCount(informer))
}
}
if err := informer.RemoveEventHandler(reg2); err != nil {
t.Errorf("removing of second handler registration failed: %s", err)
}
if isRegistered(informer, reg2) {
t.Errorf("handle2 is still active after successful remove")
return
}
if eventHandlerCount(informer) != 0 {
t.Errorf("informer has unexpected number (%d) of handlers after removal of second handler registrations", eventHandlerCount(informer))
}
}
func TestRemovingRemovedSharedInformer(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
handler := &ResourceEventHandlerFuncs{}
reg, err := informer.AddEventHandler(handler)
if err != nil {
t.Errorf("informer did not add handler for the first time: %s", err)
return
}
if err := informer.RemoveEventHandler(reg); err != nil {
t.Errorf("removing of handler registration failed: %s", err)
return
}
if isRegistered(informer, reg) {
t.Errorf("handle is still active after successful remove")
return
}
if err := informer.RemoveEventHandler(reg); err != nil {
t.Errorf("removing of already removed registration yields unexpected error: %s", err)
}
if isRegistered(informer, reg) {
t.Errorf("handle is still active after second remove")
return
}
}
// Shows that many concurrent goroutines can be manipulating shared informer
// listeners without tripping it up. There are not really many assertions in this
// test. Meant to be run with -race to find race conditions
func TestSharedInformerHandlerAbuse(t *testing.T) {
source := fcache.NewFakeControllerSource()
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
ctx, cancel := context.WithCancel(context.Background())
informerCtx, informerCancel := context.WithCancel(context.Background())
go func() {
informer.Run(informerCtx.Done())
cancel()
}()
worker := func() {
// Keep adding and removing handler
// Make sure no duplicate events?
funcs := ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
UpdateFunc: func(oldObj, newObj interface{}) {},
DeleteFunc: func(obj interface{}) {},
}
handles := []ResourceEventHandlerRegistration{}
for {
select {
case <-ctx.Done():
return
default:
switch rand.Intn(2) {
case 0:
// Register handler again
reg, err := informer.AddEventHandlerWithResyncPeriod(funcs, 1*time.Second)
if err != nil {
if strings.Contains(err.Error(), "stopped already") {
// test is over
return
}
t.Errorf("failed to add handler: %v", err)
return
}
handles = append(handles, reg)
case 1:
// Remove a random handler
if len(handles) == 0 {
continue
}
idx := rand.Intn(len(handles))
err := informer.RemoveEventHandler(handles[idx])
if err != nil {
if strings.Contains(err.Error(), "stopped already") {
// test is over
return
}
t.Errorf("failed to remove handler: %v", err)
return
}
handles = append(handles[:idx], handles[idx+1:]...)
}
}
}
}
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
worker()
wg.Done()
}()
}
objs := []*v1.Pod{}
// While workers run, randomly create events for the informer
for i := 0; i < 10000; i++ {
if len(objs) == 0 {
// Make sure there is always an object
obj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{
Name: "pod" + strconv.Itoa(i),
}}
objs = append(objs, obj)
// deep copy before adding since the Modify function mutates the obj
source.Add(obj.DeepCopy())
}
switch rand.Intn(3) {
case 0:
// Add Object
obj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{
Name: "pod" + strconv.Itoa(i),
}}
objs = append(objs, obj)
source.Add(obj.DeepCopy())
case 1:
// Update Object
idx := rand.Intn(len(objs))
source.Modify(objs[idx].DeepCopy())
case 2:
// Remove Object
idx := rand.Intn(len(objs))
source.Delete(objs[idx].DeepCopy())
objs = append(objs[:idx], objs[idx+1:]...)
}
}
// sotp informer which stops workers. stopping informer first to exercise
// contention for informer while it is closing
informerCancel()
// wait for workers to finish since they may throw errors
wg.Wait()
}
func TestStateSharedInformer(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
listener := newTestListener("listener", 0, "pod1")
informer.AddEventHandlerWithResyncPeriod(listener, listener.resyncPeriod)
if isStarted(informer) {
t.Errorf("informer already started after creation")
return
}
if informer.IsStopped() {
t.Errorf("informer already stopped after creation")
return
}
stop := make(chan struct{})
go informer.Run(stop)
if !listener.ok() {
t.Errorf("informer did not report initial objects")
close(stop)
return
}
if !isStarted(informer) {
t.Errorf("informer does not report to be started although handling events")
close(stop)
return
}
if informer.IsStopped() {
t.Errorf("informer reports to be stopped although stop channel not closed")
close(stop)
return
}
close(stop)
fmt.Println("sleeping")
time.Sleep(1 * time.Second)
if !informer.IsStopped() {
t.Errorf("informer reports not to be stopped although stop channel closed")
return
}
if !isStarted(informer) {
t.Errorf("informer reports not to be started after it has been started and stopped")
return
}
}
func TestAddOnStoppedSharedInformer(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
listener := newTestListener("listener", 0, "pod1")
stop := make(chan struct{})
go informer.Run(stop)
close(stop)
err := wait.PollImmediate(100*time.Millisecond, 2*time.Second, func() (bool, error) {
if informer.IsStopped() {
return true, nil
}
return false, nil
})
if err != nil {
t.Errorf("informer reports not to be stopped although stop channel closed")
return
}
_, err = informer.AddEventHandlerWithResyncPeriod(listener, listener.resyncPeriod)
if err == nil {
t.Errorf("stopped informer did not reject add handler")
return
}
if !strings.HasSuffix(err.Error(), "was not added to shared informer because it has stopped already") {
t.Errorf("adding handler to a stopped informer yields unexpected error: %s", err)
return
}
}
func TestRemoveOnStoppedSharedInformer(t *testing.T) {
source := fcache.NewFakeControllerSource()
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
informer := NewSharedInformer(source, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer)
listener := newTestListener("listener", 0, "pod1")
handle, err := informer.AddEventHandlerWithResyncPeriod(listener, listener.resyncPeriod)
if err != nil {
t.Errorf("informer did not add handler: %s", err)
return
}
stop := make(chan struct{})
go informer.Run(stop)
close(stop)
fmt.Println("sleeping")
time.Sleep(1 * time.Second)
if !informer.IsStopped() {
t.Errorf("informer reports not to be stopped although stop channel closed")
return
}
err = informer.RemoveEventHandler(handle)
if err != nil {
t.Errorf("informer does not remove handler on stopped informer")
return
}
}
func TestRemoveWhileActive(t *testing.T) {
// source simulates an apiserver object endpoint.
source := fcache.NewFakeControllerSource()
// create the shared informer and resync every 12 hours
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
listener := newTestListener("listener", 0, "pod1")
handle, _ := informer.AddEventHandler(listener)
stop := make(chan struct{})
defer close(stop)
go informer.Run(stop)
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
if !listener.ok() {
t.Errorf("event did not occur")
return
}
informer.RemoveEventHandler(handle)
if isRegistered(informer, handle) {
t.Errorf("handle is still active after successful remove")
return
}
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod2"}})
if !listener.ok() {
t.Errorf("unexpected event occurred")
return
}
}
func TestAddWhileActive(t *testing.T) {
// source simulates an apiserver object endpoint.
source := fcache.NewFakeControllerSource()
// create the shared informer and resync every 12 hours
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
listener1 := newTestListener("originalListener", 0, "pod1")
listener2 := newTestListener("originalListener", 0, "pod1", "pod2")
handle1, _ := informer.AddEventHandler(listener1)
stop := make(chan struct{})
defer close(stop)
go informer.Run(stop)
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
if !listener1.ok() {
t.Errorf("events on listener1 did not occur")
return
}
handle2, _ := informer.AddEventHandler(listener2)
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod2"}})
if !listener2.ok() {
t.Errorf("event on listener2 did not occur")
return
}
if !isRegistered(informer, handle1) {
t.Errorf("handle1 is not active")
return
}
if !isRegistered(informer, handle2) {
t.Errorf("handle2 is not active")
return
}
listener1.expectedItemNames = listener2.expectedItemNames
if !listener1.ok() {
t.Errorf("events on listener1 did not occur")
return
}
}

View File

@@ -20,7 +20,6 @@ import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
@@ -152,7 +151,7 @@ func FlattenContent(path *string, contents *[]byte, baseDir string) error {
var err error
absPath := ResolvePath(*path, baseDir)
*contents, err = ioutil.ReadFile(absPath)
*contents, err = os.ReadFile(absPath)
if err != nil {
return err
}

View File

@@ -18,7 +18,6 @@ package api
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
@@ -27,13 +26,13 @@ import (
)
func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caContent string, t *testing.T) Config {
if err := ioutil.WriteFile(certFile, []byte(certContent), 0644); err != nil {
if err := os.WriteFile(certFile, []byte(certContent), 0644); err != nil {
t.Errorf("unexpected error: %v", err)
}
if err := ioutil.WriteFile(keyFile, []byte(keyContent), 0600); err != nil {
if err := os.WriteFile(keyFile, []byte(keyContent), 0600); err != nil {
t.Errorf("unexpected error: %v", err)
}
if err := ioutil.WriteFile(caFile, []byte(caContent), 0644); err != nil {
if err := os.WriteFile(caFile, []byte(caContent), 0644); err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -52,11 +51,11 @@ func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caConte
}
func TestMinifySuccess(t *testing.T) {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
@@ -88,11 +87,11 @@ func TestMinifySuccess(t *testing.T) {
}
func TestMinifyMissingContext(t *testing.T) {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
@@ -106,11 +105,11 @@ func TestMinifyMissingContext(t *testing.T) {
}
func TestMinifyMissingCluster(t *testing.T) {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
@@ -124,11 +123,11 @@ func TestMinifyMissingCluster(t *testing.T) {
}
func TestMinifyMissingAuthInfo(t *testing.T) {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
@@ -142,11 +141,11 @@ func TestMinifyMissingAuthInfo(t *testing.T) {
}
func TestFlattenSuccess(t *testing.T) {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
certData := "cert"
@@ -207,11 +206,11 @@ func TestFlattenSuccess(t *testing.T) {
}
func Example_minifyAndShorten() {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
certData := "cert"
@@ -247,11 +246,11 @@ func Example_minifyAndShorten() {
}
func TestShortenSuccess(t *testing.T) {
certFile, _ := ioutil.TempFile("", "")
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := ioutil.TempFile("", "")
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := ioutil.TempFile("", "")
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())
certData := "cert"

View File

@@ -93,6 +93,11 @@ type Cluster struct {
// attach, port forward).
// +optional
ProxyURL string `json:"proxy-url,omitempty"`
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
// +optional
DisableCompression bool `json:"disable-compression,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
// +optional
Extensions map[string]runtime.Object `json:"extensions,omitempty"`

View File

@@ -46,10 +46,12 @@ func Example_ofOptionsConfig() {
Server: "https://alfa.org:8080",
InsecureSkipTLSVerify: true,
CertificateAuthority: "path/to/my/cert-ca-filename",
DisableCompression: true,
}
defaultConfig.Clusters["bravo"] = &Cluster{
Server: "https://bravo.org:8080",
InsecureSkipTLSVerify: false,
DisableCompression: false,
}
defaultConfig.AuthInfos["white-mage-via-cert"] = &AuthInfo{
ClientCertificate: "path/to/my/client-cert-filename",
@@ -94,6 +96,7 @@ func Example_ofOptionsConfig() {
// alfa:
// LocationOfOrigin: ""
// certificate-authority: path/to/my/cert-ca-filename
// disable-compression: true
// insecure-skip-tls-verify: true
// server: https://alfa.org:8080
// bravo:

View File

@@ -86,6 +86,11 @@ type Cluster struct {
// attach, port forward).
// +optional
ProxyURL string `json:"proxy-url,omitempty"`
// DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful
// to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on
// compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296.
// +optional
DisableCompression bool `json:"disable-compression,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
// +optional
Extensions []NamedExtension `json:"extensions,omitempty"`

View File

@@ -257,6 +257,7 @@ func autoConvert_v1_Cluster_To_api_Cluster(in *Cluster, out *api.Cluster, s conv
out.CertificateAuthority = in.CertificateAuthority
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
out.ProxyURL = in.ProxyURL
out.DisableCompression = in.DisableCompression
if err := Convert_Slice_v1_NamedExtension_To_Map_string_To_runtime_Object(&in.Extensions, &out.Extensions, s); err != nil {
return err
}
@@ -276,6 +277,7 @@ func autoConvert_api_Cluster_To_v1_Cluster(in *api.Cluster, out *Cluster, s conv
out.CertificateAuthority = in.CertificateAuthority
out.CertificateAuthorityData = *(*[]byte)(unsafe.Pointer(&in.CertificateAuthorityData))
out.ProxyURL = in.ProxyURL
out.DisableCompression = in.DisableCompression
if err := Convert_Map_string_To_runtime_Object_To_Slice_v1_NamedExtension(&in.Extensions, &out.Extensions, s); err != nil {
return err
}

View File

@@ -20,7 +20,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"golang.org/x/term"
@@ -59,7 +58,7 @@ func (a *PromptingAuthLoader) LoadAuth(path string) (*clientauth.Info, error) {
if err != nil {
return &auth, err
}
err = ioutil.WriteFile(path, data, 0600)
err = os.WriteFile(path, data, 0600)
return &auth, err
}
authPtr, err := clientauth.LoadFromFile(path)

View File

@@ -19,7 +19,6 @@ package clientcmd
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
@@ -165,6 +164,8 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
clientConfig.Proxy = http.ProxyURL(u)
}
clientConfig.DisableCompression = configClusterInfo.DisableCompression
if config.overrides != nil && len(config.overrides.Timeout) > 0 {
timeout, err := ParseTimeout(config.overrides.Timeout)
if err != nil {
@@ -246,7 +247,7 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
mergedConfig.BearerToken = configAuthInfo.Token
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
} else if len(configAuthInfo.TokenFile) > 0 {
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
tokenBytes, err := os.ReadFile(configAuthInfo.TokenFile)
if err != nil {
return nil, err
}
@@ -586,7 +587,7 @@ func (config *inClusterClientConfig) Namespace() (string, bool, error) {
}
// Fall back to the namespace associated with the service account token, if available
if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
return ns, false, nil
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package clientcmd
import (
"io/ioutil"
"os"
"reflect"
"strings"
@@ -139,6 +138,22 @@ func createCAValidTestConfig() *clientcmdapi.Config {
return config
}
func TestDisableCompression(t *testing.T) {
config := createValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
DisableCompression: true,
},
}, nil)
actualCfg, err := clientBuilder.ClientConfig()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
matchBoolArg(true, actualCfg.DisableCompression, t)
}
func TestInsecureOverridesCA(t *testing.T) {
config := createCAValidTestConfig()
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
@@ -158,7 +173,7 @@ func TestInsecureOverridesCA(t *testing.T) {
}
func TestCAOverridesCAData(t *testing.T) {
file, err := ioutil.TempFile("", "my.ca")
file, err := os.CreateTemp("", "my.ca")
if err != nil {
t.Fatalf("could not create tempfile: %v", err)
}
@@ -293,7 +308,7 @@ func TestModifyContext(t *testing.T) {
"clean": true,
}
tempPath, err := ioutil.TempFile("", "testclientcmd-")
tempPath, err := os.CreateTemp("", "testclientcmd-")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -478,13 +493,13 @@ func TestBasicAuthData(t *testing.T) {
func TestBasicTokenFile(t *testing.T) {
token := "exampletoken"
f, err := ioutil.TempFile("", "tokenfile")
f, err := os.CreateTemp("", "tokenfile")
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
defer os.Remove(f.Name())
if err := ioutil.WriteFile(f.Name(), []byte(token), 0644); err != nil {
if err := os.WriteFile(f.Name(), []byte(token), 0644); err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
@@ -514,13 +529,13 @@ func TestBasicTokenFile(t *testing.T) {
func TestPrecedenceTokenFile(t *testing.T) {
token := "exampletoken"
f, err := ioutil.TempFile("", "tokenfile")
f, err := os.CreateTemp("", "tokenfile")
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
defer os.Remove(f.Name())
if err := ioutil.WriteFile(f.Name(), []byte(token), 0644); err != nil {
if err := os.WriteFile(f.Name(), []byte(token), 0644); err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
@@ -904,12 +919,12 @@ users:
command: foo-command
provideClusterInfo: true
`
tmpfile, err := ioutil.TempFile("", "kubeconfig")
tmpfile, err := os.CreateTemp("", "kubeconfig")
if err != nil {
t.Error(err)
}
defer os.Remove(tmpfile.Name())
if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
if err := os.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
t.Error(err)
}
config, err := BuildConfigFromFlags("", tmpfile.Name())

View File

@@ -18,7 +18,6 @@ package clientcmd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@@ -283,12 +282,12 @@ func (rules *ClientConfigLoadingRules) Migrate() error {
return fmt.Errorf("cannot migrate %v to %v because it is a directory", source, destination)
}
data, err := ioutil.ReadFile(source)
data, err := os.ReadFile(source)
if err != nil {
return err
}
// destination is created with mode 0666 before umask
err = ioutil.WriteFile(destination, data, 0666)
err = os.WriteFile(destination, data, 0666)
if err != nil {
return err
}
@@ -363,7 +362,7 @@ func (rules *ClientConfigLoadingRules) IsDefaultConfig(config *restclient.Config
// LoadFromFile takes a filename and deserializes the contents into Config object
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
kubeconfigBytes, err := ioutil.ReadFile(filename)
kubeconfigBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
@@ -429,7 +428,7 @@ func WriteToFile(config clientcmdapi.Config, filename string) error {
}
}
if err := ioutil.WriteFile(filename, content, 0600); err != nil {
if err := os.WriteFile(filename, content, 0600); err != nil {
return err
}
return nil

View File

@@ -19,7 +19,6 @@ package clientcmd
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
@@ -74,8 +73,8 @@ var (
"red-user": {Token: "a-different-red-token"},
"yellow-user": {Token: "yellow-token"}},
Clusters: map[string]*clientcmdapi.Cluster{
"cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true},
"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}},
"cow-cluster": {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true, DisableCompression: true},
"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true, DisableCompression: true}},
CurrentContext: "federal-context",
}
)
@@ -132,10 +131,10 @@ func TestToleratingMissingFiles(t *testing.T) {
}
func TestErrorReadingFile(t *testing.T) {
commandLineFile, _ := ioutil.TempFile("", "")
commandLineFile, _ := os.CreateTemp("", "")
defer os.Remove(commandLineFile.Name())
if err := ioutil.WriteFile(commandLineFile.Name(), []byte("bogus value"), 0644); err != nil {
if err := os.WriteFile(commandLineFile.Name(), []byte("bogus value"), 0644); err != nil {
t.Fatalf("Error creating tempfile: %v", err)
}
@@ -153,7 +152,7 @@ func TestErrorReadingFile(t *testing.T) {
}
func TestErrorReadingNonFile(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
tmpdir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatalf("Couldn't create tmpdir")
}
@@ -173,9 +172,9 @@ func TestErrorReadingNonFile(t *testing.T) {
}
func TestConflictingCurrentContext(t *testing.T) {
commandLineFile, _ := ioutil.TempFile("", "")
commandLineFile, _ := os.CreateTemp("", "")
defer os.Remove(commandLineFile.Name())
envVarFile, _ := ioutil.TempFile("", "")
envVarFile, _ := os.CreateTemp("", "")
defer os.Remove(envVarFile.Name())
mockCommandLineConfig := clientcmdapi.Config{
@@ -254,7 +253,7 @@ users: null
}
func TestLoadingEmptyMaps(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
configFile, _ := os.CreateTemp("", "")
defer os.Remove(configFile.Name())
mockConfig := clientcmdapi.Config{
@@ -280,10 +279,10 @@ func TestLoadingEmptyMaps(t *testing.T) {
}
func TestDuplicateClusterName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
configFile, _ := os.CreateTemp("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
err := os.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
@@ -322,10 +321,10 @@ users:
}
func TestDuplicateContextName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
configFile, _ := os.CreateTemp("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
err := os.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
@@ -364,10 +363,10 @@ users:
}
func TestDuplicateUserName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
configFile, _ := os.CreateTemp("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
err := os.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
@@ -404,10 +403,10 @@ users:
}
func TestDuplicateExtensionName(t *testing.T) {
configFile, _ := ioutil.TempFile("", "")
configFile, _ := os.CreateTemp("", "")
defer os.Remove(configFile.Name())
err := ioutil.WriteFile(configFile.Name(), []byte(`
err := os.WriteFile(configFile.Name(), []byte(`
kind: Config
apiVersion: v1
clusters:
@@ -472,14 +471,14 @@ func TestResolveRelativePaths(t *testing.T) {
},
}
configDir1, _ := ioutil.TempDir("", "")
configDir1, _ := os.MkdirTemp("", "")
defer os.RemoveAll(configDir1)
configFile1 := path.Join(configDir1, ".kubeconfig")
configDir1, _ = filepath.Abs(configDir1)
configDir2, _ := ioutil.TempDir("", "")
configDir2, _ := os.MkdirTemp("", "")
defer os.RemoveAll(configDir2)
configDir2, _ = ioutil.TempDir(configDir2, "")
configDir2, _ = os.MkdirTemp(configDir2, "")
configFile2 := path.Join(configDir2, ".kubeconfig")
configDir2, _ = filepath.Abs(configDir2)
@@ -560,9 +559,9 @@ func TestResolveRelativePaths(t *testing.T) {
}
func TestMigratingFile(t *testing.T) {
sourceFile, _ := ioutil.TempFile("", "")
sourceFile, _ := os.CreateTemp("", "")
defer os.Remove(sourceFile.Name())
destinationFile, _ := ioutil.TempFile("", "")
destinationFile, _ := os.CreateTemp("", "")
// delete the file so that we'll write to it
os.Remove(destinationFile.Name())
@@ -579,11 +578,11 @@ func TestMigratingFile(t *testing.T) {
// the load should have recreated this file
defer os.Remove(destinationFile.Name())
sourceContent, err := ioutil.ReadFile(sourceFile.Name())
sourceContent, err := os.ReadFile(sourceFile.Name())
if err != nil {
t.Errorf("unexpected error %v", err)
}
destinationContent, err := ioutil.ReadFile(destinationFile.Name())
destinationContent, err := os.ReadFile(destinationFile.Name())
if err != nil {
t.Errorf("unexpected error %v", err)
}
@@ -594,9 +593,9 @@ func TestMigratingFile(t *testing.T) {
}
func TestMigratingFileLeaveExistingFileAlone(t *testing.T) {
sourceFile, _ := ioutil.TempFile("", "")
sourceFile, _ := os.CreateTemp("", "")
defer os.Remove(sourceFile.Name())
destinationFile, _ := ioutil.TempFile("", "")
destinationFile, _ := os.CreateTemp("", "")
defer os.Remove(destinationFile.Name())
WriteToFile(testConfigAlfa, sourceFile.Name())
@@ -609,7 +608,7 @@ func TestMigratingFileLeaveExistingFileAlone(t *testing.T) {
t.Errorf("unexpected error %v", err)
}
destinationContent, err := ioutil.ReadFile(destinationFile.Name())
destinationContent, err := os.ReadFile(destinationFile.Name())
if err != nil {
t.Errorf("unexpected error %v", err)
}
@@ -621,7 +620,7 @@ func TestMigratingFileLeaveExistingFileAlone(t *testing.T) {
func TestMigratingFileSourceMissingSkip(t *testing.T) {
sourceFilename := "some-missing-file"
destinationFile, _ := ioutil.TempFile("", "")
destinationFile, _ := os.CreateTemp("", "")
// delete the file so that we'll write to it
os.Remove(destinationFile.Name())
@@ -639,7 +638,7 @@ func TestMigratingFileSourceMissingSkip(t *testing.T) {
}
func TestFileLocking(t *testing.T) {
f, _ := ioutil.TempFile("", "")
f, _ := os.CreateTemp("", "")
defer os.Remove(f.Name())
err := lockFile(f.Name())
@@ -655,9 +654,9 @@ func TestFileLocking(t *testing.T) {
}
func Example_noMergingOnExplicitPaths() {
commandLineFile, _ := ioutil.TempFile("", "")
commandLineFile, _ := os.CreateTemp("", "")
defer os.Remove(commandLineFile.Name())
envVarFile, _ := ioutil.TempFile("", "")
envVarFile, _ := os.CreateTemp("", "")
defer os.Remove(envVarFile.Name())
WriteToFile(testConfigAlfa, commandLineFile.Name())
@@ -704,9 +703,9 @@ func Example_noMergingOnExplicitPaths() {
}
func Example_mergingSomeWithConflict() {
commandLineFile, _ := ioutil.TempFile("", "")
commandLineFile, _ := os.CreateTemp("", "")
defer os.Remove(commandLineFile.Name())
envVarFile, _ := ioutil.TempFile("", "")
envVarFile, _ := os.CreateTemp("", "")
defer os.Remove(envVarFile.Name())
WriteToFile(testConfigAlfa, commandLineFile.Name())
@@ -737,6 +736,7 @@ func Example_mergingSomeWithConflict() {
// server: http://cow.org:8080
// name: cow-cluster
// - cluster:
// disable-compression: true
// insecure-skip-tls-verify: true
// server: http://donkey.org:8080
// name: donkey-cluster
@@ -759,13 +759,13 @@ func Example_mergingSomeWithConflict() {
}
func Example_mergingEverythingNoConflicts() {
commandLineFile, _ := ioutil.TempFile("", "")
commandLineFile, _ := os.CreateTemp("", "")
defer os.Remove(commandLineFile.Name())
envVarFile, _ := ioutil.TempFile("", "")
envVarFile, _ := os.CreateTemp("", "")
defer os.Remove(envVarFile.Name())
currentDirFile, _ := ioutil.TempFile("", "")
currentDirFile, _ := os.CreateTemp("", "")
defer os.Remove(currentDirFile.Name())
homeDirFile, _ := ioutil.TempFile("", "")
homeDirFile, _ := os.CreateTemp("", "")
defer os.Remove(homeDirFile.Name())
WriteToFile(testConfigAlfa, commandLineFile.Name())

View File

@@ -17,14 +17,13 @@ limitations under the License.
package clientcmd
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestMain(m *testing.M) {
tmp, err := ioutil.TempDir("", "testkubeconfig")
tmp, err := os.MkdirTemp("", "testkubeconfig")
if err != nil {
panic(err)
}

View File

@@ -74,6 +74,7 @@ type ClusterOverrideFlags struct {
InsecureSkipTLSVerify FlagInfo
TLSServerName FlagInfo
ProxyURL FlagInfo
DisableCompression FlagInfo
}
// FlagInfo contains information about how to register a flag. This struct is useful if you want to provide a way for an extender to
@@ -143,25 +144,26 @@ func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) FlagInfo {
}
const (
FlagClusterName = "cluster"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagNamespace = "namespace"
FlagAPIServer = "server"
FlagTLSServerName = "tls-server-name"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagEmbedCerts = "embed-certs"
FlagBearerToken = "token"
FlagImpersonate = "as"
FlagImpersonateUID = "as-uid"
FlagImpersonateGroup = "as-group"
FlagUsername = "username"
FlagPassword = "password"
FlagTimeout = "request-timeout"
FlagProxyURL = "proxy-url"
FlagClusterName = "cluster"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagNamespace = "namespace"
FlagAPIServer = "server"
FlagTLSServerName = "tls-server-name"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagEmbedCerts = "embed-certs"
FlagBearerToken = "token"
FlagImpersonate = "as"
FlagImpersonateUID = "as-uid"
FlagImpersonateGroup = "as-group"
FlagUsername = "username"
FlagPassword = "password"
FlagTimeout = "request-timeout"
FlagProxyURL = "proxy-url"
FlagDisableCompression = "disable-compression"
)
// RecommendedConfigOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
@@ -198,6 +200,7 @@ func RecommendedClusterOverrideFlags(prefix string) ClusterOverrideFlags {
InsecureSkipTLSVerify: FlagInfo{prefix + FlagInsecure, "", "false", "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure"},
TLSServerName: FlagInfo{prefix + FlagTLSServerName, "", "", "If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used."},
ProxyURL: FlagInfo{prefix + FlagProxyURL, "", "", "If provided, this URL will be used to connect via proxy"},
DisableCompression: FlagInfo{prefix + FlagDisableCompression, "", "", "If true, opt-out of response compression for all requests to the server"},
}
}
@@ -238,6 +241,7 @@ func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, f
flagNames.InsecureSkipTLSVerify.BindBoolFlag(flags, &clusterInfo.InsecureSkipTLSVerify)
flagNames.TLSServerName.BindStringFlag(flags, &clusterInfo.TLSServerName)
flagNames.ProxyURL.BindStringFlag(flags, &clusterInfo.ProxyURL)
flagNames.DisableCompression.BindBoolFlag(flags, &clusterInfo.DisableCompression)
}
// BindFlags is a convenience method to bind the specified flags to their associated variables

View File

@@ -19,7 +19,6 @@ package clientcmd
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
@@ -296,7 +295,7 @@ func TestValidateCleanClusterInfo(t *testing.T) {
}
func TestValidateCleanWithCAClusterInfo(t *testing.T) {
tempFile, _ := ioutil.TempFile("", "")
tempFile, _ := os.CreateTemp("", "")
defer os.Remove(tempFile.Name())
config := clientcmdapi.NewConfig()
@@ -339,7 +338,7 @@ func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
}
func TestValidateCertDataOverridesFiles(t *testing.T) {
tempFile, _ := ioutil.TempFile("", "")
tempFile, _ := os.CreateTemp("", "")
defer os.Remove(tempFile.Name())
config := clientcmdapi.NewConfig()
@@ -359,7 +358,7 @@ func TestValidateCertDataOverridesFiles(t *testing.T) {
}
func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
tempFile, _ := ioutil.TempFile("", "")
tempFile, _ := os.CreateTemp("", "")
defer os.Remove(tempFile.Name())
config := clientcmdapi.NewConfig()

View File

@@ -1019,58 +1019,21 @@ func testReleaseOnCancellation(t *testing.T, objectType string) {
onRelease = make(chan struct{})
lockObj runtime.Object
gets int
updates int
wg sync.WaitGroup
)
resetVars := func() {
onNewLeader = make(chan struct{})
onRenewCalled = make(chan struct{})
onRenewResume = make(chan struct{})
onRelease = make(chan struct{})
resourceLockConfig := rl.ResourceLockConfig{
Identity: "baz",
EventRecorder: &record.FakeRecorder{},
lockObj = nil
gets = 0
updates = 0
}
c := &fake.Clientset{}
c.AddReactor("get", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
if lockObj != nil {
return true, lockObj, nil
}
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
})
// create lock
c.AddReactor("create", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockObj = action.(fakeclient.CreateAction).GetObject()
return true, lockObj, nil
})
c.AddReactor("update", objectType, func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
// Second update (first renew) should return our canceled error
// FakeClient doesn't do anything with the context so we're doing this ourselves
if updates == 2 {
close(onRenewCalled)
<-onRenewResume
return true, nil, context.Canceled
} else if updates == 3 {
close(onRelease)
}
lockObj = action.(fakeclient.UpdateAction).GetObject()
return true, lockObj, nil
})
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
t.Errorf("unreachable action. testclient called too many times: %+v", action)
return true, nil, fmt.Errorf("unreachable action")
})
lock, err := rl.New(objectType, "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig)
if err != nil {
t.Fatal("resourcelock.New() = ", err)
}
lec := LeaderElectionConfig{
Lock: lock,
LeaseDuration: 15 * time.Second,
RenewDeadline: 2 * time.Second,
RetryPeriod: 1 * time.Second,
@@ -1087,40 +1050,168 @@ func testReleaseOnCancellation(t *testing.T, objectType string) {
},
}
elector, err := NewLeaderElector(lec)
if err != nil {
t.Fatal("Failed to create leader elector: ", err)
tests := []struct {
name string
reactors []Reactor
}{
{
name: "release acquired lock on cancellation of update",
reactors: []Reactor{
{
verb: "get",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
gets++
if lockObj != nil {
return true, lockObj, nil
}
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
},
},
{
verb: "create",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockObj = action.(fakeclient.CreateAction).GetObject()
return true, lockObj, nil
},
},
{
verb: "update",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
// Second update (first renew) should return our canceled error
// FakeClient doesn't do anything with the context so we're doing this ourselves
if updates == 2 {
close(onRenewCalled)
<-onRenewResume
return true, nil, context.Canceled
} else if updates == 3 {
// We update the lock after the cancellation to release it
// This wg is to avoid the data race on lockObj
defer wg.Done()
close(onRelease)
}
lockObj = action.(fakeclient.UpdateAction).GetObject()
return true, lockObj, nil
},
},
},
},
{
name: "release acquired lock on cancellation of get",
reactors: []Reactor{
{
verb: "get",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
gets++
if lockObj != nil {
// Third and more get (first create, second renew) should return our canceled error
// FakeClient doesn't do anything with the context so we're doing this ourselves
if gets >= 3 {
close(onRenewCalled)
<-onRenewResume
return true, nil, context.Canceled
}
return true, lockObj, nil
}
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
},
},
{
verb: "create",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
lockObj = action.(fakeclient.CreateAction).GetObject()
return true, lockObj, nil
},
},
{
verb: "update",
objectType: objectType,
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
updates++
// Second update (first renew) should release the lock
if updates == 2 {
// We update the lock after the cancellation to release it
// This wg is to avoid the data race on lockObj
defer wg.Done()
close(onRelease)
}
lockObj = action.(fakeclient.UpdateAction).GetObject()
return true, lockObj, nil
},
},
},
},
}
ctx, cancel := context.WithCancel(context.Background())
for i := range tests {
test := &tests[i]
t.Run(test.name, func(t *testing.T) {
wg.Add(1)
resetVars()
go elector.Run(ctx)
resourceLockConfig := rl.ResourceLockConfig{
Identity: "baz",
EventRecorder: &record.FakeRecorder{},
}
c := &fake.Clientset{}
for _, reactor := range test.reactors {
c.AddReactor(reactor.verb, objectType, reactor.reaction)
}
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
t.Errorf("unreachable action. testclient called too many times: %+v", action)
return true, nil, fmt.Errorf("unreachable action")
})
lock, err := rl.New(objectType, "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig)
if err != nil {
t.Fatal("resourcelock.New() = ", err)
}
// Wait for us to become the leader
select {
case <-onNewLeader:
case <-time.After(10 * time.Second):
t.Fatal("failed to become the leader")
}
lec.Lock = lock
elector, err := NewLeaderElector(lec)
if err != nil {
t.Fatal("Failed to create leader elector: ", err)
}
// Wait for renew (update) to be invoked
select {
case <-onRenewCalled:
case <-time.After(10 * time.Second):
t.Fatal("the elector failed to renew the lock")
}
ctx, cancel := context.WithCancel(context.Background())
// Cancel the context - stopping the elector while
// it's running
cancel()
go elector.Run(ctx)
// Resume the update call to return the cancellation
// which should trigger the release flow
close(onRenewResume)
// Wait for us to become the leader
select {
case <-onNewLeader:
case <-time.After(10 * time.Second):
t.Fatal("failed to become the leader")
}
select {
case <-onRelease:
case <-time.After(10 * time.Second):
t.Fatal("the lock was not released")
// Wait for renew (update) to be invoked
select {
case <-onRenewCalled:
case <-time.After(10 * time.Second):
t.Fatal("the elector failed to renew the lock")
}
// Cancel the context - stopping the elector while
// it's running
cancel()
// Resume the tryAcquireOrRenew call to return the cancellation
// which should trigger the release flow
close(onRenewResume)
select {
case <-onRelease:
case <-time.After(10 * time.Second):
t.Fatal("the lock was not released")
}
wg.Wait()
})
}
}

View File

@@ -44,11 +44,11 @@ type configMapLock struct {
// Get returns the election record from a ConfigMap Annotation
func (cml *configMapLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
var record LeaderElectionRecord
var err error
cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Get(ctx, cml.ConfigMapMeta.Name, metav1.GetOptions{})
cm, err := cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Get(ctx, cml.ConfigMapMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
cml.cm = cm
if cml.cm.Annotations == nil {
cml.cm.Annotations = make(map[string]string)
}

View File

@@ -39,11 +39,11 @@ type endpointsLock struct {
// Get returns the election record from a Endpoints Annotation
func (el *endpointsLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
var record LeaderElectionRecord
var err error
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(ctx, el.EndpointsMeta.Name, metav1.GetOptions{})
ep, err := el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(ctx, el.EndpointsMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
el.e = ep
if el.e.Annotations == nil {
el.e.Annotations = make(map[string]string)
}

View File

@@ -39,11 +39,11 @@ type LeaseLock struct {
// Get returns the election record from a Lease spec
func (ll *LeaseLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
var err error
ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{})
lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
ll.lease = lease
record := LeaseSpecToLeaderElectionRecord(&ll.lease.Spec)
recordByte, err := json.Marshal(*record)
if err != nil {

View File

@@ -203,6 +203,11 @@ func (p *ListPager) eachListChunkBuffered(ctx context.Context, options metav1.Li
}()
for o := range chunkC {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
err := fn(o)
if err != nil {
return err // any fn error should be returned immediately

View File

@@ -301,16 +301,10 @@ func TestListPager_EachListItem(t *testing.T) {
{
name: "cancel context while processing",
fields: fields{PageSize: 10, PageFn: (&testPager{t: t, expectPage: 10, remaining: 51, rv: "rv:20"}).PagedList},
want: list(3, "rv:20"), // all the items <= the one the processor returned an error on should have been visited
want: list(10, "rv:20"), // The whole PageSize worth of items got returned.
wantErr: true,
cancelContextOnItem: 3,
},
{
name: "panic processing item",
fields: fields{PageSize: 10, PageFn: (&testPager{t: t, expectPage: 10, remaining: 51, rv: "rv:20"}).PagedList},
want: list(3, "rv:20"), // all the items <= the one the processor returned an error on should have been visited
wantPanic: true,
},
}
processorErr := fmt.Errorf("processor error")
@@ -345,8 +339,7 @@ func TestListPager_EachListItem(t *testing.T) {
err = p.EachListItem(ctx, metav1.ListOptions{}, fn)
}()
if (panic != nil) && !tt.wantPanic {
t.Fatalf(".EachListItem() panic = %v, wantPanic %v", panic, tt.wantPanic)
} else {
t.Errorf(".EachListItem() panic = %v, wantPanic %v", panic, tt.wantPanic)
return
}
if (err != nil) != tt.wantErr {

View File

@@ -20,7 +20,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"sort"
@@ -348,10 +347,11 @@ func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) {
}
// we're not writing to this stream
errorStream.Close()
defer pf.streamConn.RemoveStreams(errorStream)
errorChan := make(chan error)
go func() {
message, err := ioutil.ReadAll(errorStream)
message, err := io.ReadAll(errorStream)
switch {
case err != nil:
errorChan <- fmt.Errorf("error reading from error stream for port %d -> %d: %v", port.Local, port.Remote, err)
@@ -368,6 +368,7 @@ func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) {
runtime.HandleError(fmt.Errorf("error creating forwarding stream for port %d -> %d: %v", port.Local, port.Remote, err))
return
}
defer pf.streamConn.RemoveStreams(dataStream)
localError := make(chan struct{})
remoteDone := make(chan struct{})

View File

@@ -51,6 +51,7 @@ type fakeConnection struct {
closeChan chan bool
dataStream *fakeStream
errorStream *fakeStream
streamCount int
}
func newFakeConnection() *fakeConnection {
@@ -64,8 +65,10 @@ func newFakeConnection() *fakeConnection {
func (c *fakeConnection) CreateStream(headers http.Header) (httpstream.Stream, error) {
switch headers.Get(v1.StreamType) {
case v1.StreamTypeData:
c.streamCount++
return c.dataStream, nil
case v1.StreamTypeError:
c.streamCount++
return c.errorStream, nil
default:
return nil, fmt.Errorf("fakeStream creation not supported for stream type %s", headers.Get(v1.StreamType))
@@ -84,7 +87,10 @@ func (c *fakeConnection) CloseChan() <-chan bool {
return c.closeChan
}
func (c *fakeConnection) RemoveStreams(_ ...httpstream.Stream) {
func (c *fakeConnection) RemoveStreams(streams ...httpstream.Stream) {
for range streams {
c.streamCount--
}
}
func (c *fakeConnection) SetIdleTimeout(timeout time.Duration) {
@@ -504,7 +510,7 @@ func TestHandleConnection(t *testing.T) {
// Test handleConnection
pf.handleConnection(localConnection, ForwardedPort{Local: 1111, Remote: 2222})
assert.Equal(t, 0, remoteConnection.streamCount, "stream count should be zero")
assert.Equal(t, "test data from local", remoteDataReceived.String())
assert.Equal(t, "test data from remote", localConnection.receiveBuffer.String())
assert.Equal(t, "Handling connection for 1111\n", out.String())
@@ -538,6 +544,7 @@ func TestHandleConnectionSendsRemoteError(t *testing.T) {
// Test handleConnection, using go-routine because it needs to be able to write to unbuffered pf.errorChan
pf.handleConnection(localConnection, ForwardedPort{Local: 1111, Remote: 2222})
assert.Equal(t, 0, remoteConnection.streamCount, "stream count should be zero")
assert.Equal(t, "", remoteDataReceived.String())
assert.Equal(t, "", localConnection.receiveBuffer.String())
assert.Equal(t, "Handling connection for 1111\n", out.String())

View File

@@ -19,7 +19,6 @@ package remotecommand
import (
"fmt"
"io"
"io/ioutil"
"k8s.io/apimachinery/pkg/util/runtime"
)
@@ -39,7 +38,7 @@ func watchErrorStream(errorStream io.Reader, d errorStreamDecoder) chan error {
go func() {
defer runtime.HandleCrash()
message, err := ioutil.ReadAll(errorStream)
message, err := io.ReadAll(errorStream)
switch {
case err != nil && err != io.EOF:
errorChan <- fmt.Errorf("error reading from error stream: %s", err)

View File

@@ -20,7 +20,6 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -72,7 +71,7 @@ func fakeMassiveDataAttacher(stdin io.Reader, stdout, stderr io.WriteCloser, tty
}
go func() {
io.Copy(ioutil.Discard, stdin)
io.Copy(io.Discard, stdin)
copyDone <- struct{}{}
}()

View File

@@ -19,7 +19,6 @@ package remotecommand
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"k8s.io/api/core/v1"
@@ -111,7 +110,7 @@ func (p *streamProtocolV1) stream(conn streamCreator) error {
// always read from errorStream
go func() {
message, err := ioutil.ReadAll(p.errorStream)
message, err := io.ReadAll(p.errorStream)
if err != nil && err != io.EOF {
errorChan <- fmt.Errorf("Error reading from error stream: %s", err)
return

View File

@@ -19,7 +19,6 @@ package remotecommand
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"sync"
@@ -126,7 +125,7 @@ func (p *streamProtocolV2) copyStdin() {
// this "copy" doesn't actually read anything - it's just here to wait for
// the server to close remoteStdin.
if _, err := io.Copy(ioutil.Discard, p.remoteStdin); err != nil {
if _, err := io.Copy(io.Discard, p.remoteStdin); err != nil {
runtime.HandleError(err)
}
}()
@@ -145,7 +144,7 @@ func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) {
// make sure, packet in queue can be consumed.
// block in queue may lead to deadlock in conn.server
// issue: https://github.com/kubernetes/kubernetes/issues/96339
defer io.Copy(ioutil.Discard, p.remoteStdout)
defer io.Copy(io.Discard, p.remoteStdout)
if _, err := io.Copy(p.Stdout, p.remoteStdout); err != nil {
runtime.HandleError(err)
@@ -162,7 +161,7 @@ func (p *streamProtocolV2) copyStderr(wg *sync.WaitGroup) {
go func() {
defer runtime.HandleCrash()
defer wg.Done()
defer io.Copy(ioutil.Discard, p.remoteStderr)
defer io.Copy(io.Discard, p.remoteStderr)
if _, err := io.Copy(p.Stderr, p.remoteStderr); err != nil {
runtime.HandleError(err)

View File

@@ -17,6 +17,7 @@ limitations under the License.
package transport
import (
"context"
"fmt"
"net"
"net/http"
@@ -55,6 +56,9 @@ type tlsCacheKey struct {
serverName string
nextProtos string
disableCompression bool
// these functions are wrapped to allow them to be used as map keys
getCert *GetCertHolder
dial *DialHolder
}
func (t tlsCacheKey) String() string {
@@ -62,7 +66,8 @@ func (t tlsCacheKey) String() string {
if len(t.keyData) > 0 {
keyText = "<redacted>"
}
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t", t.insecure, t.caData, t.certData, keyText, t.serverName, t.disableCompression)
return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t, getCert:%p, dial:%p",
t.insecure, t.caData, t.certData, keyText, t.serverName, t.disableCompression, t.getCert, t.dial)
}
func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
@@ -88,12 +93,14 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
return nil, err
}
// The options didn't require a custom TLS config
if tlsConfig == nil && config.Dial == nil && config.Proxy == nil {
if tlsConfig == nil && config.DialHolder == nil && config.Proxy == nil {
return http.DefaultTransport, nil
}
dial := config.Dial
if dial == nil {
var dial func(ctx context.Context, network, address string) (net.Conn, error)
if config.DialHolder != nil {
dial = config.DialHolder.Dial
} else {
dial = (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
@@ -138,7 +145,7 @@ func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) {
return tlsCacheKey{}, false, err
}
if c.TLS.GetCert != nil || c.Dial != nil || c.Proxy != nil {
if c.Proxy != nil {
// cannot determine equality for functions
return tlsCacheKey{}, false, nil
}
@@ -149,6 +156,8 @@ func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) {
serverName: c.TLS.ServerName,
nextProtos: strings.Join(c.TLS.NextProtos, ","),
disableCompression: c.DisableCompression,
getCert: c.TLS.GetCertHolder,
dial: c.DialHolder,
}
if c.TLS.ReloadTLSFiles {

View File

@@ -21,6 +21,8 @@ import (
"crypto/tls"
"net"
"net/http"
"net/url"
"reflect"
"testing"
)
@@ -58,16 +60,21 @@ func TestTLSConfigKey(t *testing.T) {
t.Errorf("Expected identical cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
continue
}
if keyA != (tlsCacheKey{}) {
t.Errorf("Expected empty cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
continue
}
}
}
// Make sure config fields that affect the tls config affect the cache key
dialer := net.Dialer{}
getCert := func() (*tls.Certificate, error) { return nil, nil }
getCert := &GetCertHolder{GetCert: func() (*tls.Certificate, error) { return nil, nil }}
uniqueConfigurations := map[string]*Config{
"proxy": {Proxy: func(request *http.Request) (*url.URL, error) { return nil, nil }},
"no tls": {},
"dialer": {Dial: dialer.DialContext},
"dialer2": {Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return nil, nil }},
"dialer": {DialHolder: &DialHolder{Dial: dialer.DialContext}},
"dialer2": {DialHolder: &DialHolder{Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return nil, nil }}},
"insecure": {TLS: TLSConfig{Insecure: true}},
"cadata 1": {TLS: TLSConfig{CAData: []byte{1}}},
"cadata 2": {TLS: TLSConfig{CAData: []byte{2}}},
@@ -118,20 +125,20 @@ func TestTLSConfigKey(t *testing.T) {
},
"getCert1": {
TLS: TLSConfig{
KeyData: []byte{1},
GetCert: getCert,
KeyData: []byte{1},
GetCertHolder: getCert,
},
},
"getCert2": {
TLS: TLSConfig{
KeyData: []byte{1},
GetCert: func() (*tls.Certificate, error) { return nil, nil },
KeyData: []byte{1},
GetCertHolder: &GetCertHolder{GetCert: func() (*tls.Certificate, error) { return nil, nil }},
},
},
"getCert1, key 2": {
TLS: TLSConfig{
KeyData: []byte{2},
GetCert: getCert,
KeyData: []byte{2},
GetCertHolder: getCert,
},
},
"http2, http1.1": {TLS: TLSConfig{NextProtos: []string{"h2", "http/1.1"}}},
@@ -150,6 +157,17 @@ func TestTLSConfigKey(t *testing.T) {
continue
}
shouldCacheA := valueA.Proxy == nil
if shouldCacheA != canCacheA {
t.Errorf("Unexpected canCache=false for " + nameA)
}
configIsNotEmpty := !reflect.DeepEqual(*valueA, Config{})
if keyA == (tlsCacheKey{}) && shouldCacheA && configIsNotEmpty {
t.Errorf("Expected non-empty cache keys for %q and %q, got:\n\t%s\n\t%s", nameA, nameB, keyA, keyB)
continue
}
// Make sure we get the same key on the same config
if nameA == nameB {
if keyA != keyB {

View File

@@ -67,8 +67,9 @@ type Config struct {
// instead of setting this value directly.
WrapTransport WrapperFunc
// Dial specifies the dial function for creating unencrypted TCP connections.
Dial func(ctx context.Context, network, address string) (net.Conn, error)
// DialHolder specifies the dial function for creating unencrypted TCP connections.
// This struct indirection is used to make transport configs cacheable.
DialHolder *DialHolder
// Proxy is the proxy func to be used for all requests made by this
// transport. If Proxy is nil, http.ProxyFromEnvironment is used. If Proxy
@@ -78,6 +79,11 @@ type Config struct {
Proxy func(*http.Request) (*url.URL, error)
}
// DialHolder is used to make the wrapped function comparable so that it can be used as a map key.
type DialHolder struct {
Dial func(ctx context.Context, network, address string) (net.Conn, error)
}
// ImpersonationConfig has all the available impersonation options
type ImpersonationConfig struct {
// UserName matches user.Info.GetName()
@@ -112,7 +118,7 @@ func (c *Config) HasCertAuth() bool {
// HasCertCallback returns whether the configuration has certificate callback or not.
func (c *Config) HasCertCallback() bool {
return c.TLS.GetCert != nil
return c.TLS.GetCertHolder != nil
}
// Wrap adds a transport middleware function that will give the caller
@@ -143,5 +149,12 @@ type TLSConfig struct {
// To use only http/1.1, set to ["http/1.1"].
NextProtos []string
GetCert func() (*tls.Certificate, error) // Callback that returns a TLS client certificate. CertData, CertFile, KeyData and KeyFile supercede this field.
// Callback that returns a TLS client certificate. CertData, CertFile, KeyData and KeyFile supercede this field.
// This struct indirection is used to make transport configs cacheable.
GetCertHolder *GetCertHolder
}
// GetCertHolder is used to make the wrapped function comparable so that it can be used as a map key.
type GetCertHolder struct {
GetCert func() (*tls.Certificate, error)
}

View File

@@ -18,8 +18,8 @@ package transport
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"sync"
"time"
@@ -132,7 +132,7 @@ type fileTokenSource struct {
var _ = oauth2.TokenSource(&fileTokenSource{})
func (ts *fileTokenSource) Token() (*oauth2.Token, error) {
tokb, err := ioutil.ReadFile(ts.path)
tokb, err := os.ReadFile(ts.path)
if err != nil {
return nil, fmt.Errorf("failed to read token file %q: %v", ts.path, err)
}

View File

@@ -22,8 +22,8 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"os"
"sync"
"time"
@@ -39,6 +39,10 @@ func New(config *Config) (http.RoundTripper, error) {
return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
}
if !isValidHolders(config) {
return nil, fmt.Errorf("misconfigured holder for dialer or cert callback")
}
var (
rt http.RoundTripper
err error
@@ -56,6 +60,18 @@ func New(config *Config) (http.RoundTripper, error) {
return HTTPWrappersForConfig(config, rt)
}
func isValidHolders(config *Config) bool {
if config.TLS.GetCertHolder != nil && config.TLS.GetCertHolder.GetCert == nil {
return false
}
if config.DialHolder != nil && config.DialHolder.Dial == nil {
return false
}
return true
}
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
// by the provided Config. Will return nil if no transport level security is requested.
func TLSConfigFor(c *Config) (*tls.Config, error) {
@@ -116,7 +132,7 @@ func TLSConfigFor(c *Config) (*tls.Config, error) {
return dynamicCertLoader()
}
if c.HasCertCallback() {
cert, err := c.TLS.GetCert()
cert, err := c.TLS.GetCertHolder.GetCert()
if err != nil {
return nil, err
}
@@ -157,10 +173,7 @@ func loadTLSFiles(c *Config) error {
}
c.TLS.KeyData, err = dataFromSliceOrFile(c.TLS.KeyData, c.TLS.KeyFile)
if err != nil {
return err
}
return nil
return err
}
// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
@@ -170,7 +183,7 @@ func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
return data, nil
}
if len(file) > 0 {
fileData, err := ioutil.ReadFile(file)
fileData, err := os.ReadFile(file)
if err != nil {
return []byte{}, err
}

View File

@@ -21,6 +21,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"testing"
)
@@ -94,6 +95,13 @@ stR0Yiw0buV6DL/moUO0HIM9Bjh96HJp+LxiIS6UCdIhMPp5HoQa
)
func TestNew(t *testing.T) {
globalGetCert := &GetCertHolder{
GetCert: func() (*tls.Certificate, error) { return nil, nil },
}
globalDial := &DialHolder{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return nil, nil },
}
testCases := map[string]struct {
Config *Config
Err bool
@@ -209,9 +217,11 @@ func TestNew(t *testing.T) {
Config: &Config{
TLS: TLSConfig{
CAData: []byte(rootCACert),
GetCert: func() (*tls.Certificate, error) {
crt, err := tls.X509KeyPair([]byte(certData), []byte(keyData))
return &crt, err
GetCertHolder: &GetCertHolder{
GetCert: func() (*tls.Certificate, error) {
crt, err := tls.X509KeyPair([]byte(certData), []byte(keyData))
return &crt, err
},
},
},
},
@@ -223,8 +233,10 @@ func TestNew(t *testing.T) {
Config: &Config{
TLS: TLSConfig{
CAData: []byte(rootCACert),
GetCert: func() (*tls.Certificate, error) {
return nil, errors.New("GetCert failure")
GetCertHolder: &GetCertHolder{
GetCert: func() (*tls.Certificate, error) {
return nil, errors.New("GetCert failure")
},
},
},
},
@@ -235,8 +247,10 @@ func TestNew(t *testing.T) {
Config: &Config{
TLS: TLSConfig{
CAData: []byte(rootCACert),
GetCert: func() (*tls.Certificate, error) {
return nil, nil
GetCertHolder: &GetCertHolder{
GetCert: func() (*tls.Certificate, error) {
return nil, nil
},
},
CertData: []byte(certData),
KeyData: []byte(keyData),
@@ -249,12 +263,96 @@ func TestNew(t *testing.T) {
Config: &Config{
TLS: TLSConfig{
CAData: []byte(rootCACert),
GetCert: func() (*tls.Certificate, error) {
return nil, nil
GetCertHolder: &GetCertHolder{
GetCert: func() (*tls.Certificate, error) {
return nil, nil
},
},
},
},
},
"nil holders": {
Config: &Config{
TLS: TLSConfig{
GetCertHolder: nil,
},
DialHolder: nil,
},
Err: false,
TLS: false,
TLSCert: false,
TLSErr: false,
Default: true,
Insecure: false,
DefaultRoots: false,
},
"non-nil dial holder and nil internal": {
Config: &Config{
TLS: TLSConfig{
GetCertHolder: nil,
},
DialHolder: &DialHolder{},
},
Err: true,
},
"non-nil cert holder and nil internal": {
Config: &Config{
TLS: TLSConfig{
GetCertHolder: &GetCertHolder{},
},
DialHolder: nil,
},
Err: true,
},
"non-nil dial holder+internal": {
Config: &Config{
TLS: TLSConfig{
GetCertHolder: nil,
},
DialHolder: &DialHolder{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { return nil, nil },
},
},
Err: false,
TLS: true,
TLSCert: false,
TLSErr: false,
Default: false,
Insecure: false,
DefaultRoots: true,
},
"non-nil cert holder+internal": {
Config: &Config{
TLS: TLSConfig{
GetCertHolder: &GetCertHolder{
GetCert: func() (*tls.Certificate, error) { return nil, nil },
},
},
DialHolder: nil,
},
Err: false,
TLS: true,
TLSCert: true,
TLSErr: false,
Default: false,
Insecure: false,
DefaultRoots: true,
},
"non-nil holders+internal with global address": {
Config: &Config{
TLS: TLSConfig{
GetCertHolder: globalGetCert,
},
DialHolder: globalDial,
},
Err: false,
TLS: true,
TLSCert: true,
TLSErr: false,
Default: false,
Insecure: false,
DefaultRoots: true,
},
}
for k, testCase := range testCases {
t.Run(k, func(t *testing.T) {

View File

@@ -25,9 +25,9 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"path/filepath"
"strings"
"time"
@@ -101,9 +101,9 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
certFixturePath := filepath.Join(fixtureDirectory, baseName+".crt")
keyFixturePath := filepath.Join(fixtureDirectory, baseName+".key")
if len(fixtureDirectory) > 0 {
cert, err := ioutil.ReadFile(certFixturePath)
cert, err := os.ReadFile(certFixturePath)
if err == nil {
key, err := ioutil.ReadFile(keyFixturePath)
key, err := os.ReadFile(keyFixturePath)
if err == nil {
return cert, key, nil
}
@@ -188,10 +188,10 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
}
if len(fixtureDirectory) > 0 {
if err := ioutil.WriteFile(certFixturePath, certBuffer.Bytes(), 0644); err != nil {
if err := os.WriteFile(certFixturePath, certBuffer.Bytes(), 0644); err != nil {
return nil, nil, fmt.Errorf("failed to write cert fixture to %s: %v", certFixturePath, err)
}
if err := ioutil.WriteFile(keyFixturePath, keyBuffer.Bytes(), 0644); err != nil {
if err := os.WriteFile(keyFixturePath, keyBuffer.Bytes(), 0644); err != nil {
return nil, nil, fmt.Errorf("failed to write key fixture to %s: %v", certFixturePath, err)
}
}

View File

@@ -20,8 +20,8 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io/ioutil"
"net"
"os"
"testing"
"k8s.io/client-go/util/keyutil"
@@ -36,7 +36,7 @@ func TestMakeCSR(t *testing.T) {
dnsSANs := []string{"localhost"}
ipSANs := []net.IP{netutils.ParseIPSloppy("127.0.0.1")}
keyData, err := ioutil.ReadFile(keyFile)
keyData, err := os.ReadFile(keyFile)
if err != nil {
t.Fatal(err)
}

View File

@@ -19,7 +19,6 @@ package cert
import (
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
@@ -66,13 +65,13 @@ func WriteCert(certPath string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil {
return err
}
return ioutil.WriteFile(certPath, data, os.FileMode(0644))
return os.WriteFile(certPath, data, os.FileMode(0644))
}
// NewPool returns an x509.CertPool containing the certificates in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates
func NewPool(filename string) (*x509.CertPool, error) {
pemBlock, err := ioutil.ReadFile(filename)
pemBlock, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
@@ -101,7 +100,7 @@ func NewPoolFromBytes(pemBlock []byte) (*x509.CertPool, error) {
// CertsFromFile returns the x509.Certificates contained in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates
func CertsFromFile(file string) ([]*x509.Certificate, error) {
pemBlock, err := ioutil.ReadFile(file)
pemBlock, err := os.ReadFile(file)
if err != nil {
return nil, err
}

View File

@@ -18,14 +18,13 @@ package certificate
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestUpdateSymlinkExistingFileError(t *testing.T) {
dir, err := ioutil.TempDir("", "k8s-test-update-symlink")
dir, err := os.MkdirTemp("", "k8s-test-update-symlink")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -35,7 +34,7 @@ func TestUpdateSymlinkExistingFileError(t *testing.T) {
}
}()
pairFile := filepath.Join(dir, "kubelet-current.pem")
if err := ioutil.WriteFile(pairFile, nil, 0600); err != nil {
if err := os.WriteFile(pairFile, nil, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
}
@@ -49,7 +48,7 @@ func TestUpdateSymlinkExistingFileError(t *testing.T) {
}
func TestUpdateSymlinkNewFileNotExist(t *testing.T) {
dir, err := ioutil.TempDir("", "k8s-test-update-symlink")
dir, err := os.MkdirTemp("", "k8s-test-update-symlink")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -59,7 +58,7 @@ func TestUpdateSymlinkNewFileNotExist(t *testing.T) {
}
}()
oldPairFile := filepath.Join(dir, "kubelet-oldpair.pem")
if err := ioutil.WriteFile(oldPairFile, nil, 0600); err != nil {
if err := os.WriteFile(oldPairFile, nil, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", oldPairFile, err)
}
@@ -89,7 +88,7 @@ func TestUpdateSymlinkNewFileNotExist(t *testing.T) {
}
func TestUpdateSymlinkNoSymlink(t *testing.T) {
dir, err := ioutil.TempDir("", "k8s-test-update-symlink")
dir, err := os.MkdirTemp("", "k8s-test-update-symlink")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -99,7 +98,7 @@ func TestUpdateSymlinkNoSymlink(t *testing.T) {
}
}()
pairFile := filepath.Join(dir, "kubelet-newfile.pem")
if err := ioutil.WriteFile(pairFile, nil, 0600); err != nil {
if err := os.WriteFile(pairFile, nil, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
}
@@ -124,7 +123,7 @@ func TestUpdateSymlinkNoSymlink(t *testing.T) {
func TestUpdateSymlinkReplaceExistingSymlink(t *testing.T) {
prefix := "kubelet"
dir, err := ioutil.TempDir("", "k8s-test-update-symlink")
dir, err := os.MkdirTemp("", "k8s-test-update-symlink")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -134,11 +133,11 @@ func TestUpdateSymlinkReplaceExistingSymlink(t *testing.T) {
}
}()
oldPairFile := filepath.Join(dir, prefix+"-oldfile.pem")
if err := ioutil.WriteFile(oldPairFile, nil, 0600); err != nil {
if err := os.WriteFile(oldPairFile, nil, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", oldPairFile, err)
}
newPairFile := filepath.Join(dir, prefix+"-newfile.pem")
if err := ioutil.WriteFile(newPairFile, nil, 0600); err != nil {
if err := os.WriteFile(newPairFile, nil, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", newPairFile, err)
}
currentPairFile := filepath.Join(dir, prefix+"-current.pem")
@@ -178,7 +177,7 @@ func TestUpdateSymlinkReplaceExistingSymlink(t *testing.T) {
}
func TestLoadFile(t *testing.T) {
dir, err := ioutil.TempDir("", "k8s-test-load-cert-key-blocks")
dir, err := os.MkdirTemp("", "k8s-test-load-cert-key-blocks")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -199,7 +198,7 @@ func TestLoadFile(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if err := ioutil.WriteFile(pairFile, tt.data, 0600); err != nil {
if err := os.WriteFile(pairFile, tt.data, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
}
cert, err := loadFile(pairFile)
@@ -218,7 +217,7 @@ func TestLoadFile(t *testing.T) {
func TestUpdateNoRotation(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -228,11 +227,11 @@ func TestUpdateNoRotation(t *testing.T) {
}
}()
keyFile := filepath.Join(dir, "kubelet.key")
if err := ioutil.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
if err := os.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", keyFile, err)
}
certFile := filepath.Join(dir, "kubelet.crt")
if err := ioutil.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
if err := os.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", certFile, err)
}
@@ -252,7 +251,7 @@ func TestUpdateNoRotation(t *testing.T) {
func TestUpdateRotation(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -262,11 +261,11 @@ func TestUpdateRotation(t *testing.T) {
}
}()
keyFile := filepath.Join(dir, "kubelet.key")
if err := ioutil.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
if err := os.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", keyFile, err)
}
certFile := filepath.Join(dir, "kubelet.crt")
if err := ioutil.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
if err := os.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", certFile, err)
}
@@ -286,7 +285,7 @@ func TestUpdateRotation(t *testing.T) {
func TestUpdateTwoCerts(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -296,11 +295,11 @@ func TestUpdateTwoCerts(t *testing.T) {
}
}()
keyFile := filepath.Join(dir, "kubelet.key")
if err := ioutil.WriteFile(keyFile, storeTwoCertsData.keyPEM, 0600); err != nil {
if err := os.WriteFile(keyFile, storeTwoCertsData.keyPEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", keyFile, err)
}
certFile := filepath.Join(dir, "kubelet.crt")
if err := ioutil.WriteFile(certFile, storeTwoCertsData.certificatePEM, 0600); err != nil {
if err := os.WriteFile(certFile, storeTwoCertsData.certificatePEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", certFile, err)
}
@@ -323,7 +322,7 @@ func TestUpdateTwoCerts(t *testing.T) {
func TestUpdateWithBadCertKeyData(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -333,11 +332,11 @@ func TestUpdateWithBadCertKeyData(t *testing.T) {
}
}()
keyFile := filepath.Join(dir, "kubelet.key")
if err := ioutil.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
if err := os.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", keyFile, err)
}
certFile := filepath.Join(dir, "kubelet.crt")
if err := ioutil.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
if err := os.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", certFile, err)
}
@@ -357,7 +356,7 @@ func TestUpdateWithBadCertKeyData(t *testing.T) {
func TestCurrentPairFile(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -369,7 +368,7 @@ func TestCurrentPairFile(t *testing.T) {
pairFile := filepath.Join(dir, prefix+"-pair.pem")
data := append(storeCertData.certificatePEM, []byte("\n")...)
data = append(data, storeCertData.keyPEM...)
if err := ioutil.WriteFile(pairFile, data, 0600); err != nil {
if err := os.WriteFile(pairFile, data, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
}
currentFile := filepath.Join(dir, prefix+"-current.pem")
@@ -396,7 +395,7 @@ func TestCurrentPairFile(t *testing.T) {
func TestCurrentCertKeyFiles(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -406,11 +405,11 @@ func TestCurrentCertKeyFiles(t *testing.T) {
}
}()
certFile := filepath.Join(dir, "kubelet.crt")
if err := ioutil.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
if err := os.WriteFile(certFile, storeCertData.certificatePEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", certFile, err)
}
keyFile := filepath.Join(dir, "kubelet.key")
if err := ioutil.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
if err := os.WriteFile(keyFile, storeCertData.keyPEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", keyFile, err)
}
@@ -433,7 +432,7 @@ func TestCurrentCertKeyFiles(t *testing.T) {
func TestCurrentTwoCerts(t *testing.T) {
prefix := "kubelet-server"
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}
@@ -443,11 +442,11 @@ func TestCurrentTwoCerts(t *testing.T) {
}
}()
certFile := filepath.Join(dir, "kubelet.crt")
if err := ioutil.WriteFile(certFile, storeTwoCertsData.certificatePEM, 0600); err != nil {
if err := os.WriteFile(certFile, storeTwoCertsData.certificatePEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", certFile, err)
}
keyFile := filepath.Join(dir, "kubelet.key")
if err := ioutil.WriteFile(keyFile, storeTwoCertsData.keyPEM, 0600); err != nil {
if err := os.WriteFile(keyFile, storeTwoCertsData.keyPEM, 0600); err != nil {
t.Fatalf("Unable to create the file %q: %v", keyFile, err)
}
@@ -472,7 +471,7 @@ func TestCurrentTwoCerts(t *testing.T) {
}
func TestCurrentNoFiles(t *testing.T) {
dir, err := ioutil.TempDir("", "k8s-test-certstore-current")
dir, err := os.MkdirTemp("", "k8s-test-certstore-current")
if err != nil {
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
}

213
util/csaupgrade/upgrade.go Normal file
View File

@@ -0,0 +1,213 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csaupgrade
import (
"bytes"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
const csaAnnotationName = "kubectl.kubernetes.io/last-applied-configuration"
var csaAnnotationFieldSet = fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", csaAnnotationName))
// Upgrades the Manager information for fields managed with client-side-apply (CSA)
// Prepares fields owned by `csaManager` for 'Update' operations for use now
// with the given `ssaManager` for `Apply` operations.
//
// This transformation should be performed on an object if it has been previously
// managed using client-side-apply to prepare it for future use with
// server-side-apply.
//
// Caveats:
// 1. This operation is not reversible. Information about which fields the client
// owned will be lost in this operation.
// 2. Supports being performed either before or after initial server-side apply.
// 3. Client-side apply tends to own more fields (including fields that are defaulted),
// this will possibly remove this defaults, they will be re-defaulted, that's fine.
// 4. Care must be taken to not overwrite the managed fields on the server if they
// have changed before sending a patch.
//
// obj - Target of the operation which has been managed with CSA in the past
// csaManagerName - Name of FieldManager formerly used for `Update` operations
// ssaManagerName - Name of FieldManager formerly used for `Apply` operations
func UpgradeManagedFields(
obj runtime.Object,
csaManagerName string,
ssaManagerName string,
) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return fmt.Errorf("error accessing object metadata: %w", err)
}
// Create managed fields clone since we modify the values
var managedFields []metav1.ManagedFieldsEntry
managedFields = append(managedFields, accessor.GetManagedFields()...)
// Locate SSA manager
replaceIndex, managerExists := findFirstIndex(managedFields,
func(entry metav1.ManagedFieldsEntry) bool {
return entry.Manager == ssaManagerName &&
entry.Operation == metav1.ManagedFieldsOperationApply &&
entry.Subresource == ""
})
if !managerExists {
// SSA manager does not exist. Find the most recent matching CSA manager,
// convert it to an SSA manager.
//
// (find first index, since managed fields are sorted so that most recent is
// first in the list)
replaceIndex, managerExists = findFirstIndex(managedFields,
func(entry metav1.ManagedFieldsEntry) bool {
return entry.Manager == csaManagerName &&
entry.Operation == metav1.ManagedFieldsOperationUpdate &&
entry.Subresource == ""
})
if !managerExists {
// There are no CSA managers that need to be converted. Nothing to do
// Return early
return nil
}
// Convert CSA manager into SSA manager
managedFields[replaceIndex].Operation = metav1.ManagedFieldsOperationApply
managedFields[replaceIndex].Manager = ssaManagerName
}
err = unionManagerIntoIndex(managedFields, replaceIndex, csaManagerName)
if err != nil {
return err
}
// Create version of managed fields which has no CSA managers with the given name
filteredManagers := filter(managedFields, func(entry metav1.ManagedFieldsEntry) bool {
return !(entry.Manager == csaManagerName &&
entry.Operation == metav1.ManagedFieldsOperationUpdate &&
entry.Subresource == "")
})
// Wipe out last-applied-configuration annotation if it exists
annotations := accessor.GetAnnotations()
delete(annotations, csaAnnotationName)
// Commit changes to object
accessor.SetAnnotations(annotations)
accessor.SetManagedFields(filteredManagers)
return nil
}
// Locates an Update manager entry named `csaManagerName` with the same APIVersion
// as the manager at the targetIndex. Unions both manager's fields together
// into the manager specified by `targetIndex`. No other managers are modified.
func unionManagerIntoIndex(entries []metav1.ManagedFieldsEntry, targetIndex int, csaManagerName string) error {
ssaManager := entries[targetIndex]
// find Update manager of same APIVersion, union ssa fields with it.
// discard all other Update managers of the same name
csaManagerIndex, csaManagerExists := findFirstIndex(entries,
func(entry metav1.ManagedFieldsEntry) bool {
return entry.Manager == csaManagerName &&
entry.Operation == metav1.ManagedFieldsOperationUpdate &&
entry.Subresource == "" &&
entry.APIVersion == ssaManager.APIVersion
})
targetFieldSet, err := decodeManagedFieldsEntrySet(ssaManager)
if err != nil {
return fmt.Errorf("failed to convert fields to set: %w", err)
}
combinedFieldSet := &targetFieldSet
// Union the csa manager with the existing SSA manager. Do nothing if
// there was no good candidate found
if csaManagerExists {
csaManager := entries[csaManagerIndex]
csaFieldSet, err := decodeManagedFieldsEntrySet(csaManager)
if err != nil {
return fmt.Errorf("failed to convert fields to set: %w", err)
}
combinedFieldSet = combinedFieldSet.Union(&csaFieldSet)
}
// Ensure that the resultant fieldset does not include the
// last applied annotation
combinedFieldSet = combinedFieldSet.Difference(csaAnnotationFieldSet)
// Encode the fields back to the serialized format
err = encodeManagedFieldsEntrySet(&entries[targetIndex], *combinedFieldSet)
if err != nil {
return fmt.Errorf("failed to encode field set: %w", err)
}
return nil
}
func findFirstIndex[T any](
collection []T,
predicate func(T) bool,
) (int, bool) {
for idx, entry := range collection {
if predicate(entry) {
return idx, true
}
}
return -1, false
}
func filter[T any](
collection []T,
predicate func(T) bool,
) []T {
result := make([]T, 0, len(collection))
for _, value := range collection {
if predicate(value) {
result = append(result, value)
}
}
if len(result) == 0 {
return nil
}
return result
}
// Included from fieldmanager.internal to avoid dependency cycle
// FieldsToSet creates a set paths from an input trie of fields
func decodeManagedFieldsEntrySet(f metav1.ManagedFieldsEntry) (s fieldpath.Set, err error) {
err = s.FromJSON(bytes.NewReader(f.FieldsV1.Raw))
return s, err
}
// SetToFields creates a trie of fields from an input set of paths
func encodeManagedFieldsEntrySet(f *metav1.ManagedFieldsEntry, s fieldpath.Set) (err error) {
f.FieldsV1.Raw, err = s.ToJSON()
return err
}

View File

@@ -0,0 +1,482 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csaupgrade_test
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/util/csaupgrade"
)
func TestUpgradeCSA(t *testing.T) {
cases := []struct {
Name string
CSAManager string
SSAManager string
OriginalObject []byte
ExpectedObject []byte
}{
{
// Case where there is a CSA entry with the given name, but no SSA entry
// is found. Expect that the CSA entry is converted to an SSA entry
// and renamed.
Name: "csa-basic-direct-conversion",
CSAManager: "kubectl-client-side-apply",
SSAManager: "kubectl",
OriginalObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
`),
ExpectedObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations: {}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations: {}
manager: kubectl
operation: Apply
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
`),
},
{
// This is the case when kubectl --server-side is used for the first time
// Server creates duplicate managed fields entry - one for Update and another
// for Apply. Expect entries to be merged into one entry, which is unchanged
// from initial SSA.
Name: "csa-combine-with-ssa-duplicate-keys",
CSAManager: "kubectl-client-side-apply",
SSAManager: "kubectl",
OriginalObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
`),
ExpectedObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations: {}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
name: test
namespace: default
`),
},
{
// This is the case when kubectl --server-side is used for the first time,
// but then a key is removed. A bug would take place where key is left in
// CSA entry but no longer present in SSA entry, so it would not be pruned.
// This shows that upgrading such an object results in correct behavior next
// time SSA applier
// Expect final object to have unioned keys from both entries
Name: "csa-combine-with-ssa-additional-keys",
CSAManager: "kubectl-client-side-apply",
SSAManager: "kubectl",
OriginalObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
`),
ExpectedObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations: {}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
name: test
namespace: default
`),
},
{
// Case when there are multiple CSA versions on the object which do not
// match the version from the apply entry. Shows they are tossed away
// without being merged.
Name: "csa-no-applicable-version",
CSAManager: "kubectl-client-side-apply",
SSAManager: "kubectl",
OriginalObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v5
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key2: {}
f:metadata:
f:annotations:
f:hello2: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
- apiVersion: v2
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key3: {}
f:metadata:
f:annotations:
f:hello3: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
- apiVersion: v3
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key4: {}
f:metadata:
f:annotations:
f:hello3: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
- apiVersion: v4
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key5: {}
f:metadata:
f:annotations:
f:hello4: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
`),
ExpectedObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations: {}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v5
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
name: test
namespace: default
`),
},
{
// Case when there are multiple CSA versions on the object which do not
// match the version from the apply entry, and one which does.
// Shows that CSA entry with matching version is unioned into the SSA entry.
Name: "csa-single-applicable-version",
CSAManager: "kubectl-client-side-apply",
SSAManager: "kubectl",
OriginalObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v5
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
- apiVersion: v5
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key2: {}
f:metadata:
f:annotations:
f:hello2: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
- apiVersion: v2
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key3: {}
f:metadata:
f:annotations:
f:hello3: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
- apiVersion: v3
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key4: {}
f:metadata:
f:annotations:
f:hello4: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
- apiVersion: v4
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key5: {}
f:metadata:
f:annotations:
f:hello5: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
`),
ExpectedObject: []byte(`
apiVersion: v1
data: {}
kind: ConfigMap
metadata:
annotations: {}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v5
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:key2: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:hello2: {}
manager: kubectl
operation: Apply
time: "2022-08-23T23:08:23Z"
name: test
namespace: default
`),
},
}
for _, testCase := range cases {
t.Run(testCase.Name, func(t *testing.T) {
initialObject := unstructured.Unstructured{}
err := yaml.Unmarshal(testCase.OriginalObject, &initialObject)
if err != nil {
t.Fatal(err)
}
upgraded := initialObject.DeepCopy()
err = csaupgrade.UpgradeManagedFields(
upgraded,
testCase.CSAManager,
testCase.SSAManager,
)
if err != nil {
t.Fatal(err)
}
expectedObject := unstructured.Unstructured{}
err = yaml.Unmarshal(testCase.ExpectedObject, &expectedObject)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(&expectedObject, upgraded) {
t.Fatal(cmp.Diff(&expectedObject, upgraded))
}
})
}
}

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