mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-29 21:45:12 +00:00
Compare commits
98 Commits
kubernetes
...
kubernetes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e167e7c1e | ||
|
|
cbd9965a0e | ||
|
|
7571b2924a | ||
|
|
f157671357 | ||
|
|
9b03088ac3 | ||
|
|
6c1248a47f | ||
|
|
79cc619401 | ||
|
|
a1c4661c03 | ||
|
|
dcf16a0f3b | ||
|
|
f3fa17a207 | ||
|
|
7f0192792d | ||
|
|
2ccedc1ac8 | ||
|
|
ba3e01d473 | ||
|
|
9d900fc3f3 | ||
|
|
40fb7ab6be | ||
|
|
672cf70624 | ||
|
|
4c5811d72b | ||
|
|
1f8420dc26 | ||
|
|
7f1c01699a | ||
|
|
9c81604894 | ||
|
|
ae670e1f7c | ||
|
|
3e116d4527 | ||
|
|
a0aefdd1eb | ||
|
|
da56c29d61 | ||
|
|
30575d7a9f | ||
|
|
aa75b7764b | ||
|
|
155d5ec829 | ||
|
|
2a9c6c4210 | ||
|
|
61a24777f8 | ||
|
|
78bf9020c4 | ||
|
|
c49eef9ec8 | ||
|
|
e2bc6cda8d | ||
|
|
97dc22a90a | ||
|
|
bb17ab6ef1 | ||
|
|
44e7f0ea19 | ||
|
|
4d3c94690f | ||
|
|
a5d40177db | ||
|
|
087039522d | ||
|
|
e39833ec3c | ||
|
|
b8d8ea0ef0 | ||
|
|
33124a8148 | ||
|
|
c5518cda52 | ||
|
|
3655b8b061 | ||
|
|
0fa5515631 | ||
|
|
93821fae80 | ||
|
|
ca74aedde1 | ||
|
|
7f726ce02d | ||
|
|
91ab78faba | ||
|
|
0152131f08 | ||
|
|
c8e76297e1 | ||
|
|
780aa459ed | ||
|
|
bfcfd82283 | ||
|
|
cf575eb079 | ||
|
|
bd418b4186 | ||
|
|
13b096589c | ||
|
|
86dcffcdde | ||
|
|
b5eba4c261 | ||
|
|
33fabb1e0f | ||
|
|
37c3c02ec9 | ||
|
|
3652287890 | ||
|
|
9a73dd2136 | ||
|
|
f06dbfd735 | ||
|
|
ec724c24d1 | ||
|
|
22e1ddcc48 | ||
|
|
87935b98dd | ||
|
|
31ff53b616 | ||
|
|
855e8fadf4 | ||
|
|
030d971055 | ||
|
|
ada81a0d75 | ||
|
|
79cb21f5b3 | ||
|
|
6007d7f0a9 | ||
|
|
3a92319114 | ||
|
|
8dd267ed24 | ||
|
|
b53d9ecf53 | ||
|
|
5d107d4ebc | ||
|
|
35223a0681 | ||
|
|
d3f03ecc37 | ||
|
|
e1ca545b2c | ||
|
|
b95fa2e4f3 | ||
|
|
744b11616f | ||
|
|
f3c7bb05b0 | ||
|
|
b35fd0f035 | ||
|
|
eb0f6510ef | ||
|
|
69308bcf06 | ||
|
|
43b95360bd | ||
|
|
f314ed00c1 | ||
|
|
4528feba43 | ||
|
|
6dac86f710 | ||
|
|
82acb64076 | ||
|
|
045dbe7ac1 | ||
|
|
a83d2a2682 | ||
|
|
a6d1c60475 | ||
|
|
b345654764 | ||
|
|
442fc9f1c4 | ||
|
|
44530d33a7 | ||
|
|
e88f376842 | ||
|
|
4b6317c5d4 | ||
|
|
17d4c50f8a |
@@ -3,6 +3,8 @@ language: go
|
||||
go_import_path: k8s.io/client-go
|
||||
|
||||
go:
|
||||
- 1.8.1
|
||||
- 1.11.1
|
||||
|
||||
script: go build ./...
|
||||
script:
|
||||
- if [ "$TRAVIS_BRANCH" != "master" ]; then godep restore; fi
|
||||
- go build ./...
|
||||
|
||||
181
CHANGELOG.md
181
CHANGELOG.md
@@ -5,6 +5,187 @@ https://github.com/kubernetes/test-infra/issues/5843.
|
||||
Changes in `k8s.io/api` and `k8s.io/apimachinery` are mentioned here
|
||||
because `k8s.io/client-go` depends on them.
|
||||
|
||||
# v9.0.0
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* client-go now supports additional non-alpha-numeric characters in UserInfo
|
||||
"extra" data keys. It should be updated in order to properly support extra
|
||||
data containing "/" characters or other characters disallowed in HTTP headers.
|
||||
Old clients sending keys which were `%`-escaped by the user will have their
|
||||
values unescaped by new API servers.
|
||||
([#65799](https://github.com/kubernetes/kubernetes/pull/65799))
|
||||
|
||||
* `apimachinery/pkg/watch.Until` has been moved to
|
||||
`client-go/tools/watch.UntilWithoutRetry`. While switching please consider
|
||||
using the new `client-go/tools/watch.UntilWithSync` or `client-go/tools/watch.Until`.
|
||||
([#66906](https://github.com/kubernetes/kubernetes/pull/66906))
|
||||
|
||||
* [k8s.io/apimachinery] `Unstructured` metadata accessors now respect omitempty semantics
|
||||
i.e. a field having zero value will now be removed from the unstructured metadata map.
|
||||
([#67635](https://github.com/kubernetes/kubernetes/pull/67635))
|
||||
|
||||
* [k8s.io/apimachinery] The `ObjectConvertor` interface is now changed such that
|
||||
`ConvertFieldLabel` func takes GroupVersionKind as an argument instead of just
|
||||
version and kind. ([#65780](https://github.com/kubernetes/kubernetes/pull/65780))
|
||||
|
||||
* [k8s.io/apimachinery] componentconfig `ClientConnectionConfiguration` is
|
||||
moved to `k8s.io/apimachinery/pkg/apis/config`.
|
||||
([#66058](https://github.com/kubernetes/kubernetes/pull/66058))
|
||||
|
||||
* [k8s.io/apimachinery] Renamed ` KubeConfigFile` to `Kubeconfig` in
|
||||
`ClientConnectionConfiguration`.
|
||||
([#67149](https://github.com/kubernetes/kubernetes/pull/67149))
|
||||
|
||||
* [k8s.io/apimachinery] JSON patch no longer supports `int`.
|
||||
([#63522](https://github.com/kubernetes/kubernetes/pull/63522))
|
||||
|
||||
**New Features:**
|
||||
|
||||
* Add ability to cancel leader election.
|
||||
This also proves useful in integration tests where the whole app is started and
|
||||
stopped in each test. ([#57932](https://github.com/kubernetes/kubernetes/pull/57932))
|
||||
|
||||
* An example showing how to use fake clients in tests is added.
|
||||
([#65291](https://github.com/kubernetes/kubernetes/pull/65291))
|
||||
|
||||
* [k8s.io/apimachinery] Create and Update now support `CreateOptions` and `UpdateOptions`.
|
||||
([#65105](https://github.com/kubernetes/kubernetes/pull/65105))
|
||||
|
||||
**Bug fixes and Improvements:**
|
||||
|
||||
* Decrease the amount of time it takes to modify kubeconfig
|
||||
files with large amounts of contexts.
|
||||
([#67093](https://github.com/kubernetes/kubernetes/pull/67093))
|
||||
|
||||
* The leader election client now renews timeout.
|
||||
([#65094](https://github.com/kubernetes/kubernetes/pull/65094))
|
||||
|
||||
* Switched certificate data replacement from `REDACTED` to `DATA+OMITTED`.
|
||||
([#66023](https://github.com/kubernetes/kubernetes/pull/66023))
|
||||
|
||||
* Fix listing in the fake dynamic client.
|
||||
([#66078](https://github.com/kubernetes/kubernetes/pull/66078))
|
||||
|
||||
* Fix discovery so that plural names are no longer ignored if a singular name is not specified.
|
||||
([#66249](https://github.com/kubernetes/kubernetes/pull/66249))
|
||||
|
||||
* Fix kubelet startup failure when using `ExecPlugin` in kubeconfig.
|
||||
([#66395](https://github.com/kubernetes/kubernetes/pull/66395))
|
||||
|
||||
* Fix panic in the fake `SubjectAccessReview` client when object is nil.
|
||||
([#66837](https://github.com/kubernetes/kubernetes/pull/66837))
|
||||
|
||||
* Periodically reload `InClusterConfig` token.
|
||||
([#67359](https://github.com/kubernetes/kubernetes/pull/67359))
|
||||
|
||||
* [k8s.io/apimachinery] Report parsing error in json serializer.
|
||||
([#63668](https://github.com/kubernetes/kubernetes/pull/63668))
|
||||
|
||||
* [k8s.io/apimachinery] The `metav1.ObjectMeta` accessor does not deepcopy
|
||||
owner references anymore. In general, the accessor interface does not enforce
|
||||
deepcopy nor does it forbid it (e.g. for `unstructured.Unstructured`).
|
||||
([#64915](https://github.com/kubernetes/kubernetes/pull/64915))
|
||||
|
||||
* [k8s.io/apimachinery] Utility functions `SetTransportDefaults` and `DialerFor`
|
||||
once again respect custom Dial functions set on transports.
|
||||
([#65547](https://github.com/kubernetes/kubernetes/pull/65547))
|
||||
|
||||
* [k8s.io/apimachinery] Speed-up conversion function invocation by avoiding
|
||||
`reflect.Call`. Action required: regenerated conversion with conversion-gen.
|
||||
([#65771](https://github.com/kubernetes/kubernetes/pull/65771))
|
||||
|
||||
* [k8s.io/apimachinery] Establish "406 Not Acceptable" response for
|
||||
unmarshable protobuf serialization error.
|
||||
([#67041](https://github.com/kubernetes/kubernetes/pull/67041))
|
||||
|
||||
* [k8s.io/apimachinery] Immediately close the other side of the connection by
|
||||
exiting once one side closes when proxying.
|
||||
([#67288](https://github.com/kubernetes/kubernetes/pull/67288))
|
||||
|
||||
|
||||
## API changes
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* Volume dynamic provisioning scheduling has been promoted to beta.
|
||||
ACTION REQUIRED: The DynamicProvisioningScheduling alpha feature gate has been removed.
|
||||
The VolumeScheduling beta feature gate is still required for this feature.
|
||||
([#67432](https://github.com/kubernetes/kubernetes/pull/67432))
|
||||
|
||||
* The CSI file system type is no longer defaulted to ext4.
|
||||
All the production drivers listed under https://kubernetes-csi.github.io/docs/Drivers.html
|
||||
were inspected and should not be impacted after this change.
|
||||
If you are using a driver not in that list,
|
||||
please test the drivers on an updated test cluster first.
|
||||
([#65499](https://github.com/kubernetes/kubernetes/pull/65499))
|
||||
|
||||
**New Features:**
|
||||
|
||||
* Support annotations for remote admission webhooks.
|
||||
([#58679](https://github.com/kubernetes/kubernetes/pull/58679))
|
||||
|
||||
* Support both directory and block device for local volume
|
||||
plugin `FileSystem` `VolumeMode`.
|
||||
([#63011](https://github.com/kubernetes/kubernetes/pull/63011))
|
||||
|
||||
* Introduce `autoscaling/v2beta2` and `custom_metrics/v1beta2`,
|
||||
which implement metric selectors for Object and Pods metrics,
|
||||
as well as allowing AverageValue targets on Objects, similar to External metrics.
|
||||
([#64097](https://github.com/kubernetes/kubernetes/pull/64097))
|
||||
|
||||
* Add `Lease` API in the `coordination.k8s.io` API group.
|
||||
([#64246](https://github.com/kubernetes/kubernetes/pull/64246))
|
||||
|
||||
* `ProcMount` added to `SecurityContext` and `AllowedProcMounts` added to `PodSecurityPolicy`
|
||||
to allow paths in the container's `/proc` to not be masked.
|
||||
([#64283](https://github.com/kubernetes/kubernetes/pull/64283))
|
||||
|
||||
* Add the `AuditAnnotations` field to `ImageReviewStatus` to allow the
|
||||
`ImageReview` backend to return annotations to be added to the created pod.
|
||||
([#64597](https://github.com/kubernetes/kubernetes/pull/64597))
|
||||
|
||||
* SCTP is now supported as additional protocol (alpha) alongside TCP and UDP in
|
||||
Pod, Service, Endpoint, and NetworkPolicy.
|
||||
([#64973](https://github.com/kubernetes/kubernetes/pull/64973))
|
||||
|
||||
* The `PodShareProcessNamespace` feature to configure PID namespace sharing
|
||||
within a pod has been promoted to beta.
|
||||
([#66507](https://github.com/kubernetes/kubernetes/pull/66507))
|
||||
|
||||
* Add `TTLSecondsAfterFinished` to `JobSpec` for cleaning up Jobs after they finish.
|
||||
([#66840](https://github.com/kubernetes/kubernetes/pull/66840))
|
||||
|
||||
* Add `DataSource` and `TypedLocalObjectReference` fields to support
|
||||
restoring a volume from a volume snapshot data source.
|
||||
([#67087](https://github.com/kubernetes/kubernetes/pull/67087))
|
||||
|
||||
* `RuntimeClass` is a new API resource for defining different classes of runtimes
|
||||
that may be used to run containers in the cluster.
|
||||
Pods can select a `RunitmeClass` to use via the `RuntimeClassName` field.
|
||||
This feature is in alpha, and the `RuntimeClass` feature gate must be enabled
|
||||
in order to use it. ([#67737](https://github.com/kubernetes/kubernetes/pull/67737))
|
||||
|
||||
* To address the possibility dry-run requests overwhelming admission webhooks
|
||||
that rely on side effects and a reconciliation mechanism, a new field is being
|
||||
added to `admissionregistration.k8s.io/v1beta1.ValidatingWebhookConfiguration`
|
||||
and `admissionregistration.k8s.io/v1beta1.MutatingWebhookConfiguration` so that
|
||||
webhooks can explicitly register as having dry-run support.
|
||||
If a dry-run request is made on a resource that triggers a non dry-run supporting
|
||||
webhook, the request will be completely rejected, with "400: Bad Request".
|
||||
Additionally, a new field is being added to the
|
||||
`admission.k8s.io/v1beta1.AdmissionReview` API object, exposing to webhooks
|
||||
whether or not the request being reviewed is a dry-run.
|
||||
([#66936](https://github.com/kubernetes/kubernetes/pull/66936))
|
||||
|
||||
**Bug fixes and Improvements:**
|
||||
|
||||
* The `DisruptedPods` field in `PodDisruptionBudgetStatus` is now optional.
|
||||
([#63757](https://github.com/kubernetes/kubernetes/pull/63757))
|
||||
|
||||
* `extensions/v1beta1` Deployment's `ProgressDeadlineSeconds` now defaults to `MaxInt32`.
|
||||
([#66581](https://github.com/kubernetes/kubernetes/pull/66581))
|
||||
|
||||
# v8.0.0
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
182
Godeps/Godeps.json
generated
182
Godeps/Godeps.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ImportPath": "k8s.io/client-go",
|
||||
"GoVersion": "go1.10",
|
||||
"GoVersion": "go1.11",
|
||||
"GodepVersion": "v80",
|
||||
"Packages": [
|
||||
"./..."
|
||||
@@ -52,15 +52,15 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ghodss/yaml",
|
||||
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
|
||||
"Rev": "c7ce16629ff4cd059ed96ed06419dd3856fd3577"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gogo/protobuf/proto",
|
||||
"Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7"
|
||||
"Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gogo/protobuf/sortkeys",
|
||||
"Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7"
|
||||
"Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/glog",
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/imdario/mergo",
|
||||
"Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc"
|
||||
"Rev": "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/json-iterator/go",
|
||||
@@ -168,7 +168,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/modern-go/reflect2",
|
||||
"Rev": "05fbef0ca5da472bbf96c9322b84a53edc03c9fd"
|
||||
"Rev": "94122c33edd36123c84d5368cfb2b69df93a0ec8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/peterbourgon/diskv",
|
||||
@@ -268,335 +268,339 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/yaml.v2",
|
||||
"Rev": "670d4cfef0544295bc27a114dbac37980d83185a"
|
||||
"Rev": "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/admissionregistration/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/admissionregistration/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/apps/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/apps/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/apps/v1beta2",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authentication/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authentication/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authorization/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/authorization/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/autoscaling/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/autoscaling/v2beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/autoscaling/v2beta2",
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/batch/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/batch/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/batch/v2alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/certificates/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/coordination/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/core/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/events/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/extensions/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/imagepolicy/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/networking/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/policy/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/rbac/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/rbac/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/rbac/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/scheduling/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/scheduling/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/settings/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/storage/v1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/storage/v1alpha1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api/storage/v1beta1",
|
||||
"Rev": "91bfdbcf0c2cab32ec1236cee4c300793abea68a"
|
||||
"Rev": "c7463263e3f1f232e11cfe37e5017cb5104786c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/roundtrip",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/fields",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/labels",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/selection",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/types",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/version",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
||||
"Rev": "720795f37ef04a6d63ba53b0b43233b3f851c149"
|
||||
"Rev": "60666be32c5de527b69dabe8e4400b4f0aa897de"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||
|
||||
@@ -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
|
||||
|
||||
2
OWNERS
2
OWNERS
@@ -13,3 +13,5 @@ reviewers:
|
||||
- soltysh
|
||||
- sttts
|
||||
- yliaog
|
||||
labels:
|
||||
- sig/api-machinery
|
||||
|
||||
26
README.md
26
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
Go clients for talking to a [kubernetes](http://kubernetes.io/) cluster.
|
||||
|
||||
We currently recommend using the v8.0.0 tag. See [INSTALL.md](/INSTALL.md) for
|
||||
We currently recommend using the v9.0.0 tag. See [INSTALL.md](/INSTALL.md) for
|
||||
detailed installation instructions. `go get k8s.io/client-go/...` works, but
|
||||
will build `master`, which doesn't handle the dependencies well.
|
||||
|
||||
@@ -91,17 +91,16 @@ We will backport bugfixes--but not new features--into older versions of
|
||||
|
||||
#### Compatibility matrix
|
||||
|
||||
| | Kubernetes 1.5 | Kubernetes 1.6 | Kubernetes 1.7 | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 |
|
||||
|---------------------|----------------|----------------|----------------|----------------|----------------|-----------------|-----------------|
|
||||
| client-go 1.5 | - | - | - | - | - | - | - |
|
||||
| client-go 2.0 | ✓ | +- | +- | +- | +- | +- | +- |
|
||||
| client-go 3.0 | +- | ✓ | - | +- | +- | +- | +- |
|
||||
| client-go 4.0 | +- | +- | ✓ | +- | +- | +- | +- |
|
||||
| client-go 5.0 | +- | +- | +- | ✓ | +- | +- | +- |
|
||||
| client-go 6.0 | +- | +- | +- | +- | ✓ | +- | +- |
|
||||
| client-go 7.0 | +- | +- | +- | +- | +- | ✓ | +- |
|
||||
| client-go 8.0 | +- | +- | +- | +- | +- | +- | ✓ |
|
||||
| client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
|
||||
| | Kubernetes 1.6 | Kubernetes 1.7 | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 |
|
||||
|---------------------|----------------|----------------|----------------|----------------|-----------------|-----------------|-----------------|
|
||||
| client-go 3.0 | ✓ | - | +- | +- | +- | +- | +- |
|
||||
| client-go 4.0 | +- | ✓ | +- | +- | +- | +- | +- |
|
||||
| client-go 5.0 | +- | +- | ✓ | +- | +- | +- | +- |
|
||||
| client-go 6.0 | +- | +- | +- | ✓ | +- | +- | +- |
|
||||
| client-go 7.0 | +- | +- | +- | +- | ✓ | +- | +- |
|
||||
| client-go 8.0 | +- | +- | +- | +- | +- | ✓ | +- |
|
||||
| client-go 9.0 | +- | +- | +- | +- | +- | +- | ✓ |
|
||||
| client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
|
||||
|
||||
Key:
|
||||
|
||||
@@ -128,9 +127,10 @@ between client-go versions.
|
||||
| client-go 3.0 | Kubernetes main repo, 1.6 branch | = - |
|
||||
| client-go 4.0 | Kubernetes main repo, 1.7 branch | = - |
|
||||
| client-go 5.0 | Kubernetes main repo, 1.8 branch | = - |
|
||||
| client-go 6.0 | Kubernetes main repo, 1.9 branch | ✓ |
|
||||
| client-go 6.0 | Kubernetes main repo, 1.9 branch | = - |
|
||||
| client-go 7.0 | Kubernetes main repo, 1.10 branch | ✓ |
|
||||
| client-go 8.0 | Kubernetes main repo, 1.11 branch | ✓ |
|
||||
| client-go 9.0 | Kubernetes main repo, 1.12 branch | ✓ |
|
||||
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
|
||||
|
||||
Key:
|
||||
|
||||
155
dynamic/dynamicinformer/informer.go
Normal file
155
dynamic/dynamicinformer/informer.go
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamicinformer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamiclister"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// NewDynamicSharedInformerFactory constructs a new instance of dynamicSharedInformerFactory for all namespaces.
|
||||
func NewDynamicSharedInformerFactory(client dynamic.Interface, defaultResync time.Duration) DynamicSharedInformerFactory {
|
||||
return NewFilteredDynamicSharedInformerFactory(client, defaultResync, metav1.NamespaceAll, nil)
|
||||
}
|
||||
|
||||
// NewFilteredDynamicSharedInformerFactory constructs a new instance of dynamicSharedInformerFactory.
|
||||
// Listers obtained via this factory will be subject to the same filters as specified here.
|
||||
func NewFilteredDynamicSharedInformerFactory(client dynamic.Interface, defaultResync time.Duration, namespace string, tweakListOptions TweakListOptionsFunc) DynamicSharedInformerFactory {
|
||||
return &dynamicSharedInformerFactory{
|
||||
client: client,
|
||||
defaultResync: defaultResync,
|
||||
namespace: metav1.NamespaceAll,
|
||||
informers: map[schema.GroupVersionResource]informers.GenericInformer{},
|
||||
startedInformers: make(map[schema.GroupVersionResource]bool),
|
||||
}
|
||||
}
|
||||
|
||||
type dynamicSharedInformerFactory struct {
|
||||
client dynamic.Interface
|
||||
defaultResync time.Duration
|
||||
namespace string
|
||||
|
||||
lock sync.Mutex
|
||||
informers map[schema.GroupVersionResource]informers.GenericInformer
|
||||
// startedInformers is used for tracking which informers have been started.
|
||||
// This allows Start() to be called multiple times safely.
|
||||
startedInformers map[schema.GroupVersionResource]bool
|
||||
}
|
||||
|
||||
var _ DynamicSharedInformerFactory = &dynamicSharedInformerFactory{}
|
||||
|
||||
func (f *dynamicSharedInformerFactory) ForResource(gvr schema.GroupVersionResource) informers.GenericInformer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
key := gvr
|
||||
informer, exists := f.informers[key]
|
||||
if exists {
|
||||
return informer
|
||||
}
|
||||
|
||||
informer = NewFilteredDynamicInformer(f.client, gvr, f.namespace, f.defaultResync, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, nil)
|
||||
f.informers[key] = informer
|
||||
|
||||
return informer
|
||||
}
|
||||
|
||||
// Start initializes all requested informers.
|
||||
func (f *dynamicSharedInformerFactory) Start(stopCh <-chan struct{}) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
for informerType, informer := range f.informers {
|
||||
if !f.startedInformers[informerType] {
|
||||
go informer.Informer().Run(stopCh)
|
||||
f.startedInformers[informerType] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForCacheSync waits for all started informers' cache were synced.
|
||||
func (f *dynamicSharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool {
|
||||
informers := func() map[schema.GroupVersionResource]cache.SharedIndexInformer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
informers := map[schema.GroupVersionResource]cache.SharedIndexInformer{}
|
||||
for informerType, informer := range f.informers {
|
||||
if f.startedInformers[informerType] {
|
||||
informers[informerType] = informer.Informer()
|
||||
}
|
||||
}
|
||||
return informers
|
||||
}()
|
||||
|
||||
res := map[schema.GroupVersionResource]bool{}
|
||||
for informType, informer := range informers {
|
||||
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// NewFilteredDynamicInformer constructs a new informer for a dynamic type.
|
||||
func NewFilteredDynamicInformer(client dynamic.Interface, gvr schema.GroupVersionResource, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions TweakListOptionsFunc) informers.GenericInformer {
|
||||
return &dynamicInformer{
|
||||
gvr: gvr,
|
||||
informer: cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.Resource(gvr).Namespace(namespace).List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.Resource(gvr).Namespace(namespace).Watch(options)
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type dynamicInformer struct {
|
||||
informer cache.SharedIndexInformer
|
||||
gvr schema.GroupVersionResource
|
||||
}
|
||||
|
||||
var _ informers.GenericInformer = &dynamicInformer{}
|
||||
|
||||
func (d *dynamicInformer) Informer() cache.SharedIndexInformer {
|
||||
return d.informer
|
||||
}
|
||||
|
||||
func (d *dynamicInformer) Lister() cache.GenericLister {
|
||||
return dynamiclister.NewRuntimeObjectShim(dynamiclister.New(d.informer.GetIndexer(), d.gvr))
|
||||
}
|
||||
160
dynamic/dynamicinformer/informer_test.go
Normal file
160
dynamic/dynamicinformer/informer_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamicinformer_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func TestDynamicSharedInformerFactory(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
existingObj *unstructured.Unstructured
|
||||
gvr schema.GroupVersionResource
|
||||
ns string
|
||||
trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured
|
||||
handler func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs
|
||||
}{
|
||||
// scenario 1
|
||||
{
|
||||
name: "scenario 1: test if adding an object triggers AddFunc",
|
||||
ns: "ns-foo",
|
||||
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
|
||||
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, _ *unstructured.Unstructured) *unstructured.Unstructured {
|
||||
testObject := newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo")
|
||||
createdObj, err := fakeClient.Resource(gvr).Namespace(ns).Create(testObject, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
return createdObj
|
||||
},
|
||||
handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs {
|
||||
return &cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
rcvCh <- obj.(*unstructured.Unstructured)
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// scenario 2
|
||||
{
|
||||
name: "scenario 2: tests if updating an object triggers UpdateFunc",
|
||||
ns: "ns-foo",
|
||||
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
|
||||
existingObj: newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
|
||||
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured {
|
||||
testObject.Object["spec"] = "updatedName"
|
||||
updatedObj, err := fakeClient.Resource(gvr).Namespace(ns).Update(testObject, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
return updatedObj
|
||||
},
|
||||
handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs {
|
||||
return &cache.ResourceEventHandlerFuncs{
|
||||
UpdateFunc: func(old, updated interface{}) {
|
||||
rcvCh <- updated.(*unstructured.Unstructured)
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// scenario 3
|
||||
{
|
||||
name: "scenario 3: test if deleting an object triggers DeleteFunc",
|
||||
ns: "ns-foo",
|
||||
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
|
||||
existingObj: newUnstructured("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
|
||||
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeDynamicClient, testObject *unstructured.Unstructured) *unstructured.Unstructured {
|
||||
err := fakeClient.Resource(gvr).Namespace(ns).Delete(testObject.GetName(), &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
return testObject
|
||||
},
|
||||
handler: func(rcvCh chan<- *unstructured.Unstructured) *cache.ResourceEventHandlerFuncs {
|
||||
return &cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
rcvCh <- obj.(*unstructured.Unstructured)
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, ts := range scenarios {
|
||||
t.Run(ts.name, func(t *testing.T) {
|
||||
// test data
|
||||
timeout := time.Duration(3 * time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
scheme := runtime.NewScheme()
|
||||
informerReciveObjectCh := make(chan *unstructured.Unstructured, 1)
|
||||
objs := []runtime.Object{}
|
||||
if ts.existingObj != nil {
|
||||
objs = append(objs, ts.existingObj)
|
||||
}
|
||||
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...)
|
||||
target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
|
||||
|
||||
// act
|
||||
informerListerForGvr := target.ForResource(ts.gvr)
|
||||
informerListerForGvr.Informer().AddEventHandler(ts.handler(informerReciveObjectCh))
|
||||
target.Start(ctx.Done())
|
||||
if synced := target.WaitForCacheSync(ctx.Done()); !synced[ts.gvr] {
|
||||
t.Errorf("informer for %s hasn't synced", ts.gvr)
|
||||
}
|
||||
|
||||
testObject := ts.trigger(ts.gvr, ts.ns, fakeClient, ts.existingObj)
|
||||
select {
|
||||
case objFromInformer := <-informerReciveObjectCh:
|
||||
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
|
||||
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Errorf("tested informer haven't received an object, waited %v", timeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
"kind": kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": namespace,
|
||||
"name": name,
|
||||
},
|
||||
"spec": name,
|
||||
},
|
||||
}
|
||||
}
|
||||
34
dynamic/dynamicinformer/interface.go
Normal file
34
dynamic/dynamicinformer/interface.go
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamicinformer
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/informers"
|
||||
)
|
||||
|
||||
// DynamicSharedInformerFactory provides access to a shared informer and lister for dynamic client
|
||||
type DynamicSharedInformerFactory interface {
|
||||
Start(stopCh <-chan struct{})
|
||||
ForResource(gvr schema.GroupVersionResource) informers.GenericInformer
|
||||
WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool
|
||||
}
|
||||
|
||||
// TweakListOptionsFunc defines the signature of a helper function
|
||||
// that wants to provide more listing options to API
|
||||
type TweakListOptionsFunc func(*metav1.ListOptions)
|
||||
40
dynamic/dynamiclister/interface.go
Normal file
40
dynamic/dynamiclister/interface.go
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamiclister
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
// Lister helps list resources.
|
||||
type Lister interface {
|
||||
// List lists all resources in the indexer.
|
||||
List(selector labels.Selector) (ret []*unstructured.Unstructured, err error)
|
||||
// Get retrieves a resource from the indexer with the given name
|
||||
Get(name string) (*unstructured.Unstructured, error)
|
||||
// Namespace returns an object that can list and get resources in a given namespace.
|
||||
Namespace(namespace string) NamespaceLister
|
||||
}
|
||||
|
||||
// NamespaceLister helps list and get resources.
|
||||
type NamespaceLister interface {
|
||||
// List lists all resources in the indexer for a given namespace.
|
||||
List(selector labels.Selector) (ret []*unstructured.Unstructured, err error)
|
||||
// Get retrieves a resource from the indexer for a given namespace and name.
|
||||
Get(name string) (*unstructured.Unstructured, error)
|
||||
}
|
||||
91
dynamic/dynamiclister/lister.go
Normal file
91
dynamic/dynamiclister/lister.go
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamiclister
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Lister = &dynamicLister{}
|
||||
var _ NamespaceLister = &dynamicNamespaceLister{}
|
||||
|
||||
// dynamicLister implements the Lister interface.
|
||||
type dynamicLister struct {
|
||||
indexer cache.Indexer
|
||||
gvr schema.GroupVersionResource
|
||||
}
|
||||
|
||||
// New returns a new Lister.
|
||||
func New(indexer cache.Indexer, gvr schema.GroupVersionResource) Lister {
|
||||
return &dynamicLister{indexer: indexer, gvr: gvr}
|
||||
}
|
||||
|
||||
// List lists all resources in the indexer.
|
||||
func (l *dynamicLister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) {
|
||||
err = cache.ListAll(l.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*unstructured.Unstructured))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves a resource from the indexer with the given name
|
||||
func (l *dynamicLister) Get(name string) (*unstructured.Unstructured, error) {
|
||||
obj, exists, err := l.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
|
||||
}
|
||||
return obj.(*unstructured.Unstructured), nil
|
||||
}
|
||||
|
||||
// Namespace returns an object that can list and get resources from a given namespace.
|
||||
func (l *dynamicLister) Namespace(namespace string) NamespaceLister {
|
||||
return &dynamicNamespaceLister{indexer: l.indexer, namespace: namespace, gvr: l.gvr}
|
||||
}
|
||||
|
||||
// dynamicNamespaceLister implements the NamespaceLister interface.
|
||||
type dynamicNamespaceLister struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
gvr schema.GroupVersionResource
|
||||
}
|
||||
|
||||
// List lists all resources in the indexer for a given namespace.
|
||||
func (l *dynamicNamespaceLister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) {
|
||||
err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*unstructured.Unstructured))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves a resource from the indexer for a given namespace and name.
|
||||
func (l *dynamicNamespaceLister) Get(name string) (*unstructured.Unstructured, error) {
|
||||
obj, exists, err := l.indexer.GetByKey(l.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
|
||||
}
|
||||
return obj.(*unstructured.Unstructured), nil
|
||||
}
|
||||
257
dynamic/dynamiclister/lister_test.go
Normal file
257
dynamic/dynamiclister/lister_test.go
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamiclister_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/dynamic/dynamiclister"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func TestNamespaceGetMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingObjects []runtime.Object
|
||||
namespaceToSync string
|
||||
gvrToSync schema.GroupVersionResource
|
||||
objectToGet string
|
||||
expectedObject *unstructured.Unstructured
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "scenario 1: gets name-foo1 resource from the indexer from ns-foo namespace",
|
||||
existingObjects: []runtime.Object{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||
},
|
||||
namespaceToSync: "ns-foo",
|
||||
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||
objectToGet: "name-foo1",
|
||||
expectedObject: newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||
},
|
||||
{
|
||||
name: "scenario 2: gets name-foo-non-existing resource from the indexer from ns-foo namespace",
|
||||
existingObjects: []runtime.Object{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||
},
|
||||
namespaceToSync: "ns-foo",
|
||||
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||
objectToGet: "name-foo-non-existing",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// test data
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||
for _, obj := range test.existingObjects {
|
||||
err := indexer.Add(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// act
|
||||
target := dynamiclister.New(indexer, test.gvrToSync).Namespace(test.namespaceToSync)
|
||||
actualObject, err := target.Get(test.objectToGet)
|
||||
|
||||
// validate
|
||||
if test.expectError {
|
||||
if err == nil {
|
||||
t.Fatal("expected to get an error but non was returned")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.expectedObject, actualObject) {
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceListMethod(t *testing.T) {
|
||||
// test data
|
||||
objs := []runtime.Object{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||
}
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||
for _, obj := range objs {
|
||||
err := indexer.Add(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
expectedOutput := []*unstructured.Unstructured{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||
}
|
||||
namespaceToList := "ns-foo"
|
||||
|
||||
// act
|
||||
target := dynamiclister.New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"}).Namespace(namespaceToList)
|
||||
actualOutput, err := target.List(labels.Everything())
|
||||
|
||||
// validate
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertListOrDie(expectedOutput, actualOutput, t)
|
||||
}
|
||||
|
||||
func TestListerGetMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingObjects []runtime.Object
|
||||
namespaceToSync string
|
||||
gvrToSync schema.GroupVersionResource
|
||||
objectToGet string
|
||||
expectedObject *unstructured.Unstructured
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "scenario 1: gets name-foo1 resource from the indexer",
|
||||
existingObjects: []runtime.Object{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "", "name-foo1"),
|
||||
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||
},
|
||||
namespaceToSync: "",
|
||||
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||
objectToGet: "name-foo1",
|
||||
expectedObject: newUnstructured("group/version", "TheKind", "", "name-foo1"),
|
||||
},
|
||||
{
|
||||
name: "scenario 2: doesn't get name-foo resource from the indexer from ns-foo namespace",
|
||||
existingObjects: []runtime.Object{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||
newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||
},
|
||||
namespaceToSync: "ns-foo",
|
||||
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||
objectToGet: "name-foo",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// test data
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||
for _, obj := range test.existingObjects {
|
||||
err := indexer.Add(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// act
|
||||
target := dynamiclister.New(indexer, test.gvrToSync)
|
||||
actualObject, err := target.Get(test.objectToGet)
|
||||
|
||||
// validate
|
||||
if test.expectError {
|
||||
if err == nil {
|
||||
t.Fatal("expected to get an error but non was returned")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.expectedObject, actualObject) {
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListerListMethod(t *testing.T) {
|
||||
// test data
|
||||
objs := []runtime.Object{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||
}
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||
for _, obj := range objs {
|
||||
err := indexer.Add(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
expectedOutput := []*unstructured.Unstructured{
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||
}
|
||||
|
||||
// act
|
||||
target := dynamiclister.New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"})
|
||||
actualOutput, err := target.List(labels.Everything())
|
||||
|
||||
// validate
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertListOrDie(expectedOutput, actualOutput, t)
|
||||
}
|
||||
|
||||
func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
"kind": kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": namespace,
|
||||
"name": name,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func assertListOrDie(expected, actual []*unstructured.Unstructured, t *testing.T) {
|
||||
if len(actual) != len(expected) {
|
||||
t.Fatalf("unexpected number of items returned, expected = %d, actual = %d", len(expected), len(actual))
|
||||
}
|
||||
for _, expectedObject := range expected {
|
||||
found := false
|
||||
for _, actualObject := range actual {
|
||||
if actualObject.GetName() == expectedObject.GetName() {
|
||||
if !reflect.DeepEqual(expectedObject, actualObject) {
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", expectedObject, actualObject, diff.ObjectDiff(expectedObject, actualObject))
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("the resource with the name = %s was not found in the returned output", expectedObject.GetName())
|
||||
}
|
||||
}
|
||||
}
|
||||
87
dynamic/dynamiclister/shim.go
Normal file
87
dynamic/dynamiclister/shim.go
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dynamiclister
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ cache.GenericLister = &dynamicListerShim{}
|
||||
var _ cache.GenericNamespaceLister = &dynamicNamespaceListerShim{}
|
||||
|
||||
// dynamicListerShim implements the cache.GenericLister interface.
|
||||
type dynamicListerShim struct {
|
||||
lister Lister
|
||||
}
|
||||
|
||||
// NewRuntimeObjectShim returns a new shim for Lister.
|
||||
// It wraps Lister so that it implements cache.GenericLister interface
|
||||
func NewRuntimeObjectShim(lister Lister) cache.GenericLister {
|
||||
return &dynamicListerShim{lister: lister}
|
||||
}
|
||||
|
||||
// List will return all objects across namespaces
|
||||
func (s *dynamicListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||
objs, err := s.lister.List(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret = make([]runtime.Object, len(objs))
|
||||
for index, obj := range objs {
|
||||
ret[index] = obj
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get will attempt to retrieve assuming that name==key
|
||||
func (s *dynamicListerShim) Get(name string) (runtime.Object, error) {
|
||||
return s.lister.Get(name)
|
||||
}
|
||||
|
||||
func (s *dynamicListerShim) ByNamespace(namespace string) cache.GenericNamespaceLister {
|
||||
return &dynamicNamespaceListerShim{
|
||||
namespaceLister: s.lister.Namespace(namespace),
|
||||
}
|
||||
}
|
||||
|
||||
// dynamicNamespaceListerShim implements the NamespaceLister interface.
|
||||
// It wraps NamespaceLister so that it implements cache.GenericNamespaceLister interface
|
||||
type dynamicNamespaceListerShim struct {
|
||||
namespaceLister NamespaceLister
|
||||
}
|
||||
|
||||
// List will return all objects in this namespace
|
||||
func (ns *dynamicNamespaceListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||
objs, err := ns.namespaceLister.List(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret = make([]runtime.Object, len(objs))
|
||||
for index, obj := range objs {
|
||||
ret[index] = obj
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get will attempt to retrieve by namespace and name
|
||||
func (ns *dynamicNamespaceListerShim) Get(name string) (runtime.Object, error) {
|
||||
return ns.namespaceLister.Get(name)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
|
||||
}
|
||||
}
|
||||
|
||||
cs := &FakeDynamicClient{}
|
||||
cs := &FakeDynamicClient{scheme: scheme}
|
||||
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||
gvr := action.GetResource()
|
||||
|
||||
@@ -25,7 +25,13 @@ build the image on Minikube:
|
||||
docker build -t in-cluster .
|
||||
|
||||
If you are not using Minikube, you should build this image and push it to a registry
|
||||
that your Kubernetes cluster can pull from.
|
||||
that your Kubernetes cluster can pull from. If you have RBAC enabled, use the following
|
||||
snippet to create role binding which will grant the default service account view
|
||||
permissions.
|
||||
|
||||
```
|
||||
kubectl create clusterrolebinding default-view --clusterrole=view --serviceaccount=default:default
|
||||
```
|
||||
|
||||
Then, run the image in a Pod with a single instance Deployment:
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package autoscaling
|
||||
import (
|
||||
v1 "k8s.io/client-go/informers/autoscaling/v1"
|
||||
v2beta1 "k8s.io/client-go/informers/autoscaling/v2beta1"
|
||||
v2beta2 "k8s.io/client-go/informers/autoscaling/v2beta2"
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,8 @@ type Interface interface {
|
||||
V1() v1.Interface
|
||||
// V2beta1 provides access to shared informers for resources in V2beta1.
|
||||
V2beta1() v2beta1.Interface
|
||||
// V2beta2 provides access to shared informers for resources in V2beta2.
|
||||
V2beta2() v2beta2.Interface
|
||||
}
|
||||
|
||||
type group struct {
|
||||
@@ -52,3 +55,8 @@ func (g *group) V1() v1.Interface {
|
||||
func (g *group) V2beta1() v2beta1.Interface {
|
||||
return v2beta1.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
||||
|
||||
// V2beta2 returns a new v2beta2.Interface.
|
||||
func (g *group) V2beta2() v2beta2.Interface {
|
||||
return v2beta2.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
||||
|
||||
89
informers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
89
informers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
kubernetes "k8s.io/client-go/kubernetes"
|
||||
v2beta2 "k8s.io/client-go/listers/autoscaling/v2beta2"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// HorizontalPodAutoscalerInformer provides access to a shared informer and lister for
|
||||
// HorizontalPodAutoscalers.
|
||||
type HorizontalPodAutoscalerInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v2beta2.HorizontalPodAutoscalerLister
|
||||
}
|
||||
|
||||
type horizontalPodAutoscalerInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredHorizontalPodAutoscalerInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredHorizontalPodAutoscalerInformer constructs a new informer for HorizontalPodAutoscaler type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.AutoscalingV2beta2().HorizontalPodAutoscalers(namespace).Watch(options)
|
||||
},
|
||||
},
|
||||
&autoscalingv2beta2.HorizontalPodAutoscaler{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredHorizontalPodAutoscalerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&autoscalingv2beta2.HorizontalPodAutoscaler{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *horizontalPodAutoscalerInformer) Lister() v2beta2.HorizontalPodAutoscalerLister {
|
||||
return v2beta2.NewHorizontalPodAutoscalerLister(f.Informer().GetIndexer())
|
||||
}
|
||||
45
informers/autoscaling/v2beta2/interface.go
Normal file
45
informers/autoscaling/v2beta2/interface.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to all the informers in this group version.
|
||||
type Interface interface {
|
||||
// HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer.
|
||||
HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer
|
||||
}
|
||||
|
||||
type version struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalers returns a HorizontalPodAutoscalerInformer.
|
||||
func (v *version) HorizontalPodAutoscalers() HorizontalPodAutoscalerInformer {
|
||||
return &horizontalPodAutoscalerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
v1beta2 "k8s.io/api/apps/v1beta2"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
v2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
v2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
@@ -127,6 +128,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
||||
case v2beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2beta1().HorizontalPodAutoscalers().Informer()}, nil
|
||||
|
||||
// Group=autoscaling, Version=v2beta2
|
||||
case v2beta2.SchemeGroupVersion.WithResource("horizontalpodautoscalers"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Autoscaling().V2beta2().HorizontalPodAutoscalers().Informer()}, nil
|
||||
|
||||
// Group=batch, Version=v1
|
||||
case batchv1.SchemeGroupVersion.WithResource("jobs"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Batch().V1().Jobs().Informer()}, nil
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
authorizationv1beta1 "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1"
|
||||
autoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/client-go/kubernetes/typed/batch/v2alpha1"
|
||||
@@ -77,6 +78,7 @@ type Interface interface {
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Autoscaling() autoscalingv1.AutoscalingV1Interface
|
||||
AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface
|
||||
AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface
|
||||
BatchV1() batchv1.BatchV1Interface
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Batch() batchv1.BatchV1Interface
|
||||
@@ -137,6 +139,7 @@ type Clientset struct {
|
||||
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
|
||||
autoscalingV1 *autoscalingv1.AutoscalingV1Client
|
||||
autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client
|
||||
autoscalingV2beta2 *autoscalingv2beta2.AutoscalingV2beta2Client
|
||||
batchV1 *batchv1.BatchV1Client
|
||||
batchV1beta1 *batchv1beta1.BatchV1beta1Client
|
||||
batchV2alpha1 *batchv2alpha1.BatchV2alpha1Client
|
||||
@@ -243,6 +246,11 @@ func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1In
|
||||
return c.autoscalingV2beta1
|
||||
}
|
||||
|
||||
// AutoscalingV2beta2 retrieves the AutoscalingV2beta2Client
|
||||
func (c *Clientset) AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface {
|
||||
return c.autoscalingV2beta2
|
||||
}
|
||||
|
||||
// BatchV1 retrieves the BatchV1Client
|
||||
func (c *Clientset) BatchV1() batchv1.BatchV1Interface {
|
||||
return c.batchV1
|
||||
@@ -470,6 +478,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.autoscalingV2beta2, err = autoscalingv2beta2.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.batchV1, err = batchv1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -569,6 +581,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
cs.authorizationV1beta1 = authorizationv1beta1.NewForConfigOrDie(c)
|
||||
cs.autoscalingV1 = autoscalingv1.NewForConfigOrDie(c)
|
||||
cs.autoscalingV2beta1 = autoscalingv2beta1.NewForConfigOrDie(c)
|
||||
cs.autoscalingV2beta2 = autoscalingv2beta2.NewForConfigOrDie(c)
|
||||
cs.batchV1 = batchv1.NewForConfigOrDie(c)
|
||||
cs.batchV1beta1 = batchv1beta1.NewForConfigOrDie(c)
|
||||
cs.batchV2alpha1 = batchv2alpha1.NewForConfigOrDie(c)
|
||||
@@ -607,6 +620,7 @@ func New(c rest.Interface) *Clientset {
|
||||
cs.authorizationV1beta1 = authorizationv1beta1.New(c)
|
||||
cs.autoscalingV1 = autoscalingv1.New(c)
|
||||
cs.autoscalingV2beta1 = autoscalingv2beta1.New(c)
|
||||
cs.autoscalingV2beta2 = autoscalingv2beta2.New(c)
|
||||
cs.batchV1 = batchv1.New(c)
|
||||
cs.batchV1beta1 = batchv1beta1.New(c)
|
||||
cs.batchV2alpha1 = batchv2alpha1.New(c)
|
||||
|
||||
@@ -46,6 +46,8 @@ import (
|
||||
fakeautoscalingv1 "k8s.io/client-go/kubernetes/typed/autoscaling/v1/fake"
|
||||
autoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1"
|
||||
fakeautoscalingv2beta1 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1/fake"
|
||||
autoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2"
|
||||
fakeautoscalingv2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2/fake"
|
||||
batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
fakebatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1/fake"
|
||||
batchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
|
||||
@@ -209,6 +211,11 @@ func (c *Clientset) AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1In
|
||||
return &fakeautoscalingv2beta1.FakeAutoscalingV2beta1{Fake: &c.Fake}
|
||||
}
|
||||
|
||||
// AutoscalingV2beta2 retrieves the AutoscalingV2beta2Client
|
||||
func (c *Clientset) AutoscalingV2beta2() autoscalingv2beta2.AutoscalingV2beta2Interface {
|
||||
return &fakeautoscalingv2beta2.FakeAutoscalingV2beta2{Fake: &c.Fake}
|
||||
}
|
||||
|
||||
// BatchV1 retrieves the BatchV1Client
|
||||
func (c *Clientset) BatchV1() batchv1.BatchV1Interface {
|
||||
return &fakebatchv1.FakeBatchV1{Fake: &c.Fake}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
@@ -71,6 +72,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
authorizationv1beta1.AddToScheme,
|
||||
autoscalingv1.AddToScheme,
|
||||
autoscalingv2beta1.AddToScheme,
|
||||
autoscalingv2beta2.AddToScheme,
|
||||
batchv1.AddToScheme,
|
||||
batchv1beta1.AddToScheme,
|
||||
batchv2alpha1.AddToScheme,
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
@@ -71,6 +72,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
authorizationv1beta1.AddToScheme,
|
||||
autoscalingv1.AddToScheme,
|
||||
autoscalingv2beta1.AddToScheme,
|
||||
autoscalingv2beta2.AddToScheme,
|
||||
batchv1.AddToScheme,
|
||||
batchv1beta1.AddToScheme,
|
||||
batchv2alpha1.AddToScheme,
|
||||
|
||||
90
kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go
Normal file
90
kubernetes/typed/autoscaling/v2beta2/autoscaling_client.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 v2beta2
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type AutoscalingV2beta2Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
HorizontalPodAutoscalersGetter
|
||||
}
|
||||
|
||||
// AutoscalingV2beta2Client is used to interact with features provided by the autoscaling group.
|
||||
type AutoscalingV2beta2Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *AutoscalingV2beta2Client) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface {
|
||||
return newHorizontalPodAutoscalers(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new AutoscalingV2beta2Client for the given config.
|
||||
func NewForConfig(c *rest.Config) (*AutoscalingV2beta2Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AutoscalingV2beta2Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new AutoscalingV2beta2Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *AutoscalingV2beta2Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new AutoscalingV2beta2Client for the given RESTClient.
|
||||
func New(c rest.Interface) *AutoscalingV2beta2Client {
|
||||
return &AutoscalingV2beta2Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v2beta2.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
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 *AutoscalingV2beta2Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
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.
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package api (k8s.io/client-go/tools/bootstrap/token/api) contains constants and types needed for
|
||||
// bootstrap tokens as maintained by the BootstrapSigner and TokenCleaner
|
||||
// controllers (in k8s.io/kubernetes/pkg/controller/bootstrap)
|
||||
package api // import "k8s.io/client-go/tools/bootstrap/token/api"
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v2beta2
|
||||
20
kubernetes/typed/autoscaling/v2beta2/fake/doc.go
Normal file
20
kubernetes/typed/autoscaling/v2beta2/fake/doc.go
Normal 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
|
||||
@@ -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 (
|
||||
v2beta2 "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2"
|
||||
rest "k8s.io/client-go/rest"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
type FakeAutoscalingV2beta2 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeAutoscalingV2beta2) HorizontalPodAutoscalers(namespace string) v2beta2.HorizontalPodAutoscalerInterface {
|
||||
return &FakeHorizontalPodAutoscalers{c, namespace}
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FakeAutoscalingV2beta2) RESTClient() rest.Interface {
|
||||
var ret *rest.RESTClient
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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 (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface
|
||||
type FakeHorizontalPodAutoscalers struct {
|
||||
Fake *FakeAutoscalingV2beta2
|
||||
ns string
|
||||
}
|
||||
|
||||
var horizontalpodautoscalersResource = schema.GroupVersionResource{Group: "autoscaling", Version: "v2beta2", Resource: "horizontalpodautoscalers"}
|
||||
|
||||
var horizontalpodautoscalersKind = schema.GroupVersionKind{Group: "autoscaling", Version: "v2beta2", Kind: "HorizontalPodAutoscaler"}
|
||||
|
||||
// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any.
|
||||
func (c *FakeHorizontalPodAutoscalers) Get(name string, options v1.GetOptions) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetAction(horizontalpodautoscalersResource, c.ns, name), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors.
|
||||
func (c *FakeHorizontalPodAutoscalers) List(opts v1.ListOptions) (result *v2beta2.HorizontalPodAutoscalerList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListAction(horizontalpodautoscalersResource, horizontalpodautoscalersKind, c.ns, opts), &v2beta2.HorizontalPodAutoscalerList{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v2beta2.HorizontalPodAutoscalerList{ListMeta: obj.(*v2beta2.HorizontalPodAutoscalerList).ListMeta}
|
||||
for _, item := range obj.(*v2beta2.HorizontalPodAutoscalerList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers.
|
||||
func (c *FakeHorizontalPodAutoscalers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchAction(horizontalpodautoscalersResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *FakeHorizontalPodAutoscalers) Create(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateAction(horizontalpodautoscalersResource, c.ns, horizontalPodAutoscaler), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *FakeHorizontalPodAutoscalers) Update(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateAction(horizontalpodautoscalersResource, c.ns, horizontalPodAutoscaler), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeHorizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateSubresourceAction(horizontalpodautoscalersResource, "status", c.ns, horizontalPodAutoscaler), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
|
||||
// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(horizontalpodautoscalersResource, c.ns, name), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeHorizontalPodAutoscalers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionAction(horizontalpodautoscalersResource, c.ns, listOptions)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v2beta2.HorizontalPodAutoscalerList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched horizontalPodAutoscaler.
|
||||
func (c *FakeHorizontalPodAutoscalers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(horizontalpodautoscalersResource, c.ns, name, data, subresources...), &v2beta2.HorizontalPodAutoscaler{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), err
|
||||
}
|
||||
21
kubernetes/typed/autoscaling/v2beta2/generated_expansion.go
Normal file
21
kubernetes/typed/autoscaling/v2beta2/generated_expansion.go
Normal 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 v2beta2
|
||||
|
||||
type HorizontalPodAutoscalerExpansion interface{}
|
||||
174
kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
174
kubernetes/typed/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
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 v2beta2
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface.
|
||||
// A group's client should implement this interface.
|
||||
type HorizontalPodAutoscalersGetter interface {
|
||||
HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources.
|
||||
type HorizontalPodAutoscalerInterface interface {
|
||||
Create(*v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
Update(*v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
UpdateStatus(*v2beta2.HorizontalPodAutoscaler) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
Delete(name string, options *v1.DeleteOptions) error
|
||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||
Get(name string, options v1.GetOptions) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
List(opts v1.ListOptions) (*v2beta2.HorizontalPodAutoscalerList, error)
|
||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v2beta2.HorizontalPodAutoscaler, err error)
|
||||
HorizontalPodAutoscalerExpansion
|
||||
}
|
||||
|
||||
// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface
|
||||
type horizontalPodAutoscalers struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers
|
||||
func newHorizontalPodAutoscalers(c *AutoscalingV2beta2Client, namespace string) *horizontalPodAutoscalers {
|
||||
return &horizontalPodAutoscalers{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any.
|
||||
func (c *horizontalPodAutoscalers) Get(name string, options v1.GetOptions) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors.
|
||||
func (c *horizontalPodAutoscalers) List(opts v1.ListOptions) (result *v2beta2.HorizontalPodAutoscalerList, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscalerList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers.
|
||||
func (c *horizontalPodAutoscalers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Body(horizontalPodAutoscaler).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any.
|
||||
func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(horizontalPodAutoscaler.Name).
|
||||
Body(horizontalPodAutoscaler).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
|
||||
func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v2beta2.HorizontalPodAutoscaler) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(horizontalPodAutoscaler.Name).
|
||||
SubResource("status").
|
||||
Body(horizontalPodAutoscaler).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs.
|
||||
func (c *horizontalPodAutoscalers) Delete(name string, options *v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *horizontalPodAutoscalers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched horizontalPodAutoscaler.
|
||||
func (c *horizontalPodAutoscalers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
result = &v2beta2.HorizontalPodAutoscaler{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("horizontalpodautoscalers").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
@@ -26,20 +26,31 @@ import (
|
||||
func (c *FakePods) Bind(binding *v1.Binding) error {
|
||||
action := core.CreateActionImpl{}
|
||||
action.Verb = "create"
|
||||
action.Namespace = binding.Namespace
|
||||
action.Resource = podsResource
|
||||
action.Subresource = "bindings"
|
||||
action.Subresource = "binding"
|
||||
action.Object = binding
|
||||
|
||||
_, err := c.Fake.Invokes(action, binding)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *FakePods) GetBinding(name string) (result *v1.Binding, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(core.NewGetSubresourceAction(podsResource, c.ns, "binding", name), &v1.Binding{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1.Binding), err
|
||||
}
|
||||
|
||||
func (c *FakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request {
|
||||
action := core.GenericActionImpl{}
|
||||
action.Verb = "get"
|
||||
action.Namespace = c.ns
|
||||
action.Resource = podsResource
|
||||
action.Subresource = "logs"
|
||||
action.Subresource = "log"
|
||||
action.Value = opts
|
||||
|
||||
_, _ = c.Fake.Invokes(action, &v1.Pod{})
|
||||
|
||||
@@ -26,8 +26,9 @@ func (c *FakeEvictions) Evict(eviction *policy.Eviction) error {
|
||||
action := core.GetActionImpl{}
|
||||
action.Verb = "post"
|
||||
action.Namespace = c.ns
|
||||
action.Resource = schema.GroupVersionResource{Group: "", Version: "", Resource: "pods"}
|
||||
action.Resource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
action.Subresource = "eviction"
|
||||
action.Name = eviction.Name
|
||||
_, err := c.Fake.Invokes(action, eviction)
|
||||
return err
|
||||
}
|
||||
|
||||
27
listers/autoscaling/v2beta2/expansion_generated.go
Normal file
27
listers/autoscaling/v2beta2/expansion_generated.go
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
// HorizontalPodAutoscalerListerExpansion allows custom methods to be added to
|
||||
// HorizontalPodAutoscalerLister.
|
||||
type HorizontalPodAutoscalerListerExpansion interface{}
|
||||
|
||||
// HorizontalPodAutoscalerNamespaceListerExpansion allows custom methods to be added to
|
||||
// HorizontalPodAutoscalerNamespaceLister.
|
||||
type HorizontalPodAutoscalerNamespaceListerExpansion interface{}
|
||||
94
listers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
94
listers/autoscaling/v2beta2/horizontalpodautoscaler.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v2beta2
|
||||
|
||||
import (
|
||||
v2beta2 "k8s.io/api/autoscaling/v2beta2"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// HorizontalPodAutoscalerLister helps list HorizontalPodAutoscalers.
|
||||
type HorizontalPodAutoscalerLister interface {
|
||||
// List lists all HorizontalPodAutoscalers in the indexer.
|
||||
List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error)
|
||||
// HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers.
|
||||
HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister
|
||||
HorizontalPodAutoscalerListerExpansion
|
||||
}
|
||||
|
||||
// horizontalPodAutoscalerLister implements the HorizontalPodAutoscalerLister interface.
|
||||
type horizontalPodAutoscalerLister struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
// NewHorizontalPodAutoscalerLister returns a new HorizontalPodAutoscalerLister.
|
||||
func NewHorizontalPodAutoscalerLister(indexer cache.Indexer) HorizontalPodAutoscalerLister {
|
||||
return &horizontalPodAutoscalerLister{indexer: indexer}
|
||||
}
|
||||
|
||||
// List lists all HorizontalPodAutoscalers in the indexer.
|
||||
func (s *horizontalPodAutoscalerLister) List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v2beta2.HorizontalPodAutoscaler))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalers returns an object that can list and get HorizontalPodAutoscalers.
|
||||
func (s *horizontalPodAutoscalerLister) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerNamespaceLister {
|
||||
return horizontalPodAutoscalerNamespaceLister{indexer: s.indexer, namespace: namespace}
|
||||
}
|
||||
|
||||
// HorizontalPodAutoscalerNamespaceLister helps list and get HorizontalPodAutoscalers.
|
||||
type HorizontalPodAutoscalerNamespaceLister interface {
|
||||
// List lists all HorizontalPodAutoscalers in the indexer for a given namespace.
|
||||
List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error)
|
||||
// Get retrieves the HorizontalPodAutoscaler from the indexer for a given namespace and name.
|
||||
Get(name string) (*v2beta2.HorizontalPodAutoscaler, error)
|
||||
HorizontalPodAutoscalerNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// horizontalPodAutoscalerNamespaceLister implements the HorizontalPodAutoscalerNamespaceLister
|
||||
// interface.
|
||||
type horizontalPodAutoscalerNamespaceLister struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
// List lists all HorizontalPodAutoscalers in the indexer for a given namespace.
|
||||
func (s horizontalPodAutoscalerNamespaceLister) List(selector labels.Selector) (ret []*v2beta2.HorizontalPodAutoscaler, err error) {
|
||||
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v2beta2.HorizontalPodAutoscaler))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves the HorizontalPodAutoscaler from the indexer for a given namespace and name.
|
||||
func (s horizontalPodAutoscalerNamespaceLister) Get(name string) (*v2beta2.HorizontalPodAutoscaler, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(v2beta2.Resource("horizontalpodautoscaler"), name)
|
||||
}
|
||||
return obj.(*v2beta2.HorizontalPodAutoscaler), nil
|
||||
}
|
||||
@@ -16,4 +16,5 @@ limitations under the License.
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +groupName=client.authentication.k8s.io
|
||||
|
||||
package clientauthentication // import "k8s.io/client-go/pkg/apis/clientauthentication"
|
||||
|
||||
@@ -20,4 +20,5 @@ limitations under the License.
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
// +groupName=client.authentication.k8s.io
|
||||
|
||||
package v1alpha1 // import "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1"
|
||||
|
||||
@@ -20,4 +20,5 @@ limitations under the License.
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
// +groupName=client.authentication.k8s.io
|
||||
|
||||
package v1beta1 // import "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
// Package version supplies version information collected at build time to
|
||||
// kubernetes components.
|
||||
// +k8s:openapi-gen=true
|
||||
package version // import "k8s.io/client-go/pkg/version"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -44,6 +44,8 @@ const (
|
||||
DefaultBurst int = 10
|
||||
)
|
||||
|
||||
var ErrNotInCluster = errors.New("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
|
||||
|
||||
// Config holds the common attributes that can be passed to a Kubernetes client on
|
||||
// initialization.
|
||||
type Config struct {
|
||||
@@ -308,20 +310,26 @@ func DefaultKubernetesUserAgent() string {
|
||||
|
||||
// InClusterConfig returns a config object which uses the service account
|
||||
// kubernetes gives to pods. It's intended for clients that expect to be
|
||||
// running inside a pod running on kubernetes. It will return an error if
|
||||
// called from a process not running in a kubernetes environment.
|
||||
// running inside a pod running on kubernetes. It will return ErrNotInCluster
|
||||
// if called from a process not running in a kubernetes environment.
|
||||
func InClusterConfig() (*Config, error) {
|
||||
const (
|
||||
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
)
|
||||
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
|
||||
if len(host) == 0 || len(port) == 0 {
|
||||
return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
|
||||
return nil, ErrNotInCluster
|
||||
}
|
||||
|
||||
token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
||||
if err != nil {
|
||||
ts := newCachedPathTokenSource(tokenFile)
|
||||
|
||||
if _, err := ts.Token(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsClientConfig := TLSClientConfig{}
|
||||
rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
|
||||
if _, err := certutil.NewPool(rootCAFile); err != nil {
|
||||
glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
|
||||
} else {
|
||||
@@ -331,8 +339,8 @@ func InClusterConfig() (*Config, error) {
|
||||
return &Config{
|
||||
// TODO: switch to using cluster DNS.
|
||||
Host: "https://" + net.JoinHostPort(host, port),
|
||||
BearerToken: string(token),
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
WrapTransport: TokenSourceWrapTransport(ts),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request {
|
||||
return r
|
||||
}
|
||||
|
||||
// SubResource sets a sub-resource path which can be multiple segments segment after the resource
|
||||
// SubResource sets a sub-resource path which can be multiple segments after the resource
|
||||
// name but before the suffix.
|
||||
func (r *Request) SubResource(subresources ...string) *Request {
|
||||
if r.err != nil {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
138
rest/token_source.go
Normal file
138
rest/token_source.go
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// TokenSourceWrapTransport returns a WrapTransport that injects bearer tokens
|
||||
// authentication from an oauth2.TokenSource.
|
||||
func TokenSourceWrapTransport(ts oauth2.TokenSource) func(http.RoundTripper) http.RoundTripper {
|
||||
return func(rt http.RoundTripper) http.RoundTripper {
|
||||
return &tokenSourceTransport{
|
||||
base: rt,
|
||||
ort: &oauth2.Transport{
|
||||
Source: ts,
|
||||
Base: rt,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newCachedPathTokenSource(path string) oauth2.TokenSource {
|
||||
return &cachingTokenSource{
|
||||
now: time.Now,
|
||||
leeway: 1 * time.Minute,
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type tokenSourceTransport struct {
|
||||
base http.RoundTripper
|
||||
ort http.RoundTripper
|
||||
}
|
||||
|
||||
func (tst *tokenSourceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// This is to allow --token to override other bearer token providers.
|
||||
if req.Header.Get("Authorization") != "" {
|
||||
return tst.base.RoundTrip(req)
|
||||
}
|
||||
return tst.ort.RoundTrip(req)
|
||||
}
|
||||
|
||||
type fileTokenSource struct {
|
||||
path string
|
||||
period time.Duration
|
||||
}
|
||||
|
||||
var _ = oauth2.TokenSource(&fileTokenSource{})
|
||||
|
||||
func (ts *fileTokenSource) Token() (*oauth2.Token, error) {
|
||||
tokb, err := ioutil.ReadFile(ts.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read token file %q: %v", ts.path, err)
|
||||
}
|
||||
tok := strings.TrimSpace(string(tokb))
|
||||
if len(tok) == 0 {
|
||||
return nil, fmt.Errorf("read empty token from file %q", ts.path)
|
||||
}
|
||||
|
||||
return &oauth2.Token{
|
||||
AccessToken: tok,
|
||||
Expiry: time.Now().Add(ts.period),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type cachingTokenSource struct {
|
||||
base oauth2.TokenSource
|
||||
leeway time.Duration
|
||||
|
||||
sync.RWMutex
|
||||
tok *oauth2.Token
|
||||
|
||||
// for testing
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
var _ = oauth2.TokenSource(&cachingTokenSource{})
|
||||
|
||||
func (ts *cachingTokenSource) Token() (*oauth2.Token, error) {
|
||||
now := ts.now()
|
||||
// fast path
|
||||
ts.RLock()
|
||||
tok := ts.tok
|
||||
ts.RUnlock()
|
||||
|
||||
if tok != nil && tok.Expiry.Add(-1*ts.leeway).After(now) {
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// slow path
|
||||
ts.Lock()
|
||||
defer ts.Unlock()
|
||||
if tok := ts.tok; tok != nil && tok.Expiry.Add(-1*ts.leeway).After(now) {
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
tok, err := ts.base.Token()
|
||||
if err != nil {
|
||||
if ts.tok == nil {
|
||||
return nil, err
|
||||
}
|
||||
glog.Errorf("Unable to rotate token: %v", err)
|
||||
return ts.tok, nil
|
||||
}
|
||||
|
||||
ts.tok = tok
|
||||
return tok, nil
|
||||
}
|
||||
156
rest/token_source_test.go
Normal file
156
rest/token_source_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type testTokenSource struct {
|
||||
calls int
|
||||
tok *oauth2.Token
|
||||
err error
|
||||
}
|
||||
|
||||
func (ts *testTokenSource) Token() (*oauth2.Token, error) {
|
||||
ts.calls++
|
||||
return ts.tok, ts.err
|
||||
}
|
||||
|
||||
func TestCachingTokenSource(t *testing.T) {
|
||||
start := time.Now()
|
||||
tokA := &oauth2.Token{
|
||||
AccessToken: "a",
|
||||
Expiry: start.Add(10 * time.Minute),
|
||||
}
|
||||
tokB := &oauth2.Token{
|
||||
AccessToken: "b",
|
||||
Expiry: start.Add(20 * time.Minute),
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
tok *oauth2.Token
|
||||
tsTok *oauth2.Token
|
||||
tsErr error
|
||||
wait time.Duration
|
||||
|
||||
wantTok *oauth2.Token
|
||||
wantErr bool
|
||||
wantTSCalls int
|
||||
}{
|
||||
{
|
||||
name: "valid token returned from cache",
|
||||
tok: tokA,
|
||||
wantTok: tokA,
|
||||
},
|
||||
{
|
||||
name: "valid token returned from cache 1 minute before scheduled refresh",
|
||||
tok: tokA,
|
||||
wait: 8 * time.Minute,
|
||||
wantTok: tokA,
|
||||
},
|
||||
{
|
||||
name: "new token created when cache is empty",
|
||||
tsTok: tokA,
|
||||
wantTok: tokA,
|
||||
wantTSCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "new token created 1 minute after scheduled refresh",
|
||||
tok: tokA,
|
||||
tsTok: tokB,
|
||||
wait: 10 * time.Minute,
|
||||
wantTok: tokB,
|
||||
wantTSCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "error on create token returns error",
|
||||
tsErr: fmt.Errorf("error"),
|
||||
wantErr: true,
|
||||
wantTSCalls: 1,
|
||||
},
|
||||
}
|
||||
for _, c := range tests {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
tts := &testTokenSource{
|
||||
tok: c.tsTok,
|
||||
err: c.tsErr,
|
||||
}
|
||||
|
||||
ts := &cachingTokenSource{
|
||||
base: tts,
|
||||
tok: c.tok,
|
||||
leeway: 1 * time.Minute,
|
||||
now: func() time.Time { return start.Add(c.wait) },
|
||||
}
|
||||
|
||||
gotTok, gotErr := ts.Token()
|
||||
if got, want := gotTok, c.wantTok; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unexpected token:\n\tgot:\t%#v\n\twant:\t%#v", got, want)
|
||||
}
|
||||
if got, want := tts.calls, c.wantTSCalls; got != want {
|
||||
t.Errorf("unexpected number of Token() calls: got %d, want %d", got, want)
|
||||
}
|
||||
if gotErr == nil && c.wantErr {
|
||||
t.Errorf("wanted error but got none")
|
||||
}
|
||||
if gotErr != nil && !c.wantErr {
|
||||
t.Errorf("unexpected error: %v", gotErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachingTokenSourceRace(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
tts := &testTokenSource{
|
||||
tok: &oauth2.Token{
|
||||
AccessToken: "a",
|
||||
Expiry: time.Now().Add(1000 * time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
ts := &cachingTokenSource{
|
||||
now: time.Now,
|
||||
base: tts,
|
||||
leeway: 1 * time.Minute,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := ts.Token(); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if tts.calls != 1 {
|
||||
t.Errorf("expected one call to Token() but saw: %d", tts.calls)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,6 @@ type ScaleInterface interface {
|
||||
// Get fetches the scale of the given scalable resource.
|
||||
Get(resource schema.GroupResource, name string) (*autoscalingapi.Scale, error)
|
||||
|
||||
// Update updates the scale of the the given scalable resource.
|
||||
// Update updates the scale of the given scalable resource.
|
||||
Update(resource schema.GroupResource, scale *autoscalingapi.Scale) (*autoscalingapi.Scale, error)
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
approvers:
|
||||
- jbeda
|
||||
- luxas
|
||||
reviewers:
|
||||
- mattmoyer
|
||||
@@ -1,112 +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 api
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// BootstrapTokenSecretPrefix is the prefix for bootstrap token names.
|
||||
// Bootstrap tokens secrets must be named in the form
|
||||
// `bootstrap-token-<token-id>`. This is the prefix to be used before the
|
||||
// token ID.
|
||||
BootstrapTokenSecretPrefix = "bootstrap-token-"
|
||||
|
||||
// SecretTypeBootstrapToken is used during the automated bootstrap process (first
|
||||
// implemented by kubeadm). It stores tokens that are used to sign well known
|
||||
// ConfigMaps. They may also eventually be used for authentication.
|
||||
SecretTypeBootstrapToken v1.SecretType = "bootstrap.kubernetes.io/token"
|
||||
|
||||
// BootstrapTokenIDKey is the id of this token. This can be transmitted in the
|
||||
// clear and encoded in the name of the secret. It must be a random 6 character
|
||||
// string that matches the regexp `^([a-z0-9]{6})$`. Required.
|
||||
BootstrapTokenIDKey = "token-id"
|
||||
|
||||
// BootstrapTokenSecretKey is the actual secret. It must be a random 16 character
|
||||
// string that matches the regexp `^([a-z0-9]{16})$`. Required.
|
||||
BootstrapTokenSecretKey = "token-secret"
|
||||
|
||||
// BootstrapTokenExpirationKey is when this token should be expired and no
|
||||
// longer used. A controller will delete this resource after this time. This
|
||||
// is an absolute UTC time using RFC3339. If this cannot be parsed, the token
|
||||
// should be considered invalid. Optional.
|
||||
BootstrapTokenExpirationKey = "expiration"
|
||||
|
||||
// BootstrapTokenDescriptionKey is a description in human-readable format that
|
||||
// describes what the bootstrap token is used for. Optional.
|
||||
BootstrapTokenDescriptionKey = "description"
|
||||
|
||||
// BootstrapTokenExtraGroupsKey is a comma-separated list of group names.
|
||||
// The bootstrap token will authenticate as these groups in addition to the
|
||||
// "system:bootstrappers" group.
|
||||
BootstrapTokenExtraGroupsKey = "auth-extra-groups"
|
||||
|
||||
// BootstrapTokenUsagePrefix is the prefix for the other usage constants that specifies different
|
||||
// functions of a bootstrap token
|
||||
BootstrapTokenUsagePrefix = "usage-bootstrap-"
|
||||
|
||||
// BootstrapTokenUsageSigningKey signals that this token should be used to
|
||||
// sign configs as part of the bootstrap process. Value must be "true". Any
|
||||
// other value is assumed to be false. Optional.
|
||||
BootstrapTokenUsageSigningKey = "usage-bootstrap-signing"
|
||||
|
||||
// BootstrapTokenUsageAuthentication signals that this token should be used
|
||||
// as a bearer token to authenticate against the Kubernetes API. The bearer
|
||||
// token takes the form "<token-id>.<token-secret>" and authenticates as the
|
||||
// user "system:bootstrap:<token-id>" in the "system:bootstrappers" group
|
||||
// as well as any groups specified using BootstrapTokenExtraGroupsKey.
|
||||
// Value must be "true". Any other value is assumed to be false. Optional.
|
||||
BootstrapTokenUsageAuthentication = "usage-bootstrap-authentication"
|
||||
|
||||
// ConfigMapClusterInfo defines the name for the ConfigMap where the information how to connect and trust the cluster exist
|
||||
ConfigMapClusterInfo = "cluster-info"
|
||||
|
||||
// KubeConfigKey defines at which key in the Data object of the ConfigMap the KubeConfig object is stored
|
||||
KubeConfigKey = "kubeconfig"
|
||||
|
||||
// JWSSignatureKeyPrefix defines what key prefix the JWS-signed tokens have
|
||||
JWSSignatureKeyPrefix = "jws-kubeconfig-"
|
||||
|
||||
// BootstrapUserPrefix is the username prefix bootstrapping bearer tokens
|
||||
// authenticate as. The full username given is "system:bootstrap:<token-id>".
|
||||
BootstrapUserPrefix = "system:bootstrap:"
|
||||
|
||||
// BootstrapDefaultGroup is the default group for bootstrapping bearer
|
||||
// tokens (in addition to any groups from BootstrapTokenExtraGroupsKey).
|
||||
BootstrapDefaultGroup = "system:bootstrappers"
|
||||
|
||||
// BootstrapGroupPattern is the valid regex pattern that all groups
|
||||
// assigned to a bootstrap token by BootstrapTokenExtraGroupsKey must match.
|
||||
// See also util.ValidateBootstrapGroupName()
|
||||
BootstrapGroupPattern = `\Asystem:bootstrappers:[a-z0-9:-]{0,255}[a-z0-9]\z`
|
||||
|
||||
// BootstrapTokenPattern defines the {id}.{secret} regular expression pattern
|
||||
BootstrapTokenPattern = `\A([a-z0-9]{6})\.([a-z0-9]{16})\z`
|
||||
|
||||
// BootstrapTokenIDPattern defines token's id regular expression pattern
|
||||
BootstrapTokenIDPattern = `\A([a-z0-9]{6})\z`
|
||||
|
||||
// BootstrapTokenIDBytes defines the number of bytes used for the Bootstrap Token's ID field
|
||||
BootstrapTokenIDBytes = 6
|
||||
|
||||
// BootstrapTokenSecretBytes defines the number of bytes used the Bootstrap Token's Secret field
|
||||
BootstrapTokenSecretBytes = 16
|
||||
)
|
||||
|
||||
// KnownTokenUsages specifies the known functions a token will get.
|
||||
var KnownTokenUsages = []string{"signing", "authentication"}
|
||||
@@ -1,133 +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 util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/tools/bootstrap/token/api"
|
||||
)
|
||||
|
||||
// validBootstrapTokenChars defines the characters a bootstrap token can consist of
|
||||
const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
var (
|
||||
// BootstrapTokenRegexp is a compiled regular expression of TokenRegexpString
|
||||
BootstrapTokenRegexp = regexp.MustCompile(api.BootstrapTokenPattern)
|
||||
// BootstrapTokenIDRegexp is a compiled regular expression of TokenIDRegexpString
|
||||
BootstrapTokenIDRegexp = regexp.MustCompile(api.BootstrapTokenIDPattern)
|
||||
// BootstrapGroupRegexp is a compiled regular expression of BootstrapGroupPattern
|
||||
BootstrapGroupRegexp = regexp.MustCompile(api.BootstrapGroupPattern)
|
||||
)
|
||||
|
||||
// GenerateBootstrapToken generates a new, random Bootstrap Token.
|
||||
func GenerateBootstrapToken() (string, error) {
|
||||
tokenID, err := randBytes(api.BootstrapTokenIDBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tokenSecret, err := randBytes(api.BootstrapTokenSecretBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return TokenFromIDAndSecret(tokenID, tokenSecret), nil
|
||||
}
|
||||
|
||||
// randBytes returns a random string consisting of the characters in
|
||||
// validBootstrapTokenChars, with the length customized by the parameter
|
||||
func randBytes(length int) (string, error) {
|
||||
// len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide
|
||||
// the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we
|
||||
// read that are >= 252 so the bytes we evenly divide the character set.
|
||||
const maxByteValue = 252
|
||||
|
||||
var (
|
||||
b byte
|
||||
err error
|
||||
token = make([]byte, length)
|
||||
)
|
||||
|
||||
reader := bufio.NewReaderSize(rand.Reader, length*2)
|
||||
for i := range token {
|
||||
for {
|
||||
if b, err = reader.ReadByte(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b < maxByteValue {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)]
|
||||
}
|
||||
|
||||
return string(token), nil
|
||||
}
|
||||
|
||||
// TokenFromIDAndSecret returns the full token which is of the form "{id}.{secret}"
|
||||
func TokenFromIDAndSecret(id, secret string) string {
|
||||
return fmt.Sprintf("%s.%s", id, secret)
|
||||
}
|
||||
|
||||
// IsValidBootstrapToken returns whether the given string is valid as a Bootstrap Token and
|
||||
// in other words satisfies the BootstrapTokenRegexp
|
||||
func IsValidBootstrapToken(token string) bool {
|
||||
return BootstrapTokenRegexp.MatchString(token)
|
||||
}
|
||||
|
||||
// IsValidBootstrapTokenID returns whether the given string is valid as a Bootstrap Token ID and
|
||||
// in other words satisfies the BootstrapTokenIDRegexp
|
||||
func IsValidBootstrapTokenID(tokenID string) bool {
|
||||
return BootstrapTokenIDRegexp.MatchString(tokenID)
|
||||
}
|
||||
|
||||
// BootstrapTokenSecretName returns the expected name for the Secret storing the
|
||||
// Bootstrap Token in the Kubernetes API.
|
||||
func BootstrapTokenSecretName(tokenID string) string {
|
||||
return fmt.Sprintf("%s%s", api.BootstrapTokenSecretPrefix, tokenID)
|
||||
}
|
||||
|
||||
// ValidateBootstrapGroupName checks if the provided group name is a valid
|
||||
// bootstrap group name. Returns nil if valid or a validation error if invalid.
|
||||
func ValidateBootstrapGroupName(name string) error {
|
||||
if BootstrapGroupRegexp.Match([]byte(name)) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, api.BootstrapGroupPattern)
|
||||
}
|
||||
|
||||
// ValidateUsages validates that the passed in string are valid usage strings for bootstrap tokens.
|
||||
func ValidateUsages(usages []string) error {
|
||||
validUsages := sets.NewString(api.KnownTokenUsages...)
|
||||
invalidUsages := sets.NewString()
|
||||
for _, usage := range usages {
|
||||
if !validUsages.Has(usage) {
|
||||
invalidUsages.Insert(usage)
|
||||
}
|
||||
}
|
||||
if len(invalidUsages) > 0 {
|
||||
return fmt.Errorf("invalid bootstrap token usage string: %s, valid usage options: %s", strings.Join(invalidUsages.List(), ","), strings.Join(api.KnownTokenUsages, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,213 +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 util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateBootstrapToken(t *testing.T) {
|
||||
token, err := GenerateBootstrapToken()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateBootstrapToken returned an unexpected error: %+v", err)
|
||||
}
|
||||
if !IsValidBootstrapToken(token) {
|
||||
t.Errorf("GenerateBootstrapToken didn't generate a valid token: %q", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandBytes(t *testing.T) {
|
||||
var randTest = []int{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
100,
|
||||
}
|
||||
|
||||
for _, rt := range randTest {
|
||||
actual, err := randBytes(rt)
|
||||
if err != nil {
|
||||
t.Errorf("failed randBytes: %v", err)
|
||||
}
|
||||
if len(actual) != rt {
|
||||
t.Errorf("failed randBytes:\n\texpected: %d\n\t actual: %d\n", rt, len(actual))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenFromIDAndSecret(t *testing.T) {
|
||||
var tests = []struct {
|
||||
id string
|
||||
secret string
|
||||
expected string
|
||||
}{
|
||||
{"foo", "bar", "foo.bar"}, // should use default
|
||||
{"abcdef", "abcdef0123456789", "abcdef.abcdef0123456789"},
|
||||
{"h", "b", "h.b"},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := TokenFromIDAndSecret(rt.id, rt.secret)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed TokenFromIDAndSecret:\n\texpected: %s\n\t actual: %s",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidBootstrapToken(t *testing.T) {
|
||||
var tests = []struct {
|
||||
token string
|
||||
expected bool
|
||||
}{
|
||||
{token: "", expected: false},
|
||||
{token: ".", expected: false},
|
||||
{token: "1234567890123456789012", expected: false}, // invalid parcel size
|
||||
{token: "12345.1234567890123456", expected: false}, // invalid parcel size
|
||||
{token: ".1234567890123456", expected: false}, // invalid parcel size
|
||||
{token: "123456.", expected: false}, // invalid parcel size
|
||||
{token: "123456:1234567890.123456", expected: false}, // invalid separation
|
||||
{token: "abcdef:1234567890123456", expected: false}, // invalid separation
|
||||
{token: "Abcdef.1234567890123456", expected: false}, // invalid token id
|
||||
{token: "123456.AABBCCDDEEFFGGHH", expected: false}, // invalid token secret
|
||||
{token: "123456.AABBCCD-EEFFGGHH", expected: false}, // invalid character
|
||||
{token: "abc*ef.1234567890123456", expected: false}, // invalid character
|
||||
{token: "abcdef.1234567890123456", expected: true},
|
||||
{token: "123456.aabbccddeeffgghh", expected: true},
|
||||
{token: "ABCDEF.abcdef0123456789", expected: false},
|
||||
{token: "abcdef.abcdef0123456789", expected: true},
|
||||
{token: "123456.1234560123456789", expected: true},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := IsValidBootstrapToken(rt.token)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed IsValidBootstrapToken for the token %q\n\texpected: %t\n\t actual: %t",
|
||||
rt.token,
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidBootstrapTokenID(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tokenID string
|
||||
expected bool
|
||||
}{
|
||||
{tokenID: "", expected: false},
|
||||
{tokenID: "1234567890123456789012", expected: false},
|
||||
{tokenID: "12345", expected: false},
|
||||
{tokenID: "Abcdef", expected: false},
|
||||
{tokenID: "ABCDEF", expected: false},
|
||||
{tokenID: "abcdef.", expected: false},
|
||||
{tokenID: "abcdef", expected: true},
|
||||
{tokenID: "123456", expected: true},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := IsValidBootstrapTokenID(rt.tokenID)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed IsValidBootstrapTokenID for the token %q\n\texpected: %t\n\t actual: %t",
|
||||
rt.tokenID,
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapTokenSecretName(t *testing.T) {
|
||||
var tests = []struct {
|
||||
tokenID string
|
||||
expected string
|
||||
}{
|
||||
{"foo", "bootstrap-token-foo"},
|
||||
{"bar", "bootstrap-token-bar"},
|
||||
{"", "bootstrap-token-"},
|
||||
{"abcdef", "bootstrap-token-abcdef"},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := BootstrapTokenSecretName(rt.tokenID)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed BootstrapTokenSecretName:\n\texpected: %s\n\t actual: %s",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBootstrapGroupName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
valid bool
|
||||
}{
|
||||
{"valid", "system:bootstrappers:foo", true},
|
||||
{"valid nested", "system:bootstrappers:foo:bar:baz", true},
|
||||
{"valid with dashes and number", "system:bootstrappers:foo-bar-42", true},
|
||||
{"invalid uppercase", "system:bootstrappers:Foo", false},
|
||||
{"missing prefix", "foo", false},
|
||||
{"prefix with no body", "system:bootstrappers:", false},
|
||||
{"invalid spaces", "system:bootstrappers: ", false},
|
||||
{"invalid asterisk", "system:bootstrappers:*", false},
|
||||
{"trailing colon", "system:bootstrappers:foo:", false},
|
||||
{"trailing dash", "system:bootstrappers:foo-", false},
|
||||
{"script tags", "system:bootstrappers:<script> alert(\"scary?!\") </script>", false},
|
||||
{"too long", "system:bootstrappers:" + strings.Repeat("x", 300), false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
err := ValidateBootstrapGroupName(test.input)
|
||||
if err != nil && test.valid {
|
||||
t.Errorf("test %q: ValidateBootstrapGroupName(%q) returned unexpected error: %v", test.name, test.input, err)
|
||||
}
|
||||
if err == nil && !test.valid {
|
||||
t.Errorf("test %q: ValidateBootstrapGroupName(%q) was supposed to return an error but didn't", test.name, test.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUsages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
valid bool
|
||||
}{
|
||||
{"valid of signing", []string{"signing"}, true},
|
||||
{"valid of authentication", []string{"authentication"}, true},
|
||||
{"all valid", []string{"authentication", "signing"}, true},
|
||||
{"single invalid", []string{"authentication", "foo"}, false},
|
||||
{"all invalid", []string{"foo", "bar"}, false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := ValidateUsages(test.input)
|
||||
if err != nil && test.valid {
|
||||
t.Errorf("test %q: ValidateUsages(%v) returned unexpected error: %v", test.name, test.input, err)
|
||||
}
|
||||
if err == nil && !test.valid {
|
||||
t.Errorf("test %q: ValidateUsages(%v) was supposed to return an error but didn't", test.name, test.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
2
tools/cache/heap.go
vendored
2
tools/cache/heap.go
vendored
@@ -204,7 +204,7 @@ func (h *Heap) AddIfNotPresent(obj interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresentLocked assumes the lock is already held and adds the the provided
|
||||
// addIfNotPresentLocked assumes the lock is already held and adds the provided
|
||||
// item to the queue if it does not already exist.
|
||||
func (h *Heap) addIfNotPresentLocked(key string, obj interface{}) {
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
|
||||
75
tools/cache/index_test.go
vendored
75
tools/cache/index_test.go
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -70,60 +71,31 @@ func TestMultiIndexKeys(t *testing.T) {
|
||||
index.Add(pod2)
|
||||
index.Add(pod3)
|
||||
|
||||
erniePods, err := index.ByIndex("byUser", "ernie")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(erniePods) != 2 {
|
||||
t.Errorf("Expected 2 pods but got %v", len(erniePods))
|
||||
}
|
||||
for _, erniePod := range erniePods {
|
||||
if erniePod.(*v1.Pod).Name != "one" && erniePod.(*v1.Pod).Name != "tre" {
|
||||
t.Errorf("Expected only 'one' or 'tre' but got %s", erniePod.(*v1.Pod).Name)
|
||||
}
|
||||
}
|
||||
|
||||
bertPods, err := index.ByIndex("byUser", "bert")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(bertPods) != 2 {
|
||||
t.Errorf("Expected 2 pods but got %v", len(bertPods))
|
||||
}
|
||||
for _, bertPod := range bertPods {
|
||||
if bertPod.(*v1.Pod).Name != "one" && bertPod.(*v1.Pod).Name != "two" {
|
||||
t.Errorf("Expected only 'one' or 'two' but got %s", bertPod.(*v1.Pod).Name)
|
||||
}
|
||||
}
|
||||
|
||||
oscarPods, err := index.ByIndex("byUser", "oscar")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(oscarPods) != 1 {
|
||||
t.Errorf("Expected 1 pods but got %v", len(erniePods))
|
||||
}
|
||||
for _, oscarPod := range oscarPods {
|
||||
if oscarPod.(*v1.Pod).Name != "two" {
|
||||
t.Errorf("Expected only 'two' but got %s", oscarPod.(*v1.Pod).Name)
|
||||
}
|
||||
}
|
||||
|
||||
ernieAndBertKeys, err := index.Index("byUser", pod1)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(ernieAndBertKeys) != 3 {
|
||||
t.Errorf("Expected 3 pods but got %v", len(ernieAndBertKeys))
|
||||
}
|
||||
for _, ernieAndBertKey := range ernieAndBertKeys {
|
||||
if ernieAndBertKey.(*v1.Pod).Name != "one" && ernieAndBertKey.(*v1.Pod).Name != "two" && ernieAndBertKey.(*v1.Pod).Name != "tre" {
|
||||
t.Errorf("Expected only 'one', 'two' or 'tre' but got %s", ernieAndBertKey.(*v1.Pod).Name)
|
||||
expected := map[string]sets.String{}
|
||||
expected["ernie"] = sets.NewString("one", "tre")
|
||||
expected["bert"] = sets.NewString("one", "two")
|
||||
expected["elmo"] = sets.NewString("tre")
|
||||
expected["oscar"] = sets.NewString("two")
|
||||
expected["elmo"] = sets.NewString() // let's just make sure we don't get anything back in this case
|
||||
{
|
||||
for k, v := range expected {
|
||||
found := sets.String{}
|
||||
indexResults, err := index.ByIndex("byUser", k)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
for _, item := range indexResults {
|
||||
found.Insert(item.(*v1.Pod).Name)
|
||||
}
|
||||
items := v.List()
|
||||
if !found.HasAll(items...) {
|
||||
t.Errorf("missing items, index %s, expected %v but found %v", k, items, found.List())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index.Delete(pod3)
|
||||
erniePods, err = index.ByIndex("byUser", "ernie")
|
||||
erniePods, err := index.ByIndex("byUser", "ernie")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -147,7 +119,7 @@ func TestMultiIndexKeys(t *testing.T) {
|
||||
copyOfPod2 := pod2.DeepCopy()
|
||||
copyOfPod2.Annotations["users"] = "oscar"
|
||||
index.Update(copyOfPod2)
|
||||
bertPods, err = index.ByIndex("byUser", "bert")
|
||||
bertPods, err := index.ByIndex("byUser", "bert")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -159,5 +131,4 @@ func TestMultiIndexKeys(t *testing.T) {
|
||||
t.Errorf("Expected only 'one' but got %s", bertPod.(*v1.Pod).Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
86
tools/cache/listwatch.go
vendored
86
tools/cache/listwatch.go
vendored
@@ -18,17 +18,13 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
)
|
||||
|
||||
// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
|
||||
@@ -94,13 +90,6 @@ func NewFilteredListWatchFromClient(c Getter, resource string, namespace string,
|
||||
return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
|
||||
}
|
||||
|
||||
func timeoutFromListOptions(options metav1.ListOptions) time.Duration {
|
||||
if options.TimeoutSeconds != nil {
|
||||
return time.Duration(*options.TimeoutSeconds) * time.Second
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// List a set of apiserver resources
|
||||
func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
if !lw.DisableChunking {
|
||||
@@ -113,78 +102,3 @@ func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return lw.WatchFunc(options)
|
||||
}
|
||||
|
||||
// ListWatchUntil checks the provided conditions against the items returned by the list watcher, returning wait.ErrWaitTimeout
|
||||
// if timeout is exceeded without all conditions returning true, or an error if an error occurs.
|
||||
// TODO: check for watch expired error and retry watch from latest point? Same issue exists for Until.
|
||||
func ListWatchUntil(timeout time.Duration, lw ListerWatcher, conditions ...watchtools.ConditionFunc) (*watch.Event, error) {
|
||||
if len(conditions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list, err := lw.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialItems, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use the initial items as simulated "adds"
|
||||
var lastEvent *watch.Event
|
||||
currIndex := 0
|
||||
passedConditions := 0
|
||||
for _, condition := range conditions {
|
||||
// check the next condition against the previous event and short circuit waiting for the next watch
|
||||
if lastEvent != nil {
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ConditionSucceeded:
|
||||
for currIndex < len(initialItems) {
|
||||
lastEvent = &watch.Event{Type: watch.Added, Object: initialItems[currIndex]}
|
||||
currIndex++
|
||||
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
break ConditionSucceeded
|
||||
}
|
||||
}
|
||||
}
|
||||
if passedConditions == len(conditions) {
|
||||
return lastEvent, nil
|
||||
}
|
||||
remainingConditions := conditions[passedConditions:]
|
||||
|
||||
metaObj, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currResourceVersion := metaObj.GetResourceVersion()
|
||||
|
||||
watchInterface, err := lw.Watch(metav1.ListOptions{ResourceVersion: currResourceVersion})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
evt, err := watchtools.UntilWithoutRetry(ctx, watchInterface, remainingConditions...)
|
||||
if err == watchtools.ErrWatchClosed {
|
||||
// present a consistent error interface to callers
|
||||
err = wait.ErrWaitTimeout
|
||||
}
|
||||
return evt, err
|
||||
}
|
||||
|
||||
2
tools/cache/shared_informer.go
vendored
2
tools/cache/shared_informer.go
vendored
@@ -86,7 +86,7 @@ func NewSharedIndexInformer(lw ListerWatcher, objType runtime.Object, defaultEve
|
||||
resyncCheckPeriod: defaultEventHandlerResyncPeriod,
|
||||
defaultEventHandlerResyncPeriod: defaultEventHandlerResyncPeriod,
|
||||
cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", objType)),
|
||||
clock: realClock,
|
||||
clock: realClock,
|
||||
}
|
||||
return sharedIndexInformer
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
package api
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
func init() {
|
||||
sDec, _ := base64.StdEncoding.DecodeString("REDACTED+")
|
||||
redactedBytes = []byte(string(sDec))
|
||||
sDec, _ = base64.StdEncoding.DecodeString("DATA+OMITTED")
|
||||
dataOmittedBytes = []byte(string(sDec))
|
||||
}
|
||||
|
||||
// IsConfigEmpty returns true if the config is empty.
|
||||
@@ -79,7 +81,10 @@ func MinifyConfig(config *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var redactedBytes []byte
|
||||
var (
|
||||
redactedBytes []byte
|
||||
dataOmittedBytes []byte
|
||||
)
|
||||
|
||||
// Flatten redacts raw data entries from the config object for a human-readable view.
|
||||
func ShortenConfig(config *Config) {
|
||||
@@ -97,7 +102,7 @@ func ShortenConfig(config *Config) {
|
||||
}
|
||||
for key, cluster := range config.Clusters {
|
||||
if len(cluster.CertificateAuthorityData) > 0 {
|
||||
cluster.CertificateAuthorityData = redactedBytes
|
||||
cluster.CertificateAuthorityData = dataOmittedBytes
|
||||
}
|
||||
config.Clusters[key] = cluster
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func Example_minifyAndShorten() {
|
||||
// clusters:
|
||||
// cow-cluster:
|
||||
// LocationOfOrigin: ""
|
||||
// certificate-authority-data: REDACTED
|
||||
// certificate-authority-data: DATA+OMITTED
|
||||
// server: http://cow.org:8080
|
||||
// contexts:
|
||||
// federal-context:
|
||||
@@ -276,14 +276,15 @@ func TestShortenSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
redacted := string(redactedBytes)
|
||||
dataOmitted := string(dataOmittedBytes)
|
||||
if len(mutatingConfig.Clusters) != 2 {
|
||||
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
|
||||
}
|
||||
if !reflect.DeepEqual(startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster]) {
|
||||
t.Errorf("expected %v, got %v", startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster])
|
||||
}
|
||||
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != redacted {
|
||||
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
|
||||
if string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData) != dataOmitted {
|
||||
t.Errorf("expected %v, got %v", dataOmitted, string(mutatingConfig.Clusters[changingCluster].CertificateAuthorityData))
|
||||
}
|
||||
|
||||
if len(mutatingConfig.AuthInfos) != 2 {
|
||||
|
||||
@@ -15,4 +15,5 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
package v1
|
||||
|
||||
@@ -175,10 +175,6 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
// only try to read the auth information if we are secure
|
||||
if restclient.IsConfigTransportTLS(*clientConfig) {
|
||||
var err error
|
||||
|
||||
// mergo is a first write wins for map value and a last writing wins for interface values
|
||||
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
|
||||
// Our mergo.Merge version is older than this change.
|
||||
var persister restclient.AuthProviderConfigPersister
|
||||
if config.configAccess != nil {
|
||||
authInfoName, _ := config.getAuthInfoName()
|
||||
@@ -188,13 +184,13 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergo.Merge(clientConfig, userAuthPartialConfig)
|
||||
mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
|
||||
|
||||
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergo.Merge(clientConfig, serverAuthPartialConfig)
|
||||
mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
|
||||
}
|
||||
|
||||
return clientConfig, nil
|
||||
@@ -214,7 +210,7 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo,
|
||||
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
||||
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
||||
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
||||
mergo.Merge(mergedConfig, configClientConfig)
|
||||
mergo.MergeWithOverwrite(mergedConfig, configClientConfig)
|
||||
|
||||
return mergedConfig, nil
|
||||
}
|
||||
@@ -279,8 +275,8 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI
|
||||
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
||||
previouslyMergedConfig := mergedConfig
|
||||
mergedConfig = &restclient.Config{}
|
||||
mergo.Merge(mergedConfig, promptedConfig)
|
||||
mergo.Merge(mergedConfig, previouslyMergedConfig)
|
||||
mergo.MergeWithOverwrite(mergedConfig, promptedConfig)
|
||||
mergo.MergeWithOverwrite(mergedConfig, previouslyMergedConfig)
|
||||
config.promptedCredentials.username = mergedConfig.Username
|
||||
config.promptedCredentials.password = mergedConfig.Password
|
||||
}
|
||||
@@ -423,11 +419,11 @@ func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
||||
|
||||
mergedContext := clientcmdapi.NewContext()
|
||||
if configContext, exists := contexts[contextName]; exists {
|
||||
mergo.Merge(mergedContext, configContext)
|
||||
mergo.MergeWithOverwrite(mergedContext, configContext)
|
||||
} else if required {
|
||||
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
||||
}
|
||||
mergo.Merge(mergedContext, config.overrides.Context)
|
||||
mergo.MergeWithOverwrite(mergedContext, config.overrides.Context)
|
||||
|
||||
return *mergedContext, nil
|
||||
}
|
||||
@@ -439,11 +435,11 @@ func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
||||
|
||||
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
||||
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
||||
mergo.Merge(mergedAuthInfo, configAuthInfo)
|
||||
mergo.MergeWithOverwrite(mergedAuthInfo, configAuthInfo)
|
||||
} else if required {
|
||||
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
||||
}
|
||||
mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo)
|
||||
mergo.MergeWithOverwrite(mergedAuthInfo, config.overrides.AuthInfo)
|
||||
|
||||
return *mergedAuthInfo, nil
|
||||
}
|
||||
@@ -454,13 +450,13 @@ func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
||||
clusterInfoName, required := config.getClusterName()
|
||||
|
||||
mergedClusterInfo := clientcmdapi.NewCluster()
|
||||
mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults)
|
||||
mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterDefaults)
|
||||
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
||||
mergo.Merge(mergedClusterInfo, configClusterInfo)
|
||||
mergo.MergeWithOverwrite(mergedClusterInfo, configClusterInfo)
|
||||
} else if required {
|
||||
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
||||
}
|
||||
mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo)
|
||||
mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterInfo)
|
||||
// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
||||
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
|
||||
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
|
||||
|
||||
@@ -28,21 +28,83 @@ import (
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func TestOldMergoLib(t *testing.T) {
|
||||
type T struct {
|
||||
X string
|
||||
func TestMergoSemantics(t *testing.T) {
|
||||
type U struct {
|
||||
A string
|
||||
B int64
|
||||
}
|
||||
dst := T{X: "one"}
|
||||
src := T{X: "two"}
|
||||
mergo.Merge(&dst, &src)
|
||||
if dst.X != "two" {
|
||||
// mergo.Merge changed in an incompatible way with
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// We have to stay with the old version which still does eager
|
||||
// copying from src to dst in structs.
|
||||
t.Errorf("mergo.Merge library found with incompatible, new behavior")
|
||||
type T struct {
|
||||
S []string
|
||||
X string
|
||||
Y int64
|
||||
U U
|
||||
}
|
||||
var testDataStruct = []struct {
|
||||
dst T
|
||||
src T
|
||||
expected T
|
||||
}{
|
||||
{
|
||||
dst: T{X: "one"},
|
||||
src: T{X: "two"},
|
||||
expected: T{X: "two"},
|
||||
},
|
||||
{
|
||||
dst: T{X: "one", Y: 5, U: U{A: "four", B: 6}},
|
||||
src: T{X: "two", U: U{A: "three", B: 4}},
|
||||
expected: T{X: "two", Y: 5, U: U{A: "three", B: 4}},
|
||||
},
|
||||
{
|
||||
dst: T{S: []string{"test3", "test4", "test5"}},
|
||||
src: T{S: []string{"test1", "test2", "test3"}},
|
||||
expected: T{S: []string{"test1", "test2", "test3"}},
|
||||
},
|
||||
}
|
||||
for _, data := range testDataStruct {
|
||||
err := mergo.MergeWithOverwrite(&data.dst, &data.src)
|
||||
if err != nil {
|
||||
t.Errorf("error while merging: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(data.dst, data.expected) {
|
||||
// The mergo library has previously changed in a an incompatible way.
|
||||
// example:
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// This test verifies that the semantics of the merge are what we expect.
|
||||
// If they are not, the mergo library may have been updated and broken
|
||||
// unexpectedly.
|
||||
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
||||
}
|
||||
}
|
||||
|
||||
var testDataMap = []struct {
|
||||
dst map[string]int
|
||||
src map[string]int
|
||||
expected map[string]int
|
||||
}{
|
||||
{
|
||||
dst: map[string]int{"rsc": 6543, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
|
||||
src: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912},
|
||||
expected: map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912, "prt": 22},
|
||||
},
|
||||
}
|
||||
for _, data := range testDataMap {
|
||||
err := mergo.MergeWithOverwrite(&data.dst, &data.src)
|
||||
if err != nil {
|
||||
t.Errorf("error while merging: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(data.dst, data.expected) {
|
||||
// The mergo library has previously changed in a an incompatible way.
|
||||
// example:
|
||||
//
|
||||
// https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a
|
||||
//
|
||||
// This test verifies that the semantics of the merge are what we expect.
|
||||
// If they are not, the mergo library may have been updated and broken
|
||||
// unexpectedly.
|
||||
t.Errorf("mergo.MergeWithOverwrite did not provide expected output: %+v doesn't match %+v", data.dst, data.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,9 +194,17 @@ func TestModifyContext(t *testing.T) {
|
||||
"clean": true,
|
||||
}
|
||||
|
||||
tempPath, err := ioutil.TempFile("", "testclientcmd-")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.Remove(tempPath.Name())
|
||||
|
||||
pathOptions := NewDefaultPathOptions()
|
||||
config := createValidTestConfig()
|
||||
|
||||
pathOptions.GlobalFile = tempPath.Name()
|
||||
|
||||
// define new context and assign it - our path options config
|
||||
config.Contexts["updated"] = &clientcmdapi.Context{
|
||||
Cluster: "updated",
|
||||
@@ -173,7 +243,7 @@ func TestCertificateData(t *testing.T) {
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
Server: "https://localhost:8443",
|
||||
Server: "https://localhost:8443",
|
||||
CertificateAuthorityData: caData,
|
||||
}
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
@@ -566,3 +636,46 @@ func TestNamespaceOverride(t *testing.T) {
|
||||
|
||||
matchStringArg("foo", ns, t)
|
||||
}
|
||||
|
||||
func TestAuthConfigMerge(t *testing.T) {
|
||||
content := `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://localhost:8080
|
||||
name: foo-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: foo-cluster
|
||||
user: foo-user
|
||||
namespace: bar
|
||||
name: foo-context
|
||||
current-context: foo-context
|
||||
kind: Config
|
||||
users:
|
||||
- name: foo-user
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1alpha1
|
||||
args:
|
||||
- arg-1
|
||||
- arg-2
|
||||
command: foo-command
|
||||
`
|
||||
tmpfile, err := ioutil.TempFile("", "kubeconfig")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
config, err := BuildConfigFromFlags("", tmpfile.Name())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(config.ExecProvider.Args, []string{"arg-1", "arg-2"}) {
|
||||
t.Errorf("Got args %v when they should be %v\n", config.ExecProvider.Args, []string{"arg-1", "arg-2"})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
mapConfig := clientcmdapi.NewConfig()
|
||||
|
||||
for _, kubeconfig := range kubeconfigs {
|
||||
mergo.Merge(mapConfig, kubeconfig)
|
||||
mergo.MergeWithOverwrite(mapConfig, kubeconfig)
|
||||
}
|
||||
|
||||
// merge all of the struct values in the reverse order so that priority is given correctly
|
||||
@@ -219,14 +219,14 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
nonMapConfig := clientcmdapi.NewConfig()
|
||||
for i := len(kubeconfigs) - 1; i >= 0; i-- {
|
||||
kubeconfig := kubeconfigs[i]
|
||||
mergo.Merge(nonMapConfig, kubeconfig)
|
||||
mergo.MergeWithOverwrite(nonMapConfig, kubeconfig)
|
||||
}
|
||||
|
||||
// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
|
||||
// get the values we expect.
|
||||
config := clientcmdapi.NewConfig()
|
||||
mergo.Merge(config, mapConfig)
|
||||
mergo.Merge(config, nonMapConfig)
|
||||
mergo.MergeWithOverwrite(config, mapConfig)
|
||||
mergo.MergeWithOverwrite(config, nonMapConfig)
|
||||
|
||||
if rules.ResolvePaths() {
|
||||
if err := ResolveLocalPaths(config); err != nil {
|
||||
|
||||
@@ -80,7 +80,7 @@ func (cml *ConfigMapLock) Create(ler LeaderElectionRecord) error {
|
||||
// Update will update an existing annotation on a given resource.
|
||||
func (cml *ConfigMapLock) Update(ler LeaderElectionRecord) error {
|
||||
if cml.cm == nil {
|
||||
return errors.New("endpoint not initialized, call get or create first")
|
||||
return errors.New("configmap not initialized, call get or create first")
|
||||
}
|
||||
recordBytes, err := json.Marshal(ler)
|
||||
if err != nil {
|
||||
|
||||
@@ -39,8 +39,9 @@ const PortForwardProtocolV1Name = "portforward.k8s.io"
|
||||
// PortForwarder knows how to listen for local connections and forward them to
|
||||
// a remote pod via an upgraded HTTP request.
|
||||
type PortForwarder struct {
|
||||
ports []ForwardedPort
|
||||
stopChan <-chan struct{}
|
||||
addresses []listenAddress
|
||||
ports []ForwardedPort
|
||||
stopChan <-chan struct{}
|
||||
|
||||
dialer httpstream.Dialer
|
||||
streamConn httpstream.Connection
|
||||
@@ -110,8 +111,52 @@ func parsePorts(ports []string) ([]ForwardedPort, error) {
|
||||
return forwards, nil
|
||||
}
|
||||
|
||||
// New creates a new PortForwarder.
|
||||
type listenAddress struct {
|
||||
address string
|
||||
protocol string
|
||||
failureMode string
|
||||
}
|
||||
|
||||
func parseAddresses(addressesToParse []string) ([]listenAddress, error) {
|
||||
var addresses []listenAddress
|
||||
parsed := make(map[string]listenAddress)
|
||||
for _, address := range addressesToParse {
|
||||
if address == "localhost" {
|
||||
ip := listenAddress{address: "127.0.0.1", protocol: "tcp4", failureMode: "all"}
|
||||
parsed[ip.address] = ip
|
||||
ip = listenAddress{address: "::1", protocol: "tcp6", failureMode: "all"}
|
||||
parsed[ip.address] = ip
|
||||
} else if net.ParseIP(address).To4() != nil {
|
||||
parsed[address] = listenAddress{address: address, protocol: "tcp4", failureMode: "any"}
|
||||
} else if net.ParseIP(address) != nil {
|
||||
parsed[address] = listenAddress{address: address, protocol: "tcp6", failureMode: "any"}
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s is not a valid IP", address)
|
||||
}
|
||||
}
|
||||
addresses = make([]listenAddress, len(parsed))
|
||||
id := 0
|
||||
for _, v := range parsed {
|
||||
addresses[id] = v
|
||||
id++
|
||||
}
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// New creates a new PortForwarder with localhost listen addresses.
|
||||
func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
return NewOnAddresses(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut)
|
||||
}
|
||||
|
||||
// NewOnAddresses creates a new PortForwarder with custom listen addresses.
|
||||
func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
if len(addresses) == 0 {
|
||||
return nil, errors.New("You must specify at least 1 address")
|
||||
}
|
||||
parsedAddresses, err := parseAddresses(addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
return nil, errors.New("You must specify at least 1 port")
|
||||
}
|
||||
@@ -120,12 +165,13 @@ func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, rea
|
||||
return nil, err
|
||||
}
|
||||
return &PortForwarder{
|
||||
dialer: dialer,
|
||||
ports: parsedPorts,
|
||||
stopChan: stopChan,
|
||||
Ready: readyChan,
|
||||
out: out,
|
||||
errOut: errOut,
|
||||
dialer: dialer,
|
||||
addresses: parsedAddresses,
|
||||
ports: parsedPorts,
|
||||
stopChan: stopChan,
|
||||
Ready: readyChan,
|
||||
out: out,
|
||||
errOut: errOut,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -181,13 +227,26 @@ func (pf *PortForwarder) forward() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnPort delegates tcp4 and tcp6 listener creation and waits for connections on both of these addresses.
|
||||
// If both listener creation fail, an error is raised.
|
||||
// listenOnPort delegates listener creation and waits for connections on requested bind addresses.
|
||||
// An error is raised based on address groups (default and localhost) and their failure modes
|
||||
func (pf *PortForwarder) listenOnPort(port *ForwardedPort) error {
|
||||
errTcp4 := pf.listenOnPortAndAddress(port, "tcp4", "127.0.0.1")
|
||||
errTcp6 := pf.listenOnPortAndAddress(port, "tcp6", "::1")
|
||||
if errTcp4 != nil && errTcp6 != nil {
|
||||
return fmt.Errorf("All listeners failed to create with the following errors: %s, %s", errTcp4, errTcp6)
|
||||
var errors []error
|
||||
failCounters := make(map[string]int, 2)
|
||||
successCounters := make(map[string]int, 2)
|
||||
for _, addr := range pf.addresses {
|
||||
err := pf.listenOnPortAndAddress(port, addr.protocol, addr.address)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
failCounters[addr.failureMode]++
|
||||
} else {
|
||||
successCounters[addr.failureMode]++
|
||||
}
|
||||
}
|
||||
if successCounters["all"] == 0 && failCounters["all"] > 0 {
|
||||
return fmt.Errorf("%s: %v", "Listeners failed to create with the following errors", errors)
|
||||
}
|
||||
if failCounters["any"] > 0 {
|
||||
return fmt.Errorf("%s: %v", "Listeners failed to create with the following errors", errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -216,6 +275,7 @@ func (pf *PortForwarder) getListener(protocol string, hostname string, port *For
|
||||
localPortUInt, err := strconv.ParseUint(localPort, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(pf.out, "Failed to forward from %s:%d -> %d\n", hostname, localPortUInt, port.Remote)
|
||||
return nil, fmt.Errorf("Error parsing local port: %s from %s (%s)", err, listenerAddress, host)
|
||||
}
|
||||
port.Local = uint16(localPortUInt)
|
||||
@@ -340,3 +400,20 @@ func (pf *PortForwarder) Close() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPorts will return the ports that were forwarded; this can be used to
|
||||
// retrieve the locally-bound port in cases where the input was port 0. This
|
||||
// function will signal an error if the Ready channel is nil or if the
|
||||
// listeners are not ready yet; this function will succeed after the Ready
|
||||
// channel has been closed.
|
||||
func (pf *PortForwarder) GetPorts() ([]ForwardedPort, error) {
|
||||
if pf.Ready == nil {
|
||||
return nil, fmt.Errorf("no Ready channel provided")
|
||||
}
|
||||
select {
|
||||
case <-pf.Ready:
|
||||
return pf.ports, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("listeners not ready")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -40,23 +41,52 @@ func (d *fakeDialer) Dial(protocols ...string) (httpstream.Connection, string, e
|
||||
|
||||
func TestParsePortsAndNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []string
|
||||
expected []ForwardedPort
|
||||
expectParseError bool
|
||||
expectNewError bool
|
||||
input []string
|
||||
addresses []string
|
||||
expectedPorts []ForwardedPort
|
||||
expectedAddresses []listenAddress
|
||||
expectPortParseError bool
|
||||
expectAddressParseError bool
|
||||
expectNewError bool
|
||||
}{
|
||||
{input: []string{}, expectNewError: true},
|
||||
{input: []string{"a"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{":a"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"-1"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"65536"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"0"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"0:0"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"a:5000"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"5000:a"}, expectParseError: true, expectNewError: true},
|
||||
{input: []string{"a"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{":a"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"-1"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"65536"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"0"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"0:0"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"a:5000"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"5000:a"}, expectPortParseError: true, expectAddressParseError: false, expectNewError: true},
|
||||
{input: []string{"5000:5000"}, addresses: []string{"127.0.0.257"}, expectPortParseError: false, expectAddressParseError: true, expectNewError: true},
|
||||
{input: []string{"5000:5000"}, addresses: []string{"::g"}, expectPortParseError: false, expectAddressParseError: true, expectNewError: true},
|
||||
{input: []string{"5000:5000"}, addresses: []string{"domain.invalid"}, expectPortParseError: false, expectAddressParseError: true, expectNewError: true},
|
||||
{
|
||||
input: []string{"5000", "5000:5000", "8888:5000", "5000:8888", ":5000", "0:5000"},
|
||||
expected: []ForwardedPort{
|
||||
input: []string{"5000:5000"},
|
||||
addresses: []string{"localhost"},
|
||||
expectedPorts: []ForwardedPort{
|
||||
{5000, 5000},
|
||||
},
|
||||
expectedAddresses: []listenAddress{
|
||||
{protocol: "tcp4", address: "127.0.0.1", failureMode: "all"},
|
||||
{protocol: "tcp6", address: "::1", failureMode: "all"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []string{"5000:5000"},
|
||||
addresses: []string{"localhost", "127.0.0.1"},
|
||||
expectedPorts: []ForwardedPort{
|
||||
{5000, 5000},
|
||||
},
|
||||
expectedAddresses: []listenAddress{
|
||||
{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
|
||||
{protocol: "tcp6", address: "::1", failureMode: "all"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []string{"5000", "5000:5000", "8888:5000", "5000:8888", ":5000", "0:5000"},
|
||||
addresses: []string{"127.0.0.1", "::1"},
|
||||
expectedPorts: []ForwardedPort{
|
||||
{5000, 5000},
|
||||
{5000, 5000},
|
||||
{8888, 5000},
|
||||
@@ -64,34 +94,63 @@ func TestParsePortsAndNew(t *testing.T) {
|
||||
{0, 5000},
|
||||
{0, 5000},
|
||||
},
|
||||
expectedAddresses: []listenAddress{
|
||||
{protocol: "tcp4", address: "127.0.0.1", failureMode: "any"},
|
||||
{protocol: "tcp6", address: "::1", failureMode: "any"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
parsed, err := parsePorts(test.input)
|
||||
parsedPorts, err := parsePorts(test.input)
|
||||
haveError := err != nil
|
||||
if e, a := test.expectParseError, haveError; e != a {
|
||||
if e, a := test.expectPortParseError, haveError; e != a {
|
||||
t.Fatalf("%d: parsePorts: error expected=%t, got %t: %s", i, e, a, err)
|
||||
}
|
||||
|
||||
// default to localhost
|
||||
if len(test.addresses) == 0 && len(test.expectedAddresses) == 0 {
|
||||
test.addresses = []string{"localhost"}
|
||||
test.expectedAddresses = []listenAddress{{protocol: "tcp4", address: "127.0.0.1"}, {protocol: "tcp6", address: "::1"}}
|
||||
}
|
||||
// assert address parser
|
||||
parsedAddresses, err := parseAddresses(test.addresses)
|
||||
haveError = err != nil
|
||||
if e, a := test.expectAddressParseError, haveError; e != a {
|
||||
t.Fatalf("%d: parseAddresses: error expected=%t, got %t: %s", i, e, a, err)
|
||||
}
|
||||
|
||||
dialer := &fakeDialer{}
|
||||
expectedStopChan := make(chan struct{})
|
||||
readyChan := make(chan struct{})
|
||||
pf, err := New(dialer, test.input, expectedStopChan, readyChan, os.Stdout, os.Stderr)
|
||||
|
||||
var pf *PortForwarder
|
||||
if len(test.addresses) > 0 {
|
||||
pf, err = NewOnAddresses(dialer, test.addresses, test.input, expectedStopChan, readyChan, os.Stdout, os.Stderr)
|
||||
} else {
|
||||
pf, err = New(dialer, test.input, expectedStopChan, readyChan, os.Stdout, os.Stderr)
|
||||
}
|
||||
haveError = err != nil
|
||||
if e, a := test.expectNewError, haveError; e != a {
|
||||
t.Fatalf("%d: New: error expected=%t, got %t: %s", i, e, a, err)
|
||||
}
|
||||
|
||||
if test.expectParseError || test.expectNewError {
|
||||
if test.expectPortParseError || test.expectAddressParseError || test.expectNewError {
|
||||
continue
|
||||
}
|
||||
|
||||
for pi, expectedPort := range test.expected {
|
||||
if e, a := expectedPort.Local, parsed[pi].Local; e != a {
|
||||
sort.Slice(test.expectedAddresses, func(i, j int) bool { return test.expectedAddresses[i].address < test.expectedAddresses[j].address })
|
||||
sort.Slice(parsedAddresses, func(i, j int) bool { return parsedAddresses[i].address < parsedAddresses[j].address })
|
||||
|
||||
if !reflect.DeepEqual(test.expectedAddresses, parsedAddresses) {
|
||||
t.Fatalf("%d: expectedAddresses: %v, got: %v", i, test.expectedAddresses, parsedAddresses)
|
||||
}
|
||||
|
||||
for pi, expectedPort := range test.expectedPorts {
|
||||
if e, a := expectedPort.Local, parsedPorts[pi].Local; e != a {
|
||||
t.Fatalf("%d: local expected: %d, got: %d", i, e, a)
|
||||
}
|
||||
if e, a := expectedPort.Remote, parsed[pi].Remote; e != a {
|
||||
if e, a := expectedPort.Remote, parsedPorts[pi].Remote; e != a {
|
||||
t.Fatalf("%d: remote expected: %d, got: %d", i, e, a)
|
||||
}
|
||||
}
|
||||
@@ -99,8 +158,17 @@ func TestParsePortsAndNew(t *testing.T) {
|
||||
if dialer.dialed {
|
||||
t.Fatalf("%d: expected not dialed", i)
|
||||
}
|
||||
if e, a := test.expected, pf.ports; !reflect.DeepEqual(e, a) {
|
||||
t.Fatalf("%d: ports: expected %#v, got %#v", i, e, a)
|
||||
if _, portErr := pf.GetPorts(); portErr == nil {
|
||||
t.Fatalf("%d: GetPorts: error expected but got nil", i)
|
||||
}
|
||||
|
||||
// mock-signal the Ready channel
|
||||
close(readyChan)
|
||||
|
||||
if ports, portErr := pf.GetPorts(); portErr != nil {
|
||||
t.Fatalf("%d: GetPorts: unable to retrieve ports: %s", i, portErr)
|
||||
} else if !reflect.DeepEqual(test.expectedPorts, ports) {
|
||||
t.Fatalf("%d: ports: expected %#v, got %#v", i, test.expectedPorts, ports)
|
||||
}
|
||||
if e, a := expectedStopChan, pf.stopChan; e != a {
|
||||
t.Fatalf("%d: stopChan: expected %#v, got %#v", i, e, a)
|
||||
|
||||
@@ -84,11 +84,6 @@ func getSpamKey(event *v1.Event) string {
|
||||
// EventFilterFunc is a function that returns true if the event should be skipped
|
||||
type EventFilterFunc func(event *v1.Event) bool
|
||||
|
||||
// DefaultEventFilterFunc returns false for all incoming events
|
||||
func DefaultEventFilterFunc(event *v1.Event) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// EventSourceObjectSpamFilter is responsible for throttling
|
||||
// the amount of events a source and object can produce.
|
||||
type EventSourceObjectSpamFilter struct {
|
||||
|
||||
@@ -127,14 +127,6 @@ func validateEvent(messagePrefix string, actualEvent *v1.Event, expectedEvent *v
|
||||
return actualEvent, nil
|
||||
}
|
||||
|
||||
// TestDefaultEventFilterFunc ensures that no events are filtered
|
||||
func TestDefaultEventFilterFunc(t *testing.T) {
|
||||
event := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other"))
|
||||
if DefaultEventFilterFunc(&event) {
|
||||
t.Fatalf("DefaultEventFilterFunc should always return false")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEventAggregatorByReasonFunc ensures that two events are aggregated if they vary only by event.message
|
||||
func TestEventAggregatorByReasonFunc(t *testing.T) {
|
||||
event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other"))
|
||||
|
||||
114
tools/watch/informerwatcher.go
Normal file
114
tools/watch/informerwatcher.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 watch
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
func newTicketer() *ticketer {
|
||||
return &ticketer{
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
}
|
||||
|
||||
type ticketer struct {
|
||||
counter uint64
|
||||
|
||||
cond *sync.Cond
|
||||
current uint64
|
||||
}
|
||||
|
||||
func (t *ticketer) GetTicket() uint64 {
|
||||
// -1 to start from 0
|
||||
return atomic.AddUint64(&t.counter, 1) - 1
|
||||
}
|
||||
|
||||
func (t *ticketer) WaitForTicket(ticket uint64, f func()) {
|
||||
t.cond.L.Lock()
|
||||
defer t.cond.L.Unlock()
|
||||
for ticket != t.current {
|
||||
t.cond.Wait()
|
||||
}
|
||||
|
||||
f()
|
||||
|
||||
t.current++
|
||||
t.cond.Broadcast()
|
||||
}
|
||||
|
||||
// NewIndexerInformerWatcher will create an IndexerInformer and wrap it into watch.Interface
|
||||
// so you can use it anywhere where you'd have used a regular Watcher returned from Watch method.
|
||||
func NewIndexerInformerWatcher(lw cache.ListerWatcher, objType runtime.Object) (cache.Indexer, cache.Controller, watch.Interface) {
|
||||
ch := make(chan watch.Event)
|
||||
w := watch.NewProxyWatcher(ch)
|
||||
t := newTicketer()
|
||||
|
||||
indexer, informer := cache.NewIndexerInformer(lw, objType, 0, cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
go t.WaitForTicket(t.GetTicket(), func() {
|
||||
select {
|
||||
case ch <- watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: obj.(runtime.Object),
|
||||
}:
|
||||
case <-w.StopChan():
|
||||
}
|
||||
})
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
go t.WaitForTicket(t.GetTicket(), func() {
|
||||
select {
|
||||
case ch <- watch.Event{
|
||||
Type: watch.Modified,
|
||||
Object: new.(runtime.Object),
|
||||
}:
|
||||
case <-w.StopChan():
|
||||
}
|
||||
})
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
go t.WaitForTicket(t.GetTicket(), func() {
|
||||
staleObj, stale := obj.(cache.DeletedFinalStateUnknown)
|
||||
if stale {
|
||||
// We have no means of passing the additional information down using watch API based on watch.Event
|
||||
// but the caller can filter such objects by checking if metadata.deletionTimestamp is set
|
||||
obj = staleObj
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- watch.Event{
|
||||
Type: watch.Deleted,
|
||||
Object: obj.(runtime.Object),
|
||||
}:
|
||||
case <-w.StopChan():
|
||||
}
|
||||
})
|
||||
},
|
||||
}, cache.Indexers{})
|
||||
|
||||
go func() {
|
||||
informer.Run(w.StopChan())
|
||||
}()
|
||||
|
||||
return indexer, informer, w
|
||||
}
|
||||
236
tools/watch/informerwatcher_test.go
Normal file
236
tools/watch/informerwatcher_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
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 watch
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
testcore "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type byEventTypeAndName []watch.Event
|
||||
|
||||
func (a byEventTypeAndName) Len() int { return len(a) }
|
||||
func (a byEventTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byEventTypeAndName) Less(i, j int) bool {
|
||||
if a[i].Type < a[j].Type {
|
||||
return true
|
||||
}
|
||||
|
||||
if a[i].Type > a[j].Type {
|
||||
return false
|
||||
}
|
||||
|
||||
return a[i].Object.(*corev1.Secret).Name < a[j].Object.(*corev1.Secret).Name
|
||||
}
|
||||
|
||||
func TestTicketer(t *testing.T) {
|
||||
tg := newTicketer()
|
||||
|
||||
const numTickets = 100 // current golang limit for race detector is 8192 simultaneously alive goroutines
|
||||
var tickets []uint64
|
||||
for i := 0; i < numTickets; i++ {
|
||||
ticket := tg.GetTicket()
|
||||
tickets = append(tickets, ticket)
|
||||
|
||||
exp, got := uint64(i), ticket
|
||||
if got != exp {
|
||||
t.Fatalf("expected ticket %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
// shuffle tickets
|
||||
rand.Shuffle(len(tickets), func(i, j int) {
|
||||
tickets[i], tickets[j] = tickets[j], tickets[i]
|
||||
})
|
||||
|
||||
res := make(chan uint64, len(tickets))
|
||||
for _, ticket := range tickets {
|
||||
go func(ticket uint64) {
|
||||
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
|
||||
tg.WaitForTicket(ticket, func() {
|
||||
res <- ticket
|
||||
})
|
||||
}(ticket)
|
||||
}
|
||||
|
||||
for i := 0; i < numTickets; i++ {
|
||||
exp, got := uint64(i), <-res
|
||||
if got != exp {
|
||||
t.Fatalf("expected ticket %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInformerWatcher(t *testing.T) {
|
||||
// Make sure there are no 2 same types of events on a secret with the same name or that might be flaky.
|
||||
tt := []struct {
|
||||
name string
|
||||
objects []runtime.Object
|
||||
events []watch.Event
|
||||
}{
|
||||
{
|
||||
name: "basic test",
|
||||
objects: []runtime.Object{
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-1",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-1": "initial",
|
||||
},
|
||||
},
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-2",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-2": "initial",
|
||||
},
|
||||
},
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-3",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-3": "initial",
|
||||
},
|
||||
},
|
||||
},
|
||||
events: []watch.Event{
|
||||
{
|
||||
Type: watch.Added,
|
||||
Object: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-4",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-4": "initial",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: watch.Modified,
|
||||
Object: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-2",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"foo-2": "new",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: watch.Deleted,
|
||||
Object: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod-3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var expected []watch.Event
|
||||
for _, o := range tc.objects {
|
||||
expected = append(expected, watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: o.DeepCopyObject(),
|
||||
})
|
||||
}
|
||||
for _, e := range tc.events {
|
||||
expected = append(expected, *e.DeepCopy())
|
||||
}
|
||||
|
||||
fake := fakeclientset.NewSimpleClientset(tc.objects...)
|
||||
fakeWatch := watch.NewFakeWithChanSize(len(tc.events), false)
|
||||
fake.PrependWatchReactor("secrets", testcore.DefaultWatchReactor(fakeWatch, nil))
|
||||
|
||||
for _, e := range tc.events {
|
||||
fakeWatch.Action(e.Type, e.Object)
|
||||
}
|
||||
|
||||
lw := &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return fake.Core().Secrets("").List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return fake.Core().Secrets("").Watch(options)
|
||||
},
|
||||
}
|
||||
_, _, w := NewIndexerInformerWatcher(lw, &corev1.Secret{})
|
||||
|
||||
var result []watch.Event
|
||||
loop:
|
||||
for {
|
||||
var event watch.Event
|
||||
var ok bool
|
||||
select {
|
||||
case event, ok = <-w.ResultChan():
|
||||
if !ok {
|
||||
t.Errorf("Failed to read event: channel is already closed!")
|
||||
return
|
||||
}
|
||||
|
||||
result = append(result, *event.DeepCopy())
|
||||
case <-time.After(time.Second * 1):
|
||||
// All the events are buffered -> this means we are done
|
||||
// Also the one sec will make sure that we would detect RetryWatcher's incorrect behaviour after last event
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Informers don't guarantee event order so we need to sort these arrays to compare them
|
||||
sort.Sort(byEventTypeAndName(expected))
|
||||
sort.Sort(byEventTypeAndName(result))
|
||||
|
||||
if !reflect.DeepEqual(expected, result) {
|
||||
t.Error(spew.Errorf("\nexpected: %#v,\ngot: %#v,\ndiff: %s", expected, result, diff.ObjectReflectDiff(expected, result)))
|
||||
return
|
||||
}
|
||||
|
||||
// Fill in some data to test watch closing while there are some events to be read
|
||||
for _, e := range tc.events {
|
||||
fakeWatch.Action(e.Type, e.Object)
|
||||
}
|
||||
|
||||
// Stop before reading all the data to make sure the informer can deal with closed channel
|
||||
w.Stop()
|
||||
|
||||
// Wait a bit to see if the informer won't panic
|
||||
// TODO: Try to figure out a more reliable mechanism than time.Sleep (https://github.com/kubernetes/kubernetes/pull/50102/files#r184716591)
|
||||
time.Sleep(1 * time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,13 +19,22 @@ package watch
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// PreconditionFunc returns true if the condition has been reached, false if it has not been reached yet,
|
||||
// or an error if the condition failed or detected an error state.
|
||||
type PreconditionFunc func(store cache.Store) (bool, error)
|
||||
|
||||
// ConditionFunc returns true if the condition has been reached, false if it has not been reached yet,
|
||||
// or an error if the condition cannot be checked and should terminate. In general, it is better to define
|
||||
// level driven conditions over edge driven conditions (pod has ready=true, vs pod modified and ready changed
|
||||
@@ -86,6 +95,42 @@ func UntilWithoutRetry(ctx context.Context, watcher watch.Interface, conditions
|
||||
return lastEvent, nil
|
||||
}
|
||||
|
||||
// UntilWithSync creates an informer from lw, optionally checks precondition when the store is synced,
|
||||
// and watches the output until each provided condition succeeds, in a way that is identical
|
||||
// to function UntilWithoutRetry. (See above.)
|
||||
// UntilWithSync can deal with all errors like API timeout, lost connections and 'Resource version too old'.
|
||||
// It is the only function that can recover from 'Resource version too old', Until and UntilWithoutRetry will
|
||||
// just fail in that case. On the other hand it can't provide you with guarantees as strong as using simple
|
||||
// Watch method with Until. It can skip some intermediate events in case of watch function failing but it will
|
||||
// re-list to recover and you always get an event, if there has been a change, after recovery.
|
||||
// Also with the current implementation based on DeltaFIFO, order of the events you receive is guaranteed only for
|
||||
// particular object, not between more of them even it's the same resource.
|
||||
// The most frequent usage would be a command that needs to watch the "state of the world" and should't fail, like:
|
||||
// waiting for object reaching a state, "small" controllers, ...
|
||||
func UntilWithSync(ctx context.Context, lw cache.ListerWatcher, objType runtime.Object, precondition PreconditionFunc, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
indexer, informer, watcher := NewIndexerInformerWatcher(lw, objType)
|
||||
// Proxy watcher can be stopped multiple times so it's fine to use defer here to cover alternative branches and
|
||||
// let UntilWithoutRetry to stop it
|
||||
defer watcher.Stop()
|
||||
|
||||
if precondition != nil {
|
||||
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
|
||||
return nil, fmt.Errorf("UntilWithSync: unable to sync caches: %v", ctx.Err())
|
||||
}
|
||||
|
||||
done, err := precondition(indexer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if done {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return UntilWithoutRetry(ctx, watcher, conditions...)
|
||||
}
|
||||
|
||||
// ContextWithOptionalTimeout wraps context.WithTimeout and handles infinite timeouts expressed as 0 duration.
|
||||
func ContextWithOptionalTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
if timeout < 0 {
|
||||
@@ -100,3 +145,81 @@ func ContextWithOptionalTimeout(parent context.Context, timeout time.Duration) (
|
||||
|
||||
return context.WithTimeout(parent, timeout)
|
||||
}
|
||||
|
||||
// ListWatchUntil checks the provided conditions against the items returned by the list watcher, returning wait.ErrWaitTimeout
|
||||
// if timeout is exceeded without all conditions returning true, or an error if an error occurs.
|
||||
// TODO: check for watch expired error and retry watch from latest point? Same issue exists for Until.
|
||||
// TODO: remove when no longer used
|
||||
//
|
||||
// Deprecated: Use UntilWithSync instead.
|
||||
func ListWatchUntil(timeout time.Duration, lw cache.ListerWatcher, conditions ...ConditionFunc) (*watch.Event, error) {
|
||||
if len(conditions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list, err := lw.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialItems, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use the initial items as simulated "adds"
|
||||
var lastEvent *watch.Event
|
||||
currIndex := 0
|
||||
passedConditions := 0
|
||||
for _, condition := range conditions {
|
||||
// check the next condition against the previous event and short circuit waiting for the next watch
|
||||
if lastEvent != nil {
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ConditionSucceeded:
|
||||
for currIndex < len(initialItems) {
|
||||
lastEvent = &watch.Event{Type: watch.Added, Object: initialItems[currIndex]}
|
||||
currIndex++
|
||||
|
||||
done, err := condition(*lastEvent)
|
||||
if err != nil {
|
||||
return lastEvent, err
|
||||
}
|
||||
if done {
|
||||
passedConditions = passedConditions + 1
|
||||
break ConditionSucceeded
|
||||
}
|
||||
}
|
||||
}
|
||||
if passedConditions == len(conditions) {
|
||||
return lastEvent, nil
|
||||
}
|
||||
remainingConditions := conditions[passedConditions:]
|
||||
|
||||
metaObj, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currResourceVersion := metaObj.GetResourceVersion()
|
||||
|
||||
watchInterface, err := lw.Watch(metav1.ListOptions{ResourceVersion: currResourceVersion})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := ContextWithOptionalTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
evt, err := UntilWithoutRetry(ctx, watchInterface, remainingConditions...)
|
||||
if err == ErrWatchClosed {
|
||||
// present a consistent error interface to callers
|
||||
err = wait.ErrWaitTimeout
|
||||
}
|
||||
return evt, err
|
||||
}
|
||||
|
||||
@@ -19,14 +19,19 @@ package watch
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
fakeclient "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type fakePod struct {
|
||||
@@ -172,3 +177,127 @@ func TestUntilErrorCondition(t *testing.T) {
|
||||
t.Fatalf("expected %q in error string, got %q", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntilWithSync(t *testing.T) {
|
||||
// FIXME: test preconditions
|
||||
tt := []struct {
|
||||
name string
|
||||
lw *cache.ListWatch
|
||||
preconditionFunc PreconditionFunc
|
||||
conditionFunc ConditionFunc
|
||||
expectedErr error
|
||||
expectedEvent *watch.Event
|
||||
}{
|
||||
{
|
||||
name: "doesn't wait for sync with no precondition",
|
||||
lw: &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
select {}
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
select {}
|
||||
},
|
||||
},
|
||||
preconditionFunc: nil,
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
expectedErr: errors.New("timed out waiting for the condition"),
|
||||
expectedEvent: nil,
|
||||
},
|
||||
{
|
||||
name: "waits indefinitely with precondition if it can't sync",
|
||||
lw: &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
select {}
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
select {}
|
||||
},
|
||||
},
|
||||
preconditionFunc: func(store cache.Store) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
expectedErr: errors.New("UntilWithSync: unable to sync caches: context deadline exceeded"),
|
||||
expectedEvent: nil,
|
||||
},
|
||||
{
|
||||
name: "precondition can stop the loop",
|
||||
lw: func() *cache.ListWatch {
|
||||
fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}})
|
||||
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return fakeclient.CoreV1().Secrets("").List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return fakeclient.CoreV1().Secrets("").Watch(options)
|
||||
},
|
||||
}
|
||||
}(),
|
||||
preconditionFunc: func(store cache.Store) (bool, error) {
|
||||
_, exists, err := store.Get(&metav1.ObjectMeta{Namespace: "", Name: "first"})
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if exists {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
return true, errors.New("should never reach this")
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedEvent: nil,
|
||||
},
|
||||
{
|
||||
name: "precondition lets it proceed to regular condition",
|
||||
lw: func() *cache.ListWatch {
|
||||
fakeclient := fakeclient.NewSimpleClientset(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}})
|
||||
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return fakeclient.CoreV1().Secrets("").List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return fakeclient.CoreV1().Secrets("").Watch(options)
|
||||
},
|
||||
}
|
||||
}(),
|
||||
preconditionFunc: func(store cache.Store) (bool, error) {
|
||||
return false, nil
|
||||
},
|
||||
conditionFunc: func(e watch.Event) (bool, error) {
|
||||
if e.Type == watch.Added {
|
||||
return true, nil
|
||||
}
|
||||
panic("no other events are expected")
|
||||
},
|
||||
expectedErr: nil,
|
||||
expectedEvent: &watch.Event{Type: watch.Added, Object: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Informer waits for caches to sync by polling in 100ms intervals,
|
||||
// timeout needs to be reasonably higher
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
event, err := UntilWithSync(ctx, tc.lw, &corev1.Secret{}, tc.preconditionFunc, tc.conditionFunc)
|
||||
|
||||
if !reflect.DeepEqual(err, tc.expectedErr) {
|
||||
t.Errorf("expected error %#v, got %#v", tc.expectedErr, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(event, tc.expectedEvent) {
|
||||
t.Errorf("expected event %#v, got %#v", tc.expectedEvent, event)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func TestImpersonationRoundTripper(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
ImpersonateUserHeader: {"user"},
|
||||
ImpersonateUserHeader: {"user"},
|
||||
ImpersonateUserExtraHeaderPrefix + `Test.example.com%2fthing.thing`: {"A", "a"},
|
||||
},
|
||||
},
|
||||
@@ -219,11 +219,11 @@ func TestAuthProxyRoundTripper(t *testing.T) {
|
||||
username: "user",
|
||||
groups: []string{"groupA", "groupB"},
|
||||
extra: map[string][]string{
|
||||
"one": {"alpha", "bravo"},
|
||||
"one": {"alpha", "bravo"},
|
||||
"example.com/two%20three": {"charlie", "delta"},
|
||||
},
|
||||
expectedExtra: map[string][]string{
|
||||
"one": {"alpha", "bravo"},
|
||||
"one": {"alpha", "bravo"},
|
||||
"example.com%2ftwo%2520three": {"charlie", "delta"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,8 +18,10 @@ package cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
@@ -63,7 +65,7 @@ func NewPrivateKey() (*rsa.PrivateKey, error) {
|
||||
}
|
||||
|
||||
// NewSelfSignedCACert creates a CA certificate
|
||||
func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, error) {
|
||||
func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
|
||||
now := time.Now()
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: new(big.Int).SetInt64(0),
|
||||
@@ -75,7 +77,7 @@ func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, er
|
||||
NotAfter: now.Add(duration365d * 10).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
|
||||
@@ -86,8 +88,8 @@ func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, er
|
||||
}
|
||||
|
||||
// NewSignedCert creates a signed certificate using the given CA certificate and key
|
||||
func NewSignedCert(cfg Config, key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, error) {
|
||||
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -186,7 +188,7 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
caDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -171,11 +170,9 @@ func (s *fileStore) Current() (*tls.Certificate, error) {
|
||||
}
|
||||
|
||||
func loadFile(pairFile string) (*tls.Certificate, error) {
|
||||
certBlock, keyBlock, err := loadCertKeyBlocks(pairFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
|
||||
// LoadX509KeyPair knows how to parse combined cert and private key from
|
||||
// the same file.
|
||||
cert, err := tls.LoadX509KeyPair(pairFile, pairFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert data from %q into cert/key pair: %v", pairFile, err)
|
||||
}
|
||||
@@ -187,22 +184,6 @@ func loadFile(pairFile string) (*tls.Certificate, error) {
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func loadCertKeyBlocks(pairFile string) (cert *pem.Block, key *pem.Block, err error) {
|
||||
data, err := ioutil.ReadFile(pairFile)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not load cert/key pair from %q: %v", pairFile, err)
|
||||
}
|
||||
certBlock, rest := pem.Decode(data)
|
||||
if certBlock == nil {
|
||||
return nil, nil, fmt.Errorf("could not decode the first block from %q from expected PEM format", pairFile)
|
||||
}
|
||||
keyBlock, _ := pem.Decode(rest)
|
||||
if keyBlock == nil {
|
||||
return nil, nil, fmt.Errorf("could not decode the second block from %q from expected PEM format", pairFile)
|
||||
}
|
||||
return certBlock, keyBlock, nil
|
||||
}
|
||||
|
||||
func (s *fileStore) Update(certData, keyData []byte) (*tls.Certificate, error) {
|
||||
ts := time.Now().Format("2006-01-02-15-04-05")
|
||||
pemFilename := s.filename(ts)
|
||||
|
||||
@@ -17,12 +17,11 @@ limitations under the License.
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
func TestUpdateSymlinkExistingFileError(t *testing.T) {
|
||||
@@ -69,16 +68,16 @@ func TestUpdateSymlinkNewFileNotExist(t *testing.T) {
|
||||
pairNamePrefix: "kubelet",
|
||||
}
|
||||
if err := s.updateSymlink(oldPairFile); err != nil {
|
||||
t.Errorf("Got %v, wanted successful update of the symlink to point to %q", err, oldPairFile)
|
||||
t.Errorf("Got error %v, wanted successful update of the symlink to point to %q", err, oldPairFile)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(oldPairFile); err != nil {
|
||||
t.Errorf("Got %v, wanted file %q to be there.", oldPairFile, err)
|
||||
t.Errorf("Got error %v, wanted file %q to be there.", err, oldPairFile)
|
||||
}
|
||||
|
||||
currentPairFile := filepath.Join(dir, "kubelet-current.pem")
|
||||
if fi, err := os.Lstat(currentPairFile); err != nil {
|
||||
t.Errorf("Got %v, wanted file %q to be there", currentPairFile, err)
|
||||
t.Errorf("Got error %v, wanted file %q to be there", err, currentPairFile)
|
||||
} else if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
t.Errorf("Got %q not a symlink.", currentPairFile)
|
||||
}
|
||||
@@ -113,7 +112,7 @@ func TestUpdateSymlinkNoSymlink(t *testing.T) {
|
||||
}
|
||||
|
||||
if _, err := os.Stat(pairFile); err != nil {
|
||||
t.Errorf("Got error %v, wanted file %q to be there", pairFile, err)
|
||||
t.Errorf("Got error %v, wanted file %q to be there", err, pairFile)
|
||||
}
|
||||
currentPairFile := filepath.Join(dir, "kubelet-current.pem")
|
||||
if fi, err := os.Lstat(currentPairFile); err != nil {
|
||||
@@ -178,96 +177,6 @@ func TestUpdateSymlinkReplaceExistingSymlink(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadCertKeyBlocksNoFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "k8s-test-load-cert-key-blocks")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unable to clean up test directory %q: %v", dir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
pairFile := filepath.Join(dir, "kubelet-pair.pem")
|
||||
|
||||
if _, _, err := loadCertKeyBlocks(pairFile); err == nil {
|
||||
t.Errorf("Got no error, but expected %q not found.", pairFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadCertKeyBlocksEmptyFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "k8s-test-load-cert-key-blocks")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unable to clean up test directory %q: %v", dir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
pairFile := filepath.Join(dir, "kubelet-pair.pem")
|
||||
if err := ioutil.WriteFile(pairFile, nil, 0600); err != nil {
|
||||
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
|
||||
}
|
||||
|
||||
if _, _, err := loadCertKeyBlocks(pairFile); err == nil {
|
||||
t.Errorf("Got no error, but expected %q not found.", pairFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadCertKeyBlocksPartialFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "k8s-test-load-cert-key-blocks")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unable to clean up test directory %q: %v", dir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
pairFile := filepath.Join(dir, "kubelet-pair.pem")
|
||||
if err := ioutil.WriteFile(pairFile, storeCertData.certificatePEM, 0600); err != nil {
|
||||
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
|
||||
}
|
||||
|
||||
if _, _, err := loadCertKeyBlocks(pairFile); err == nil {
|
||||
t.Errorf("Got no error, but expected %q invalid.", pairFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadCertKeyBlocks(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "k8s-test-load-cert-key-blocks")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create the test directory %q: %v", dir, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Errorf("Unable to clean up test directory %q: %v", dir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
pairFile := filepath.Join(dir, "kubelet-pair.pem")
|
||||
data := append(storeCertData.certificatePEM, []byte("\n")...)
|
||||
data = append(data, storeCertData.keyPEM...)
|
||||
if err := ioutil.WriteFile(pairFile, data, 0600); err != nil {
|
||||
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
|
||||
}
|
||||
|
||||
certBlock, keyBlock, err := loadCertKeyBlocks(pairFile)
|
||||
if err != nil {
|
||||
t.Errorf("Got %v, but expected no error.", pairFile)
|
||||
}
|
||||
if certBlock.Type != cert.CertificateBlockType {
|
||||
t.Errorf("Got %q loaded from the pair file, expected a %q.", certBlock.Type, cert.CertificateBlockType)
|
||||
}
|
||||
if keyBlock.Type != cert.RSAPrivateKeyBlockType {
|
||||
t.Errorf("Got %q loaded from the pair file, expected a %q.", keyBlock.Type, cert.RSAPrivateKeyBlockType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "k8s-test-load-cert-key-blocks")
|
||||
if err != nil {
|
||||
@@ -280,21 +189,30 @@ func TestLoadFile(t *testing.T) {
|
||||
}()
|
||||
|
||||
pairFile := filepath.Join(dir, "kubelet-pair.pem")
|
||||
data := append(storeCertData.certificatePEM, []byte("\n")...)
|
||||
data = append(data, storeCertData.keyPEM...)
|
||||
if err := ioutil.WriteFile(pairFile, data, 0600); err != nil {
|
||||
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
|
||||
}
|
||||
|
||||
cert, err := loadFile(pairFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not load certificate from disk: %v", err)
|
||||
tests := []struct {
|
||||
desc string
|
||||
data []byte
|
||||
}{
|
||||
{desc: "cert and key", data: bytes.Join([][]byte{storeCertData.certificatePEM, storeCertData.keyPEM}, []byte("\n"))},
|
||||
{desc: "key and cert", data: bytes.Join([][]byte{storeCertData.keyPEM, storeCertData.certificatePEM}, []byte("\n"))},
|
||||
}
|
||||
if cert == nil {
|
||||
t.Fatalf("There was no error, but no certificate data was returned.")
|
||||
}
|
||||
if cert.Leaf == nil {
|
||||
t.Fatalf("Got an empty leaf, expected private data.")
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
if err := ioutil.WriteFile(pairFile, tt.data, 0600); err != nil {
|
||||
t.Fatalf("Unable to create the file %q: %v", pairFile, err)
|
||||
}
|
||||
cert, err := loadFile(pairFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not load certificate from disk: %v", err)
|
||||
}
|
||||
if cert == nil {
|
||||
t.Fatalf("There was no error, but no certificate data was returned.")
|
||||
}
|
||||
if cert.Leaf == nil {
|
||||
t.Fatalf("Got an empty leaf, expected private data.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
certificates "k8s.io/api/certificates/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -38,6 +39,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
@@ -121,7 +123,7 @@ func RequestCertificate(client certificatesclient.CertificateSigningRequestInter
|
||||
func WaitForCertificate(client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest, timeout time.Duration) (certData []byte, err error) {
|
||||
fieldSelector := fields.OneTermEqualSelector("metadata.name", req.Name).String()
|
||||
|
||||
event, err := cache.ListWatchUntil(
|
||||
event, err := watchtools.ListWatchUntil(
|
||||
timeout,
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package workqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
@@ -24,9 +25,22 @@ import (
|
||||
|
||||
type DoWorkPieceFunc func(piece int)
|
||||
|
||||
// Parallelize is a very simple framework that allow for parallelizing
|
||||
// Parallelize is a very simple framework that allows for parallelizing
|
||||
// N independent pieces of work.
|
||||
//
|
||||
// Deprecated: Use ParallelizeUntil instead.
|
||||
func Parallelize(workers, pieces int, doWorkPiece DoWorkPieceFunc) {
|
||||
ParallelizeUntil(nil, workers, pieces, doWorkPiece)
|
||||
}
|
||||
|
||||
// ParallelizeUntil is a framework that allows for parallelizing N
|
||||
// independent pieces of work until done or the context is canceled.
|
||||
func ParallelizeUntil(ctx context.Context, workers, pieces int, doWorkPiece DoWorkPieceFunc) {
|
||||
var stop <-chan struct{}
|
||||
if ctx != nil {
|
||||
stop = ctx.Done()
|
||||
}
|
||||
|
||||
toProcess := make(chan int, pieces)
|
||||
for i := 0; i < pieces; i++ {
|
||||
toProcess <- i
|
||||
@@ -44,7 +58,12 @@ func Parallelize(workers, pieces int, doWorkPiece DoWorkPieceFunc) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer wg.Done()
|
||||
for piece := range toProcess {
|
||||
doWorkPiece(piece)
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
doWorkPiece(piece)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ package workqueue
|
||||
type RateLimitingInterface interface {
|
||||
DelayingInterface
|
||||
|
||||
// AddRateLimited adds an item to the workqueue after the rate limiter says its ok
|
||||
// AddRateLimited adds an item to the workqueue after the rate limiter says it's ok
|
||||
AddRateLimited(item interface{})
|
||||
|
||||
// Forget indicates that an item is finished being retried. Doesn't matter whether its for perm failing
|
||||
// Forget indicates that an item is finished being retried. Doesn't matter whether it's for perm failing
|
||||
// or for success, we'll stop the rate limiter from tracking it. This only clears the `rateLimiter`, you
|
||||
// still have to call `Done` on the queue.
|
||||
Forget(item interface{})
|
||||
@@ -55,7 +55,7 @@ type rateLimitingType struct {
|
||||
rateLimiter RateLimiter
|
||||
}
|
||||
|
||||
// AddRateLimited AddAfter's the item based on the time when the rate limiter says its ok
|
||||
// AddRateLimited AddAfter's the item based on the time when the rate limiter says it's ok
|
||||
func (q *rateLimitingType) AddRateLimited(item interface{}) {
|
||||
q.DelayingInterface.AddAfter(item, q.rateLimiter.When(item))
|
||||
}
|
||||
|
||||
5
vendor/github.com/ghodss/yaml/.travis.yml
generated
vendored
5
vendor/github.com/ghodss/yaml/.travis.yml
generated
vendored
@@ -1,7 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.3
|
||||
- 1.4
|
||||
- "1.3"
|
||||
- "1.4"
|
||||
- "1.10"
|
||||
script:
|
||||
- go test
|
||||
- go build
|
||||
|
||||
17
vendor/github.com/ghodss/yaml/README.md
generated
vendored
17
vendor/github.com/ghodss/yaml/README.md
generated
vendored
@@ -4,13 +4,13 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
|
||||
A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
|
||||
|
||||
In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
|
||||
|
||||
## Compatibility
|
||||
|
||||
This package uses [go-yaml v2](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility).
|
||||
This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility).
|
||||
|
||||
## Caveats
|
||||
|
||||
@@ -44,6 +44,8 @@ import "github.com/ghodss/yaml"
|
||||
Usage is very similar to the JSON library:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@@ -51,8 +53,8 @@ import (
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string `json:"name"` // Affects YAML field names too.
|
||||
Age int `json:"name"`
|
||||
Name string `json:"name"` // Affects YAML field names too.
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -65,13 +67,13 @@ func main() {
|
||||
}
|
||||
fmt.Println(string(y))
|
||||
/* Output:
|
||||
name: John
|
||||
age: 30
|
||||
name: John
|
||||
*/
|
||||
|
||||
// Unmarshal the YAML back into a Person struct.
|
||||
var p2 Person
|
||||
err := yaml.Unmarshal(y, &p2)
|
||||
err = yaml.Unmarshal(y, &p2)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
@@ -86,11 +88,14 @@ func main() {
|
||||
`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
j := []byte(`{"name": "John", "age": 30}`)
|
||||
y, err := yaml.JSONToYAML(j)
|
||||
|
||||
6
vendor/github.com/ghodss/yaml/fields.go
generated
vendored
6
vendor/github.com/ghodss/yaml/fields.go
generated
vendored
@@ -45,7 +45,11 @@ func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.Te
|
||||
break
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
if v.CanSet() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
} else {
|
||||
v = reflect.New(v.Type().Elem())
|
||||
}
|
||||
}
|
||||
if v.Type().NumMethod() > 0 {
|
||||
if u, ok := v.Interface().(json.Unmarshaler); ok {
|
||||
|
||||
52
vendor/github.com/ghodss/yaml/yaml.go
generated
vendored
52
vendor/github.com/ghodss/yaml/yaml.go
generated
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
@@ -15,26 +16,30 @@ import (
|
||||
func Marshal(o interface{}) ([]byte, error) {
|
||||
j, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling into JSON: ", err)
|
||||
return nil, fmt.Errorf("error marshaling into JSON: %v", err)
|
||||
}
|
||||
|
||||
y, err := JSONToYAML(j)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting JSON to YAML: ", err)
|
||||
return nil, fmt.Errorf("error converting JSON to YAML: %v", err)
|
||||
}
|
||||
|
||||
return y, nil
|
||||
}
|
||||
|
||||
// Converts YAML to JSON then uses JSON to unmarshal into an object.
|
||||
func Unmarshal(y []byte, o interface{}) error {
|
||||
// JSONOpt is a decoding option for decoding from JSON format.
|
||||
type JSONOpt func(*json.Decoder) *json.Decoder
|
||||
|
||||
// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object,
|
||||
// optionally configuring the behavior of the JSON unmarshal.
|
||||
func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error {
|
||||
vo := reflect.ValueOf(o)
|
||||
j, err := yamlToJSON(y, &vo)
|
||||
j, err := yamlToJSON(y, &vo, yaml.Unmarshal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting YAML to JSON: %v", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(j, o)
|
||||
err = jsonUnmarshal(bytes.NewReader(j), o, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling JSON: %v", err)
|
||||
}
|
||||
@@ -42,13 +47,28 @@ func Unmarshal(y []byte, o interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// jsonUnmarshal unmarshals the JSON byte stream from the given reader into the
|
||||
// object, optionally applying decoder options prior to decoding. We are not
|
||||
// using json.Unmarshal directly as we want the chance to pass in non-default
|
||||
// options.
|
||||
func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error {
|
||||
d := json.NewDecoder(r)
|
||||
for _, opt := range opts {
|
||||
d = opt(d)
|
||||
}
|
||||
if err := d.Decode(&o); err != nil {
|
||||
return fmt.Errorf("while decoding JSON: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert JSON to YAML.
|
||||
func JSONToYAML(j []byte) ([]byte, error) {
|
||||
// Convert the JSON to an object.
|
||||
var jsonObj interface{}
|
||||
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
|
||||
// Go JSON library doesn't try to pick the right number type (int, float,
|
||||
// etc.) when unmarshling to interface{}, it just picks float64
|
||||
// etc.) when unmarshalling to interface{}, it just picks float64
|
||||
// universally. go-yaml does go through the effort of picking the right
|
||||
// number type, so we can preserve number type throughout this process.
|
||||
err := yaml.Unmarshal(j, &jsonObj)
|
||||
@@ -60,8 +80,8 @@ func JSONToYAML(j []byte) ([]byte, error) {
|
||||
return yaml.Marshal(jsonObj)
|
||||
}
|
||||
|
||||
// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through
|
||||
// this method should be a no-op.
|
||||
// YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML,
|
||||
// passing JSON through this method should be a no-op.
|
||||
//
|
||||
// Things YAML can do that are not supported by JSON:
|
||||
// * In YAML you can have binary and null keys in your maps. These are invalid
|
||||
@@ -70,14 +90,22 @@ func JSONToYAML(j []byte) ([]byte, error) {
|
||||
// use binary data with this library, encode the data as base64 as usual but do
|
||||
// not use the !!binary tag in your YAML. This will ensure the original base64
|
||||
// encoded data makes it all the way through to the JSON.
|
||||
//
|
||||
// For strict decoding of YAML, use YAMLToJSONStrict.
|
||||
func YAMLToJSON(y []byte) ([]byte, error) {
|
||||
return yamlToJSON(y, nil)
|
||||
return yamlToJSON(y, nil, yaml.Unmarshal)
|
||||
}
|
||||
|
||||
func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
|
||||
// YAMLToJSONStrict is like YAMLToJSON but enables strict YAML decoding,
|
||||
// returning an error on any duplicate field names.
|
||||
func YAMLToJSONStrict(y []byte) ([]byte, error) {
|
||||
return yamlToJSON(y, nil, yaml.UnmarshalStrict)
|
||||
}
|
||||
|
||||
func yamlToJSON(y []byte, jsonTarget *reflect.Value, yamlUnmarshal func([]byte, interface{}) error) ([]byte, error) {
|
||||
// Convert the YAML to an object.
|
||||
var yamlObj interface{}
|
||||
err := yaml.Unmarshal(y, &yamlObj)
|
||||
err := yamlUnmarshal(y, &yamlObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
14
vendor/github.com/ghodss/yaml/yaml_go110.go
generated
vendored
Normal file
14
vendor/github.com/ghodss/yaml/yaml_go110.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file contains changes that are only compatible with go 1.10 and onwards.
|
||||
|
||||
// +build go1.10
|
||||
|
||||
package yaml
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// DisallowUnknownFields configures the JSON decoder to error out if unknown
|
||||
// fields come along, instead of dropping them by default.
|
||||
func DisallowUnknownFields(d *json.Decoder) *json.Decoder {
|
||||
d.DisallowUnknownFields()
|
||||
return d
|
||||
}
|
||||
1
vendor/github.com/gogo/protobuf/AUTHORS
generated
vendored
1
vendor/github.com/gogo/protobuf/AUTHORS
generated
vendored
@@ -10,5 +10,6 @@
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Sendgrid, Inc
|
||||
Vastech SA (PTY) LTD
|
||||
Walter Schulze <awalterschulze@gmail.com>
|
||||
|
||||
4
vendor/github.com/gogo/protobuf/CONTRIBUTORS
generated
vendored
4
vendor/github.com/gogo/protobuf/CONTRIBUTORS
generated
vendored
@@ -1,4 +1,5 @@
|
||||
Anton Povarov <anton.povarov@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Clayton Coleman <ccoleman@redhat.com>
|
||||
Denis Smirnov <denis.smirnov.91@gmail.com>
|
||||
DongYun Kang <ceram1000@gmail.com>
|
||||
@@ -10,9 +11,12 @@ John Shahid <jvshahid@gmail.com>
|
||||
John Tuley <john@tuley.org>
|
||||
Laurent <laurent@adyoulike.com>
|
||||
Patrick Lee <patrick@dropbox.com>
|
||||
Roger Johansson <rogeralsing@gmail.com>
|
||||
Sam Nguyen <sam.nguyen@sendgrid.com>
|
||||
Sergio Arbeo <serabe@gmail.com>
|
||||
Stephen J Day <stephen.day@docker.com>
|
||||
Tamir Duberstein <tamird@gmail.com>
|
||||
Todd Eisenberger <teisenberger@dropbox.com>
|
||||
Tormod Erevik Lea <tormodlea@gmail.com>
|
||||
Vyacheslav Kim <kane@sendgrid.com>
|
||||
Walter Schulze <awalterschulze@gmail.com>
|
||||
|
||||
4
vendor/github.com/gogo/protobuf/proto/encode.go
generated
vendored
4
vendor/github.com/gogo/protobuf/proto/encode.go
generated
vendored
@@ -174,11 +174,11 @@ func sizeFixed32(x uint64) int {
|
||||
// This is the format used for the sint64 protocol buffer type.
|
||||
func (p *Buffer) EncodeZigzag64(x uint64) error {
|
||||
// use signed number to get arithmetic right shift.
|
||||
return p.EncodeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
return p.EncodeVarint((x << 1) ^ uint64((int64(x) >> 63)))
|
||||
}
|
||||
|
||||
func sizeZigzag64(x uint64) int {
|
||||
return sizeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
return sizeVarint((x << 1) ^ uint64((int64(x) >> 63)))
|
||||
}
|
||||
|
||||
// EncodeZigzag32 writes a zigzag-encoded 32-bit integer
|
||||
|
||||
1
vendor/github.com/gogo/protobuf/proto/lib.go
generated
vendored
1
vendor/github.com/gogo/protobuf/proto/lib.go
generated
vendored
@@ -73,7 +73,6 @@ for a protocol buffer variable v:
|
||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
The simplest way to describe this is to see an example.
|
||||
|
||||
3
vendor/github.com/gogo/protobuf/proto/properties.go
generated
vendored
3
vendor/github.com/gogo/protobuf/proto/properties.go
generated
vendored
@@ -193,6 +193,7 @@ type Properties struct {
|
||||
Default string // default value
|
||||
HasDefault bool // whether an explicit default was provided
|
||||
CustomType string
|
||||
CastType string
|
||||
StdTime bool
|
||||
StdDuration bool
|
||||
|
||||
@@ -341,6 +342,8 @@ func (p *Properties) Parse(s string) {
|
||||
p.OrigName = strings.Split(f, "=")[1]
|
||||
case strings.HasPrefix(f, "customtype="):
|
||||
p.CustomType = strings.Split(f, "=")[1]
|
||||
case strings.HasPrefix(f, "casttype="):
|
||||
p.CastType = strings.Split(f, "=")[1]
|
||||
case f == "stdtime":
|
||||
p.StdTime = true
|
||||
case f == "stdduration":
|
||||
|
||||
23
vendor/github.com/gogo/protobuf/proto/text.go
generated
vendored
23
vendor/github.com/gogo/protobuf/proto/text.go
generated
vendored
@@ -522,6 +522,17 @@ func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Propert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else if len(props.CastType) > 0 {
|
||||
if _, ok := v.Interface().(interface {
|
||||
String() string
|
||||
}); ok {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
_, err := fmt.Fprintf(w, "%d", v.Interface())
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if props.StdTime {
|
||||
t, ok := v.Interface().(time.Time)
|
||||
if !ok {
|
||||
@@ -531,9 +542,9 @@ func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Propert
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
props.StdTime = false
|
||||
err = tm.writeAny(w, reflect.ValueOf(tproto), props)
|
||||
props.StdTime = true
|
||||
propsCopy := *props // Make a copy so that this is goroutine-safe
|
||||
propsCopy.StdTime = false
|
||||
err = tm.writeAny(w, reflect.ValueOf(tproto), &propsCopy)
|
||||
return err
|
||||
} else if props.StdDuration {
|
||||
d, ok := v.Interface().(time.Duration)
|
||||
@@ -541,9 +552,9 @@ func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Propert
|
||||
return fmt.Errorf("stdtime is not time.Duration, but %T", v.Interface())
|
||||
}
|
||||
dproto := durationProto(d)
|
||||
props.StdDuration = false
|
||||
err := tm.writeAny(w, reflect.ValueOf(dproto), props)
|
||||
props.StdDuration = true
|
||||
propsCopy := *props // Make a copy so that this is goroutine-safe
|
||||
propsCopy.StdDuration = false
|
||||
err := tm.writeAny(w, reflect.ValueOf(dproto), &propsCopy)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/gogo/protobuf/proto/text_parser.go
generated
vendored
2
vendor/github.com/gogo/protobuf/proto/text_parser.go
generated
vendored
@@ -983,7 +983,7 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||
return p.readStruct(fv, terminator)
|
||||
case reflect.Uint32:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||
fv.SetUint(uint64(x))
|
||||
fv.SetUint(x)
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint64:
|
||||
|
||||
33
vendor/github.com/imdario/mergo/.gitignore
generated
vendored
Normal file
33
vendor/github.com/imdario/mergo/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
#### joe made this: http://goel.io/joe
|
||||
|
||||
#### go ####
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
#### vim ####
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
7
vendor/github.com/imdario/mergo/.travis.yml
generated
vendored
7
vendor/github.com/imdario/mergo/.travis.yml
generated
vendored
@@ -1,2 +1,7 @@
|
||||
language: go
|
||||
install: go get -t
|
||||
install:
|
||||
- go get -t
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
|
||||
|
||||
46
vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md
generated
vendored
Normal file
46
vendor/github.com/imdario/mergo/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
194
vendor/github.com/imdario/mergo/README.md
generated
vendored
194
vendor/github.com/imdario/mergo/README.md
generated
vendored
@@ -2,14 +2,72 @@
|
||||
|
||||
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||
|
||||
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region Marche.
|
||||
|
||||

|
||||
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
|
||||
|
||||
## Status
|
||||
|
||||
It is ready for production use. It works fine although it may use more of testing. Here some projects in the wild using Mergo:
|
||||
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
|
||||
|
||||
[![GoDoc][3]][4]
|
||||
[![GoCard][5]][6]
|
||||
[![Build Status][1]][2]
|
||||
[![Coverage Status][7]][8]
|
||||
[![Sourcegraph][9]][10]
|
||||
|
||||
[1]: https://travis-ci.org/imdario/mergo.png
|
||||
[2]: https://travis-ci.org/imdario/mergo
|
||||
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||
[4]: https://godoc.org/github.com/imdario/mergo
|
||||
[5]: https://goreportcard.com/badge/imdario/mergo
|
||||
[6]: https://goreportcard.com/report/github.com/imdario/mergo
|
||||
[7]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
|
||||
[8]: https://coveralls.io/github/imdario/mergo?branch=master
|
||||
[9]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
|
||||
[10]: https://sourcegraph.com/github.com/imdario/mergo?badge
|
||||
|
||||
### Latest release
|
||||
|
||||
[Release v0.3.4](https://github.com/imdario/mergo/releases/tag/v0.3.4).
|
||||
|
||||
### Important note
|
||||
|
||||
Please keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2) Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). An optional/variadic argument has been added, so it won't break existing code.
|
||||
|
||||
If you were using Mergo **before** April 6th 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause (I hope it won't!) in existing projects after the change (release 0.2.0).
|
||||
|
||||
### Donations
|
||||
|
||||
If Mergo is useful to you, consider buying me a coffee, a beer or making a monthly donation so I can keep building great free software. :heart_eyes:
|
||||
|
||||
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
[](https://beerpay.io/imdario/mergo)
|
||||
[](https://beerpay.io/imdario/mergo)
|
||||
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||
|
||||
### Mergo in the wild
|
||||
|
||||
- [moby/moby](https://github.com/moby/moby)
|
||||
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
|
||||
- [vmware/dispatch](https://github.com/vmware/dispatch)
|
||||
- [Shopify/themekit](https://github.com/Shopify/themekit)
|
||||
- [imdario/zas](https://github.com/imdario/zas)
|
||||
- [matcornic/hermes](https://github.com/matcornic/hermes)
|
||||
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
|
||||
- [kataras/iris](https://github.com/kataras/iris)
|
||||
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
|
||||
- [go-task/task](https://github.com/go-task/task)
|
||||
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
|
||||
- [ory/hydra](https://github.com/ory/hydra)
|
||||
- [sisatech/vcli](https://github.com/sisatech/vcli)
|
||||
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
|
||||
- [projectcalico/felix](https://github.com/projectcalico/felix)
|
||||
- [resin-os/balena](https://github.com/resin-os/balena)
|
||||
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
|
||||
- [Telefonica/govice](https://github.com/Telefonica/govice)
|
||||
- [supergiant/supergiant](supergiant/supergiant)
|
||||
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
|
||||
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
|
||||
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
|
||||
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
|
||||
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
|
||||
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
|
||||
@@ -17,12 +75,17 @@ It is ready for production use. It works fine although it may use more of testin
|
||||
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
|
||||
- [divshot/gitling](https://github.com/divshot/gitling)
|
||||
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
|
||||
|
||||
[![Build Status][1]][2]
|
||||
[](https://godoc.org/github.com/imdario/mergo)
|
||||
|
||||
[1]: https://travis-ci.org/imdario/mergo.png
|
||||
[2]: https://travis-ci.org/imdario/mergo
|
||||
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
|
||||
- [elwinar/rambler](https://github.com/elwinar/rambler)
|
||||
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
|
||||
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
|
||||
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
|
||||
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
|
||||
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
|
||||
- [thoas/picfit](https://github.com/thoas/picfit)
|
||||
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
||||
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
||||
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -35,25 +98,116 @@ It is ready for production use. It works fine although it may use more of testin
|
||||
|
||||
## Usage
|
||||
|
||||
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are not considered zero values](https://golang.org/ref/spec#The_zero_value) either. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||
|
||||
if err := mergo.Merge(&dst, src); err != nil {
|
||||
// ...
|
||||
}
|
||||
```go
|
||||
if err := mergo.Merge(&dst, src); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
|
||||
Also, you can merge overwriting values using the transformer `WithOverride`.
|
||||
|
||||
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||
// ...
|
||||
}
|
||||
```go
|
||||
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
|
||||
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
|
||||
|
||||
```go
|
||||
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
|
||||
|
||||
More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
|
||||
|
||||
### Nice example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
type Foo struct {
|
||||
A string
|
||||
B int64
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Foo{
|
||||
A: "one",
|
||||
B: 2,
|
||||
}
|
||||
dest := Foo{
|
||||
A: "two",
|
||||
}
|
||||
mergo.Merge(&dest, src)
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// {two 2}
|
||||
}
|
||||
```
|
||||
|
||||
Note: if test are failing due missing package, please execute:
|
||||
|
||||
go get gopkg.in/yaml.v1
|
||||
go get gopkg.in/yaml.v2
|
||||
|
||||
### Transformers
|
||||
|
||||
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timeTransfomer struct {
|
||||
}
|
||||
|
||||
func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||
if typ == reflect.TypeOf(time.Time{}) {
|
||||
return func(dst, src reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
isZero := dst.MethodByName("IsZero")
|
||||
result := isZero.Call([]reflect.Value{})
|
||||
if result[0].Bool() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
Time time.Time
|
||||
// ...
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Snapshot{time.Now()}
|
||||
dest := Snapshot{}
|
||||
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransfomer{}))
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Contact me
|
||||
|
||||
|
||||
54
vendor/github.com/imdario/mergo/map.go
generated
vendored
54
vendor/github.com/imdario/mergo/map.go
generated
vendored
@@ -31,7 +31,8 @@ func isExported(field reflect.StructField) bool {
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
|
||||
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||
overwrite := config.Overwrite
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
@@ -57,10 +58,17 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err
|
||||
}
|
||||
fieldName := field.Name
|
||||
fieldName = changeInitialCase(fieldName, unicode.ToLower)
|
||||
if v, ok := dstMap[fieldName]; !ok || isEmptyValue(reflect.ValueOf(v)) {
|
||||
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
|
||||
dstMap[fieldName] = src.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if dst.IsNil() {
|
||||
v := reflect.New(dst.Type().Elem())
|
||||
dst.Set(v)
|
||||
}
|
||||
dst = dst.Elem()
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
srcMap := src.Interface().(map[string]interface{})
|
||||
for key := range srcMap {
|
||||
@@ -85,21 +93,24 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err
|
||||
srcKind = reflect.Ptr
|
||||
}
|
||||
}
|
||||
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
if srcKind == dstKind {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1); err != nil {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if srcKind == reflect.Map {
|
||||
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if srcKind == reflect.Map {
|
||||
if err = deepMap(dstElement, srcElement, visited, depth+1); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
|
||||
}
|
||||
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,18 +128,35 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err
|
||||
// doesn't apply if dst is a map.
|
||||
// This is separated method from Merge because it is cleaner and it keeps sane
|
||||
// semantics: merging equal types, mapping different (restricted) types.
|
||||
func Map(dst, src interface{}) error {
|
||||
func Map(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return _map(dst, src, opts...)
|
||||
}
|
||||
|
||||
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
|
||||
// non-empty src attribute values.
|
||||
// Deprecated: Use Map(…) with WithOverride
|
||||
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return _map(dst, src, append(opts, WithOverride)...)
|
||||
}
|
||||
|
||||
func _map(dst, src interface{}, opts ...func(*Config)) error {
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
config := &Config{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
// To be friction-less, we redirect equal-type arguments
|
||||
// to deepMerge. Only because arguments can be anything.
|
||||
if vSrc.Kind() == vDst.Kind() {
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0)
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
switch vSrc.Kind() {
|
||||
case reflect.Struct:
|
||||
@@ -142,5 +170,5 @@ func Map(dst, src interface{}) error {
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0)
|
||||
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
|
||||
190
vendor/github.com/imdario/mergo/merge.go
generated
vendored
190
vendor/github.com/imdario/mergo/merge.go
generated
vendored
@@ -12,10 +12,34 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func hasExportedField(dst reflect.Value) (exported bool) {
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
field := dst.Type().Field(i)
|
||||
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
||||
exported = exported || hasExportedField(dst.Field(i))
|
||||
} else {
|
||||
exported = exported || len(field.PkgPath) == 0
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Overwrite bool
|
||||
AppendSlice bool
|
||||
Transformers Transformers
|
||||
}
|
||||
|
||||
type Transformers interface {
|
||||
Transformer(reflect.Type) func(dst, src reflect.Value) error
|
||||
}
|
||||
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
|
||||
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||
overwrite := config.Overwrite
|
||||
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
@@ -32,68 +56,190 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (e
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
|
||||
if config.Transformers != nil && !isEmptyValue(dst) {
|
||||
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
|
||||
err = fn(dst, src)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch dst.Kind() {
|
||||
case reflect.Struct:
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1); err != nil {
|
||||
return
|
||||
if hasExportedField(dst) {
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if dst.IsNil() && !src.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
for _, key := range src.MapKeys() {
|
||||
srcElement := src.MapIndex(key)
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
dstElement := dst.MapIndex(key)
|
||||
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||
case reflect.Struct:
|
||||
switch srcElement.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
||||
if srcElement.IsNil() {
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Map:
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1); err != nil {
|
||||
return
|
||||
default:
|
||||
if !srcElement.CanInterface() {
|
||||
continue
|
||||
}
|
||||
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||
case reflect.Struct:
|
||||
fallthrough
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Map:
|
||||
srcMapElm := srcElement
|
||||
dstMapElm := dstElement
|
||||
if srcMapElm.CanInterface() {
|
||||
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
|
||||
if dstMapElm.IsValid() {
|
||||
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
|
||||
}
|
||||
}
|
||||
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
case reflect.Slice:
|
||||
srcSlice := reflect.ValueOf(srcElement.Interface())
|
||||
|
||||
var dstSlice reflect.Value
|
||||
if !dstElement.IsValid() || dstElement.IsNil() {
|
||||
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
||||
} else {
|
||||
dstSlice = reflect.ValueOf(dstElement.Interface())
|
||||
}
|
||||
|
||||
if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
|
||||
dstSlice = srcSlice
|
||||
} else if config.AppendSlice {
|
||||
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
||||
}
|
||||
dst.SetMapIndex(key, dstSlice)
|
||||
}
|
||||
}
|
||||
if !dstElement.IsValid() {
|
||||
if dstElement.IsValid() && reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
|
||||
if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dstElement))) {
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
dst.SetMapIndex(key, srcElement)
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
if !dst.CanSet() {
|
||||
break
|
||||
}
|
||||
if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
|
||||
dst.Set(src)
|
||||
} else if config.AppendSlice {
|
||||
dst.Set(reflect.AppendSlice(dst, src))
|
||||
}
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Interface:
|
||||
if src.IsNil() {
|
||||
break
|
||||
} else if dst.IsNil() {
|
||||
if dst.CanSet() && isEmptyValue(dst) {
|
||||
}
|
||||
if src.Kind() != reflect.Interface {
|
||||
if dst.IsNil() || overwrite {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else if src.Kind() == reflect.Ptr {
|
||||
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dst.Elem().Type() == src.Type() {
|
||||
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
break
|
||||
}
|
||||
if dst.IsNil() || overwrite {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1); err != nil {
|
||||
} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
if dst.CanSet() && !isEmptyValue(src) {
|
||||
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge sets fields' values in dst from src if they have a zero
|
||||
// value of their type.
|
||||
// dst and src must be valid same-type structs and dst must be
|
||||
// a pointer to struct.
|
||||
// It won't merge unexported (private) fields and will do recursively
|
||||
// any exported field.
|
||||
func Merge(dst, src interface{}) error {
|
||||
// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
||||
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
|
||||
// and dst must be a pointer to struct.
|
||||
// It won't merge unexported (private) fields and will do recursively any exported field.
|
||||
func Merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return merge(dst, src, opts...)
|
||||
}
|
||||
|
||||
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
|
||||
// non-empty src attribute values.
|
||||
// Deprecated: use Merge(…) with WithOverride
|
||||
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return merge(dst, src, append(opts, WithOverride)...)
|
||||
}
|
||||
|
||||
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
||||
func WithTransformers(transformers Transformers) func(*Config) {
|
||||
return func(config *Config) {
|
||||
config.Transformers = transformers
|
||||
}
|
||||
}
|
||||
|
||||
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
||||
func WithOverride(config *Config) {
|
||||
config.Overwrite = true
|
||||
}
|
||||
|
||||
// WithAppendSlice will make merge append slices instead of overwriting it
|
||||
func WithAppendSlice(config *Config) {
|
||||
config.AppendSlice = true
|
||||
}
|
||||
|
||||
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
|
||||
config := &Config{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
if vDst.Type() != vSrc.Type() {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0)
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
|
||||
9
vendor/github.com/imdario/mergo/mergo.go
generated
vendored
9
vendor/github.com/imdario/mergo/mergo.go
generated
vendored
@@ -32,7 +32,7 @@ type visit struct {
|
||||
next *visit
|
||||
}
|
||||
|
||||
// From src/pkg/encoding/json.
|
||||
// From src/pkg/encoding/json/encode.go.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
@@ -46,7 +46,14 @@ func isEmptyValue(v reflect.Value) bool {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return isEmptyValue(v.Elem())
|
||||
case reflect.Func:
|
||||
return v.IsNil()
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
1
vendor/github.com/modern-go/reflect2/.travis.yml
generated
vendored
1
vendor/github.com/modern-go/reflect2/.travis.yml
generated
vendored
@@ -6,6 +6,7 @@ go:
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
- go get -t -v github.com/modern-go/reflect2-tests/...
|
||||
|
||||
script:
|
||||
- ./test.sh
|
||||
|
||||
2
vendor/github.com/modern-go/reflect2/Gopkg.toml
generated
vendored
2
vendor/github.com/modern-go/reflect2/Gopkg.toml
generated
vendored
@@ -24,7 +24,7 @@
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
ignored = ["github.com/modern-go/test","github.com/modern-go/test/must","github.com/modern-go/test/should"]
|
||||
ignored = []
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/modern-go/concurrent"
|
||||
|
||||
3
vendor/github.com/modern-go/reflect2/reflect2.go
generated
vendored
3
vendor/github.com/modern-go/reflect2/reflect2.go
generated
vendored
@@ -150,6 +150,9 @@ func (cfg *frozenConfig) TypeOf(obj interface{}) Type {
|
||||
}
|
||||
|
||||
func (cfg *frozenConfig) Type2(type1 reflect.Type) Type {
|
||||
if type1 == nil {
|
||||
return nil
|
||||
}
|
||||
cacheKey := uintptr(unpackEFace(type1).data)
|
||||
typeObj, found := cfg.cache.Load(cacheKey)
|
||||
if found {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user