Compare commits

...

34 Commits

Author SHA1 Message Date
Kubernetes Publisher
2298e76bb3 Fix Godeps.json to point to kubernetes-1.12.11-beta.0 tags 2019-07-08 09:44:34 +00:00
Kubernetes Publisher
b6aa6aafe3 Merge pull request #77896 from mikedanese/automated-cherry-pick-of-#77613-upstream-release-1.12
Automated cherry pick of #77613 upstream release 1.12

Kubernetes-commit: df1282c3086f7103032f6d6a5eecaf7b2841d071
2019-05-18 07:04:19 +00:00
Kubernetes Publisher
d9857e2e80 Merge pull request #78032 from yuchengwu/automated-cherry-pick-of-#77874-github-release-1.12
Automated cherry pick of #77874: fix CVE-2019-11244: `kubectl --http-cache=<world-accessible

Kubernetes-commit: f6cee7a330a3b6f67701da4d0e76e65aa02a9159
2019-05-17 12:58:10 -07:00
Jordan Liggitt
8ca6fa75ef honor overridden tokenfile, add InClusterConfig override tests
Kubernetes-commit: c3c2c9b35cdedbf6dfac198b8ffc8fc137dab85e
2019-05-15 08:15:02 -04:00
Mike Danese
90c3c9898f BoundServiceAccountTokenVolume: fix InClusterConfig
Kubernetes-commit: 110387870d2dbe40b09df84c6b7196f53111e509
2019-05-14 09:29:16 -07:00
Yucheng Wu
57fa10648d fix CVE-2019-11244: kubectl --http-cache=<world-accessible dir> creates world-writeable cached schema files
Kubernetes-commit: b83756b3181f464720bfb468a171a58fc110c3e8
2019-05-14 14:49:38 +08:00
Kubernetes Publisher
77e032213d Merge pull request #74673 from logicalhan/automated-cherry-pick-of-#74636-upstream-release-1.12
Automated cherry pick of #74636: Remove reflector metrics as they currently cause a memory

Kubernetes-commit: b17cdb9d0b95c4812325739f5fd40edd9e3daa4d
2019-02-28 13:39:56 +00:00
Han Kang
f8885b25ae Remove reflector metrics as they currently cause a memory leak
Kubernetes-commit: 6cd8e914d2db604f49caedc67d68abe8c1356c79
2019-02-26 16:22:24 -08:00
Kubernetes Publisher
701b913670 Merge pull request #72838 from liggitt/automated-cherry-pick-of-#72825-upstream-release-1.12
Automated cherry pick of #72825: Find current resourceVersion for waiting for

Kubernetes-commit: 51dd616cdd25d6ee22c83a858773b607328a18ec
2019-01-15 08:48:55 -08:00
Jordan Liggitt
76bdf5a9dd Find current resourceVersion for waiting for deletion/conditions
Kubernetes-commit: d3299c036b091c7b0fb4453c574a25fa5d7d43df
2019-01-11 10:38:18 -05:00
Kubernetes Publisher
832c4d8903 Merge pull request #71937 from liggitt/automated-cherry-pick-of-#71713-#71857-upstream-release-1.12
Automated cherry pick of #71713: Plumb token and token file through rest.Config #71857: plumb bearer token and token file for e2e

Kubernetes-commit: fec194683d4ea96a78a1c9f8eb00d7496602cc50
2019-01-11 08:59:38 +00:00
Kubernetes Publisher
7bee181585 Merge pull request #72673 from liggitt/automated-cherry-pick-of-#72437-upstream-release-1.12
Automated cherry pick of #72437: Shorten re-read period for token files to work with

Kubernetes-commit: 4875c91e02890e082af959e94abc91f6e5e0c939
2019-01-10 19:33:21 +00:00
Jordan Liggitt
8d99f9c3d9 Shorten re-read period for token files to work with ProjectedTokenVolumeSource
Kubernetes-commit: cac030d46840b24f21c28c758447afeeae21ec0e
2018-12-29 22:18:28 -05:00
Jordan Liggitt
8ae8e474b0 partial: periodically reload tokens read from TokenFile in kubeconfig
partial pick of 718adb7473d2249d706b2031e0b8e0ffbd835be9

Kubernetes-commit: 58243f1c4c59fffa3423f3ec0a316eccae8db30c
2018-12-11 10:13:14 -08:00
Kubernetes Publisher
5e6a3d4e34 Merge pull request #71285 from cheftako/automated-cherry-pick-of-#70753-#70676-#70971-upstream-release-1.12
Automated cherry pick of #70753 #70676 #70971 upstream release 1.12

Kubernetes-commit: ee860a5df536d3626686b99007f68fc150447104
2018-12-05 11:04:44 +00:00
Jordan Liggitt
f1cd4ed075 Plumb token and token file through rest.Config
Kubernetes-commit: a8dd22a3d55b6efb2ee3602d94af5d79609dfef7
2018-12-04 11:24:29 -05:00
Kubernetes Publisher
02f343fb27 Merge pull request #70764 from wenjiaswe/automated-cherry-pick-of-#70663-upstream-release-1.12
Automated cherry pick of #70663: update  godeps for golang.org/x/net/... to

Kubernetes-commit: 6370ab3b63cbeed89bbe38aa7a757005eea9f22d
2018-11-28 19:22:47 +00:00
Walter Fender
f9c22737cd Report KCM as unhealthy if leader election is wedged. Feedback from lavalamp and deads2k. Changed Check() logic to be central to LeaderElector. Further changes, especially cleaning up the test code.
Kubernetes-commit: 48440fd281490813e789174667344950f800251b
2018-11-12 18:45:21 -08:00
Wenjia Zhang
bf41d317a1 update staging godeps for golang.org/x/net/... to release-branch.go1.10
Kubernetes-commit: d6b81437ef9e54ba05446acaaa71dec94093cc0e
2018-11-06 15:49:50 -08:00
Kubernetes Publisher
13596e875a Merge pull request #69942 from tallclair/automated-cherry-pick-of-#66516-upstream-release-1.12-1539730092
Automated cherry-pick of #66516 to v1.12

Kubernetes-commit: 687c04c53006441ed40f4fa7c09a726cfcec380e
2018-10-26 15:04:22 +00:00
Kubernetes Publisher
3e32c83330 Merge remote-tracking branch 'origin/master' into release-1.12
Kubernetes-commit: 4e599c848401470c3c10eb52252c5e3bb5463d3b
2018-09-19 15:55:54 -07:00
k8s-ci-robot
2126806484 Merge pull request #469 from nikhita/remove-installmd-from-release-9
Remove INSTALL.md from release-9.0
2018-09-20 05:00:59 -07:00
k8s-ci-robot
c99ff00c5d Merge pull request #470 from nikhita/fix-travis-release-9
Bump go version to 1.10.2 for travis for release-9.0
2018-09-20 04:46:04 -07:00
Nikhita Raghunath
073aba60c8 Bump go version to 1.10.2 for travis 2018-09-20 10:42:18 +05:30
Nikhita Raghunath
d914ce8e0a Remove INSTALL.md 2018-09-20 10:33:26 +05:30
Kubernetes Publisher
5b1220ffef sync: update godeps 2018-09-18 21:58:36 +00:00
Nikhita Raghunath
782e840778 Move INSTALL.md from k8s.io/client-go to staging
Kubernetes-commit: 331ae66491a775c24887413f44549d83b797b61e
2018-09-18 20:55:22 +05:30
Kubernetes Publisher
8f44824f9b Merge remote-tracking branch 'origin/master' into release-1.12
Kubernetes-commit: c185d97b8d9f2b687ecac6fc88350476155da716
2018-09-18 03:42:26 +00:00
Kubernetes Publisher
1fb328fb33 Merge pull request #68530 from wenjiaswe/68115
Rewrite finalURLTemplate used only for metrics because of dynamic client change

Kubernetes-commit: 817d420d6807d0b3bd0a4baa20d709ab755ad602
2018-09-18 03:41:55 +00:00
Kubernetes Publisher
539ab164ee sync: update godeps 2018-09-16 05:08:00 +00:00
Kubernetes Publisher
b4527731f1 Merge remote-tracking branch 'origin/master' into release-1.12
Kubernetes-commit: bcc764f53331c68809b58f2c77568e95dde49956
2018-09-15 06:32:46 +00:00
Wenjia Zhang
c268c175b4 Rewrite finalURLTemplate used only for metrics because of dynamic client change
Kubernetes-commit: 99248b8fe1fe1c28188657d811dc7baf8cd12982
2018-09-14 11:31:21 -07:00
Tim Allclair
32ec4b1882 Only allow apiserver to follow redriects to the same host
Kubernetes-commit: e385ba8c4a0860746e661b39611c88aff5bee5f2
2018-07-23 13:16:25 -07:00
Jeff Lowdermilk
cfba9881e4 gcp client auth plugin: persist default cache on unauthorized
The default cache for a cachedTokenSource is not always empty. In the
case of commandTokenSource, it contains calling details for the
external command that is used to generate refresh tokens. Persisting
a completely empty cache will thus break ability for the plugin to
obtain refresh tokens. This changes the roundtripper to persist
the default cache instead of assuming an empty map.

Kubernetes-commit: 73e5e43711043c6a80d41b1be080b3b2d3dfbacb
2018-07-17 14:06:11 -07:00
27 changed files with 865 additions and 204 deletions

View File

@@ -3,6 +3,6 @@ language: go
go_import_path: k8s.io/client-go
go:
- 1.8.1
- 1.10.2
script: go build ./...

178
Godeps/Godeps.json generated
View File

@@ -192,27 +192,27 @@
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
},
{
"ImportPath": "golang.org/x/net/http2",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
},
{
"ImportPath": "golang.org/x/net/http2/hpack",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
},
{
"ImportPath": "golang.org/x/net/idna",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
},
{
"ImportPath": "golang.org/x/net/lex/httplex",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
"Rev": "0ed95abb35c445290478a5348a7b38bb154135fd"
},
{
"ImportPath": "golang.org/x/oauth2",
@@ -272,335 +272,335 @@
},
{
"ImportPath": "k8s.io/api/admissionregistration/v1alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/admissionregistration/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/apps/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/apps/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/apps/v1beta2",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/authentication/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/authentication/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/authorization/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/authorization/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/autoscaling/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/autoscaling/v2beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/autoscaling/v2beta2",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/batch/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/batch/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/batch/v2alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/certificates/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/coordination/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/core/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/events/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/extensions/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/imagepolicy/v1alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/networking/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/policy/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/rbac/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/rbac/v1alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/rbac/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/scheduling/v1alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/scheduling/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/settings/v1alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/storage/v1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/storage/v1alpha1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/api/storage/v1beta1",
"Rev": "0527d9f2238346a310e6cf1e6afe2422f329cc3d"
"Rev": "bec99f884545c2e29b47e1934fdcd4d61ee63302"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/roundtrip",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/labels",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/selection",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/types",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/version",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/watch",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "63dd81ab0848cd58da3257a806f599808708029c"
"Rev": "01f179d85dbce0f2e0e4351a92394b38694b7cae"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",

View File

@@ -63,7 +63,7 @@ To install `client-go` and place its dependencies in your `$GOPATH`:
```sh
go get k8s.io/client-go/...
cd $GOPATH/src/k8s.io/client-go
git checkout v6.0.0
git checkout v9.0.0 # replace v9.0.0 with the required version
# cd 1.5 # only necessary with 1.5 and 1.4 clients.
godep restore ./...
```
@@ -99,7 +99,7 @@ your project:
package: ( your project's import path ) # e.g. github.com/foo/bar
import:
- package: k8s.io/client-go
version: v6.0.0
version: v9.0.0 # replace v9.0.0 with the required version
```
Second, add a Go file that imports `client-go` somewhere in your project,
@@ -132,7 +132,7 @@ requests can override the version manually in `glide.yaml`. For example:
package: ( your project's import path ) # e.g. github.com/foo/bar
import:
- package: k8s.io/client-go
version: v6.0.0
version: v9.0.0 # replace v9.0.0 with the required version
# Use a newer version of go-spew even though client-go wants an old one.
- package: github.com/davecgh/go-spew
version: v1.1.0

View File

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

View File

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

View File

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

View File

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

View File

@@ -303,6 +303,7 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns
}
list := &unstructured.UnstructuredList{}
list.SetResourceVersion(entireList.GetResourceVersion())
for i := range entireList.Items {
item := &entireList.Items[i]
metadata, err := meta.Accessor(item)

View File

@@ -174,7 +174,13 @@ func parseScopes(gcpConfig map[string]string) []string {
}
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister}
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 }
@@ -247,6 +253,19 @@ func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {
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
@@ -337,6 +356,7 @@ func parseJSONPath(input interface{}, name, template string) (string, error) {
type conditionalTransport struct {
oauthTransport *oauth2.Transport
persister restclient.AuthProviderConfigPersister
resetCache map[string]string
}
var _ net.RoundTripperWrapper = &conditionalTransport{}
@@ -354,8 +374,7 @@ func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, err
if res.StatusCode == 401 {
glog.V(4).Infof("The credentials that were supplied are invalid for the target cluster")
emptyCache := make(map[string]string)
t.persister.Persist(emptyCache)
t.persister.Persist(t.resetCache)
}
return res, nil

View File

@@ -442,37 +442,61 @@ func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.res, nil
}
func TestClearingCredentials(t *testing.T) {
func Test_cmdTokenSource_roundTrip(t *testing.T) {
accessToken := "fakeToken"
fakeExpiry := time.Now().Add(time.Hour)
cache := map[string]string{
"access-token": "fakeToken",
"expiry": fakeExpiry.String(),
fakeExpiryStr := fakeExpiry.Format(time.RFC3339Nano)
fs := &fakeTokenSource{
token: &oauth2.Token{
AccessToken: accessToken,
Expiry: fakeExpiry,
},
}
cts := cachedTokenSource{
source: nil,
accessToken: cache["access-token"],
expiry: fakeExpiry,
persister: nil,
cache: nil,
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
cache map[string]string
name string
res http.Response
baseCache, expectedCache map[string]string
}{
{
"Unauthorized",
http.Response{StatusCode: 401},
make(map[string]string),
make(map[string]string),
},
{
"Unauthorized, nonempty defaultCache",
http.Response{StatusCode: 401},
cmdCache,
cmdCache,
},
{
"Authorized",
http.Response{StatusCode: 200},
cache,
make(map[string]string),
simpleCacheUpdated,
},
{
"Authorized, nonempty defaultCache",
http.Response{StatusCode: 200},
cmdCache,
cmdCacheUpdated,
},
}
@@ -480,17 +504,23 @@ func TestClearingCredentials(t *testing.T) {
req := http.Request{Header: http.Header{}}
for _, tc := range tests {
authProvider := gcpAuthProvider{&cts, persister}
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))
persister.Persist(cache)
// 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.cache) {
t.Errorf("got cache %v, want %v", got, tc.cache)
if got := persister.read(); !reflect.DeepEqual(got, tc.expectedCache) {
t.Errorf("got cache %v, want %v", got, tc.expectedCache)
}
}

View File

@@ -70,6 +70,11 @@ type Config struct {
// TODO: demonstrate an OAuth2 compatible client.
BearerToken string
// Path to a file containing a BearerToken.
// If set, the contents are periodically read.
// The last successfully read value takes precedence over BearerToken.
BearerTokenFile string
// Impersonate is the configuration that RESTClient will use for impersonation.
Impersonate ImpersonationConfig
@@ -322,9 +327,8 @@ func InClusterConfig() (*Config, error) {
return nil, ErrNotInCluster
}
ts := newCachedPathTokenSource(tokenFile)
if _, err := ts.Token(); err != nil {
token, err := ioutil.ReadFile(tokenFile)
if err != nil {
return nil, err
}
@@ -340,7 +344,8 @@ func InClusterConfig() (*Config, error) {
// TODO: switch to using cluster DNS.
Host: "https://" + net.JoinHostPort(host, port),
TLSClientConfig: tlsClientConfig,
WrapTransport: TokenSourceWrapTransport(ts),
BearerToken: string(token),
BearerTokenFile: tokenFile,
}, nil
}
@@ -430,12 +435,13 @@ func AnonymousClientConfig(config *Config) *Config {
// CopyConfig returns a copy of the given config
func CopyConfig(config *Config) *Config {
return &Config{
Host: config.Host,
APIPath: config.APIPath,
ContentConfig: config.ContentConfig,
Username: config.Username,
Password: config.Password,
BearerToken: config.BearerToken,
Host: config.Host,
APIPath: config.APIPath,
ContentConfig: config.ContentConfig,
Username: config.Username,
Password: config.Password,
BearerToken: config.BearerToken,
BearerTokenFile: config.BearerTokenFile,
Impersonate: ImpersonationConfig{
Groups: config.Impersonate.Groups,
Extra: config.Impersonate.Extra,

View File

@@ -264,6 +264,7 @@ func TestAnonymousConfig(t *testing.T) {
// is added to Config, update AnonymousClientConfig to preserve the field otherwise.
expected.Impersonate = ImpersonationConfig{}
expected.BearerToken = ""
expected.BearerTokenFile = ""
expected.Username = ""
expected.Password = ""
expected.AuthProvider = nil

View File

@@ -455,17 +455,9 @@ func (r *Request) URL() *url.URL {
// 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 request so as not to change the
// underlying object. This means some useful request info (like the types of field
// selectors in use) will be lost.
// TODO: preserve field selector keys
// 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 {
if len(r.resourceName) != 0 {
r.resourceName = "{name}"
}
if r.namespaceSet && len(r.namespace) != 0 {
r.namespace = "{namespace}"
}
newParams := url.Values{}
v := []string{"{value}"}
for k := range r.params {
@@ -473,6 +465,59 @@ func (r Request) finalURLTemplate() url.URL {
}
r.params = newParams
url := r.URL()
segments := strings.Split(r.URL().Path, "/")
groupIndex := 0
index := 0
if r.URL() != nil && r.baseURL != nil && strings.Contains(r.URL().Path, r.baseURL.Path) {
groupIndex += len(strings.Split(r.baseURL.Path, "/"))
}
if groupIndex >= len(segments) {
return *url
}
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
url.Path = "/{prefix}"
url.RawQuery = ""
return *url
}
//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}"
}
}
url.Path = path.Join(segments...)
return *url
}

View File

@@ -340,21 +340,169 @@ func TestResultIntoWithNoBodyReturnsErr(t *testing.T) {
}
func TestURLTemplate(t *testing.T) {
uri, _ := url.Parse("http://localhost")
r := NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0)
r.Prefix("pre1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0")
full := r.URL()
if full.String() != "http://localhost/pre1/namespaces/ns/r1/nm?p0=v0" {
t.Errorf("unexpected initial URL: %s", full)
uri, _ := url.Parse("http://localhost/some/base/url/path")
testCases := []struct {
Request *Request
ExpectedFullURL string
ExpectedFinalURL string
}{
{
// non dynamic client
Request: NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).
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",
},
}
actualURL := r.finalURLTemplate()
actual := actualURL.String()
expected := "http://localhost/pre1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D"
if actual != expected {
t.Errorf("unexpected URL template: %s %s", actual, expected)
}
if r.URL().String() != full.String() {
t.Errorf("creating URL template changed request: %s -> %s", full.String(), r.URL().String())
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())
}
}
}

View File

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

View File

@@ -24,10 +24,8 @@ import (
"net"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
@@ -95,17 +93,10 @@ func NewReflector(lw ListerWatcher, expectedType interface{}, store Store, resyn
return NewNamedReflector(naming.GetNameFromCallsite(internalPackages...), lw, expectedType, store, resyncPeriod)
}
// reflectorDisambiguator is used to disambiguate started reflectors.
// initialized to an unstable value to ensure meaning isn't attributed to the suffix.
var reflectorDisambiguator = int64(time.Now().UnixNano() % 12345)
// NewNamedReflector same as NewReflector, but with a specified name for logging
func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
reflectorSuffix := atomic.AddInt64(&reflectorDisambiguator, 1)
r := &Reflector{
name: name,
// we need this to be unique per process (some names are still the same) but obvious who it belongs to
metrics: newReflectorMetrics(makeValidPrometheusMetricLabel(fmt.Sprintf("reflector_"+name+"_%d", reflectorSuffix))),
name: name,
listerWatcher: lw,
store: store,
expectedType: reflect.TypeOf(expectedType),
@@ -173,13 +164,10 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
// to be served from cache and potentially be delayed relative to
// etcd contents. Reflector framework will catch up via Watch() eventually.
options := metav1.ListOptions{ResourceVersion: "0"}
r.metrics.numberOfLists.Inc()
start := r.clock.Now()
list, err := r.listerWatcher.List(options)
if err != nil {
return fmt.Errorf("%s: Failed to list %v: %v", r.name, r.expectedType, err)
}
r.metrics.listDuration.Observe(time.Since(start).Seconds())
listMetaInterface, err := meta.ListAccessor(list)
if err != nil {
return fmt.Errorf("%s: Unable to understand list result %#v: %v", r.name, list, err)
@@ -189,7 +177,6 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
if err != nil {
return fmt.Errorf("%s: Unable to understand list result %#v (%v)", r.name, list, err)
}
r.metrics.numberOfItemsInList.Observe(float64(len(items)))
if err := r.syncWith(items, resourceVersion); err != nil {
return fmt.Errorf("%s: Unable to sync list result: %v", r.name, err)
}
@@ -239,7 +226,6 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
TimeoutSeconds: &timeoutSeconds,
}
r.metrics.numberOfWatches.Inc()
w, err := r.listerWatcher.Watch(options)
if err != nil {
switch err {
@@ -291,11 +277,6 @@ func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, err
// Stopping the watcher should be idempotent and if we return from this function there's no way
// we're coming back in with the same watch interface.
defer w.Stop()
// update metrics
defer func() {
r.metrics.numberOfItemsInWatch.Observe(float64(eventCount))
r.metrics.watchDuration.Observe(time.Since(start).Seconds())
}()
loop:
for {
@@ -351,7 +332,6 @@ loop:
watchDuration := r.clock.Now().Sub(start)
if watchDuration < 1*time.Second && eventCount == 0 {
r.metrics.numberOfShortWatches.Inc()
return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)
}
glog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedType, eventCount)
@@ -370,9 +350,4 @@ func (r *Reflector) setLastSyncResourceVersion(v string) {
r.lastSyncResourceVersionMutex.Lock()
defer r.lastSyncResourceVersionMutex.Unlock()
r.lastSyncResourceVersion = v
rv, err := strconv.Atoi(v)
if err == nil {
r.metrics.lastResourceVersion.Set(float64(rv))
}
}

View File

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

View File

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

View File

@@ -0,0 +1,69 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"net/http"
"sync"
"time"
)
// HealthzAdaptor associates the /healthz endpoint with the LeaderElection object.
// It helps deal with the /healthz endpoint being set up prior to the LeaderElection.
// This contains the code needed to act as an adaptor between the leader
// election code the health check code. It allows us to provide health
// status about the leader election. Most specifically about if the leader
// has failed to renew without exiting the process. In that case we should
// report not healthy and rely on the kubelet to take down the process.
type HealthzAdaptor struct {
pointerLock sync.Mutex
le *LeaderElector
timeout time.Duration
}
// Name returns the name of the health check we are implementing.
func (l *HealthzAdaptor) Name() string {
return "leaderElection"
}
// Check is called by the healthz endpoint handler.
// It fails (returns an error) if we own the lease but had not been able to renew it.
func (l *HealthzAdaptor) Check(req *http.Request) error {
l.pointerLock.Lock()
defer l.pointerLock.Unlock()
if l.le == nil {
return nil
}
return l.le.Check(l.timeout)
}
// SetLeaderElection ties a leader election object to a HealthzAdaptor
func (l *HealthzAdaptor) SetLeaderElection(le *LeaderElector) {
l.pointerLock.Lock()
defer l.pointerLock.Unlock()
l.le = le
}
// NewLeaderHealthzAdaptor creates a basic healthz adaptor to monitor a leader election.
// timeout determines the time beyond the lease expiry to be allowed for timeout.
// checks within the timeout period after the lease expires will still return healthy.
func NewLeaderHealthzAdaptor(timeout time.Duration) *HealthzAdaptor {
result := &HealthzAdaptor{
timeout: timeout,
}
return result
}

View File

@@ -0,0 +1,175 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leaderelection
import (
"fmt"
"testing"
"time"
"k8s.io/apimachinery/pkg/util/clock"
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
"net/http"
)
type fakeLock struct {
identity string
}
// Get is a dummy to allow us to have a fakeLock for testing.
func (fl *fakeLock) Get() (ler *rl.LeaderElectionRecord, err error) {
return nil, nil
}
// Create is a dummy to allow us to have a fakeLock for testing.
func (fl *fakeLock) Create(ler rl.LeaderElectionRecord) error {
return nil
}
// Update is a dummy to allow us to have a fakeLock for testing.
func (fl *fakeLock) Update(ler rl.LeaderElectionRecord) error {
return nil
}
// RecordEvent is a dummy to allow us to have a fakeLock for testing.
func (fl *fakeLock) RecordEvent(string) {}
// Identity is a dummy to allow us to have a fakeLock for testing.
func (fl *fakeLock) Identity() string {
return fl.identity
}
// Describe is a dummy to allow us to have a fakeLock for testing.
func (fl *fakeLock) Describe() string {
return "Dummy implementation of lock for testing"
}
// TestLeaderElectionHealthChecker tests that the healthcheck for leader election handles its edge cases.
func TestLeaderElectionHealthChecker(t *testing.T) {
current := time.Now()
req := &http.Request{}
tests := []struct {
description string
expected error
adaptorTimeout time.Duration
elector *LeaderElector
}{
{
description: "call check before leader elector initialized",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: nil,
},
{
description: "call check when the the lease is far expired",
expected: fmt.Errorf("failed election to renew leadership on lease %s", "foo"),
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
config: LeaderElectionConfig{
Lock: &fakeLock{identity: "healthTest"},
LeaseDuration: time.Minute,
Name: "foo",
},
observedRecord: rl.LeaderElectionRecord{
HolderIdentity: "healthTest",
},
observedTime: current,
clock: clock.NewFakeClock(current.Add(time.Hour)),
},
},
{
description: "call check when the the lease is far expired but held by another server",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
config: LeaderElectionConfig{
Lock: &fakeLock{identity: "healthTest"},
LeaseDuration: time.Minute,
Name: "foo",
},
observedRecord: rl.LeaderElectionRecord{
HolderIdentity: "otherServer",
},
observedTime: current,
clock: clock.NewFakeClock(current.Add(time.Hour)),
},
},
{
description: "call check when the the lease is not expired",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
config: LeaderElectionConfig{
Lock: &fakeLock{identity: "healthTest"},
LeaseDuration: time.Minute,
Name: "foo",
},
observedRecord: rl.LeaderElectionRecord{
HolderIdentity: "healthTest",
},
observedTime: current,
clock: clock.NewFakeClock(current),
},
},
{
description: "call check when the the lease is expired but inside the timeout",
expected: nil,
adaptorTimeout: time.Second * 20,
elector: &LeaderElector{
config: LeaderElectionConfig{
Lock: &fakeLock{identity: "healthTest"},
LeaseDuration: time.Minute,
Name: "foo",
},
observedRecord: rl.LeaderElectionRecord{
HolderIdentity: "healthTest",
},
observedTime: current,
clock: clock.NewFakeClock(current.Add(time.Minute).Add(time.Second)),
},
},
}
for _, test := range tests {
adaptor := NewLeaderHealthzAdaptor(test.adaptorTimeout)
if adaptor.le != nil {
t.Errorf("[%s] leaderChecker started with a LeaderElector %v", test.description, adaptor.le)
}
if test.elector != nil {
test.elector.config.WatchDog = adaptor
adaptor.SetLeaderElection(test.elector)
if adaptor.le == nil {
t.Errorf("[%s] adaptor failed to set the LeaderElector", test.description)
}
}
err := adaptor.Check(req)
if test.expected == nil {
if err == nil {
continue
}
t.Errorf("[%s] called check, expected no error but received \"%v\"", test.description, err)
} else {
if err == nil {
t.Errorf("[%s] called check and failed to received the expected error \"%v\"", test.description, test.expected)
}
if err.Error() != test.expected.Error() {
t.Errorf("[%s] called check, expected %v, received %v", test.description, test.expected, err)
}
}
}
}

View File

@@ -56,6 +56,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
@@ -90,6 +91,7 @@ func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {
}
return &LeaderElector{
config: lec,
clock: clock.RealClock{},
}, nil
}
@@ -111,6 +113,13 @@ type LeaderElectionConfig struct {
// Callbacks are callbacks that are triggered during certain lifecycle
// events of the LeaderElector
Callbacks LeaderCallbacks
// WatchDog is the associated health checker
// WatchDog may be null if its not needed/configured.
WatchDog *HealthzAdaptor
// Name is the name of the resource lock for debugging
Name string
}
// LeaderCallbacks are callbacks that are triggered during certain
@@ -139,6 +148,12 @@ type LeaderElector struct {
// value observedRecord.HolderIdentity if the transition has
// not yet been reported.
reportedLeader string
// clock is wrapper around time to allow for less flaky testing
clock clock.Clock
// name is the name of the resource lock for debugging
name string
}
// Run starts the leader election loop
@@ -163,6 +178,9 @@ func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
if err != nil {
panic(err)
}
if lec.WatchDog != nil {
lec.WatchDog.SetLeaderElection(le)
}
le.Run(ctx)
}
@@ -257,14 +275,14 @@ func (le *LeaderElector) tryAcquireOrRenew() bool {
return false
}
le.observedRecord = leaderElectionRecord
le.observedTime = time.Now()
le.observedTime = le.clock.Now()
return true
}
// 2. Record obtained, check the Identity & Time
if !reflect.DeepEqual(le.observedRecord, *oldLeaderElectionRecord) {
le.observedRecord = *oldLeaderElectionRecord
le.observedTime = time.Now()
le.observedTime = le.clock.Now()
}
if le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
!le.IsLeader() {
@@ -287,7 +305,7 @@ func (le *LeaderElector) tryAcquireOrRenew() bool {
return false
}
le.observedRecord = leaderElectionRecord
le.observedTime = time.Now()
le.observedTime = le.clock.Now()
return true
}
@@ -300,3 +318,19 @@ func (le *LeaderElector) maybeReportTransition() {
go le.config.Callbacks.OnNewLeader(le.reportedLeader)
}
}
// Check will determine if the current lease is expired by more than timeout.
func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error {
if !le.IsLeader() {
// Currently not concerned with the case that we are hot standby
return nil
}
// If we are more than timeout seconds after the lease duration that is past the timeout
// on the lease renew. Time to start reporting ourselves as unhealthy. We should have
// died but conditions like deadlock can prevent this. (See #70819)
if le.clock.Since(le.observedTime) > le.config.LeaseDuration+maxTolerableExpiredLease {
return fmt.Errorf("failed election to renew leadership on lease %s", le.config.Name)
}
return nil
}

View File

@@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/clock"
fakecorev1 "k8s.io/client-go/kubernetes/typed/core/v1/fake"
core "k8s.io/client-go/testing"
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
@@ -257,6 +258,7 @@ func testTryAcquireOrRenew(t *testing.T, objectType string) {
config: lec,
observedRecord: test.observedRecord,
observedTime: test.observedTime,
clock: clock.RealClock{},
}
if test.expectSuccess != le.tryAcquireOrRenew() {

View File

@@ -39,6 +39,11 @@ type Config struct {
// Bearer token for authentication
BearerToken string
// Path to a file containing a BearerToken.
// If set, the contents are periodically read.
// The last successfully read value takes precedence over BearerToken.
BearerTokenFile string
// Impersonate is the config that this Config will impersonate using
Impersonate ImpersonationConfig
@@ -80,7 +85,7 @@ func (c *Config) HasBasicAuth() bool {
// HasTokenAuth returns whether the configuration has token authentication or not.
func (c *Config) HasTokenAuth() bool {
return len(c.BearerToken) != 0
return len(c.BearerToken) != 0 || len(c.BearerTokenFile) != 0
}
// HasCertAuth returns whether the configuration has certificate authentication or not.

View File

@@ -23,6 +23,7 @@ import (
"time"
"github.com/golang/glog"
"golang.org/x/oauth2"
utilnet "k8s.io/apimachinery/pkg/util/net"
)
@@ -44,7 +45,11 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
case config.HasBasicAuth() && config.HasTokenAuth():
return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
case config.HasTokenAuth():
rt = NewBearerAuthRoundTripper(config.BearerToken, rt)
var err error
rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt)
if err != nil {
return nil, err
}
case config.HasBasicAuth():
rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
}
@@ -265,13 +270,35 @@ func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { r
type bearerAuthRoundTripper struct {
bearer string
source oauth2.TokenSource
rt http.RoundTripper
}
// NewBearerAuthRoundTripper adds the provided bearer token to a request
// unless the authorization header has already been set.
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
return &bearerAuthRoundTripper{bearer, rt}
return &bearerAuthRoundTripper{bearer, nil, rt}
}
// NewBearerAuthRoundTripper adds the provided bearer token to a request
// unless the authorization header has already been set.
// If tokenFile is non-empty, it is periodically read,
// and the last successfully read content is used as the bearer token.
// If tokenFile is non-empty and bearer is empty, the tokenFile is read
// immediately to populate the initial bearer token.
func NewBearerAuthWithRefreshRoundTripper(bearer string, tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
if len(tokenFile) == 0 {
return &bearerAuthRoundTripper{bearer, nil, rt}, nil
}
source := NewCachedFileTokenSource(tokenFile)
if len(bearer) == 0 {
token, err := source.Token()
if err != nil {
return nil, err
}
bearer = token.AccessToken
}
return &bearerAuthRoundTripper{bearer, source, rt}, nil
}
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -280,7 +307,13 @@ func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response,
}
req = utilnet.CloneRequest(req)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer))
token := rt.bearer
if rt.source != nil {
if refreshedToken, err := rt.source.Token(); err == nil {
token = refreshedToken.AccessToken
}
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return rt.rt.RoundTrip(req)
}

View File

@@ -38,7 +38,7 @@ func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, er
if err != nil {
return nil, nil, err
}
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true)
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true, false)
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper)
if err != nil {
return nil, nil, err

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package rest
package transport
import (
"fmt"
@@ -42,17 +42,19 @@ func TokenSourceWrapTransport(ts oauth2.TokenSource) func(http.RoundTripper) htt
}
}
func newCachedPathTokenSource(path string) oauth2.TokenSource {
// NewCachedFileTokenSource returns a oauth2.TokenSource reads a token from a
// file at a specified path and periodically reloads it.
func NewCachedFileTokenSource(path string) oauth2.TokenSource {
return &cachingTokenSource{
now: time.Now,
leeway: 1 * time.Minute,
leeway: 10 * time.Second,
base: &fileTokenSource{
path: path,
// This period was picked because it is half of the minimum validity
// duration for a token provisioned by they TokenRequest API. This is
// unsophisticated and should induce rotation at a frequency that should
// work with the token volume source.
period: 5 * time.Minute,
// This period was picked because it is half of the duration between when the kubelet
// refreshes a projected service account token and when the original token expires.
// Default token lifetime is 10 minutes, and the kubelet starts refreshing at 80% of lifetime.
// This should induce re-reading at a frequency that works with the token volume source.
period: time.Minute,
},
}
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package rest
package transport
import (
"fmt"