Compare commits

..

1 Commits

Author SHA1 Message Date
Kubernetes Publisher
1356628796 Fix Godeps.json to point to kubernetes-1.11.0-alpha.2 tags 2018-05-03 02:27:18 +00:00
1344 changed files with 281773 additions and 25973 deletions

View File

@@ -1,3 +1,2 @@
Sorry, we do not accept changes directly against this repository, unless the
change is to the `README.md` itself. Please see
`CONTRIBUTING.md` for information on where and how to contribute instead.
Sorry, we do not accept changes directly against this repository. Please see
CONTRIBUTING.md for information on where and how to contribute instead.

View File

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

View File

@@ -1,469 +1,10 @@
TODO: This document was manually maintained so might be incomplete. The
automation effort is tracked in
https://github.com/kubernetes/test-infra/issues/5843.
https://github.com/kubernetes/client-go/issues/234.
Changes in `k8s.io/api` and `k8s.io/apimachinery` are mentioned here
because `k8s.io/client-go` depends on them.
# v10.0.0
**Breaking Changes:**
* Action required: client-go will no longer have bootstrap
(`k8s.io/client-go/tools/bootstrap`) related code. Any reference to it will
break. Please redirect all references to `k8s.io/bootstrap` instead.
([#67356](https://github.com/kubernetes/kubernetes/pull/67356))
* The methods `NewSelfSignedCACert` and `NewSignedCert` now use `crypto.Signer`
interface instead of `rsa.PrivateKey` for certificate creation. This is done
to allow different kind of private keys (for example: ecdsa).
([#69329](https://github.com/kubernetes/kubernetes/pull/69329))
* `GetScale` and `UpdateScale` methods have been added for `apps/v1` clients
and with this, no-verb scale clients have been removed.
([#70437](https://github.com/kubernetes/kubernetes/pull/70437))
* `k8s.io/client-go/util/cert/triple` package has been removed.
([#70966](https://github.com/kubernetes/kubernetes/pull/70966))
**New Features:**
* `unfinished_work_microseconds` is added to the workqueue metrics.
It can be used to detect stuck worker threads (kube-controller-manager runs many workqueues.).
([#70884](https://github.com/kubernetes/kubernetes/pull/70884))
* A method `GetPorts` is added to expose the ports that were forwarded.
This can be used to retrieve the locally-bound port in cases where the input was port 0.
([#67513](https://github.com/kubernetes/kubernetes/pull/67513))
* Dynamic listers and informers, that work with `runtime.Unstructured` objects,
are added. These are useful for writing generic, non-generated controllers.
([68748](https://github.com/kubernetes/kubernetes/pull/68748))
* The dynamic fake client now supports JSONPatch.
([#69330](https://github.com/kubernetes/kubernetes/pull/69330))
* The `GetBinding` method is added for pods in the fake client.
([#69412](https://github.com/kubernetes/kubernetes/pull/69412))
**Bug fixes and Improvements:**
* The `apiVersion` and action name values for fake evictions are now set.
([#69035](https://github.com/kubernetes/kubernetes/pull/69035))
* PEM files containing both TLS certificate and key can now be parsed in
arbitrary order. Previously key was always required to be first.
([#69536](https://github.com/kubernetes/kubernetes/pull/69536))
* Go clients created from a kubeconfig that specifies a `TokenFile` now
periodically reload the token from the specified file.
([#70606](https://github.com/kubernetes/kubernetes/pull/70606))
* It is now ensured that oversized data frames are not written to
spdystreams in `remotecommand.NewSPDYExecutor`.
([#70999](https://github.com/kubernetes/kubernetes/pull/70999))
* A panic occuring on calling `scheme.Convert` is fixed by populating the fake
dynamic client scheme. ([#69125](https://github.com/kubernetes/kubernetes/pull/69125))
* Add step to correctly setup permissions for the in-cluster-client-configuration example.
([#69232](https://github.com/kubernetes/kubernetes/pull/69232))
* The function `Parallelize` is deprecated. Use `ParallelizeUntil` instead.
([#68403](https://github.com/kubernetes/kubernetes/pull/68403))
* [k8s.io/apimachinery] Restrict redirect following from the apiserver to
same-host redirects, and ignore redirects in some cases.
([#66516](https://github.com/kubernetes/kubernetes/pull/66516))
## API changes
**New Features:**
* GlusterFS PersistentVolumes sources can now reference endpoints in any
namespace using the `spec.glusterfs.endpointsNamespace` field.
Ensure all kubelets are upgraded to 1.13+ before using this capability.
([#60195](https://github.com/kubernetes/kubernetes/pull/60195))
* The [dynamic audit configuration](https://github.com/kubernetes/community/blob/master/keps/sig-auth/0014-dynamic-audit-configuration.md)
API is added. ([#67547](https://github.com/kubernetes/kubernetes/pull/67547))
* A new field `EnableServiceLinks` is added to the `PodSpec` to indicate whether
information about services should be injected into pod's environment variables.
([#68754](https://github.com/kubernetes/kubernetes/pull/68754))
* `CSIPersistentVolume` feature, i.e. `PersistentVolumes` with
`CSIPersistentVolumeSource`, is GA. `CSIPersistentVolume` feature gate is now
deprecated and will be removed according to deprecation policy.
([#69929](https://github.com/kubernetes/kubernetes/pull/69929))
* Raw block volume support is promoted to beta, and enabled by default.
This is accessible via the `volumeDevices` container field in pod specs,
and the `volumeMode` field in persistent volume and persistent volume claims definitions.
([#71167](https://github.com/kubernetes/kubernetes/pull/71167))
**Bug fixes and Improvements:**
* The default value of extensions/v1beta1 Deployment's `RevisionHistoryLimit`
is set to `MaxInt32`. ([#66605](https://github.com/kubernetes/kubernetes/pull/66605))
* `procMount` field is no longer incorrectly marked as required in openapi schema.
([#69694](https://github.com/kubernetes/kubernetes/pull/69694))
* The caBundle and service fields in admission webhook API objects now correctly
indicate they are optional. ([#70138](https://github.com/kubernetes/kubernetes/pull/70138))
# 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:**
* `KUBE_API_VERSIONS` has been removed.
* [https://github.com/kubernetes/kubernetes/pull/63165](https://github.com/kubernetes/kubernetes/pull/63165)
* The client-go/discovery `RESTMapper` has been moved to client-go/restmapper.
* [https://github.com/kubernetes/kubernetes/pull/63507](https://github.com/kubernetes/kubernetes/pull/63507)
* `CachedDiscoveryClient` has been moved from kubectl to client-go.
* [https://github.com/kubernetes/kubernetes/pull/63550](https://github.com/kubernetes/kubernetes/pull/63550)
* The `EventRecorder` interface is changed to include an `AnnotatedEventf` method, which can add annotations to an event.
* [https://github.com/kubernetes/kubernetes/pull/64213](https://github.com/kubernetes/kubernetes/pull/64213)
* [k8s.io/apimachinery] The deprecated `RepairMalformedUpdates` flag has been removed.
* [https://github.com/kubernetes/kubernetes/pull/61455](https://github.com/kubernetes/kubernetes/pull/61455)
**New Features:**
* A new easy-to-use dynamic client is added and the old dynamic client is now deprecated.
* [https://github.com/kubernetes/kubernetes/pull/62913](https://github.com/kubernetes/kubernetes/pull/62913)
* client-go and kubectl now detect and report an error on duplicated name for user, cluster and context, while loading the kubeconfig.
* [https://github.com/kubernetes/kubernetes/pull/60464](https://github.com/kubernetes/kubernetes/pull/60464)
* The informer code-generator now allows specifying a custom resync period for certain informer types and uses the default resync period if none is specified.
* [https://github.com/kubernetes/kubernetes/pull/61400](https://github.com/kubernetes/kubernetes/pull/61400)
* Exec authenticator plugin now supports TLS client certificates.
* [https://github.com/kubernetes/kubernetes/pull/61803](https://github.com/kubernetes/kubernetes/pull/61803)
* The discovery client now has a default request timeout of 32 seconds.
* [https://github.com/kubernetes/kubernetes/pull/62733](https://github.com/kubernetes/kubernetes/pull/62733)
* The OpenStack auth config from is now read from the client config. If the client config is not available, it falls back to reading from the environment variables.
* [https://github.com/kubernetes/kubernetes/pull/60200](https://github.com/kubernetes/kubernetes/pull/60200)
* The in-tree support for openstack credentials is now deprecated. Please use the `client-keystone-auth` from the cloud-provider-openstack repository. Details on how to use this new capability is documented [here](https://github.com/kubernetes/cloud-provider-openstack/blob/master/docs/using-client-keystone-auth.md)
* [https://github.com/kubernetes/kubernetes/pull/64346](https://github.com/kubernetes/kubernetes/pull/64346)
**Bug fixes and Improvements:**
* 406 mime-type errors are now tolerated while attempting to load new openapi schema. This improves compatibility with older servers when creating/updating API objects.
* [https://github.com/kubernetes/kubernetes/pull/61949](https://github.com/kubernetes/kubernetes/pull/61949)
* Removes the generated `DeleteCollection()` method for `Services` since the API does not support it.
* [https://github.com/kubernetes/kubernetes/pull/63861](https://github.com/kubernetes/kubernetes/pull/63861)
* Event object references with apiversion now report an apiversion, instead of just the group.
* [https://github.com/kubernetes/kubernetes/pull/63913](https://github.com/kubernetes/kubernetes/pull/63913)
[https://github.com/kubernetes/kubernetes/pull/62462](https://github.com/kubernetes/kubernetes/pull/62462)
* [k8s.io/apimachinery] `runtime.Unstructured.UnstructuredContent()` no longer mutates the source while returning the contents.
* [https://github.com/kubernetes/kubernetes/pull/62063](https://github.com/kubernetes/kubernetes/pull/62063)
* [k8s.io/apimachinery] Incomplete support for `uint64` is now removed. This fixes a panic encountered while using `DeepCopyJSON` with `uint64`.
* [https://github.com/kubernetes/kubernetes/pull/62981](https://github.com/kubernetes/kubernetes/pull/62981)
* [k8s.io/apimachinery] API server can now parse `propagationPolicy` when it sent as a query parameter sent with a delete request.
* [https://github.com/kubernetes/kubernetes/pull/63414](https://github.com/kubernetes/kubernetes/pull/63414)
* [k8s.io/apimachinery] APIServices with kube-like versions (e.g. v1, v2beta1, etc.) will be sorted appropriately within each group.
* [https://github.com/kubernetes/kubernetes/pull/64004](https://github.com/kubernetes/kubernetes/pull/64004)
* [k8s.io/apimachinery] `int64` is the only allowed integer for printers.
* [https://github.com/kubernetes/kubernetes/pull/64639](https://github.com/kubernetes/kubernetes/pull/64639)
## API changes
**Breaking Changes:**
* Support for `alpha.kubernetes.io/nvidia-gpu` resource which was deprecated in 1.10 is removed. Please use the resource exposed by `DevicePlugins` instead (`nvidia.com/gpu`).
* [https://github.com/kubernetes/kubernetes/pull/61498](https://github.com/kubernetes/kubernetes/pull/61498)
* Alpha annotation for `PersistentVolume` node affinity has been removed. Update your `PersistentVolume`s to use the beta `PersistentVolume.nodeAffinity` field before upgrading.
* [https://github.com/kubernetes/kubernetes/pull/61816](https://github.com/kubernetes/kubernetes/pull/61816)
* `ObjectMeta ` `ListOptions` `DeleteOptions` are removed from the core api group. Please use the ones in `meta/v1` instead.
* [https://github.com/kubernetes/kubernetes/pull/61809](https://github.com/kubernetes/kubernetes/pull/61809)
* `ExternalID` in `NodeSpec` is deprecated. The externalID of the node is no longer set in the Node spec.
* [https://github.com/kubernetes/kubernetes/pull/61877](https://github.com/kubernetes/kubernetes/pull/61877)
* PSP-related types in the `extensions/v1beta1` API group are now deprecated. It is suggested to use the `policy/v1beta1` API group instead.
* [https://github.com/kubernetes/kubernetes/pull/61777](https://github.com/kubernetes/kubernetes/pull/61777)
**New Features:**
* `PodSecurityPolicy` now supports restricting hostPath volume mounts to be readOnly and under specific path prefixes.
* [https://github.com/kubernetes/kubernetes/pull/58647](https://github.com/kubernetes/kubernetes/pull/58647)
* `Node.Spec.ConfigSource.ConfigMap.KubeletConfigKey` must be specified when using dynamic Kubelet config to tell the Kubelet which key of the `ConfigMap` identifies its config file.
* [https://github.com/kubernetes/kubernetes/pull/59847](https://github.com/kubernetes/kubernetes/pull/59847)
* `serverAddressByClientCIDRs` in `meta/v1` APIGroup is now optional.
* [https://github.com/kubernetes/kubernetes/pull/61963](https://github.com/kubernetes/kubernetes/pull/61963)
* A new field `MatchFields` is added to `NodeSelectorTerm`. Currently, it only supports `metadata.name`.
* [https://github.com/kubernetes/kubernetes/pull/62002](https://github.com/kubernetes/kubernetes/pull/62002)
* The `PriorityClass` API is promoted to `scheduling.k8s.io/v1beta1`.
* [https://github.com/kubernetes/kubernetes/pull/63100](https://github.com/kubernetes/kubernetes/pull/63100)
* The status of dynamic Kubelet config is now reported via `Node.Status.Config`, rather than the `KubeletConfigOk` node condition.
* [https://github.com/kubernetes/kubernetes/pull/63314](https://github.com/kubernetes/kubernetes/pull/63314)
* The `GitRepo` volume type is deprecated. To provision a container with a git repo, mount an `EmptyDir` into an `InitContainer` that clones the repo using git, then mount the `EmptyDir` into the Pod's container.
* [https://github.com/kubernetes/kubernetes/pull/63445](https://github.com/kubernetes/kubernetes/pull/63445)
* The Sysctls experimental feature has been promoted to beta (enabled by default via the `Sysctls` feature flag). `PodSecurityPolicy` and `Pod` objects now have fields for specifying and controlling sysctls. Alpha sysctl annotations will be ignored by 1.11+ kubelets. All alpha sysctl annotations in existing deployments must be converted to API fields to be effective.
* [https://github.com/kubernetes/kubernetes/pull/63717](https://github.com/kubernetes/kubernetes/pull/63717)
* The annotation `service.alpha.kubernetes.io/tolerate-unready-endpoints` is deprecated. Users should use `Service.spec.publishNotReadyAddresses` instead.
* [https://github.com/kubernetes/kubernetes/pull/63742](https://github.com/kubernetes/kubernetes/pull/63742)
* `VerticalPodAutoscaler` has been added to `autoscaling/v1` API group.
* [https://github.com/kubernetes/kubernetes/pull/63797](https://github.com/kubernetes/kubernetes/pull/63797)
* Alpha support is added for dynamic volume limits based on node type.
* [https://github.com/kubernetes/kubernetes/pull/64154](https://github.com/kubernetes/kubernetes/pull/64154)
* `ContainersReady` condition is added to the Pod status.
* [https://github.com/kubernetes/kubernetes/pull/64646](https://github.com/kubernetes/kubernetes/pull/64646)
**Bug fixes and Improvements:**
* Default mount propagation has changed from `HostToContainer` (`rslave` in Linux terminology) to `None` (`private`) to match the behavior in 1.9 and earlier releases. `HostToContainer` as a default caused regressions in some pods.
* [https://github.com/kubernetes/kubernetes/pull/62462](https://github.com/kubernetes/kubernetes/pull/62462)
# v7.0.0
**Breaking Changes:**

View File

@@ -1,9 +1,7 @@
# Contributing guidelines
Do not open pull requests directly against this repository. They will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/).
The exception is changes to the `README.md` itself.
Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes.
Do not open pull requests directly against this repository, they will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes.
This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/client-go](https://git.k8s.io/kubernetes/staging/src/k8s.io/client-go) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot).
This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/client-go](https://git.k8s.io/kubernetes/staging/src/k8s.io/client-go) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot).
Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/sig-architecture/staging.md) for more information
Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/staging.md) for more information

360
Godeps/Godeps.json generated
View File

@@ -1,7 +1,7 @@
{
"ImportPath": "k8s.io/client-go",
"GoVersion": "go1.12",
"GodepVersion": "v80-k8s-r1",
"GoVersion": "go1.9",
"GodepVersion": "v80",
"Packages": [
"./..."
],
@@ -16,27 +16,19 @@
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest",
"Rev": "ea233b6412b0421a65dc6160e16c893364664a95"
"Rev": "1ff28809256a84bb6966640ff3d0371af82ccba4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/adal",
"Rev": "ea233b6412b0421a65dc6160e16c893364664a95"
"Rev": "1ff28809256a84bb6966640ff3d0371af82ccba4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
"Rev": "ea233b6412b0421a65dc6160e16c893364664a95"
"Rev": "1ff28809256a84bb6966640ff3d0371af82ccba4"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
"Rev": "ea233b6412b0421a65dc6160e16c893364664a95"
},
{
"ImportPath": "github.com/Azure/go-autorest/logger",
"Rev": "ea233b6412b0421a65dc6160e16c893364664a95"
},
{
"ImportPath": "github.com/Azure/go-autorest/version",
"Rev": "ea233b6412b0421a65dc6160e16c893364664a95"
"Rev": "1ff28809256a84bb6966640ff3d0371af82ccba4"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
@@ -55,16 +47,20 @@
"Rev": "449fdfce4d962303d702fec724ef0ad181c92528"
},
{
"ImportPath": "github.com/evanphx/json-patch",
"Rev": "5858425f75500d40c52783dce87d085a483ce135"
"ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
"Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02"
"Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7"
},
{
"ImportPath": "github.com/gogo/protobuf/sortkeys",
"Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02"
"Rev": "c0656edd0d9eab7c66d1eb0c568f9039345796f7"
},
{
"ImportPath": "github.com/golang/glog",
"Rev": "44145f04b68cf362d9c4df2182967c2275eaefed"
},
{
"ImportPath": "github.com/golang/groupcache/lru",
@@ -72,31 +68,27 @@
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "b4deda0973fb4c70b50d226b1af49f3da59f5265"
"Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes",
"Rev": "b4deda0973fb4c70b50d226b1af49f3da59f5265"
"Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/any",
"Rev": "b4deda0973fb4c70b50d226b1af49f3da59f5265"
"Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/duration",
"Rev": "b4deda0973fb4c70b50d226b1af49f3da59f5265"
"Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
},
{
"ImportPath": "github.com/golang/protobuf/ptypes/timestamp",
"Rev": "b4deda0973fb4c70b50d226b1af49f3da59f5265"
},
{
"ImportPath": "github.com/google/btree",
"Rev": "7d79101e329e5a3adf994758c578dab82b90c017"
"Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
},
{
"ImportPath": "github.com/google/gofuzz",
"Rev": "24818f796faf91cd76ec7bddd72458fbced7a6c1"
"Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c"
},
{
"ImportPath": "github.com/googleapis/gnostic/OpenAPIv2",
@@ -112,55 +104,47 @@
},
{
"ImportPath": "github.com/gophercloud/gophercloud",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/utils",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/pagination",
"Rev": "c818fa66e4c88b30db28038fe3f18f2f4a0db9a8"
},
{
"ImportPath": "github.com/gregjones/httpcache",
"Rev": "787624de3eb7bd915c329cba748687a3b22666a6"
},
{
"ImportPath": "github.com/gregjones/httpcache/diskcache",
"Rev": "787624de3eb7bd915c329cba748687a3b22666a6"
"Rev": "6da026c32e2d622cc242d32984259c77237aefe1"
},
{
"ImportPath": "github.com/hashicorp/golang-lru",
"Rev": "20f1fb78b0740ba8c3cb143a61e86ba5c8669768"
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
},
{
"ImportPath": "github.com/hashicorp/golang-lru/simplelru",
"Rev": "20f1fb78b0740ba8c3cb143a61e86ba5c8669768"
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
},
{
"ImportPath": "github.com/imdario/mergo",
"Rev": "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
"Rev": "6633656539c1639d9d78127b7d47c622b5d7b6dc"
},
{
"ImportPath": "github.com/json-iterator/go",
"Rev": "ab8a2e0c74be9d3be70b3184d9acc634935ded82"
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798"
},
{
"ImportPath": "github.com/modern-go/concurrent",
@@ -168,15 +152,11 @@
},
{
"ImportPath": "github.com/modern-go/reflect2",
"Rev": "94122c33edd36123c84d5368cfb2b69df93a0ec8"
},
{
"ImportPath": "github.com/peterbourgon/diskv",
"Rev": "5f041e8faa004a95c88a202771f4cc3e991971e6"
"Rev": "05fbef0ca5da472bbf96c9322b84a53edc03c9fd"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Rev": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc"
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
},
{
"ImportPath": "github.com/spf13/pflag",
@@ -188,31 +168,31 @@
},
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "de0752318171da717af4ce24d0a2e8626afaeb11"
"Rev": "49796115aa4b964c318aad4f3084fdb41e9aa067"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "cdfb69ac37fc6fa907650654115ebebb3aae2087"
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "cdfb69ac37fc6fa907650654115ebebb3aae2087"
},
{
"ImportPath": "golang.org/x/net/http/httpguts",
"Rev": "cdfb69ac37fc6fa907650654115ebebb3aae2087"
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
},
{
"ImportPath": "golang.org/x/net/http2",
"Rev": "cdfb69ac37fc6fa907650654115ebebb3aae2087"
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
},
{
"ImportPath": "golang.org/x/net/http2/hpack",
"Rev": "cdfb69ac37fc6fa907650654115ebebb3aae2087"
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
},
{
"ImportPath": "golang.org/x/net/idna",
"Rev": "cdfb69ac37fc6fa907650654115ebebb3aae2087"
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
},
{
"ImportPath": "golang.org/x/net/lex/httplex",
"Rev": "1c05540f6879653db88113bc4a2b70aec4bd491f"
},
{
"ImportPath": "golang.org/x/oauth2",
@@ -268,383 +248,339 @@
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "f221b8435cfb71e54062f6c6e99e9ade30b124d5"
"Rev": "670d4cfef0544295bc27a114dbac37980d83185a"
},
{
"ImportPath": "k8s.io/api/admissionregistration/v1alpha1",
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/admissionregistration/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/apps/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/apps/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/apps/v1beta2",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/auditregistration/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/authentication/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/authentication/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/authorization/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/authorization/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/autoscaling/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/autoscaling/v2beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/autoscaling/v2beta2",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/batch/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/batch/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/batch/v2alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/certificates/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/coordination/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/coordination/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/core/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/events/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/extensions/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/imagepolicy/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/networking/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/networking/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/node/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/node/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/policy/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/rbac/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/rbac/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/rbac/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/scheduling/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/scheduling/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/api/scheduling/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/settings/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/storage/v1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/storage/v1alpha1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/api/storage/v1beta1",
"Rev": "159aefb8556bb8ed4be7631461d1558546d304db"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/roundtrip",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "27b40428219e344a796d7a610ce48617c9b55b72"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/testing",
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer",
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/testing/roundtrip",
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery",
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery/announced",
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered",
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/labels",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/selection",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/types",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/version",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/watch",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "c5d2f014d689246b84637774317fa0288dd3129b"
},
{
"ImportPath": "k8s.io/klog",
"Rev": "8e90cee79f823779174776412c13478955131846"
"Rev": "6426d21f392c6d0e710af98bdfe6fb7334b3e11a"
},
{
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "b3a7cee44a305be0a69e1b9ac03018307287e1b0"
},
{
"ImportPath": "k8s.io/utils/buffer",
"Rev": "c2654d5206da6b7b6ace12841e8f359bb89b443c"
},
{
"ImportPath": "k8s.io/utils/integer",
"Rev": "c2654d5206da6b7b6ace12841e8f359bb89b443c"
},
{
"ImportPath": "k8s.io/utils/trace",
"Rev": "c2654d5206da6b7b6ace12841e8f359bb89b443c"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"
"Rev": "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf"
}
]
}

2
Godeps/OWNERS generated
View File

@@ -1,4 +1,2 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- dep-approvers

View File

@@ -63,7 +63,7 @@ To install `client-go` and place its dependencies in your `$GOPATH`:
```sh
go get k8s.io/client-go/...
cd $GOPATH/src/k8s.io/client-go
git checkout v9.0.0 # replace v9.0.0 with the required version
git checkout v6.0.0
# 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: v9.0.0 # replace v9.0.0 with the required version
version: v6.0.0
```
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: v9.0.0 # replace v9.0.0 with the required version
version: v6.0.0
# 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

48
OWNERS
View File

@@ -1,19 +1,45 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caesarxuchao
- deads2k
- krousey
- lavalamp
- liggitt
- smarterclayton
- sttts
reviewers:
- caesarxuchao
- deads2k
- lavalamp
- liggitt
- soltysh
reviewers:
- thockin
- lavalamp
- smarterclayton
- wojtek-t
- deads2k
- yujuhong
- derekwaynecarr
- caesarxuchao
- vishh
- mikedanese
- liggitt
- nikhiljindal
- gmarek
- erictune
- davidopp
- pmorie
- sttts
- yliaog
labels:
- sig/api-machinery
- dchen1107
- saad-ali
- zmerlynn
- luxas
- janetkuo
- justinsb
- roberthbailey
- ncdc
- tallclair
- yifan-gu
- eparis
- mwielgus
- timothysc
- feiskyer
- jlowdermilk
- soltysh
- piosz
- jsafrane
- awly

View File

@@ -2,9 +2,9 @@
Go clients for talking to a [kubernetes](http://kubernetes.io/) cluster.
We currently recommend using the v10.0.0 tag. See [INSTALL.md](/INSTALL.md) for
We currently recommend using the v7.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.
will give you head and doesn't handle the dependencies well.
[![BuildStatus Widget]][BuildStatus Result]
[![GoReport Widget]][GoReport Status]
@@ -38,7 +38,6 @@ will build `master`, which doesn't handle the dependencies well.
* The `kubernetes` package contains the clientset to access Kubernetes API.
* The `discovery` package is used to discover APIs supported by a Kubernetes API server.
* The `dynamic` package contains a dynamic client that can perform generic operations on arbitrary Kubernetes API objects.
* The `plugin/pkg/client/auth` packages contain optional authentication plugins for obtaining credentials from external sources.
* The `transport` package is used to set up auth and start a connection.
* The `tools/cache` package is useful for writing controllers.
@@ -92,16 +91,17 @@ We will backport bugfixes--but not new features--into older versions of
#### Compatibility matrix
| | Kubernetes 1.7 | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 | Kubernetes 1.11 | Kubernetes 1.12 | Kubernetes 1.13 |
|---------------------|----------------|----------------|----------------|-----------------|-----------------|-----------------|-----------------|
| 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 10.0 | +- | +- | +- | +- | +- | +- | |
| client-go HEAD | +- | +- | +- | +- | +- | +- | +- |
| | Kubernetes 1.4 | Kubernetes 1.5 | Kubernetes 1.6 | Kubernetes 1.7 | Kubernetes 1.8 | Kubernetes 1.9 | Kubernetes 1.10 |
|---------------------|----------------|----------------|----------------|----------------|----------------|----------------|-----------------|
| client-go 1.4 | ✓ | - | - | - | - | - | - |
| 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 HEAD | +- | +- | +- | +- | +- | + | + |
Key:
@@ -127,12 +127,9 @@ between client-go versions.
| client-go 2.0 | Kubernetes main repo, 1.5 branch | = - |
| 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 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 10.0 | Kubernetes main repo, 1.13 branch | ✓ |
| client-go 5.0 | Kubernetes main repo, 1.8 branch | |
| client-go 6.0 | Kubernetes main repo, 1.9 branch | |
| client-go 7.0 | Kubernetes main repo, 1.10 branch | |
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
Key:
@@ -157,7 +154,7 @@ existing users won't be broken.
### Kubernetes tags
This repository is still a mirror of
As of April 2018, client-go is still a mirror of
[k8s.io/kubernetes/staging/src/client-go](https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/client-go),
the code development is still done in the staging area. Since Kubernetes 1.8
release, when syncing the code from the staging area, we also sync the Kubernetes

View File

@@ -1,17 +0,0 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Committee to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
cjcullen
jessfraz
liggitt
philips
tallclair

View File

@@ -1,623 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package deprecated_dynamic
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest"
restclientwatch "k8s.io/client-go/rest/watch"
)
func getJSON(version, kind, name string) []byte {
return []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": %q}}`, version, kind, name))
}
func getListJSON(version, kind string, items ...[]byte) []byte {
json := fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "items": [%s]}`,
version, kind, bytes.Join(items, []byte(",")))
return []byte(json)
}
func getObject(version, kind, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": version,
"kind": kind,
"metadata": map[string]interface{}{
"name": name,
},
},
}
}
func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) {
srv := httptest.NewServer(http.HandlerFunc(h))
cl, err := NewClient(&restclient.Config{
Host: srv.URL,
ContentConfig: restclient.ContentConfig{GroupVersion: gv},
}, *gv)
if err != nil {
srv.Close()
return nil, nil, err
}
return cl, srv, nil
}
func TestList(t *testing.T) {
tcs := []struct {
name string
namespace string
path string
resp []byte
want *unstructured.UnstructuredList
}{
{
name: "normal_list",
path: "/apis/gtest/vtest/rtest",
resp: getListJSON("vTest", "rTestList",
getJSON("vTest", "rTest", "item1"),
getJSON("vTest", "rTest", "item2")),
want: &unstructured.UnstructuredList{
Object: map[string]interface{}{
"apiVersion": "vTest",
"kind": "rTestList",
},
Items: []unstructured.Unstructured{
*getObject("vTest", "rTest", "item1"),
*getObject("vTest", "rTest", "item2"),
},
},
},
{
name: "namespaced_list",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
resp: getListJSON("vTest", "rTestList",
getJSON("vTest", "rTest", "item1"),
getJSON("vTest", "rTest", "item2")),
want: &unstructured.UnstructuredList{
Object: map[string]interface{}{
"apiVersion": "vTest",
"kind": "rTestList",
},
Items: []unstructured.Unstructured{
*getObject("vTest", "rTest", "item1"),
*getObject("vTest", "rTest", "item2"),
},
},
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("List(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
w.Write(tc.resp)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
got, err := cl.Resource(resource, tc.namespace).List(metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error when listing %q: %v", tc.name, err)
continue
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("List(%q) want: %v\ngot: %v", tc.name, tc.want, got)
}
}
}
func TestGet(t *testing.T) {
tcs := []struct {
resource string
namespace string
name string
path string
resp []byte
want *unstructured.Unstructured
}{
{
resource: "rtest",
name: "normal_get",
path: "/apis/gtest/vtest/rtest/normal_get",
resp: getJSON("vTest", "rTest", "normal_get"),
want: getObject("vTest", "rTest", "normal_get"),
},
{
resource: "rtest",
namespace: "nstest",
name: "namespaced_get",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_get",
resp: getJSON("vTest", "rTest", "namespaced_get"),
want: getObject("vTest", "rTest", "namespaced_get"),
},
{
resource: "rtest/srtest",
name: "normal_subresource_get",
path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest",
resp: getJSON("vTest", "srTest", "normal_subresource_get"),
want: getObject("vTest", "srTest", "normal_subresource_get"),
},
{
resource: "rtest/srtest",
namespace: "nstest",
name: "namespaced_subresource_get",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest",
resp: getJSON("vTest", "srTest", "namespaced_subresource_get"),
want: getObject("vTest", "srTest", "namespaced_subresource_get"),
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("Get(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
w.Write(tc.resp)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
got, err := cl.Resource(resource, tc.namespace).Get(tc.name, metav1.GetOptions{})
if err != nil {
t.Errorf("unexpected error when getting %q: %v", tc.name, err)
continue
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("Get(%q) want: %v\ngot: %v", tc.name, tc.want, got)
}
}
}
func TestDelete(t *testing.T) {
background := metav1.DeletePropagationBackground
uid := types.UID("uid")
statusOK := &metav1.Status{
TypeMeta: metav1.TypeMeta{Kind: "Status"},
Status: metav1.StatusSuccess,
}
tcs := []struct {
namespace string
name string
path string
deleteOptions *metav1.DeleteOptions
}{
{
name: "normal_delete",
path: "/apis/gtest/vtest/rtest/normal_delete",
},
{
namespace: "nstest",
name: "namespaced_delete",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete",
},
{
namespace: "nstest",
name: "namespaced_delete_with_options",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete_with_options",
deleteOptions: &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}, PropagationPolicy: &background},
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("Delete(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
unstructured.UnstructuredJSONScheme.Encode(statusOK, w)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
err = cl.Resource(resource, tc.namespace).Delete(tc.name, tc.deleteOptions)
if err != nil {
t.Errorf("unexpected error when deleting %q: %v", tc.name, err)
continue
}
}
}
func TestDeleteCollection(t *testing.T) {
statusOK := &metav1.Status{
TypeMeta: metav1.TypeMeta{Kind: "Status"},
Status: metav1.StatusSuccess,
}
tcs := []struct {
namespace string
name string
path string
}{
{
name: "normal_delete_collection",
path: "/apis/gtest/vtest/rtest",
},
{
namespace: "nstest",
name: "namespaced_delete_collection",
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("DeleteCollection(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
unstructured.UnstructuredJSONScheme.Encode(statusOK, w)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
err = cl.Resource(resource, tc.namespace).DeleteCollection(nil, metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err)
continue
}
}
}
func TestCreate(t *testing.T) {
tcs := []struct {
resource string
name string
namespace string
obj *unstructured.Unstructured
path string
}{
{
resource: "rtest",
name: "normal_create",
path: "/apis/gtest/vtest/rtest",
obj: getObject("gtest/vTest", "rTest", "normal_create"),
},
{
resource: "rtest",
name: "namespaced_create",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
obj: getObject("gtest/vTest", "rTest", "namespaced_create"),
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("Create(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(data)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
got, err := cl.Resource(resource, tc.namespace).Create(tc.obj)
if err != nil {
t.Errorf("unexpected error when creating %q: %v", tc.name, err)
continue
}
if !reflect.DeepEqual(got, tc.obj) {
t.Errorf("Create(%q) want: %v\ngot: %v", tc.name, tc.obj, got)
}
}
}
func TestUpdate(t *testing.T) {
tcs := []struct {
resource string
name string
namespace string
obj *unstructured.Unstructured
path string
}{
{
resource: "rtest",
name: "normal_update",
path: "/apis/gtest/vtest/rtest/normal_update",
obj: getObject("gtest/vTest", "rTest", "normal_update"),
},
{
resource: "rtest",
name: "namespaced_update",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update",
obj: getObject("gtest/vTest", "rTest", "namespaced_update"),
},
{
resource: "rtest/srtest",
name: "normal_subresource_update",
path: "/apis/gtest/vtest/rtest/normal_update/srtest",
obj: getObject("gtest/vTest", "srTest", "normal_update"),
},
{
resource: "rtest/srtest",
name: "namespaced_subresource_update",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest",
obj: getObject("gtest/vTest", "srTest", "namespaced_update"),
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("Update(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(data)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
got, err := cl.Resource(resource, tc.namespace).Update(tc.obj)
if err != nil {
t.Errorf("unexpected error when updating %q: %v", tc.name, err)
continue
}
if !reflect.DeepEqual(got, tc.obj) {
t.Errorf("Update(%q) want: %v\ngot: %v", tc.name, tc.obj, got)
}
}
}
func TestWatch(t *testing.T) {
tcs := []struct {
name string
namespace string
events []watch.Event
path string
query string
}{
{
name: "normal_watch",
path: "/apis/gtest/vtest/rtest",
query: "watch=true",
events: []watch.Event{
{Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
{Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
{Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
},
},
{
name: "namespaced_watch",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
query: "watch=true",
events: []watch.Event{
{Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
{Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
{Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
},
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
if r.URL.RawQuery != tc.query {
t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query)
}
enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, dynamicCodec{}), dynamicCodec{})
for _, e := range tc.events {
enc.Encode(&e)
}
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
watcher, err := cl.Resource(resource, tc.namespace).Watch(metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error when watching %q: %v", tc.name, err)
continue
}
for _, want := range tc.events {
got := <-watcher.ResultChan()
if !reflect.DeepEqual(got, want) {
t.Errorf("Watch(%q) want: %v\ngot: %v", tc.name, want, got)
}
}
}
}
func TestPatch(t *testing.T) {
tcs := []struct {
resource string
name string
namespace string
patch []byte
want *unstructured.Unstructured
path string
}{
{
resource: "rtest",
name: "normal_patch",
path: "/apis/gtest/vtest/rtest/normal_patch",
patch: getJSON("gtest/vTest", "rTest", "normal_patch"),
want: getObject("gtest/vTest", "rTest", "normal_patch"),
},
{
resource: "rtest",
name: "namespaced_patch",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_patch",
patch: getJSON("gtest/vTest", "rTest", "namespaced_patch"),
want: getObject("gtest/vTest", "rTest", "namespaced_patch"),
},
{
resource: "rtest/srtest",
name: "normal_subresource_patch",
path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest",
patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"),
want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"),
},
{
resource: "rtest/srtest",
name: "namespaced_subresource_patch",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest",
patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"),
want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"),
},
}
for _, tc := range tcs {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PATCH" {
t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method)
}
if r.URL.Path != tc.path {
t.Errorf("Patch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}
content := r.Header.Get("Content-Type")
if content != string(types.StrategicMergePatchType) {
t.Errorf("Patch(%q) got Content-Type %s. wanted %s", tc.name, content, types.StrategicMergePatchType)
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Patch(%q) unexpected error reading body: %v", tc.name, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
})
if err != nil {
t.Errorf("unexpected error when creating client: %v", err)
continue
}
defer srv.Close()
got, err := cl.Resource(resource, tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch)
if err != nil {
t.Errorf("unexpected error when patching %q: %v", tc.name, err)
continue
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("Patch(%q) want: %v\ngot: %v", tc.name, tc.want, got)
}
}
}

View File

@@ -1,300 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package disk
import (
"errors"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sync"
"time"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/klog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes/scheme"
restclient "k8s.io/client-go/rest"
)
// CachedDiscoveryClient implements the functions that discovery server-supported API groups,
// versions and resources.
type CachedDiscoveryClient struct {
delegate discovery.DiscoveryInterface
// cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
cacheDirectory string
// ttl is how long the cache should be considered valid
ttl time.Duration
// mutex protects the variables below
mutex sync.Mutex
// ourFiles are all filenames of cache files created by this process
ourFiles map[string]struct{}
// invalidated is true if all cache files should be ignored that are not ours (e.g. after Invalidate() was called)
invalidated bool
// fresh is true if all used cache files were ours
fresh bool
}
var _ discovery.CachedDiscoveryInterface = &CachedDiscoveryClient{}
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
cachedBytes, err := d.getCachedFile(filename)
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
if err == nil {
cachedResources := &metav1.APIResourceList{}
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
klog.V(10).Infof("returning cached discovery info from %v", filename)
return cachedResources, nil
}
}
liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
klog.V(3).Infof("skipped caching discovery info due to %v", err)
return liveResources, err
}
if liveResources == nil || len(liveResources.APIResources) == 0 {
klog.V(3).Infof("skipped caching discovery info, no resources found")
return liveResources, err
}
if err := d.writeCachedFile(filename, liveResources); err != nil {
klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
}
return liveResources, nil
}
// ServerResources returns the supported resources for all groups and versions.
// Deprecated: use ServerGroupsAndResources instead.
func (d *CachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
_, rs, err := discovery.ServerGroupsAndResources(d)
return rs, err
}
// ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
func (d *CachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return discovery.ServerGroupsAndResources(d)
}
// ServerGroups returns the supported groups, with information like supported versions and the
// preferred version.
func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
filename := filepath.Join(d.cacheDirectory, "servergroups.json")
cachedBytes, err := d.getCachedFile(filename)
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
if err == nil {
cachedGroups := &metav1.APIGroupList{}
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
klog.V(10).Infof("returning cached discovery info from %v", filename)
return cachedGroups, nil
}
}
liveGroups, err := d.delegate.ServerGroups()
if err != nil {
klog.V(3).Infof("skipped caching discovery info due to %v", err)
return liveGroups, err
}
if liveGroups == nil || len(liveGroups.Groups) == 0 {
klog.V(3).Infof("skipped caching discovery info, no groups found")
return liveGroups, err
}
if err := d.writeCachedFile(filename, liveGroups); err != nil {
klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
}
return liveGroups, nil
}
func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
// after invalidation ignore cache files not created by this process
d.mutex.Lock()
_, ourFile := d.ourFiles[filename]
if d.invalidated && !ourFile {
d.mutex.Unlock()
return nil, errors.New("cache invalidated")
}
d.mutex.Unlock()
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return nil, err
}
if time.Now().After(fileInfo.ModTime().Add(d.ttl)) {
return nil, errors.New("cache expired")
}
// the cache is present and its valid. Try to read and use it.
cachedBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
d.mutex.Lock()
defer d.mutex.Unlock()
d.fresh = d.fresh && ourFile
return cachedBytes, nil
}
func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
if err := os.MkdirAll(filepath.Dir(filename), 0750); err != nil {
return err
}
bytes, err := runtime.Encode(scheme.Codecs.LegacyCodec(), obj)
if err != nil {
return err
}
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".")
if err != nil {
return err
}
defer os.Remove(f.Name())
_, err = f.Write(bytes)
if err != nil {
return err
}
err = os.Chmod(f.Name(), 0660)
if err != nil {
return err
}
name := f.Name()
err = f.Close()
if err != nil {
return err
}
// atomic rename
d.mutex.Lock()
defer d.mutex.Unlock()
err = os.Rename(name, filename)
if err == nil {
d.ourFiles[filename] = struct{}{}
}
return err
}
// RESTClient returns a RESTClient that is used to communicate with API server
// by this client implementation.
func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
return d.delegate.RESTClient()
}
// ServerPreferredResources returns the supported resources with the version preferred by the
// server.
func (d *CachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return discovery.ServerPreferredResources(d)
}
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
// version preferred by the server.
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return discovery.ServerPreferredNamespacedResources(d)
}
// ServerVersion retrieves and parses the server's version (git version).
func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
return d.delegate.ServerVersion()
}
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return d.delegate.OpenAPISchema()
}
// Fresh is supposed to tell the caller whether or not to retry if the cache
// fails to find something (false = retry, true = no need to retry).
func (d *CachedDiscoveryClient) Fresh() bool {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.fresh
}
// Invalidate enforces that no cached data is used in the future that is older than the current time.
func (d *CachedDiscoveryClient) Invalidate() {
d.mutex.Lock()
defer d.mutex.Unlock()
d.ourFiles = map[string]struct{}{}
d.fresh = true
d.invalidated = true
}
// NewCachedDiscoveryClientForConfig creates a new DiscoveryClient for the given config, and wraps
// the created client in a CachedDiscoveryClient. The provided configuration is updated with a
// custom transport that understands cache responses.
// We receive two distinct cache directories for now, in order to preserve old behavior
// which makes use of the --cache-dir flag value for storing cache data from the CacheRoundTripper,
// and makes use of the hardcoded destination (~/.kube/cache/discovery/...) for storing
// CachedDiscoveryClient cache data. If httpCacheDir is empty, the restconfig's transport will not
// be updated with a roundtripper that understands cache responses.
// If discoveryCacheDir is empty, cached server resource data will be looked up in the current directory.
// TODO(juanvallejo): the value of "--cache-dir" should be honored. Consolidate discoveryCacheDir with httpCacheDir
// so that server resources and http-cache data are stored in the same location, provided via config flags.
func NewCachedDiscoveryClientForConfig(config *restclient.Config, discoveryCacheDir, httpCacheDir string, ttl time.Duration) (*CachedDiscoveryClient, error) {
if len(httpCacheDir) > 0 {
// update the given restconfig with a custom roundtripper that
// understands how to handle cache responses.
config = restclient.CopyConfig(config)
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
return newCacheRoundTripper(httpCacheDir, rt)
})
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
return newCachedDiscoveryClient(discoveryClient, discoveryCacheDir, ttl), nil
}
// NewCachedDiscoveryClient creates a new DiscoveryClient. cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
func newCachedDiscoveryClient(delegate discovery.DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
return &CachedDiscoveryClient{
delegate: delegate,
cacheDirectory: cacheDirectory,
ttl: ttl,
ourFiles: map[string]struct{}{},
fresh: true,
}
}

View File

@@ -1,215 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package disk
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
)
func TestCachedDiscoveryClient_Fresh(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(d)
c := fakeDiscoveryClient{}
cdc := newCachedDiscoveryClient(&c, d, 60*time.Second)
assert.True(cdc.Fresh(), "should be fresh after creation")
cdc.ServerGroups()
assert.True(cdc.Fresh(), "should be fresh after groups call without cache")
assert.Equal(c.groupCalls, 1)
cdc.ServerGroups()
assert.True(cdc.Fresh(), "should be fresh after another groups call")
assert.Equal(c.groupCalls, 1)
cdc.ServerResources()
assert.True(cdc.Fresh(), "should be fresh after resources call")
assert.Equal(c.resourceCalls, 1)
cdc.ServerResources()
assert.True(cdc.Fresh(), "should be fresh after another resources call")
assert.Equal(c.resourceCalls, 1)
cdc = newCachedDiscoveryClient(&c, d, 60*time.Second)
cdc.ServerGroups()
assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing groups cache")
assert.Equal(c.groupCalls, 1)
cdc.ServerResources()
assert.False(cdc.Fresh(), "should NOT be fresh after recreation with existing resources cache")
assert.Equal(c.resourceCalls, 1)
cdc.Invalidate()
assert.True(cdc.Fresh(), "should be fresh after cache invalidation")
cdc.ServerResources()
assert.True(cdc.Fresh(), "should ignore existing resources cache after invalidation")
assert.Equal(c.resourceCalls, 2)
}
func TestNewCachedDiscoveryClient_TTL(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(d)
c := fakeDiscoveryClient{}
cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
cdc.ServerGroups()
assert.Equal(c.groupCalls, 1)
time.Sleep(1 * time.Second)
cdc.ServerGroups()
assert.Equal(c.groupCalls, 2)
}
func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
assert := assert.New(t)
d, err := ioutil.TempDir("", "")
assert.NoError(err)
os.RemoveAll(d)
defer os.RemoveAll(d)
c := fakeDiscoveryClient{}
cdc := newCachedDiscoveryClient(&c, d, 1*time.Nanosecond)
cdc.ServerGroups()
err = filepath.Walk(d, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
assert.Equal(os.FileMode(0750), info.Mode().Perm())
} else {
assert.Equal(os.FileMode(0660), info.Mode().Perm())
}
return nil
})
assert.NoError(err)
}
type fakeDiscoveryClient struct {
groupCalls int
resourceCalls int
versionCalls int
openAPICalls int
serverResourcesHandler func() ([]*metav1.APIResourceList, error)
}
var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
return &fake.RESTClient{}
}
func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
c.groupCalls = c.groupCalls + 1
return c.serverGroups()
}
func (c *fakeDiscoveryClient) serverGroups() (*metav1.APIGroupList, error) {
return &metav1.APIGroupList{
Groups: []metav1.APIGroup{
{
Name: "a",
Versions: []metav1.GroupVersionForDiscovery{
{
GroupVersion: "a/v1",
Version: "v1",
},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "a/v1",
Version: "v1",
},
},
},
}, nil
}
func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
if groupVersion == "a/v1" {
return &metav1.APIResourceList{APIResources: []metav1.APIResource{{Name: "widgets", Kind: "Widget"}}}, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "")
}
// Deprecated: use ServerGroupsAndResources instead.
func (c *fakeDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
_, rs, err := c.ServerGroupsAndResources()
return rs, err
}
func (c *fakeDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
gs, _ := c.serverGroups()
resultGroups := []*metav1.APIGroup{}
for i := range gs.Groups {
resultGroups = append(resultGroups, &gs.Groups[i])
}
if c.serverResourcesHandler != nil {
rs, err := c.serverResourcesHandler()
return resultGroups, rs, err
}
return resultGroups, []*metav1.APIResourceList{}, nil
}
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
return nil, nil
}
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
c.resourceCalls = c.resourceCalls + 1
return nil, nil
}
func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
c.versionCalls = c.versionCalls + 1
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
c.openAPICalls = c.openAPICalls + 1
return &openapi_v2.Document{}, nil
}

View File

@@ -1,65 +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 disk
import (
"net/http"
"os"
"path/filepath"
"github.com/gregjones/httpcache"
"github.com/gregjones/httpcache/diskcache"
"github.com/peterbourgon/diskv"
"k8s.io/klog"
)
type cacheRoundTripper struct {
rt *httpcache.Transport
}
// newCacheRoundTripper creates a roundtripper that reads the ETag on
// response headers and send the If-None-Match header on subsequent
// corresponding requests.
func newCacheRoundTripper(cacheDir string, rt http.RoundTripper) http.RoundTripper {
d := diskv.New(diskv.Options{
PathPerm: os.FileMode(0750),
FilePerm: os.FileMode(0660),
BasePath: cacheDir,
TempDir: filepath.Join(cacheDir, ".diskv-temp"),
})
t := httpcache.NewTransport(diskcache.NewWithDiskv(d))
t.Transport = rt
return &cacheRoundTripper{rt: t}
}
func (rt *cacheRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return rt.rt.RoundTrip(req)
}
func (rt *cacheRoundTripper) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(*http.Request)
}
if cr, ok := rt.rt.Transport.(canceler); ok {
cr.CancelRequest(req)
} else {
klog.Errorf("CancelRequest not implemented by %T", rt.rt.Transport)
}
}
func (rt *cacheRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt.Transport }

View File

@@ -1,147 +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 disk
import (
"bytes"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
// copied from k8s.io/client-go/transport/round_trippers_test.go
type testRoundTripper struct {
Request *http.Request
Response *http.Response
Err error
}
func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
rt.Request = req
return rt.Response, rt.Err
}
func TestCacheRoundTripper(t *testing.T) {
rt := &testRoundTripper{}
cacheDir, err := ioutil.TempDir("", "cache-rt")
defer os.RemoveAll(cacheDir)
if err != nil {
t.Fatal(err)
}
cache := newCacheRoundTripper(cacheDir, rt)
// First call, caches the response
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{Host: "localhost"},
}
rt.Response = &http.Response{
Header: http.Header{"ETag": []string{`"123456"`}},
Body: ioutil.NopCloser(bytes.NewReader([]byte("Content"))),
StatusCode: http.StatusOK,
}
resp, err := cache.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if string(content) != "Content" {
t.Errorf(`Expected Body to be "Content", got %q`, string(content))
}
// Second call, returns cached response
req = &http.Request{
Method: http.MethodGet,
URL: &url.URL{Host: "localhost"},
}
rt.Response = &http.Response{
StatusCode: http.StatusNotModified,
Body: ioutil.NopCloser(bytes.NewReader([]byte("Other Content"))),
}
resp, err = cache.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
// Read body and make sure we have the initial content
content, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatal(err)
}
if string(content) != "Content" {
t.Errorf("Invalid content read from cache %q", string(content))
}
}
func TestCacheRoundTripperPathPerm(t *testing.T) {
assert := assert.New(t)
rt := &testRoundTripper{}
cacheDir, err := ioutil.TempDir("", "cache-rt")
os.RemoveAll(cacheDir)
defer os.RemoveAll(cacheDir)
if err != nil {
t.Fatal(err)
}
cache := newCacheRoundTripper(cacheDir, rt)
// First call, caches the response
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{Host: "localhost"},
}
rt.Response = &http.Response{
Header: http.Header{"ETag": []string{`"123456"`}},
Body: ioutil.NopCloser(bytes.NewReader([]byte("Content"))),
StatusCode: http.StatusOK,
}
resp, err := cache.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if string(content) != "Content" {
t.Errorf(`Expected Body to be "Content", got %q`, string(content))
}
err = filepath.Walk(cacheDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
assert.Equal(os.FileMode(0750), info.Mode().Perm())
} else {
assert.Equal(os.FileMode(0660), info.Mode().Perm())
}
return nil
})
assert.NoError(err)
}

View File

@@ -1,30 +0,0 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package memory
import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
)
// NewMemCacheClient is DEPRECATED. Use memory.NewMemCacheClient directly.
func NewMemCacheClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
return memory.NewMemCacheClient(delegate)
}
// ErrCacheNotFound is DEPRECATED. Use memory.ErrCacheNotFound directly.
var ErrCacheNotFound = memory.ErrCacheNotFound

View File

@@ -14,19 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package memory
package cached
import (
"errors"
"fmt"
"net"
"net/url"
"sync"
"syscall"
"github.com/googleapis/gnostic/OpenAPIv2"
errorsutil "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/version"
@@ -34,107 +30,64 @@ import (
restclient "k8s.io/client-go/rest"
)
type cacheEntry struct {
resourceList *metav1.APIResourceList
err error
}
// memCacheClient can Invalidate() to stay up-to-date with discovery
// information.
//
// TODO: Switch to a watch interface. Right now it will poll after each
// Invalidate() call.
// TODO: Switch to a watch interface. Right now it will poll anytime
// Invalidate() is called.
type memCacheClient struct {
delegate discovery.DiscoveryInterface
lock sync.RWMutex
groupToServerResources map[string]*cacheEntry
groupToServerResources map[string]*metav1.APIResourceList
groupList *metav1.APIGroupList
cacheValid bool
}
// Error Constants
var (
ErrCacheEmpty = errors.New("the cache has not been filled yet")
ErrCacheNotFound = errors.New("not found")
)
var _ discovery.CachedDiscoveryInterface = &memCacheClient{}
// isTransientConnectionError checks whether given error is "Connection refused" or
// "Connection reset" error which usually means that apiserver is temporarily
// unavailable.
func isTransientConnectionError(err error) bool {
urlError, ok := err.(*url.Error)
if !ok {
return false
}
opError, ok := urlError.Err.(*net.OpError)
if !ok {
return false
}
errno, ok := opError.Err.(syscall.Errno)
if !ok {
return false
}
return errno == syscall.ECONNREFUSED || errno == syscall.ECONNRESET
}
func isTransientError(err error) bool {
if isTransientConnectionError(err) {
return true
}
if t, ok := err.(errorsutil.APIStatus); ok && t.Status().Code >= 500 {
return true
}
return errorsutil.IsTooManyRequests(err)
}
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
d.lock.Lock()
defer d.lock.Unlock()
d.lock.RLock()
defer d.lock.RUnlock()
if !d.cacheValid {
if err := d.refreshLocked(); err != nil {
return nil, err
}
return nil, ErrCacheEmpty
}
cachedVal, ok := d.groupToServerResources[groupVersion]
if !ok {
return nil, ErrCacheNotFound
}
if cachedVal.err != nil && isTransientError(cachedVal.err) {
r, err := d.serverResourcesForGroupVersion(groupVersion)
if err != nil {
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
}
cachedVal = &cacheEntry{r, err}
d.groupToServerResources[groupVersion] = cachedVal
}
return cachedVal.resourceList, cachedVal.err
return cachedVal, nil
}
// ServerResources returns the supported resources for all groups and versions.
// Deprecated: use ServerGroupsAndResources instead.
func (d *memCacheClient) ServerResources() ([]*metav1.APIResourceList, error) {
return discovery.ServerResources(d)
}
// ServerGroupsAndResources returns the groups and supported resources for all groups and versions.
func (d *memCacheClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return discovery.ServerGroupsAndResources(d)
apiGroups, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersions := metav1.ExtractGroupVersions(apiGroups)
result := []*metav1.APIResourceList{}
for _, groupVersion := range groupVersions {
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return nil, err
}
result = append(result, resources)
}
return result, nil
}
func (d *memCacheClient) ServerGroups() (*metav1.APIGroupList, error) {
d.lock.Lock()
defer d.lock.Unlock()
if !d.cacheValid {
if err := d.refreshLocked(); err != nil {
return nil, err
}
d.lock.RLock()
defer d.lock.RUnlock()
if d.groupList == nil {
return nil, ErrCacheEmpty
}
return d.groupList, nil
}
@@ -143,12 +96,18 @@ func (d *memCacheClient) RESTClient() restclient.Interface {
return d.delegate.RESTClient()
}
// TODO: Should this also be cached? The results seem more likely to be
// inconsistent with ServerGroups and ServerResources given the requirement to
// actively Invalidate.
func (d *memCacheClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return discovery.ServerPreferredResources(d)
return d.delegate.ServerPreferredResources()
}
// TODO: Should this also be cached? The results seem more likely to be
// inconsistent with ServerGroups and ServerResources given the requirement to
// actively Invalidate.
func (d *memCacheClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return discovery.ServerPreferredNamespacedResources(d)
return d.delegate.ServerPreferredNamespacedResources()
}
func (d *memCacheClient) ServerVersion() (*version.Info, error) {
@@ -162,59 +121,49 @@ func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) {
func (d *memCacheClient) Fresh() bool {
d.lock.RLock()
defer d.lock.RUnlock()
// Return whether the cache is populated at all. It is still possible that
// a single entry is missing due to transient errors and the attempt to read
// that entry will trigger retry.
// Fresh is supposed to tell the caller whether or not to retry if the cache
// fails to find something. The idea here is that Invalidate will be called
// periodically and therefore we'll always be returning the latest data. (And
// in the future we can watch and stay even more up-to-date.) So we only
// return false if the cache has never been filled.
return d.cacheValid
}
// Invalidate enforces that no cached data that is older than the current time
// is used.
// Invalidate refreshes the cache, blocking calls until the cache has been
// refreshed. It would be trivial to make a version that does this in the
// background while continuing to respond to requests if needed.
func (d *memCacheClient) Invalidate() {
d.lock.Lock()
defer d.lock.Unlock()
d.cacheValid = false
d.groupToServerResources = nil
d.groupList = nil
}
// refreshLocked refreshes the state of cache. The caller must hold d.lock for
// writing.
func (d *memCacheClient) refreshLocked() error {
// TODO: Could this multiplicative set of calls be replaced by a single call
// to ServerResources? If it's possible for more than one resulting
// APIResourceList to have the same GroupVersion, the lists would need merged.
gl, err := d.delegate.ServerGroups()
if err != nil || len(gl.Groups) == 0 {
utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list: %v", err))
return err
utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list; will keep using cached value. (%v)", err))
return
}
rl := map[string]*cacheEntry{}
rl := map[string]*metav1.APIResourceList{}
for _, g := range gl.Groups {
for _, v := range g.Versions {
r, err := d.serverResourcesForGroupVersion(v.GroupVersion)
rl[v.GroupVersion] = &cacheEntry{r, err}
if err != nil {
r, err := d.delegate.ServerResourcesForGroupVersion(v.GroupVersion)
if err != nil || len(r.APIResources) == 0 {
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", v.GroupVersion, err))
if cur, ok := d.groupToServerResources[v.GroupVersion]; ok {
// retain the existing list, if we had it.
r = cur
} else {
continue
}
}
rl[v.GroupVersion] = r
}
}
d.groupToServerResources, d.groupList = rl, gl
d.cacheValid = true
return nil
}
func (d *memCacheClient) serverResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
r, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return r, err
}
if len(r.APIResources) == 0 {
return r, fmt.Errorf("Got empty response for: %v", groupVersion)
}
return r, nil
}
// NewMemCacheClient creates a new CachedDiscoveryInterface which caches
@@ -225,6 +174,6 @@ func (d *memCacheClient) serverResourcesForGroupVersion(groupVersion string) (*m
func NewMemCacheClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
return &memCacheClient{
delegate: delegate,
groupToServerResources: map[string]*cacheEntry{},
groupToServerResources: map[string]*metav1.APIResourceList{},
}
}

View File

@@ -0,0 +1,132 @@
/*
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 cached
import (
"errors"
"reflect"
"sync"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery/fake"
)
type fakeDiscovery struct {
*fake.FakeDiscovery
lock sync.Mutex
groupList *metav1.APIGroupList
resourceMap map[string]*metav1.APIResourceList
}
func (c *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
c.lock.Lock()
defer c.lock.Unlock()
if rl, ok := c.resourceMap[groupVersion]; ok {
return rl, nil
}
return nil, errors.New("doesn't exist")
}
func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
c.lock.Lock()
defer c.lock.Unlock()
if c.groupList == nil {
return nil, errors.New("doesn't exist")
}
return c.groupList, nil
}
func TestClient(t *testing.T) {
fake := &fakeDiscovery{
groupList: &metav1.APIGroupList{
Groups: []metav1.APIGroup{{
Name: "astronomy",
Versions: []metav1.GroupVersionForDiscovery{{
GroupVersion: "astronomy/v8beta1",
Version: "v8beta1",
}},
}},
},
resourceMap: map[string]*metav1.APIResourceList{
"astronomy/v8beta1": {
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
},
}
c := NewMemCacheClient(fake)
g, err := c.ServerGroups()
if err == nil {
t.Errorf("Unexpected non-error.")
}
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
c.Invalidate()
if !c.Fresh() {
t.Errorf("Expected fresh.")
}
g, err = c.ServerGroups()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"], r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
fake.lock.Lock()
fake.resourceMap = map[string]*metav1.APIResourceList{
"astronomy/v8beta1": {
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "stars",
SingularName: "star",
Namespaced: true,
Kind: "Star",
ShortNames: []string{"s"},
}},
},
}
fake.lock.Unlock()
c.Invalidate()
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"], r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}

View File

@@ -1,377 +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 memory
import (
"errors"
"net/http"
"reflect"
"sync"
"testing"
errorsutil "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery/fake"
)
type resourceMapEntry struct {
list *metav1.APIResourceList
err error
}
type fakeDiscovery struct {
*fake.FakeDiscovery
lock sync.Mutex
groupList *metav1.APIGroupList
groupListErr error
resourceMap map[string]*resourceMapEntry
}
func (c *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
c.lock.Lock()
defer c.lock.Unlock()
if rl, ok := c.resourceMap[groupVersion]; ok {
return rl.list, rl.err
}
return nil, errors.New("doesn't exist")
}
func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
c.lock.Lock()
defer c.lock.Unlock()
if c.groupList == nil {
return nil, errors.New("doesn't exist")
}
return c.groupList, c.groupListErr
}
func TestClient(t *testing.T) {
fake := &fakeDiscovery{
groupList: &metav1.APIGroupList{
Groups: []metav1.APIGroup{{
Name: "astronomy",
Versions: []metav1.GroupVersionForDiscovery{{
GroupVersion: "astronomy/v8beta1",
Version: "v8beta1",
}},
}},
},
resourceMap: map[string]*resourceMapEntry{
"astronomy/v8beta1": {
list: &metav1.APIResourceList{
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
},
},
}
c := NewMemCacheClient(fake)
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
g, err := c.ServerGroups()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !c.Fresh() {
t.Errorf("Expected fresh.")
}
c.Invalidate()
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
g, err = c.ServerGroups()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
if !c.Fresh() {
t.Errorf("Expected fresh.")
}
r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
fake.lock.Lock()
fake.resourceMap = map[string]*resourceMapEntry{
"astronomy/v8beta1": {
list: &metav1.APIResourceList{
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "stars",
SingularName: "star",
Namespaced: true,
Kind: "Star",
ShortNames: []string{"s"},
}},
},
},
}
fake.lock.Unlock()
c.Invalidate()
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}
func TestServerGroupsFails(t *testing.T) {
fake := &fakeDiscovery{
groupList: &metav1.APIGroupList{
Groups: []metav1.APIGroup{{
Name: "astronomy",
Versions: []metav1.GroupVersionForDiscovery{{
GroupVersion: "astronomy/v8beta1",
Version: "v8beta1",
}},
}},
},
groupListErr: errors.New("some error"),
resourceMap: map[string]*resourceMapEntry{
"astronomy/v8beta1": {
list: &metav1.APIResourceList{
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
},
},
}
c := NewMemCacheClient(fake)
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
_, err := c.ServerGroups()
if err == nil {
t.Errorf("Expected error")
}
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
fake.lock.Lock()
fake.groupListErr = nil
fake.lock.Unlock()
r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
if !c.Fresh() {
t.Errorf("Expected not fresh.")
}
}
func TestPartialPermanentFailure(t *testing.T) {
fake := &fakeDiscovery{
groupList: &metav1.APIGroupList{
Groups: []metav1.APIGroup{{
Name: "astronomy",
Versions: []metav1.GroupVersionForDiscovery{{
GroupVersion: "astronomy/v8beta1",
Version: "v8beta1",
}, {
GroupVersion: "astronomy2/v8beta1",
Version: "v8beta1",
}},
}},
},
resourceMap: map[string]*resourceMapEntry{
"astronomy/v8beta1": {
err: errors.New("some permanent error"),
},
"astronomy2/v8beta1": {
list: &metav1.APIResourceList{
GroupVersion: "astronomy2/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
},
},
}
c := NewMemCacheClient(fake)
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
_, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err == nil {
t.Errorf("Expected error, got nil")
}
fake.lock.Lock()
fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{
list: &metav1.APIResourceList{
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
err: nil,
}
fake.lock.Unlock()
// We don't retry permanent errors, so it should fail.
_, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err == nil {
t.Errorf("Expected error, got nil")
}
c.Invalidate()
// After Invalidate, we should retry.
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}
func TestPartialRetryableFailure(t *testing.T) {
fake := &fakeDiscovery{
groupList: &metav1.APIGroupList{
Groups: []metav1.APIGroup{{
Name: "astronomy",
Versions: []metav1.GroupVersionForDiscovery{{
GroupVersion: "astronomy/v8beta1",
Version: "v8beta1",
}, {
GroupVersion: "astronomy2/v8beta1",
Version: "v8beta1",
}},
}},
},
resourceMap: map[string]*resourceMapEntry{
"astronomy/v8beta1": {
err: &errorsutil.StatusError{
ErrStatus: metav1.Status{
Message: "Some retryable error",
Code: int32(http.StatusServiceUnavailable),
Reason: metav1.StatusReasonServiceUnavailable,
},
},
},
"astronomy2/v8beta1": {
list: &metav1.APIResourceList{
GroupVersion: "astronomy2/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
},
},
}
c := NewMemCacheClient(fake)
if c.Fresh() {
t.Errorf("Expected not fresh.")
}
r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
_, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err == nil {
t.Errorf("Expected error, got nil")
}
fake.lock.Lock()
fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{
list: &metav1.APIResourceList{
GroupVersion: "astronomy/v8beta1",
APIResources: []metav1.APIResource{{
Name: "dwarfplanets",
SingularName: "dwarfplanet",
Namespaced: true,
Kind: "DwarfPlanet",
ShortNames: []string{"dp"},
}},
},
err: nil,
}
fake.lock.Unlock()
// We should retry retryable error even without Invalidate() being called,
// so no error is expected.
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
// Check that the last result was cached and we don't retry further.
fake.lock.Lock()
fake.resourceMap["astronomy/v8beta1"].err = errors.New("some permanent error")
fake.lock.Unlock()
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}

View File

@@ -22,18 +22,16 @@ import (
"net/url"
"sort"
"strings"
"sync"
"time"
"github.com/golang/protobuf/proto"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes/scheme"
restclient "k8s.io/client-go/rest"
@@ -44,8 +42,12 @@ const (
defaultRetries = 2
// protobuf mime type
mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"
)
var (
// defaultTimeout is the maximum amount of time per request when no timeout has been set on a RESTClient.
// Defaults to 32s in order to have a distinguishable length of time, relative to other timeouts that exist.
// It's a variable to be able to change it in tests.
defaultTimeout = 32 * time.Second
)
@@ -60,9 +62,6 @@ type DiscoveryInterface interface {
}
// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness.
// Note that If the ServerResourcesForGroupVersion method returns a cache miss
// error, the user needs to explicitly call Invalidate to clear the cache,
// otherwise the same cache miss error will be returned next time.
type CachedDiscoveryInterface interface {
DiscoveryInterface
// Fresh is supposed to tell the caller whether or not to retry if the cache
@@ -71,8 +70,7 @@ type CachedDiscoveryInterface interface {
// TODO: this needs to be revisited, this interface can't be locked properly
// and doesn't make a lot of sense.
Fresh() bool
// Invalidate enforces that no cached data that is older than the current time
// is used.
// Invalidate enforces that no cached data is used in the future that is older than the current time.
Invalidate()
}
@@ -88,28 +86,12 @@ type ServerResourcesInterface interface {
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
// ServerResources returns the supported resources for all groups and versions.
//
// The returned resource list might be non-nil with partial results even in the case of
// non-nil error.
//
// Deprecated: use ServerGroupsAndResources instead.
ServerResources() ([]*metav1.APIResourceList, error)
// ServerResources returns the supported groups and resources for all groups and versions.
//
// The returned group and resource lists might be non-nil with partial results even in the
// case of non-nil error.
ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)
// ServerPreferredResources returns the supported resources with the version preferred by the
// server.
//
// The returned group and resource lists might be non-nil with partial results even in the
// case of non-nil error.
ServerPreferredResources() ([]*metav1.APIResourceList, error)
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
// version preferred by the server.
//
// The returned resource list might be non-nil with partial results even in the case of
// non-nil error.
ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
}
@@ -207,18 +189,40 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
return resources, nil
}
// ServerResources returns the supported resources for all groups and versions.
// Deprecated: use ServerGroupsAndResources instead.
func (d *DiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
_, rs, err := d.ServerGroupsAndResources()
return rs, err
// serverResources returns the supported resources for all groups and versions.
func (d *DiscoveryClient) serverResources() ([]*metav1.APIResourceList, error) {
apiGroups, err := d.ServerGroups()
if err != nil {
return nil, err
}
result := []*metav1.APIResourceList{}
failedGroups := make(map[schema.GroupVersion]error)
for _, apiGroup := range apiGroups.Groups {
for _, version := range apiGroup.Versions {
gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
resources, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
if err != nil {
// TODO: maybe restrict this to NotFound errors
failedGroups[gv] = err
continue
}
result = append(result, resources)
}
}
if len(failedGroups) == 0 {
return result, nil
}
return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
// ServerGroupsAndResources returns the supported resources for all groups and versions.
func (d *DiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return ServerGroupsAndResources(d)
})
// ServerResources returns the supported resources for all groups and versions.
func (d *DiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
return withRetries(defaultRetries, d.serverResources)
}
// ErrGroupDiscoveryFailed is returned if one or more API groups fail to load.
@@ -244,72 +248,36 @@ func IsGroupDiscoveryFailedError(err error) bool {
return err != nil && ok
}
// ServerResources uses the provided discovery interface to look up supported resources for all groups and versions.
// Deprecated: use ServerGroupsAndResources instead.
func ServerResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
_, rs, err := ServerGroupsAndResources(d)
return rs, err
}
func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
sgs, err := d.ServerGroups()
if sgs == nil {
return nil, nil, err
}
resultGroups := []*metav1.APIGroup{}
for i := range sgs.Groups {
resultGroups = append(resultGroups, &sgs.Groups[i])
}
groupVersionResources, failedGroups := fetchGroupVersionResources(d, sgs)
// order results by group/version discovery order
result := []*metav1.APIResourceList{}
for _, apiGroup := range sgs.Groups {
for _, version := range apiGroup.Versions {
gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
if resources, ok := groupVersionResources[gv]; ok {
result = append(result, resources)
}
}
}
if len(failedGroups) == 0 {
return resultGroups, result, nil
}
return resultGroups, result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
// ServerPreferredResources uses the provided discovery interface to look up preferred resources
func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
// serverPreferredResources returns the supported resources with the version preferred by the server.
func (d *DiscoveryClient) serverPreferredResources() ([]*metav1.APIResourceList, error) {
serverGroupList, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersionResources, failedGroups := fetchGroupVersionResources(d, serverGroupList)
result := []*metav1.APIResourceList{}
failedGroups := make(map[schema.GroupVersion]error)
grVersions := map[schema.GroupResource]string{} // selected version of a GroupResource
grAPIResources := map[schema.GroupResource]*metav1.APIResource{} // selected APIResource for a GroupResource
gvAPIResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // blueprint for a APIResourceList for later grouping
grApiResources := map[schema.GroupResource]*metav1.APIResource{} // selected APIResource for a GroupResource
gvApiResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // blueprint for a APIResourceList for later grouping
for _, apiGroup := range serverGroupList.Groups {
for _, version := range apiGroup.Versions {
groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
apiResourceList, ok := groupVersionResources[groupVersion]
if !ok {
apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
if err != nil {
// TODO: maybe restrict this to NotFound errors
failedGroups[groupVersion] = err
continue
}
// create empty list which is filled later in another loop
emptyAPIResourceList := metav1.APIResourceList{
emptyApiResourceList := metav1.APIResourceList{
GroupVersion: version.GroupVersion,
}
gvAPIResourceLists[groupVersion] = &emptyAPIResourceList
result = append(result, &emptyAPIResourceList)
gvApiResourceLists[groupVersion] = &emptyApiResourceList
result = append(result, &emptyApiResourceList)
for i := range apiResourceList.APIResources {
apiResource := &apiResourceList.APIResources[i]
@@ -317,21 +285,21 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList,
continue
}
gv := schema.GroupResource{Group: apiGroup.Name, Resource: apiResource.Name}
if _, ok := grAPIResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {
if _, ok := grApiResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {
// only override with preferred version
continue
}
grVersions[gv] = version.Version
grAPIResources[gv] = apiResource
grApiResources[gv] = apiResource
}
}
}
// group selected APIResources according to GroupVersion into APIResourceLists
for groupResource, apiResource := range grAPIResources {
for groupResource, apiResource := range grApiResources {
version := grVersions[groupResource]
groupVersion := schema.GroupVersion{Group: groupResource.Group, Version: version}
apiResourceList := gvAPIResourceLists[groupVersion]
apiResourceList := gvApiResourceLists[groupVersion]
apiResourceList.APIResources = append(apiResourceList.APIResources, *apiResource)
}
@@ -342,62 +310,16 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList,
return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}
// fetchServerResourcesForGroupVersions uses the discovery client to fetch the resources for the specified groups in parallel.
func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) {
groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList)
failedGroups := make(map[schema.GroupVersion]error)
wg := &sync.WaitGroup{}
resultLock := &sync.Mutex{}
for _, apiGroup := range apiGroups.Groups {
for _, version := range apiGroup.Versions {
groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
wg.Add(1)
go func() {
defer wg.Done()
defer utilruntime.HandleCrash()
apiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String())
// lock to record results
resultLock.Lock()
defer resultLock.Unlock()
if err != nil {
// TODO: maybe restrict this to NotFound errors
failedGroups[groupVersion] = err
}
if apiResourceList != nil {
// even in case of error, some fallback might have been returned
groupVersionResources[groupVersion] = apiResourceList
}
}()
}
}
wg.Wait()
return groupVersionResources, failedGroups
}
// ServerPreferredResources returns the supported resources with the version preferred by the
// server.
func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
_, rs, err := withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
rs, err := ServerPreferredResources(d)
return nil, rs, err
})
return rs, err
return withRetries(defaultRetries, d.serverPreferredResources)
}
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
// version preferred by the server.
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return ServerPreferredNamespacedResources(d)
}
// ServerPreferredNamespacedResources uses the provided discovery interface to look up preferred namespaced resources
func ServerPreferredNamespacedResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {
all, err := ServerPreferredResources(d)
all, err := d.ServerPreferredResources()
return FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {
return r.Namespaced
}), all), err
@@ -412,7 +334,7 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
var info version.Info
err = json.Unmarshal(body, &info)
if err != nil {
return nil, fmt.Errorf("unable to parse the server version: %v", err)
return nil, fmt.Errorf("got '%s': %v", string(body), err)
}
return &info, nil
}
@@ -423,7 +345,7 @@ func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
if err != nil {
if errors.IsForbidden(err) || errors.IsNotFound(err) || errors.IsNotAcceptable(err) {
// single endpoint not found/registered in old server, try to fetch old endpoint
// TODO: remove this when kubectl/client-go don't work with 1.9 server
// TODO(roycaihw): remove this in 1.11
data, err = d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do().Raw()
if err != nil {
return nil, err
@@ -441,20 +363,19 @@ func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
}
// withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns.
func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
func withRetries(maxRetries int, f func() ([]*metav1.APIResourceList, error)) ([]*metav1.APIResourceList, error) {
var result []*metav1.APIResourceList
var resultGroups []*metav1.APIGroup
var err error
for i := 0; i < maxRetries; i++ {
resultGroups, result, err = f()
result, err = f()
if err == nil {
return resultGroups, result, nil
return result, nil
}
if _, ok := err.(*ErrGroupDiscoveryFailed); !ok {
return nil, nil, err
return nil, err
}
}
return resultGroups, result, err
return result, err
}
func setDiscoveryDefaults(config *restclient.Config) error {
@@ -500,9 +421,9 @@ func NewDiscoveryClient(c restclient.Interface) *DiscoveryClient {
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (d *DiscoveryClient) RESTClient() restclient.Interface {
if d == nil {
func (c *DiscoveryClient) RESTClient() restclient.Interface {
if c == nil {
return nil
}
return d.restClient
return c.restClient
}

View File

@@ -23,15 +23,15 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
"github.com/gogo/protobuf/proto"
"github.com/googleapis/gnostic/OpenAPIv2"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/version"
restclient "k8s.io/client-go/rest"
@@ -130,11 +130,25 @@ func TestGetServerGroupsWithBrokenServer(t *testing.T) {
}
}
}
func TestTimeoutIsSet(t *testing.T) {
cfg := &restclient.Config{}
setDiscoveryDefaults(cfg)
assert.Equal(t, defaultTimeout, cfg.Timeout)
func TestGetServerGroupsWithTimeout(t *testing.T) {
done := make(chan bool)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// first we need to write headers, otherwise http client will complain about
// exceeding timeout awaiting headers, only after we can block the call
w.WriteHeader(http.StatusOK)
<-done
}))
defer server.Close()
defer close(done)
tmp := defaultTimeout
defaultTimeout = 2 * time.Second
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
_, err := client.ServerGroups()
if err == nil || !strings.Contains(err.Error(), "deadline") {
t.Fatalf("unexpected error: %v", err)
}
done <- true
defaultTimeout = tmp
}
func TestGetServerResourcesWithV1Server(t *testing.T) {
@@ -190,14 +204,6 @@ func TestGetServerResources(t *testing.T) {
{Name: "jobs", Namespaced: true, Kind: "Job"},
},
}
beta2 := metav1.APIResourceList{
GroupVersion: "extensions/v1beta2",
APIResources: []metav1.APIResource{
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
{Name: "jobs", Namespaced: true, Kind: "Job"},
},
}
tests := []struct {
resourcesList *metav1.APIResourceList
path string
@@ -230,8 +236,6 @@ func TestGetServerResources(t *testing.T) {
list = &stable
case "/apis/extensions/v1beta1":
list = &beta
case "/apis/extensions/v1beta2":
list = &beta2
case "/api":
list = &metav1.APIVersions{
Versions: []string{
@@ -242,10 +246,8 @@ func TestGetServerResources(t *testing.T) {
list = &metav1.APIGroupList{
Groups: []metav1.APIGroup{
{
Name: "extensions",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "extensions/v1beta1", Version: "v1beta1"},
{GroupVersion: "extensions/v1beta2", Version: "v1beta2"},
{GroupVersion: "extensions/v1beta1"},
},
},
},
@@ -287,10 +289,11 @@ func TestGetServerResources(t *testing.T) {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
serverGroupVersions := groupVersions(serverResources)
expectedGroupVersions := []string{"v1", "extensions/v1beta1", "extensions/v1beta2"}
if !reflect.DeepEqual(expectedGroupVersions, serverGroupVersions) {
t.Errorf("unexpected group versions: %v", diff.ObjectReflectDiff(expectedGroupVersions, serverGroupVersions))
serverGroupVersions := sets.NewString(groupVersions(serverResources)...)
for _, api := range []string{"v1", "extensions/v1beta1"} {
if !serverGroupVersions.Has(api) {
t.Errorf("missing expected api %q in %v", api, serverResources)
}
}
}
@@ -636,7 +639,7 @@ func TestServerPreferredResourcesRetries(t *testing.T) {
{
Name: "extensions",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "extensions/v1beta1", Version: "v1beta1"},
{GroupVersion: "extensions/v1beta1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "extensions/v1beta1",

View File

@@ -1,19 +0,0 @@
/*
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 discovery provides ways to discover server-supported
// API groups, versions and resources.
package discovery

View File

@@ -36,8 +36,6 @@ type FakeDiscovery struct {
FakedServerVersion *version.Info
}
// ServerResourcesForGroupVersion returns the supported resources for a group
// and version.
func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
action := testing.ActionImpl{
Verb: "get",
@@ -52,46 +50,23 @@ func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*me
return nil, fmt.Errorf("GroupVersion %q not found", groupVersion)
}
// ServerResources returns the supported resources for all groups and versions.
// Deprecated: use ServerGroupsAndResources instead.
func (c *FakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
_, rs, err := c.ServerGroupsAndResources()
return rs, err
}
// ServerGroupsAndResources returns the supported groups and resources for all groups and versions.
func (c *FakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
sgs, err := c.ServerGroups()
if err != nil {
return nil, nil, err
}
resultGroups := []*metav1.APIGroup{}
for i := range sgs.Groups {
resultGroups = append(resultGroups, &sgs.Groups[i])
}
action := testing.ActionImpl{
Verb: "get",
Resource: schema.GroupVersionResource{Resource: "resource"},
}
c.Invokes(action, nil)
return resultGroups, c.Resources, nil
return c.Resources, nil
}
// ServerPreferredResources returns the supported resources with the version
// preferred by the server.
func (c *FakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
// ServerPreferredNamespacedResources returns the supported namespaced resources
// with the version preferred by the server.
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
// ServerGroups returns the supported groups, with information like supported
// versions and the preferred version.
func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
action := testing.ActionImpl{
Verb: "get",
@@ -133,7 +108,6 @@ func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
}
// ServerVersion retrieves and parses the server's version.
func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
action := testing.ActionImpl{}
action.Verb = "get"
@@ -148,13 +122,10 @@ func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
return &versionInfo, nil
}
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}
// RESTClient returns a RESTClient that is used to communicate with API server
// by this client implementation.
func (c *FakeDiscovery) RESTClient() restclient.Interface {
return nil
}

View File

@@ -31,11 +31,11 @@ import (
func MatchesServerVersion(clientVersion apimachineryversion.Info, client DiscoveryInterface) error {
sVer, err := client.ServerVersion()
if err != nil {
return fmt.Errorf("couldn't read version from server: %v", err)
return fmt.Errorf("couldn't read version from server: %v\n", err)
}
// GitVersion includes GitCommit and GitTreeState, but best to be safe?
if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit || clientVersion.GitTreeState != sVer.GitTreeState {
return fmt.Errorf("server version (%#v) differs from client version (%#v)", sVer, clientVersion)
return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", sVer, clientVersion)
}
return nil
@@ -101,15 +101,12 @@ func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1
return result
}
// ResourcePredicate has a method to check if a resource matches a given condition.
type ResourcePredicate interface {
Match(groupVersion string, r *metav1.APIResource) bool
}
// ResourcePredicateFunc returns true if it matches a resource based on a custom condition.
type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool
// Match is a wrapper around ResourcePredicateFunc.
func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool {
return fn(groupVersion, r)
}
@@ -119,7 +116,6 @@ type SupportsAllVerbs struct {
Verbs []string
}
// Match checks if a resource contains all the given verbs.
func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool {
return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...)
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package restmapper
package discovery
import (
"fmt"
@@ -24,9 +24,8 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/klog"
"github.com/golang/glog"
)
// APIGroupResources is an API group with a mapping of versions to
@@ -38,9 +37,9 @@ type APIGroupResources struct {
VersionedResources map[string][]metav1.APIResource
}
// NewDiscoveryRESTMapper returns a PriorityRESTMapper based on the discovered
// NewRESTMapper returns a PriorityRESTMapper based on the discovered
// groups and resources passed in.
func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper {
func NewRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper {
unionMapper := meta.MultiRESTMapper{}
var groupPriority []string
@@ -99,20 +98,18 @@ func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper
scope = meta.RESTScopeRoot
}
// if we have a slash, then this is a subresource and we shouldn't create mappings for those.
if strings.Contains(resource.Name, "/") {
// this is for legacy resources and servers which don't list singular forms. For those we must still guess.
if len(resource.SingularName) == 0 {
versionMapper.Add(gv.WithKind(resource.Kind), scope)
// TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior
versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
continue
}
plural := gv.WithResource(resource.Name)
singular := gv.WithResource(resource.SingularName)
// this is for legacy resources and servers which don't list singular forms. For those we must still guess.
if len(resource.SingularName) == 0 {
_, singular = meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind))
}
versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope)
versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope)
versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope)
// TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior
versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
}
@@ -144,27 +141,28 @@ func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper
// GetAPIGroupResources uses the provided discovery client to gather
// discovery information and populate a slice of APIGroupResources.
func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources, error) {
gs, rs, err := cl.ServerGroupsAndResources()
if rs == nil || gs == nil {
return nil, err
func GetAPIGroupResources(cl DiscoveryInterface) ([]*APIGroupResources, error) {
apiGroups, err := cl.ServerGroups()
if err != nil {
if apiGroups == nil || len(apiGroups.Groups) == 0 {
return nil, err
}
// TODO track the errors and update callers to handle partial errors.
}
rsm := map[string]*metav1.APIResourceList{}
for _, r := range rs {
rsm[r.GroupVersion] = r
}
var result []*APIGroupResources
for _, group := range gs {
for _, group := range apiGroups.Groups {
groupResources := &APIGroupResources{
Group: *group,
Group: group,
VersionedResources: make(map[string][]metav1.APIResource),
}
for _, version := range group.Versions {
resources, ok := rsm[version.GroupVersion]
if !ok {
continue
resources, err := cl.ServerResourcesForGroupVersion(version.GroupVersion)
if err != nil {
// continue as best we can
// TODO track the errors and update callers to handle partial errors.
if resources == nil || len(resources.APIResources) == 0 {
continue
}
}
groupResources.VersionedResources[version.Version] = resources.APIResources
}
@@ -179,13 +177,13 @@ func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources
type DeferredDiscoveryRESTMapper struct {
initMu sync.Mutex
delegate meta.RESTMapper
cl discovery.CachedDiscoveryInterface
cl CachedDiscoveryInterface
}
// NewDeferredDiscoveryRESTMapper returns a
// DeferredDiscoveryRESTMapper that will lazily query the provided
// client for discovery information to do REST mappings.
func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
func NewDeferredDiscoveryRESTMapper(cl CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
return &DeferredDiscoveryRESTMapper{
cl: cl,
}
@@ -204,14 +202,14 @@ func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
return nil, err
}
d.delegate = NewDiscoveryRESTMapper(groupResources)
d.delegate = NewRESTMapper(groupResources)
return d.delegate, err
}
// Reset resets the internally cached Discovery information and will
// cause the next mapping request to re-discover.
func (d *DeferredDiscoveryRESTMapper) Reset() {
klog.V(5).Info("Invalidating discovery information")
glog.V(5).Info("Invalidating discovery information")
d.initMu.Lock()
defer d.initMu.Unlock()

View File

@@ -14,14 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package restmapper
package discovery_test
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -96,7 +94,7 @@ func TestRESTMapper(t *testing.T) {
},
}
restMapper := NewDiscoveryRESTMapper(resources)
restMapper := NewRESTMapper(resources)
kindTCs := []struct {
input schema.GroupVersionResource
@@ -285,146 +283,10 @@ func TestDeferredDiscoveryRESTMapper_CacheMiss(t *testing.T) {
assert.Equal(cdc.invalidateCalls, 2, "should HAVE called Invalidate() again after another cache-miss, but with fresh==false")
}
func TestGetAPIGroupResources(t *testing.T) {
type Test struct {
name string
discovery DiscoveryInterface
expected []*APIGroupResources
expectedError error
}
for _, test := range []Test{
{"nil", &fakeFailingDiscovery{nil, nil, nil, nil}, nil, nil},
{"normal",
&fakeFailingDiscovery{
[]metav1.APIGroup{aGroup, bGroup}, nil,
map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, nil,
},
[]*APIGroupResources{
{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
}, nil,
},
{"groups failed, but has fallback with a only",
&fakeFailingDiscovery{
[]metav1.APIGroup{aGroup}, fmt.Errorf("error fetching groups"),
map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, nil,
},
[]*APIGroupResources{
{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
}, nil,
},
{"groups failed, but has no fallback",
&fakeFailingDiscovery{
nil, fmt.Errorf("error fetching groups"),
map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, nil,
},
nil, fmt.Errorf("error fetching groups"),
},
{"a failed, but has fallback",
&fakeFailingDiscovery{
[]metav1.APIGroup{aGroup, bGroup}, nil,
map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, map[string]error{"a/v1": fmt.Errorf("a failed")},
},
[]*APIGroupResources{
{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
}, nil, // TODO: do we want this?
},
{"a failed, but has no fallback",
&fakeFailingDiscovery{
[]metav1.APIGroup{aGroup, bGroup}, nil,
map[string]*metav1.APIResourceList{"b/v1": &bResources}, map[string]error{"a/v1": fmt.Errorf("a failed")},
},
[]*APIGroupResources{
{aGroup, map[string][]metav1.APIResource{}},
{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
}, nil, // TODO: do we want this?
},
{"a and b failed, but have fallbacks",
&fakeFailingDiscovery{
[]metav1.APIGroup{aGroup, bGroup}, nil,
map[string]*metav1.APIResourceList{"a/v1": &aResources, "b/v1": &bResources}, // TODO: both fallbacks are ignored
map[string]error{"a/v1": fmt.Errorf("a failed"), "b/v1": fmt.Errorf("b failed")},
},
[]*APIGroupResources{
{aGroup, map[string][]metav1.APIResource{"v1": {aFoo}}},
{bGroup, map[string][]metav1.APIResource{"v1": {bBar}}},
}, nil, // TODO: do we want this?
},
} {
t.Run(test.name, func(t *testing.T) {
got, err := GetAPIGroupResources(test.discovery)
if err == nil && test.expectedError != nil {
t.Fatalf("expected error %q, but got none", test.expectedError)
} else if err != nil && test.expectedError == nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expected, got) {
t.Errorf("unexpected result:\nexpected = %s\ngot = %s", spew.Sdump(test.expected), spew.Sdump(got))
}
})
}
}
var _ DiscoveryInterface = &fakeFailingDiscovery{}
type fakeFailingDiscovery struct {
groups []metav1.APIGroup
groupsErr error
resourcesForGroupVersion map[string]*metav1.APIResourceList
resourcesForGroupVersionErr map[string]error
}
func (*fakeFailingDiscovery) RESTClient() restclient.Interface {
return nil
}
func (d *fakeFailingDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
if d.groups == nil && d.groupsErr != nil {
return nil, d.groupsErr
}
return &metav1.APIGroupList{Groups: d.groups}, d.groupsErr
}
func (d *fakeFailingDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return ServerGroupsAndResources(d)
}
func (d *fakeFailingDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
if rs, found := d.resourcesForGroupVersion[groupVersion]; found {
return rs, d.resourcesForGroupVersionErr[groupVersion]
}
return nil, fmt.Errorf("not found")
}
func (d *fakeFailingDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
return ServerResources(d)
}
func (d *fakeFailingDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return ServerPreferredResources(d)
}
func (d *fakeFailingDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return ServerPreferredNamespacedResources(d)
}
func (*fakeFailingDiscovery) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (*fakeFailingDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
panic("implement me")
}
type fakeCachedDiscoveryInterface struct {
invalidateCalls int
fresh bool
enabledGroupA bool
enabledA bool
}
var _ CachedDiscoveryInterface = &fakeCachedDiscoveryInterface{}
@@ -436,7 +298,7 @@ func (c *fakeCachedDiscoveryInterface) Fresh() bool {
func (c *fakeCachedDiscoveryInterface) Invalidate() {
c.invalidateCalls = c.invalidateCalls + 1
c.fresh = true
c.enabledGroupA = true
c.enabledA = true
}
func (c *fakeCachedDiscoveryInterface) RESTClient() restclient.Interface {
@@ -444,33 +306,55 @@ func (c *fakeCachedDiscoveryInterface) RESTClient() restclient.Interface {
}
func (c *fakeCachedDiscoveryInterface) ServerGroups() (*metav1.APIGroupList, error) {
if c.enabledGroupA {
if c.enabledA {
return &metav1.APIGroupList{
Groups: []metav1.APIGroup{aGroup},
Groups: []metav1.APIGroup{
{
Name: "a",
Versions: []metav1.GroupVersionForDiscovery{
{
GroupVersion: "a/v1",
Version: "v1",
},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "a/v1",
Version: "v1",
},
},
},
}, nil
}
return &metav1.APIGroupList{}, nil
}
func (c *fakeCachedDiscoveryInterface) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return ServerGroupsAndResources(c)
}
func (c *fakeCachedDiscoveryInterface) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
if c.enabledGroupA && groupVersion == "a/v1" {
return &aResources, nil
if c.enabledA && groupVersion == "a/v1" {
return &metav1.APIResourceList{
GroupVersion: "a/v1",
APIResources: []metav1.APIResource{
{
Name: "foo",
Kind: "Foo",
Namespaced: false,
},
},
}, nil
}
return nil, errors.NewNotFound(schema.GroupResource{}, "")
}
// Deprecated: use ServerGroupsAndResources instead.
func (c *fakeCachedDiscoveryInterface) ServerResources() ([]*metav1.APIResourceList, error) {
return ServerResources(c)
if c.enabledA {
av1, _ := c.ServerResourcesForGroupVersion("a/v1")
return []*metav1.APIResourceList{av1}, nil
}
return []*metav1.APIResourceList{}, nil
}
func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
if c.enabledGroupA {
if c.enabledA {
return []*metav1.APIResourceList{
{
GroupVersion: "a/v1",
@@ -498,50 +382,3 @@ func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) {
func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}
var (
aGroup = metav1.APIGroup{
Name: "a",
Versions: []metav1.GroupVersionForDiscovery{
{
GroupVersion: "a/v1",
Version: "v1",
},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "a/v1",
Version: "v1",
},
}
bGroup = metav1.APIGroup{
Name: "b",
Versions: []metav1.GroupVersionForDiscovery{
{
GroupVersion: "b/v1",
Version: "v1",
},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "b/v1",
Version: "v1",
},
}
aResources = metav1.APIResourceList{
GroupVersion: "a/v1",
APIResources: []metav1.APIResource{aFoo},
}
aFoo = metav1.APIResource{
Name: "foo",
Kind: "Foo",
Namespaced: false,
}
bResources = metav1.APIResourceList{
GroupVersion: "b/v1",
APIResources: []metav1.APIResource{bBar},
}
bBar = metav1.APIResource{
Name: "bar",
Kind: "Bar",
Namespaced: true,
}
)

95
discovery/unstructured.go Normal file
View File

@@ -0,0 +1,95 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package discovery
import (
"reflect"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// UnstructuredObjectTyper provides a runtime.ObjectTyper implementation for
// runtime.Unstructured object based on discovery information.
type UnstructuredObjectTyper struct {
registered map[schema.GroupVersionKind]bool
typers []runtime.ObjectTyper
}
// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for
// unstructured objects based on discovery information. It accepts a list of fallback typers
// for handling objects that are not runtime.Unstructured. It does not delegate the Recognizes
// check, only ObjectKinds.
func NewUnstructuredObjectTyper(groupResources []*APIGroupResources, typers ...runtime.ObjectTyper) *UnstructuredObjectTyper {
dot := &UnstructuredObjectTyper{
registered: make(map[schema.GroupVersionKind]bool),
typers: typers,
}
for _, group := range groupResources {
for _, discoveryVersion := range group.Group.Versions {
resources, ok := group.VersionedResources[discoveryVersion.Version]
if !ok {
continue
}
gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
for _, resource := range resources {
dot.registered[gv.WithKind(resource.Kind)] = true
}
}
}
return dot
}
// ObjectKinds returns a slice of one element with the group,version,kind of the
// provided object, or an error if the object is not runtime.Unstructured or
// has no group,version,kind information. unversionedType will always be false
// because runtime.Unstructured object should always have group,version,kind
// information set.
func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) {
if _, ok := obj.(runtime.Unstructured); ok {
gvk := obj.GetObjectKind().GroupVersionKind()
if len(gvk.Kind) == 0 {
return nil, false, runtime.NewMissingKindErr("object has no kind field ")
}
if len(gvk.Version) == 0 {
return nil, false, runtime.NewMissingVersionErr("object has no apiVersion field")
}
return []schema.GroupVersionKind{gvk}, false, nil
}
var lastErr error
for _, typer := range d.typers {
gvks, unversioned, err := typer.ObjectKinds(obj)
if err != nil {
lastErr = err
continue
}
return gvks, unversioned, nil
}
if lastErr == nil {
lastErr = runtime.NewNotRegisteredErrForType(reflect.TypeOf(obj))
}
return nil, false, lastErr
}
// Recognizes returns true if the provided group,version,kind was in the
// discovery information.
func (d *UnstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool {
return d.registered[gvk]
}
var _ runtime.ObjectTyper = &UnstructuredObjectTyper{}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package deprecated_dynamic
package dynamic
import (
"encoding/json"

View File

@@ -17,7 +17,7 @@ limitations under the License.
// Package dynamic provides a client interface to arbitrary Kubernetes
// APIs that exposes common high level operations and exposes common
// metadata.
package deprecated_dynamic
package dynamic
import (
"strings"
@@ -28,7 +28,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
restclient "k8s.io/client-go/rest"
)
@@ -66,13 +65,13 @@ type ResourceInterface interface {
// and manipulate metadata of a Kubernetes API group, and implements Interface.
type Client struct {
version schema.GroupVersion
delegate dynamic.Interface
delegate DynamicInterface
}
// NewClient returns a new client based on the passed in config. The
// codec is ignored, as the dynamic client uses it's own codec.
func NewClient(conf *restclient.Config, version schema.GroupVersion) (*Client, error) {
delegate, err := dynamic.NewForConfig(conf)
delegate, err := NewForConfig(conf)
if err != nil {
return nil, err
}
@@ -85,47 +84,30 @@ func NewClient(conf *restclient.Config, version schema.GroupVersion) (*Client, e
// is ignored. The ResourceInterface inherits the parameter codec of c.
func (c *Client) Resource(resource *metav1.APIResource, namespace string) ResourceInterface {
resourceTokens := strings.SplitN(resource.Name, "/", 2)
subresources := []string{}
subresource := ""
if len(resourceTokens) > 1 {
subresources = strings.Split(resourceTokens[1], "/")
subresource = resourceTokens[1]
}
if len(namespace) == 0 {
return oldResourceShim(c.delegate.Resource(c.version.WithResource(resourceTokens[0])), subresources)
return oldResourceShim(c.delegate.ClusterSubresource(c.version.WithResource(resourceTokens[0]), subresource))
}
return oldResourceShim(c.delegate.Resource(c.version.WithResource(resourceTokens[0])).Namespace(namespace), subresources)
return oldResourceShim(c.delegate.NamespacedSubresource(c.version.WithResource(resourceTokens[0]), subresource, namespace))
}
// the old interfaces used the wrong type for lists. this fixes that
func oldResourceShim(in dynamic.ResourceInterface, subresources []string) ResourceInterface {
return oldResourceShimType{ResourceInterface: in, subresources: subresources}
func oldResourceShim(in DynamicResourceInterface) ResourceInterface {
return oldResourceShimType{DynamicResourceInterface: in}
}
type oldResourceShimType struct {
dynamic.ResourceInterface
subresources []string
}
func (s oldResourceShimType) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
return s.ResourceInterface.Create(obj, metav1.CreateOptions{}, s.subresources...)
}
func (s oldResourceShimType) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
return s.ResourceInterface.Update(obj, metav1.UpdateOptions{}, s.subresources...)
}
func (s oldResourceShimType) Delete(name string, opts *metav1.DeleteOptions) error {
return s.ResourceInterface.Delete(name, opts, s.subresources...)
}
func (s oldResourceShimType) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) {
return s.ResourceInterface.Get(name, opts, s.subresources...)
DynamicResourceInterface
}
func (s oldResourceShimType) List(opts metav1.ListOptions) (runtime.Object, error) {
return s.ResourceInterface.List(opts)
return s.DynamicResourceInterface.List(opts)
}
func (s oldResourceShimType) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) {
return s.ResourceInterface.Patch(name, pt, data, metav1.PatchOptions{}, s.subresources...)
return s.DynamicResourceInterface.Patch(name, pt, data)
}

View File

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

View File

@@ -58,11 +58,12 @@ func getObject(version, kind, name string) *unstructured.Unstructured {
}
}
func getClientServer(h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) {
func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) {
srv := httptest.NewServer(http.HandlerFunc(h))
cl, err := NewForConfig(&restclient.Config{
Host: srv.URL,
})
cl, err := NewClient(&restclient.Config{
Host: srv.URL,
ContentConfig: restclient.ContentConfig{GroupVersion: gv},
}, *gv)
if err != nil {
srv.Close()
return nil, nil, err
@@ -115,8 +116,9 @@ func TestList(t *testing.T) {
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
}
@@ -134,7 +136,7 @@ func TestList(t *testing.T) {
}
defer srv.Close()
got, err := cl.Resource(resource).Namespace(tc.namespace).List(metav1.ListOptions{})
got, err := cl.Resource(resource, tc.namespace).List(metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error when listing %q: %v", tc.name, err)
continue
@@ -148,13 +150,12 @@ func TestList(t *testing.T) {
func TestGet(t *testing.T) {
tcs := []struct {
resource string
subresource []string
namespace string
name string
path string
resp []byte
want *unstructured.Unstructured
resource string
namespace string
name string
path string
resp []byte
want *unstructured.Unstructured
}{
{
resource: "rtest",
@@ -172,26 +173,25 @@ func TestGet(t *testing.T) {
want: getObject("vTest", "rTest", "namespaced_get"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
name: "normal_subresource_get",
path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest",
resp: getJSON("vTest", "srTest", "normal_subresource_get"),
want: getObject("vTest", "srTest", "normal_subresource_get"),
resource: "rtest/srtest",
name: "normal_subresource_get",
path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest",
resp: getJSON("vTest", "srTest", "normal_subresource_get"),
want: getObject("vTest", "srTest", "normal_subresource_get"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
namespace: "nstest",
name: "namespaced_subresource_get",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest",
resp: getJSON("vTest", "srTest", "namespaced_subresource_get"),
want: getObject("vTest", "srTest", "namespaced_subresource_get"),
resource: "rtest/srtest",
namespace: "nstest",
name: "namespaced_subresource_get",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest",
resp: getJSON("vTest", "srTest", "namespaced_subresource_get"),
want: getObject("vTest", "srTest", "namespaced_subresource_get"),
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
}
@@ -209,7 +209,7 @@ func TestGet(t *testing.T) {
}
defer srv.Close()
got, err := cl.Resource(resource).Namespace(tc.namespace).Get(tc.name, metav1.GetOptions{}, tc.subresource...)
got, err := cl.Resource(resource, tc.namespace).Get(tc.name, metav1.GetOptions{})
if err != nil {
t.Errorf("unexpected error when getting %q: %v", tc.name, err)
continue
@@ -230,7 +230,6 @@ func TestDelete(t *testing.T) {
Status: metav1.StatusSuccess,
}
tcs := []struct {
subresource []string
namespace string
name string
path string
@@ -245,17 +244,6 @@ func TestDelete(t *testing.T) {
name: "namespaced_delete",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete",
},
{
subresource: []string{"srtest"},
name: "normal_delete",
path: "/apis/gtest/vtest/rtest/normal_delete/srtest",
},
{
subresource: []string{"srtest"},
namespace: "nstest",
name: "namespaced_delete",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete/srtest",
},
{
namespace: "nstest",
name: "namespaced_delete_with_options",
@@ -264,8 +252,9 @@ func TestDelete(t *testing.T) {
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
}
@@ -283,7 +272,7 @@ func TestDelete(t *testing.T) {
}
defer srv.Close()
err = cl.Resource(resource).Namespace(tc.namespace).Delete(tc.name, tc.deleteOptions, tc.subresource...)
err = cl.Resource(resource, tc.namespace).Delete(tc.name, tc.deleteOptions)
if err != nil {
t.Errorf("unexpected error when deleting %q: %v", tc.name, err)
continue
@@ -312,8 +301,9 @@ func TestDeleteCollection(t *testing.T) {
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
}
@@ -331,7 +321,7 @@ func TestDeleteCollection(t *testing.T) {
}
defer srv.Close()
err = cl.Resource(resource).Namespace(tc.namespace).DeleteCollection(nil, metav1.ListOptions{})
err = cl.Resource(resource, tc.namespace).DeleteCollection(nil, metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err)
continue
@@ -341,12 +331,11 @@ func TestDeleteCollection(t *testing.T) {
func TestCreate(t *testing.T) {
tcs := []struct {
resource string
subresource []string
name string
namespace string
obj *unstructured.Unstructured
path string
resource string
name string
namespace string
obj *unstructured.Unstructured
path string
}{
{
resource: "rtest",
@@ -361,25 +350,11 @@ func TestCreate(t *testing.T) {
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
obj: getObject("gtest/vTest", "rTest", "namespaced_create"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
name: "normal_subresource_create",
path: "/apis/gtest/vtest/rtest/normal_subresource_create/srtest",
obj: getObject("vTest", "srTest", "normal_subresource_create"),
},
{
resource: "rtest/",
subresource: []string{"srtest"},
name: "namespaced_subresource_create",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_create/srtest",
obj: getObject("vTest", "srTest", "namespaced_subresource_create"),
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method)
}
@@ -404,7 +379,7 @@ func TestCreate(t *testing.T) {
}
defer srv.Close()
got, err := cl.Resource(resource).Namespace(tc.namespace).Create(tc.obj, metav1.CreateOptions{}, tc.subresource...)
got, err := cl.Resource(resource, tc.namespace).Create(tc.obj)
if err != nil {
t.Errorf("unexpected error when creating %q: %v", tc.name, err)
continue
@@ -418,12 +393,11 @@ func TestCreate(t *testing.T) {
func TestUpdate(t *testing.T) {
tcs := []struct {
resource string
subresource []string
name string
namespace string
obj *unstructured.Unstructured
path string
resource string
name string
namespace string
obj *unstructured.Unstructured
path string
}{
{
resource: "rtest",
@@ -439,24 +413,23 @@ func TestUpdate(t *testing.T) {
obj: getObject("gtest/vTest", "rTest", "namespaced_update"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
name: "normal_subresource_update",
path: "/apis/gtest/vtest/rtest/normal_update/srtest",
obj: getObject("gtest/vTest", "srTest", "normal_update"),
resource: "rtest/srtest",
name: "normal_subresource_update",
path: "/apis/gtest/vtest/rtest/normal_update/srtest",
obj: getObject("gtest/vTest", "srTest", "normal_update"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
name: "namespaced_subresource_update",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest",
obj: getObject("gtest/vTest", "srTest", "namespaced_update"),
resource: "rtest/srtest",
name: "namespaced_subresource_update",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest",
obj: getObject("gtest/vTest", "srTest", "namespaced_update"),
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method)
}
@@ -481,7 +454,7 @@ func TestUpdate(t *testing.T) {
}
defer srv.Close()
got, err := cl.Resource(resource).Namespace(tc.namespace).Update(tc.obj, metav1.UpdateOptions{}, tc.subresource...)
got, err := cl.Resource(resource, tc.namespace).Update(tc.obj)
if err != nil {
t.Errorf("unexpected error when updating %q: %v", tc.name, err)
continue
@@ -524,8 +497,9 @@ func TestWatch(t *testing.T) {
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
}
@@ -537,7 +511,7 @@ func TestWatch(t *testing.T) {
t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query)
}
enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, unstructured.UnstructuredJSONScheme), unstructured.UnstructuredJSONScheme)
enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, dynamicCodec{}), dynamicCodec{})
for _, e := range tc.events {
enc.Encode(&e)
}
@@ -548,7 +522,7 @@ func TestWatch(t *testing.T) {
}
defer srv.Close()
watcher, err := cl.Resource(resource).Namespace(tc.namespace).Watch(metav1.ListOptions{})
watcher, err := cl.Resource(resource, tc.namespace).Watch(metav1.ListOptions{})
if err != nil {
t.Errorf("unexpected error when watching %q: %v", tc.name, err)
continue
@@ -565,13 +539,12 @@ func TestWatch(t *testing.T) {
func TestPatch(t *testing.T) {
tcs := []struct {
resource string
subresource []string
name string
namespace string
patch []byte
want *unstructured.Unstructured
path string
resource string
name string
namespace string
patch []byte
want *unstructured.Unstructured
path string
}{
{
resource: "rtest",
@@ -589,26 +562,25 @@ func TestPatch(t *testing.T) {
want: getObject("gtest/vTest", "rTest", "namespaced_patch"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
name: "normal_subresource_patch",
path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest",
patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"),
want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"),
resource: "rtest/srtest",
name: "normal_subresource_patch",
path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest",
patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"),
want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"),
},
{
resource: "rtest",
subresource: []string{"srtest"},
name: "namespaced_subresource_patch",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest",
patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"),
want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"),
resource: "rtest/srtest",
name: "namespaced_subresource_patch",
namespace: "nstest",
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest",
patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"),
want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"),
},
}
for _, tc := range tcs {
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: tc.resource}
cl, srv, err := getClientServer(func(w http.ResponseWriter, r *http.Request) {
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PATCH" {
t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method)
}
@@ -638,7 +610,7 @@ func TestPatch(t *testing.T) {
}
defer srv.Close()
got, err := cl.Resource(resource).Namespace(tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch, metav1.PatchOptions{}, tc.subresource...)
got, err := cl.Resource(resource, tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch)
if err != nil {
t.Errorf("unexpected error when patching %q: %v", tc.name, err)
continue

87
dynamic/dynamic_util.go Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamic
import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// NewDiscoveryRESTMapper returns a RESTMapper based on discovery information.
func NewDiscoveryRESTMapper(resources []*metav1.APIResourceList) (*meta.DefaultRESTMapper, error) {
rm := meta.NewDefaultRESTMapper(nil)
for _, resourceList := range resources {
gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
return nil, err
}
for _, resource := range resourceList.APIResources {
gvk := gv.WithKind(resource.Kind)
scope := meta.RESTScopeRoot
if resource.Namespaced {
scope = meta.RESTScopeNamespace
}
rm.Add(gvk, scope)
}
}
return rm, nil
}
// ObjectTyper provides an ObjectTyper implementation for
// unstructured.Unstructured object based on discovery information.
type ObjectTyper struct {
registered map[schema.GroupVersionKind]bool
}
// NewObjectTyper constructs an ObjectTyper from discovery information.
func NewObjectTyper(resources []*metav1.APIResourceList) (runtime.ObjectTyper, error) {
ot := &ObjectTyper{registered: make(map[schema.GroupVersionKind]bool)}
for _, resourceList := range resources {
gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
return nil, err
}
for _, resource := range resourceList.APIResources {
ot.registered[gv.WithKind(resource.Kind)] = true
}
}
return ot, nil
}
// ObjectKinds returns a slice of one element with the
// group,version,kind of the provided object, or an error if the
// object is not *unstructured.Unstructured or has no group,version,kind
// information.
func (ot *ObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
if _, ok := obj.(*unstructured.Unstructured); !ok {
return nil, false, fmt.Errorf("type %T is invalid for determining dynamic object types", obj)
}
return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil
}
// Recognizes returns true if the provided group,version,kind was in
// the discovery information.
func (ot *ObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool {
return ot.registered[gvk]
}

View File

@@ -0,0 +1,79 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamic
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestDiscoveryRESTMapper(t *testing.T) {
resources := []*metav1.APIResourceList{
{
GroupVersion: "test/beta1",
APIResources: []metav1.APIResource{
{
Name: "test_kinds",
Namespaced: true,
Kind: "test_kind",
},
},
},
}
gvk := schema.GroupVersionKind{
Group: "test",
Version: "beta1",
Kind: "test_kind",
}
mapper, err := NewDiscoveryRESTMapper(resources)
if err != nil {
t.Fatalf("unexpected error creating mapper: %s", err)
}
for _, res := range []schema.GroupVersionResource{
{
Group: "test",
Version: "beta1",
Resource: "test_kinds",
},
{
Version: "beta1",
Resource: "test_kinds",
},
{
Group: "test",
Resource: "test_kinds",
},
{
Resource: "test_kinds",
},
} {
got, err := mapper.KindFor(res)
if err != nil {
t.Errorf("KindFor(%#v) unexpected error: %s", res, err)
continue
}
if got != gvk {
t.Errorf("KindFor(%#v) = %#v; want %#v", res, got, gvk)
}
}
}

View File

@@ -1,157 +0,0 @@
/*
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),
tweakListOptions: tweakListOptions,
}
}
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
tweakListOptions TweakListOptionsFunc
}
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}, f.tweakListOptions)
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))
}

View File

@@ -1,160 +0,0 @@
/*
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,
},
}
}

View File

@@ -1,34 +0,0 @@
/*
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)

View File

@@ -1,40 +0,0 @@
/*
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)
}

View File

@@ -1,91 +0,0 @@
/*
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
}

View File

@@ -1,257 +0,0 @@
/*
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())
}
}
}

View File

@@ -1,87 +0,0 @@
/*
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)
}

163
dynamic/fake/client.go Normal file
View File

@@ -0,0 +1,163 @@
/*
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 fake provides a fake client interface to arbitrary Kubernetes
// APIs that exposes common high level operations and exposes common
// metadata.
package fake
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/testing"
"k8s.io/client-go/util/flowcontrol"
)
// FakeClient is a fake implementation of dynamic.Interface.
type FakeClient struct {
GroupVersion schema.GroupVersion
*testing.Fake
}
// GetRateLimiter returns the rate limiter for this client.
func (c *FakeClient) GetRateLimiter() flowcontrol.RateLimiter {
return nil
}
// Resource returns an API interface to the specified resource for this client's
// group and version. If resource is not a namespaced resource, then namespace
// is ignored. The ResourceClient inherits the parameter codec of this client
func (c *FakeClient) Resource(resource *metav1.APIResource, namespace string) dynamic.ResourceInterface {
return &FakeResourceClient{
Resource: c.GroupVersion.WithResource(resource.Name),
Kind: c.GroupVersion.WithKind(resource.Kind),
Namespace: namespace,
Fake: c.Fake,
}
}
// ParameterCodec returns a client with the provided parameter codec.
func (c *FakeClient) ParameterCodec(parameterCodec runtime.ParameterCodec) dynamic.Interface {
return &FakeClient{
Fake: c.Fake,
}
}
// FakeResourceClient is a fake implementation of dynamic.ResourceInterface
type FakeResourceClient struct {
Resource schema.GroupVersionResource
Kind schema.GroupVersionKind
Namespace string
*testing.Fake
}
// List returns a list of objects for this resource.
func (c *FakeResourceClient) List(opts metav1.ListOptions) (runtime.Object, error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(c.Resource, c.Kind, c.Namespace, opts), &unstructured.UnstructuredList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &unstructured.UnstructuredList{}
for _, item := range obj.(*unstructured.UnstructuredList).Items {
if label.Matches(labels.Set(item.GetLabels())) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Get gets the resource with the specified name.
func (c *FakeResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(c.Resource, c.Namespace, name), &unstructured.Unstructured{})
if obj == nil {
return nil, err
}
return obj.(*unstructured.Unstructured), err
}
// Delete deletes the resource with the specified name.
func (c *FakeResourceClient) Delete(name string, opts *metav1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(c.Resource, c.Namespace, name), &unstructured.Unstructured{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeResourceClient) DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteCollectionAction(c.Resource, c.Namespace, listOptions), &unstructured.Unstructured{})
return err
}
// Create creates the provided resource.
func (c *FakeResourceClient) Create(inObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(c.Resource, c.Namespace, inObj), &unstructured.Unstructured{})
if obj == nil {
return nil, err
}
return obj.(*unstructured.Unstructured), err
}
// Update updates the provided resource.
func (c *FakeResourceClient) Update(inObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(c.Resource, c.Namespace, inObj), &unstructured.Unstructured{})
if obj == nil {
return nil, err
}
return obj.(*unstructured.Unstructured), err
}
// Watch returns a watch.Interface that watches the resource.
func (c *FakeResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(c.Resource, c.Namespace, opts))
}
// Patch patches the provided resource.
func (c *FakeResourceClient) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) {
obj, err := c.Fake.
Invokes(testing.NewPatchAction(c.Resource, c.Namespace, name, data), &unstructured.Unstructured{})
if obj == nil {
return nil, err
}
return obj.(*unstructured.Unstructured), err
}

View File

@@ -0,0 +1,48 @@
/*
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 fake provides a fake client interface to arbitrary Kubernetes
// APIs that exposes common high level operations and exposes common
// metadata.
package fake
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/testing"
)
// FakeClientPool provides a fake implementation of dynamic.ClientPool.
// It assumes resource GroupVersions are the same as their corresponding kind GroupVersions.
type FakeClientPool struct {
testing.Fake
}
// ClientForGroupVersionKind returns a client configured for the specified groupVersionResource.
// Resource may be empty.
func (p *FakeClientPool) ClientForGroupVersionResource(resource schema.GroupVersionResource) (dynamic.Interface, error) {
return p.ClientForGroupVersionKind(resource.GroupVersion().WithKind(""))
}
// ClientForGroupVersionKind returns a client configured for the specified groupVersionKind.
// Kind may be empty.
func (p *FakeClientPool) ClientForGroupVersionKind(kind schema.GroupVersionKind) (dynamic.Interface, error) {
// we can just create a new client every time for testing purposes
return &FakeClient{
GroupVersion: kind.GroupVersion(),
Fake: &p.Fake,
}, nil
}

View File

@@ -17,8 +17,6 @@ limitations under the License.
package fake
import (
"strings"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -33,10 +31,6 @@ import (
)
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
// In order to use List with this client, you have to have the v1.List registered in your scheme. Neat thing though
// it does NOT have to be the *same* list
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "List"}, &unstructured.UnstructuredList{})
codecs := serializer.NewCodecFactory(scheme)
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
for _, obj := range objects {
@@ -45,7 +39,7 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
}
}
cs := &FakeDynamicClient{scheme: scheme}
cs := &FakeDynamicClient{}
cs.AddReactor("*", "*", testing.ObjectReaction(o))
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
gvr := action.GetResource()
@@ -69,54 +63,37 @@ type FakeDynamicClient struct {
}
type dynamicResourceClient struct {
client *FakeDynamicClient
namespace string
resource schema.GroupVersionResource
client *FakeDynamicClient
namespace string
resource schema.GroupVersionResource
subresource string
}
var _ dynamic.Interface = &FakeDynamicClient{}
var _ dynamic.DynamicInterface = &FakeDynamicClient{}
func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableDynamicResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
// Deprecated, this isn't how we want to do it
func (c *FakeDynamicClient) ClusterSubresource(resource schema.GroupVersionResource, subresource string) dynamic.DynamicResourceInterface {
return &dynamicResourceClient{client: c, resource: resource, subresource: subresource}
}
// Deprecated, this isn't how we want to do it
func (c *FakeDynamicClient) NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) dynamic.DynamicResourceInterface {
return &dynamicResourceClient{client: c, resource: resource, namespace: namespace, subresource: subresource}
}
func (c *dynamicResourceClient) Namespace(ns string) dynamic.DynamicResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
case len(c.namespace) == 0 && len(subresources) > 0:
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
case len(c.namespace) > 0 && len(subresources) > 0:
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
uncastRet, err = c.client.Fake.
Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
}
func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
uncastRet, err := c.client.Fake.
Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
if err != nil {
return nil, err
@@ -132,27 +109,9 @@ func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, opts meta
return ret, err
}
func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
}
func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
uncastRet, err := c.client.Fake.
Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
if err != nil {
return nil, err
@@ -168,19 +127,9 @@ func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, opts meta
return ret, err
}
func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
case len(c.namespace) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
}
func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
uncastRet, err := c.client.Fake.
Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
if err != nil {
return nil, err
@@ -196,65 +145,23 @@ func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opt
return ret, err
}
func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
_, err = c.client.Fake.
Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
_, err = c.client.Fake.
Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
_, err = c.client.Fake.
Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
_, err = c.client.Fake.
Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
}
func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions) error {
_, err := c.client.Fake.
Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
return err
}
func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
var err error
switch {
case len(c.namespace) == 0:
action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
case len(c.namespace) > 0:
action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
}
action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
_, err := c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
return err
}
func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
}
func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) {
uncastRet, err := c.client.Fake.
Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"})
if err != nil {
return nil, err
@@ -271,18 +178,8 @@ func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subreso
}
func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
var obj runtime.Object
var err error
switch {
case len(c.namespace) == 0:
obj, err = c.client.Fake.
Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "dynamic list fail"})
case len(c.namespace) > 0:
obj, err = c.client.Fake.
Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
}
obj, err := c.client.Fake.
Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
if obj == nil {
return nil, err
@@ -303,57 +200,26 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns
}
list := &unstructured.UnstructuredList{}
list.SetResourceVersion(entireList.GetResourceVersion())
for i := range entireList.Items {
item := &entireList.Items[i]
for _, item := range entireList.Items {
metadata, err := meta.Accessor(item)
if err != nil {
return nil, err
}
if label.Matches(labels.Set(metadata.GetLabels())) {
list.Items = append(list.Items, *item)
list.Items = append(list.Items, item)
}
}
return list, nil
}
func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
switch {
case len(c.namespace) == 0:
return c.client.Fake.
InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
case len(c.namespace) > 0:
return c.client.Fake.
InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
}
panic("math broke")
return c.client.Fake.
InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
}
// TODO: opts are currently ignored.
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
var uncastRet runtime.Object
var err error
switch {
case len(c.namespace) == 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
case len(c.namespace) == 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
case len(c.namespace) > 0 && len(subresources) == 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
case len(c.namespace) > 0 && len(subresources) > 0:
uncastRet, err = c.client.Fake.
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
}
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) {
uncastRet, err := c.client.Fake.
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
if err != nil {
return nil, err

View File

@@ -1,197 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fake
import (
"fmt"
"testing"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff"
)
const (
testGroup = "testgroup"
testVersion = "testversion"
testResource = "testkinds"
testNamespace = "testns"
testName = "testname"
testKind = "TestKind"
testAPIVersion = "testgroup/testversion"
)
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 newUnstructuredWithSpec(spec map[string]interface{}) *unstructured.Unstructured {
u := newUnstructured(testAPIVersion, testKind, testNamespace, testName)
u.Object["spec"] = spec
return u
}
func TestList(t *testing.T) {
scheme := runtime.NewScheme()
client := NewSimpleDynamicClient(scheme,
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-baz"),
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-baz"),
)
listFirst, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).List(metav1.ListOptions{})
if err != nil {
t.Fatal(err)
}
expected := []unstructured.Unstructured{
*newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
*newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
*newUnstructured("group/version", "TheKind", "ns-foo", "name-baz"),
}
if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
}
}
type patchTestCase struct {
name string
object runtime.Object
patchType types.PatchType
patchBytes []byte
wantErrMsg string
expectedPatchedObject runtime.Object
}
func (tc *patchTestCase) runner(t *testing.T) {
client := NewSimpleDynamicClient(runtime.NewScheme(), tc.object)
resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)
got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.PatchOptions{})
if err := tc.verifyErr(recErr); err != nil {
t.Error(err)
}
if err := tc.verifyResult(got); err != nil {
t.Error(err)
}
}
// verifyErr verifies that the given error returned from Patch is the error
// expected by the test case.
func (tc *patchTestCase) verifyErr(err error) error {
if tc.wantErrMsg != "" && err == nil {
return fmt.Errorf("want error, got nil")
}
if tc.wantErrMsg == "" && err != nil {
return fmt.Errorf("want no error, got %v", err)
}
if err != nil {
if want, got := tc.wantErrMsg, err.Error(); want != got {
return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
}
}
return nil
}
func (tc *patchTestCase) verifyResult(result *unstructured.Unstructured) error {
if tc.expectedPatchedObject == nil && result == nil {
return nil
}
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
}
return nil
}
func TestPatch(t *testing.T) {
testCases := []patchTestCase{
{
name: "jsonpatch fails with merge type",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
patchBytes: []byte(`[]`),
wantErrMsg: "invalid JSON document",
}, {
name: "jsonpatch works with empty patch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// No-op
patchBytes: []byte(`[]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
}, {
name: "jsonpatch works with simple change patch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// change spec.foo from bar to foobar
patchBytes: []byte(`[{"op": "replace", "path": "/spec/foo", "value": "foobar"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "foobar"}),
}, {
name: "jsonpatch works with simple addition",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.JSONPatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/spec/newvalue", "value": "dummy"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar", "newvalue": "dummy"}),
}, {
name: "jsonpatch works with simple deletion",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar", "toremove": "shouldnotbehere"}),
patchType: types.JSONPatchType,
// remove spec.newvalue = dummy
patchBytes: []byte(`[{"op": "remove", "path": "/spec/toremove"}]`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
}, {
name: "strategic merge patch fails with JSONPatch",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.StrategicMergePatchType,
// add spec.newvalue = dummy
patchBytes: []byte(`[{"op": "add", "path": "/spec/newvalue", "value": "dummy"}]`),
wantErrMsg: "invalid JSON document",
}, {
name: "merge patch works with simple replacement",
object: newUnstructuredWithSpec(map[string]interface{}{"foo": "bar"}),
patchType: types.MergePatchType,
patchBytes: []byte(`{ "spec": { "foo": "baz" } }`),
expectedPatchedObject: newUnstructuredWithSpec(map[string]interface{}{"foo": "baz"}),
},
// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
// demonstrate expected use cases.
}
for _, tc := range testCases {
t.Run(tc.name, tc.runner)
}
}

View File

@@ -1,59 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamic
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
)
type Interface interface {
Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface
}
type ResourceInterface interface {
Create(obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
Update(obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error)
UpdateStatus(obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error)
Delete(name string, options *metav1.DeleteOptions, subresources ...string) error
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error)
List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error)
}
type NamespaceableResourceInterface interface {
Namespace(string) ResourceInterface
ResourceInterface
}
// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is optional.
// TODO find a better place to move this for existing callers
type APIPathResolverFunc func(kind schema.GroupVersionKind) string
// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API.
// TODO find a better place to move this for existing callers
func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string {
if len(kind.Group) == 0 {
return "/api"
}
return "/apis"
}

View File

@@ -31,23 +31,39 @@ import (
"k8s.io/client-go/rest"
)
type DynamicInterface interface {
Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface
// Deprecated, this isn't how we want to do it
ClusterSubresource(resource schema.GroupVersionResource, subresource string) DynamicResourceInterface
// Deprecated, this isn't how we want to do it
NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) DynamicResourceInterface
}
type DynamicResourceInterface interface {
Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
Delete(name string, options *metav1.DeleteOptions) error
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(name string, options metav1.GetOptions) (*unstructured.Unstructured, error)
List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error)
}
type NamespaceableDynamicResourceInterface interface {
Namespace(string) DynamicResourceInterface
DynamicResourceInterface
}
type dynamicClient struct {
client *rest.RESTClient
}
var _ Interface = &dynamicClient{}
var _ DynamicInterface = &dynamicClient{}
// NewForConfigOrDie creates a new Interface for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
ret, err := NewForConfig(c)
if err != nil {
panic(err)
}
return ret
}
func NewForConfig(inConfig *rest.Config) (Interface, error) {
func NewForConfig(inConfig *rest.Config) (DynamicInterface, error) {
config := rest.CopyConfig(inConfig)
// for serializing the options
config.GroupVersion = &schema.GroupVersion{}
@@ -68,44 +84,40 @@ func NewForConfig(inConfig *rest.Config) (Interface, error) {
}
type dynamicResourceClient struct {
client *dynamicClient
namespace string
resource schema.GroupVersionResource
client *dynamicClient
namespace string
resource schema.GroupVersionResource
subresource string
}
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableDynamicResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
func (c *dynamicClient) ClusterSubresource(resource schema.GroupVersionResource, subresource string) DynamicResourceInterface {
return &dynamicResourceClient{client: c, resource: resource, subresource: subresource}
}
func (c *dynamicClient) NamespacedSubresource(resource schema.GroupVersionResource, subresource, namespace string) DynamicResourceInterface {
return &dynamicResourceClient{client: c, resource: resource, namespace: namespace, subresource: subresource}
}
func (c *dynamicResourceClient) Namespace(ns string) DynamicResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
if len(c.subresource) > 0 {
return nil, fmt.Errorf("create not supported for subresources")
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
name := ""
if len(subresources) > 0 {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name = accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
}
result := c.client.client.
Post().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(outBytes).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
result := c.client.client.Post().AbsPath(c.makeURLSegments("")...).Body(outBytes).Do()
if err := result.Error(); err != nil {
return nil, err
}
@@ -121,26 +133,17 @@ func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, opts meta
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
result := c.client.client.
Put().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(outBytes).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
result := c.client.client.Put().AbsPath(c.makeURLSegments(accessor.GetName())...).Body(outBytes).Do()
if err := result.Error(); err != nil {
return nil, err
}
@@ -156,31 +159,18 @@ func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, opts meta
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
result := c.client.client.
Put().
AbsPath(append(c.makeURLSegments(name), "status")...).
Body(outBytes).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
result := c.client.client.Put().AbsPath(append(c.makeURLSegments(accessor.GetName()), "status")...).Body(outBytes).Do()
retBytes, err := result.Raw()
if err != nil {
return nil, err
@@ -192,10 +182,7 @@ func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opt
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
if len(name) == 0 {
return fmt.Errorf("name is required")
}
func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions) error {
if opts == nil {
opts = &metav1.DeleteOptions{}
}
@@ -204,15 +191,15 @@ func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions,
return err
}
result := c.client.client.
Delete().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(deleteOptionsByte).
Do()
result := c.client.client.Delete().AbsPath(c.makeURLSegments(name)...).Body(deleteOptionsByte).Do()
return result.Error()
}
func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
if len(c.subresource) > 0 {
return fmt.Errorf("deletecollection not supported for subresources")
}
if opts == nil {
opts = &metav1.DeleteOptions{}
}
@@ -221,20 +208,12 @@ func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, lis
return err
}
result := c.client.client.
Delete().
AbsPath(c.makeURLSegments("")...).
Body(deleteOptionsByte).
SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
Do()
result := c.client.client.Delete().AbsPath(c.makeURLSegments("")...).Body(deleteOptionsByte).SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).Do()
return result.Error()
}
func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do()
func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) {
result := c.client.client.Get().AbsPath(c.makeURLSegments(name)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do()
if err := result.Error(); err != nil {
return nil, err
}
@@ -250,6 +229,10 @@ func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subreso
}
func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
if len(c.subresource) > 0 {
return nil, fmt.Errorf("list not supported for subresources")
}
result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do()
if err := result.Error(); err != nil {
return nil, err
@@ -274,6 +257,10 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns
}
func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
if len(c.subresource) > 0 {
return nil, fmt.Errorf("watch not supported for subresources")
}
internalGV := schema.GroupVersions{
{Group: c.resource.Group, Version: runtime.APIVersionInternal},
// always include the legacy group as a decoding target to handle non-error `Status` return types
@@ -301,16 +288,8 @@ func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface,
WatchWithSpecificDecoders(wrappedDecoderFn, unstructured.UnstructuredJSONScheme)
}
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.
Patch(pt).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(data).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) {
result := c.client.client.Patch(pt).AbsPath(append(c.makeURLSegments(name), subresources...)...).Body(data).Do()
if err := result.Error(); err != nil {
return nil, err
}
@@ -341,6 +320,15 @@ func (c *dynamicResourceClient) makeURLSegments(name string) []string {
if len(name) > 0 {
url = append(url, name)
// subresources only work on things with names
if len(c.subresource) > 0 {
url = append(url, c.subresource)
}
} else {
if len(c.subresource) > 0 {
panic("somehow snuck a subresource and an empty name. programmer error")
}
}
return url

View File

@@ -3,25 +3,6 @@
This directory contains examples that cover various use cases and functionality
for client-go.
### Auth plugins
Client configuration is typically loaded from kubeconfig files containing server and credential configuration.
Several plugins for obtaining credentials from external sources are available, but are not loaded by default.
To enable these plugins in your program, import them in your main package.
You can load all auth plugins:
```go
import _ "k8s.io/client-go/plugin/pkg/client/auth
```
Or you can load specific auth plugins:
```go
import _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
import _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
import _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
import _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
```
### Configuration
- [**Authenticate in cluster**](./in-cluster-client-configuration): Configure a
@@ -44,7 +25,3 @@ import _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
the custom resources.
[informer]: https://godoc.org/k8s.io/client-go/tools/cache#NewInformer
### Testing
- [**Fake Client**](./fake-client): Use a fake client in tests.

View File

@@ -36,14 +36,20 @@ Running this command will execute the following operations on your cluster:
image to `nginx:1.13`. You are encouraged to inspect the retry loop that
handles conflicts. Verify the new replica count and container image with
`kubectl describe deployment demo`.
3. **List Deployments:** This will retrieve Deployments in the `default`
3. **Rollback Deployment:** This will rollback the Deployment to the last
revision. In this case, it's the revision that was created in Step 1.
Use `kubectl describe` to verify the container image is now `nginx:1.12`.
Also note that the Deployment's replica count is still 1; this is because a
Deployment revision is created if and only if the Deployment's pod template
(`.spec.template`) is changed.
4. **List Deployments:** This will retrieve Deployments in the `default`
namespace and print their names and replica counts.
4. **Delete Deployment:** This will delete the Deployment object and its
5. **Delete Deployment:** This will delete the Deployment object and its
dependent ReplicaSet resource. Verify with `kubectl get deployments`.
Each step is separated by an interactive prompt. You must hit the
<kbd>Return</kbd> key to proceed to the next step. You can use these prompts as
a break to take time to run `kubectl` and inspect the result of the operations
a break to take time to run `kubectl` and inspect the result of the operations
executed.
You should see an output like the following:
@@ -57,6 +63,10 @@ Updating deployment...
Updated deployment...
-> Press Return key to continue.
Rolling back deployment...
Rolled back deployment...
-> Press Return key to continue.
Listing deployments in namespace "default":
* demo-deployment (1 replicas)
-> Press Return key to continue.

View File

@@ -31,15 +31,8 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/client-go/util/retry"
//
// Uncomment to load all auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth
//
// Or uncomment to load specific auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)
func main() {
@@ -120,7 +113,7 @@ func main() {
// using the retry utility package included with client-go. (RECOMMENDED)
//
// More Info:
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
// https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
// Retrieve the latest version of Deployment before attempting update

View File

@@ -1,14 +0,0 @@
# Fake Client Example
This example demonstrates how to use a fake client with SharedInformerFactory in tests.
It covers:
* Creating the fake client
* Setting up real informers
* Injecting events into those informers
## Running
```
go test -v k8s.io/client-go/examples/fake-client
```

View File

@@ -1,73 +0,0 @@
/*
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 fakeclient
import (
"context"
"testing"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
)
// TestFakeClient demonstrates how to use a fake client with SharedInformerFactory in tests.
func TestFakeClient(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create the fake client.
client := fake.NewSimpleClientset()
// We will create an informer that writes added pods to a channel.
pods := make(chan *v1.Pod, 1)
informers := informers.NewSharedInformerFactory(client, 0)
podInformer := informers.Core().V1().Pods().Informer()
podInformer.AddEventHandler(&cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pod := obj.(*v1.Pod)
t.Logf("pod added: %s/%s", pod.Namespace, pod.Name)
pods <- pod
},
})
// Make sure informers are running.
informers.Start(ctx.Done())
// This is not required in tests, but it serves as a proof-of-concept by
// ensuring that the informer goroutine have warmed up and called List before
// we send any events to it.
cache.WaitForCacheSync(ctx.Done(), podInformer.HasSynced)
// Inject an event into the fake client.
p := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "my-pod"}}
_, err := client.CoreV1().Pods("test-ns").Create(p)
if err != nil {
t.Fatalf("error injecting pod add: %v", err)
}
select {
case pod := <-pods:
t.Logf("Got pod from channel: %s/%s", pod.Namespace, pod.Name)
case <-time.After(wait.ForeverTestTimeout):
t.Error("Informer did not get the added pod")
}
}

View File

@@ -27,14 +27,6 @@ build the image on Minikube:
If you are not using Minikube, you should build this image and push it to a registry
that your Kubernetes cluster can pull from.
If you have RBAC enabled on your cluster, 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:
$ kubectl run --rm -i demo --image=in-cluster --image-pull-policy=Never

View File

@@ -25,15 +25,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
//
// Uncomment to load all auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth
//
// Or uncomment to load specific auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)
func main() {

View File

@@ -20,7 +20,7 @@ Run this application with:
./app
Running this application will use the kubeconfig file and then authenticate to the
cluster, and print the number of pods in the cluster every 10 seconds:
cluster, and print the number of nodes in the cluster every 10 seconds:
$ ./app
There are 3 pods in the cluster

View File

@@ -28,15 +28,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
//
// Uncomment to load all auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth"
//
// Or uncomment to load specific auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)
func main() {

View File

@@ -7,7 +7,7 @@ It demonstrates how to:
* combine the workqueue with a cache to a full controller
* synchronize the controller on startup
The example is based on https://git.k8s.io/community/contributors/devel/sig-api-machinery/controllers.md.
The example is based on https://git.k8s.io/community/contributors/devel/controllers.md.
## Running

View File

@@ -21,7 +21,7 @@ import (
"fmt"
"time"
"k8s.io/klog"
"github.com/golang/glog"
"k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -72,7 +72,7 @@ func (c *Controller) processNextItem() bool {
func (c *Controller) syncToStdout(key string) error {
obj, exists, err := c.indexer.GetByKey(key)
if err != nil {
klog.Errorf("Fetching object with key %s from store failed with %v", key, err)
glog.Errorf("Fetching object with key %s from store failed with %v", key, err)
return err
}
@@ -99,7 +99,7 @@ func (c *Controller) handleErr(err error, key interface{}) {
// This controller retries 5 times if something goes wrong. After that, it stops trying.
if c.queue.NumRequeues(key) < 5 {
klog.Infof("Error syncing pod %v: %v", key, err)
glog.Infof("Error syncing pod %v: %v", key, err)
// Re-enqueue the key rate limited. Based on the rate limiter on the
// queue and the re-enqueue history, the key will be processed later again.
@@ -110,7 +110,7 @@ func (c *Controller) handleErr(err error, key interface{}) {
c.queue.Forget(key)
// Report to an external entity that, even after several retries, we could not successfully process this key
runtime.HandleError(err)
klog.Infof("Dropping pod %q out of the queue: %v", key, err)
glog.Infof("Dropping pod %q out of the queue: %v", key, err)
}
func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
@@ -118,7 +118,7 @@ func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
// Let the workers stop when we are done
defer c.queue.ShutDown()
klog.Info("Starting Pod controller")
glog.Info("Starting Pod controller")
go c.informer.Run(stopCh)
@@ -133,7 +133,7 @@ func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
}
<-stopCh
klog.Info("Stopping Pod controller")
glog.Info("Stopping Pod controller")
}
func (c *Controller) runWorker() {
@@ -152,13 +152,13 @@ func main() {
// creates the connection
config, err := clientcmd.BuildConfigFromFlags(master, kubeconfig)
if err != nil {
klog.Fatal(err)
glog.Fatal(err)
}
// creates the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
klog.Fatal(err)
glog.Fatal(err)
}
// create the pod watcher

View File

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

View File

@@ -0,0 +1,88 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
time "time"
admissionregistration_v1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
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"
v1alpha1 "k8s.io/client-go/listers/admissionregistration/v1alpha1"
cache "k8s.io/client-go/tools/cache"
)
// InitializerConfigurationInformer provides access to a shared informer and lister for
// InitializerConfigurations.
type InitializerConfigurationInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.InitializerConfigurationLister
}
type initializerConfigurationInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewInitializerConfigurationInformer constructs a new informer for InitializerConfiguration 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 NewInitializerConfigurationInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredInitializerConfigurationInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredInitializerConfigurationInformer constructs a new informer for InitializerConfiguration 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 NewFilteredInitializerConfigurationInformer(client kubernetes.Interface, 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.AdmissionregistrationV1alpha1().InitializerConfigurations().List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AdmissionregistrationV1alpha1().InitializerConfigurations().Watch(options)
},
},
&admissionregistration_v1alpha1.InitializerConfiguration{},
resyncPeriod,
indexers,
)
}
func (f *initializerConfigurationInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredInitializerConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *initializerConfigurationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&admissionregistration_v1alpha1.InitializerConfiguration{}, f.defaultInformer)
}
func (f *initializerConfigurationInformer) Lister() v1alpha1.InitializerConfigurationLister {
return v1alpha1.NewInitializerConfigurationLister(f.Informer().GetIndexer())
}

View File

@@ -24,8 +24,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// RuntimeClasses returns a RuntimeClassInformer.
RuntimeClasses() RuntimeClassInformer
// InitializerConfigurations returns a InitializerConfigurationInformer.
InitializerConfigurations() InitializerConfigurationInformer
}
type version struct {
@@ -39,7 +39,7 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// RuntimeClasses returns a RuntimeClassInformer.
func (v *version) RuntimeClasses() RuntimeClassInformer {
return &runtimeClassInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
// InitializerConfigurations returns a InitializerConfigurationInformer.
func (v *version) InitializerConfigurations() InitializerConfigurationInformer {
return &initializerConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
admissionregistration_v1beta1 "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -69,7 +69,7 @@ func NewFilteredMutatingWebhookConfigurationInformer(client kubernetes.Interface
return client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Watch(options)
},
},
&admissionregistrationv1beta1.MutatingWebhookConfiguration{},
&admissionregistration_v1beta1.MutatingWebhookConfiguration{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *mutatingWebhookConfigurationInformer) defaultInformer(client kubernetes
}
func (f *mutatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&admissionregistrationv1beta1.MutatingWebhookConfiguration{}, f.defaultInformer)
return f.factory.InformerFor(&admissionregistration_v1beta1.MutatingWebhookConfiguration{}, f.defaultInformer)
}
func (f *mutatingWebhookConfigurationInformer) Lister() v1beta1.MutatingWebhookConfigurationLister {

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
admissionregistration_v1beta1 "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -69,7 +69,7 @@ func NewFilteredValidatingWebhookConfigurationInformer(client kubernetes.Interfa
return client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Watch(options)
},
},
&admissionregistrationv1beta1.ValidatingWebhookConfiguration{},
&admissionregistration_v1beta1.ValidatingWebhookConfiguration{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *validatingWebhookConfigurationInformer) defaultInformer(client kubernet
}
func (f *validatingWebhookConfigurationInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&admissionregistrationv1beta1.ValidatingWebhookConfiguration{}, f.defaultInformer)
return f.factory.InformerFor(&admissionregistration_v1beta1.ValidatingWebhookConfiguration{}, f.defaultInformer)
}
func (f *validatingWebhookConfigurationInformer) Lister() v1beta1.ValidatingWebhookConfigurationLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apps_v1 "k8s.io/api/apps/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewControllerRevisionInformer(client kubernetes.Interface, namespace string
func NewFilteredControllerRevisionInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().ControllerRevisions(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().ControllerRevisions(namespace).Watch(options)
},
},
&appsv1.ControllerRevision{},
&apps_v1.ControllerRevision{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *controllerRevisionInformer) defaultInformer(client kubernetes.Interface
}
func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1.ControllerRevision{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1.ControllerRevision{}, f.defaultInformer)
}
func (f *controllerRevisionInformer) Lister() v1.ControllerRevisionLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apps_v1 "k8s.io/api/apps/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewDaemonSetInformer(client kubernetes.Interface, namespace string, resyncP
func NewFilteredDaemonSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().DaemonSets(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().DaemonSets(namespace).Watch(options)
},
},
&appsv1.DaemonSet{},
&apps_v1.DaemonSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *daemonSetInformer) defaultInformer(client kubernetes.Interface, resyncP
}
func (f *daemonSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1.DaemonSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1.DaemonSet{}, f.defaultInformer)
}
func (f *daemonSetInformer) Lister() v1.DaemonSetLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apps_v1 "k8s.io/api/apps/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewDeploymentInformer(client kubernetes.Interface, namespace string, resync
func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().Deployments(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().Deployments(namespace).Watch(options)
},
},
&appsv1.Deployment{},
&apps_v1.Deployment{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resync
}
func (f *deploymentInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1.Deployment{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1.Deployment{}, f.defaultInformer)
}
func (f *deploymentInformer) Lister() v1.DeploymentLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apps_v1 "k8s.io/api/apps/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewReplicaSetInformer(client kubernetes.Interface, namespace string, resync
func NewFilteredReplicaSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().ReplicaSets(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().ReplicaSets(namespace).Watch(options)
},
},
&appsv1.ReplicaSet{},
&apps_v1.ReplicaSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *replicaSetInformer) defaultInformer(client kubernetes.Interface, resync
}
func (f *replicaSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1.ReplicaSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1.ReplicaSet{}, f.defaultInformer)
}
func (f *replicaSetInformer) Lister() v1.ReplicaSetLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apps_v1 "k8s.io/api/apps/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewStatefulSetInformer(client kubernetes.Interface, namespace string, resyn
func NewFilteredStatefulSetInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().StatefulSets(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AppsV1().StatefulSets(namespace).Watch(options)
},
},
&appsv1.StatefulSet{},
&apps_v1.StatefulSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *statefulSetInformer) defaultInformer(client kubernetes.Interface, resyn
}
func (f *statefulSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1.StatefulSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1.StatefulSet{}, f.defaultInformer)
}
func (f *statefulSetInformer) Lister() v1.StatefulSetLister {

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
appsv1beta1 "k8s.io/api/apps/v1beta1"
apps_v1beta1 "k8s.io/api/apps/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredControllerRevisionInformer(client kubernetes.Interface, namespac
return client.AppsV1beta1().ControllerRevisions(namespace).Watch(options)
},
},
&appsv1beta1.ControllerRevision{},
&apps_v1beta1.ControllerRevision{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *controllerRevisionInformer) defaultInformer(client kubernetes.Interface
}
func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta1.ControllerRevision{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta1.ControllerRevision{}, f.defaultInformer)
}
func (f *controllerRevisionInformer) Lister() v1beta1.ControllerRevisionLister {

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
appsv1beta1 "k8s.io/api/apps/v1beta1"
apps_v1beta1 "k8s.io/api/apps/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string
return client.AppsV1beta1().Deployments(namespace).Watch(options)
},
},
&appsv1beta1.Deployment{},
&apps_v1beta1.Deployment{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resync
}
func (f *deploymentInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta1.Deployment{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta1.Deployment{}, f.defaultInformer)
}
func (f *deploymentInformer) Lister() v1beta1.DeploymentLister {

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
appsv1beta1 "k8s.io/api/apps/v1beta1"
apps_v1beta1 "k8s.io/api/apps/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredStatefulSetInformer(client kubernetes.Interface, namespace strin
return client.AppsV1beta1().StatefulSets(namespace).Watch(options)
},
},
&appsv1beta1.StatefulSet{},
&apps_v1beta1.StatefulSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *statefulSetInformer) defaultInformer(client kubernetes.Interface, resyn
}
func (f *statefulSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta1.StatefulSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta1.StatefulSet{}, f.defaultInformer)
}
func (f *statefulSetInformer) Lister() v1beta1.StatefulSetLister {

View File

@@ -21,7 +21,7 @@ package v1beta2
import (
time "time"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apps_v1beta2 "k8s.io/api/apps/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredControllerRevisionInformer(client kubernetes.Interface, namespac
return client.AppsV1beta2().ControllerRevisions(namespace).Watch(options)
},
},
&appsv1beta2.ControllerRevision{},
&apps_v1beta2.ControllerRevision{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *controllerRevisionInformer) defaultInformer(client kubernetes.Interface
}
func (f *controllerRevisionInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta2.ControllerRevision{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta2.ControllerRevision{}, f.defaultInformer)
}
func (f *controllerRevisionInformer) Lister() v1beta2.ControllerRevisionLister {

View File

@@ -21,7 +21,7 @@ package v1beta2
import (
time "time"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apps_v1beta2 "k8s.io/api/apps/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredDaemonSetInformer(client kubernetes.Interface, namespace string,
return client.AppsV1beta2().DaemonSets(namespace).Watch(options)
},
},
&appsv1beta2.DaemonSet{},
&apps_v1beta2.DaemonSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *daemonSetInformer) defaultInformer(client kubernetes.Interface, resyncP
}
func (f *daemonSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta2.DaemonSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta2.DaemonSet{}, f.defaultInformer)
}
func (f *daemonSetInformer) Lister() v1beta2.DaemonSetLister {

View File

@@ -21,7 +21,7 @@ package v1beta2
import (
time "time"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apps_v1beta2 "k8s.io/api/apps/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredDeploymentInformer(client kubernetes.Interface, namespace string
return client.AppsV1beta2().Deployments(namespace).Watch(options)
},
},
&appsv1beta2.Deployment{},
&apps_v1beta2.Deployment{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *deploymentInformer) defaultInformer(client kubernetes.Interface, resync
}
func (f *deploymentInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta2.Deployment{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta2.Deployment{}, f.defaultInformer)
}
func (f *deploymentInformer) Lister() v1beta2.DeploymentLister {

View File

@@ -21,7 +21,7 @@ package v1beta2
import (
time "time"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apps_v1beta2 "k8s.io/api/apps/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredReplicaSetInformer(client kubernetes.Interface, namespace string
return client.AppsV1beta2().ReplicaSets(namespace).Watch(options)
},
},
&appsv1beta2.ReplicaSet{},
&apps_v1beta2.ReplicaSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *replicaSetInformer) defaultInformer(client kubernetes.Interface, resync
}
func (f *replicaSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta2.ReplicaSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta2.ReplicaSet{}, f.defaultInformer)
}
func (f *replicaSetInformer) Lister() v1beta2.ReplicaSetLister {

View File

@@ -21,7 +21,7 @@ package v1beta2
import (
time "time"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apps_v1beta2 "k8s.io/api/apps/v1beta2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredStatefulSetInformer(client kubernetes.Interface, namespace strin
return client.AppsV1beta2().StatefulSets(namespace).Watch(options)
},
},
&appsv1beta2.StatefulSet{},
&apps_v1beta2.StatefulSet{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *statefulSetInformer) defaultInformer(client kubernetes.Interface, resyn
}
func (f *statefulSetInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&appsv1beta2.StatefulSet{}, f.defaultInformer)
return f.factory.InformerFor(&apps_v1beta2.StatefulSet{}, f.defaultInformer)
}
func (f *statefulSetInformer) Lister() v1beta2.StatefulSetLister {

View File

@@ -1,46 +0,0 @@
/*
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 auditregistration
import (
v1alpha1 "k8s.io/client-go/informers/auditregistration/v1alpha1"
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1alpha1 provides access to shared informers for resources in V1alpha1.
V1alpha1() v1alpha1.Interface
}
type group 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 &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1alpha1 returns a new v1alpha1.Interface.
func (g *group) V1alpha1() v1alpha1.Interface {
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
}

View File

@@ -1,88 +0,0 @@
/*
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 v1alpha1
import (
time "time"
auditregistrationv1alpha1 "k8s.io/api/auditregistration/v1alpha1"
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"
v1alpha1 "k8s.io/client-go/listers/auditregistration/v1alpha1"
cache "k8s.io/client-go/tools/cache"
)
// AuditSinkInformer provides access to a shared informer and lister for
// AuditSinks.
type AuditSinkInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.AuditSinkLister
}
type auditSinkInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewAuditSinkInformer constructs a new informer for AuditSink 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 NewAuditSinkInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredAuditSinkInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredAuditSinkInformer constructs a new informer for AuditSink 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 NewFilteredAuditSinkInformer(client kubernetes.Interface, 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.AuditregistrationV1alpha1().AuditSinks().List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AuditregistrationV1alpha1().AuditSinks().Watch(options)
},
},
&auditregistrationv1alpha1.AuditSink{},
resyncPeriod,
indexers,
)
}
func (f *auditSinkInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredAuditSinkInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *auditSinkInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&auditregistrationv1alpha1.AuditSink{}, f.defaultInformer)
}
func (f *auditSinkInformer) Lister() v1alpha1.AuditSinkLister {
return v1alpha1.NewAuditSinkLister(f.Informer().GetIndexer())
}

View File

@@ -1,45 +0,0 @@
/*
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 v1alpha1
import (
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// AuditSinks returns a AuditSinkInformer.
AuditSinks() AuditSinkInformer
}
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}
}
// AuditSinks returns a AuditSinkInformer.
func (v *version) AuditSinks() AuditSinkInformer {
return &auditSinkInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}

View File

@@ -21,7 +21,6 @@ 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"
)
@@ -31,8 +30,6 @@ 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 {
@@ -55,8 +52,3 @@ 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)
}

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
autoscaling_v1 "k8s.io/api/autoscaling/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewHorizontalPodAutoscalerInformer(client kubernetes.Interface, namespace s
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 metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Watch(options)
},
},
&autoscalingv1.HorizontalPodAutoscaler{},
&autoscaling_v1.HorizontalPodAutoscaler{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Inte
}
func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&autoscalingv1.HorizontalPodAutoscaler{}, f.defaultInformer)
return f.factory.InformerFor(&autoscaling_v1.HorizontalPodAutoscaler{}, f.defaultInformer)
}
func (f *horizontalPodAutoscalerInformer) Lister() v1.HorizontalPodAutoscalerLister {

View File

@@ -21,7 +21,7 @@ package v2beta1
import (
time "time"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
autoscaling_v2beta1 "k8s.io/api/autoscaling/v2beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredHorizontalPodAutoscalerInformer(client kubernetes.Interface, nam
return client.AutoscalingV2beta1().HorizontalPodAutoscalers(namespace).Watch(options)
},
},
&autoscalingv2beta1.HorizontalPodAutoscaler{},
&autoscaling_v2beta1.HorizontalPodAutoscaler{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *horizontalPodAutoscalerInformer) defaultInformer(client kubernetes.Inte
}
func (f *horizontalPodAutoscalerInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&autoscalingv2beta1.HorizontalPodAutoscaler{}, f.defaultInformer)
return f.factory.InformerFor(&autoscaling_v2beta1.HorizontalPodAutoscaler{}, f.defaultInformer)
}
func (f *horizontalPodAutoscalerInformer) Lister() v2beta1.HorizontalPodAutoscalerLister {

View File

@@ -1,89 +0,0 @@
/*
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())
}

View File

@@ -1,45 +0,0 @@
/*
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}
}

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
batch_v1 "k8s.io/api/batch/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewJobInformer(client kubernetes.Interface, namespace string, resyncPeriod
func NewFilteredJobInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.BatchV1().Jobs(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.BatchV1().Jobs(namespace).Watch(options)
},
},
&batchv1.Job{},
&batch_v1.Job{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *jobInformer) defaultInformer(client kubernetes.Interface, resyncPeriod
}
func (f *jobInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&batchv1.Job{}, f.defaultInformer)
return f.factory.InformerFor(&batch_v1.Job{}, f.defaultInformer)
}
func (f *jobInformer) Lister() v1.JobLister {

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
batchv1beta1 "k8s.io/api/batch/v1beta1"
batch_v1beta1 "k8s.io/api/batch/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredCronJobInformer(client kubernetes.Interface, namespace string, r
return client.BatchV1beta1().CronJobs(namespace).Watch(options)
},
},
&batchv1beta1.CronJob{},
&batch_v1beta1.CronJob{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *cronJobInformer) defaultInformer(client kubernetes.Interface, resyncPer
}
func (f *cronJobInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&batchv1beta1.CronJob{}, f.defaultInformer)
return f.factory.InformerFor(&batch_v1beta1.CronJob{}, f.defaultInformer)
}
func (f *cronJobInformer) Lister() v1beta1.CronJobLister {

View File

@@ -21,7 +21,7 @@ package v2alpha1
import (
time "time"
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
batch_v2alpha1 "k8s.io/api/batch/v2alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -70,7 +70,7 @@ func NewFilteredCronJobInformer(client kubernetes.Interface, namespace string, r
return client.BatchV2alpha1().CronJobs(namespace).Watch(options)
},
},
&batchv2alpha1.CronJob{},
&batch_v2alpha1.CronJob{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *cronJobInformer) defaultInformer(client kubernetes.Interface, resyncPer
}
func (f *cronJobInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&batchv2alpha1.CronJob{}, f.defaultInformer)
return f.factory.InformerFor(&batch_v2alpha1.CronJob{}, f.defaultInformer)
}
func (f *cronJobInformer) Lister() v2alpha1.CronJobLister {

View File

@@ -21,7 +21,7 @@ package v1beta1
import (
time "time"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
certificates_v1beta1 "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -69,7 +69,7 @@ func NewFilteredCertificateSigningRequestInformer(client kubernetes.Interface, r
return client.CertificatesV1beta1().CertificateSigningRequests().Watch(options)
},
},
&certificatesv1beta1.CertificateSigningRequest{},
&certificates_v1beta1.CertificateSigningRequest{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *certificateSigningRequestInformer) defaultInformer(client kubernetes.In
}
func (f *certificateSigningRequestInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&certificatesv1beta1.CertificateSigningRequest{}, f.defaultInformer)
return f.factory.InformerFor(&certificates_v1beta1.CertificateSigningRequest{}, f.defaultInformer)
}
func (f *certificateSigningRequestInformer) Lister() v1beta1.CertificateSigningRequestLister {

View File

@@ -1,54 +0,0 @@
/*
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 coordination
import (
v1 "k8s.io/client-go/informers/coordination/v1"
v1beta1 "k8s.io/client-go/informers/coordination/v1beta1"
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to each of this group's versions.
type Interface interface {
// V1 provides access to shared informers for resources in V1.
V1() v1.Interface
// V1beta1 provides access to shared informers for resources in V1beta1.
V1beta1() v1beta1.Interface
}
type group 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 &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// V1 returns a new v1.Interface.
func (g *group) V1() v1.Interface {
return v1.New(g.factory, g.namespace, g.tweakListOptions)
}
// V1beta1 returns a new v1beta1.Interface.
func (g *group) V1beta1() v1beta1.Interface {
return v1beta1.New(g.factory, g.namespace, g.tweakListOptions)
}

View File

@@ -1,45 +0,0 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// Leases returns a LeaseInformer.
Leases() LeaseInformer
}
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}
}
// Leases returns a LeaseInformer.
func (v *version) Leases() LeaseInformer {
return &leaseInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}

View File

@@ -1,89 +0,0 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
time "time"
coordinationv1 "k8s.io/api/coordination/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
kubernetes "k8s.io/client-go/kubernetes"
v1 "k8s.io/client-go/listers/coordination/v1"
cache "k8s.io/client-go/tools/cache"
)
// LeaseInformer provides access to a shared informer and lister for
// Leases.
type LeaseInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.LeaseLister
}
type leaseInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLeaseInformer constructs a new informer for Lease 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 NewLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLeaseInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLeaseInformer constructs a new informer for Lease 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 NewFilteredLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoordinationV1().Leases(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoordinationV1().Leases(namespace).Watch(options)
},
},
&coordinationv1.Lease{},
resyncPeriod,
indexers,
)
}
func (f *leaseInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLeaseInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *leaseInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&coordinationv1.Lease{}, f.defaultInformer)
}
func (f *leaseInformer) Lister() v1.LeaseLister {
return v1.NewLeaseLister(f.Informer().GetIndexer())
}

View File

@@ -1,45 +0,0 @@
/*
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 v1beta1
import (
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
type Interface interface {
// Leases returns a LeaseInformer.
Leases() LeaseInformer
}
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}
}
// Leases returns a LeaseInformer.
func (v *version) Leases() LeaseInformer {
return &leaseInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}

View File

@@ -1,89 +0,0 @@
/*
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 v1beta1
import (
time "time"
coordinationv1beta1 "k8s.io/api/coordination/v1beta1"
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"
v1beta1 "k8s.io/client-go/listers/coordination/v1beta1"
cache "k8s.io/client-go/tools/cache"
)
// LeaseInformer provides access to a shared informer and lister for
// Leases.
type LeaseInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1beta1.LeaseLister
}
type leaseInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewLeaseInformer constructs a new informer for Lease 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 NewLeaseInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredLeaseInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredLeaseInformer constructs a new informer for Lease 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 NewFilteredLeaseInformer(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.CoordinationV1beta1().Leases(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoordinationV1beta1().Leases(namespace).Watch(options)
},
},
&coordinationv1beta1.Lease{},
resyncPeriod,
indexers,
)
}
func (f *leaseInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredLeaseInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *leaseInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&coordinationv1beta1.Lease{}, f.defaultInformer)
}
func (f *leaseInformer) Lister() v1beta1.LeaseLister {
return v1beta1.NewLeaseLister(f.Informer().GetIndexer())
}

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -56,20 +56,20 @@ func NewComponentStatusInformer(client kubernetes.Interface, resyncPeriod time.D
func NewFilteredComponentStatusInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().ComponentStatuses().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().ComponentStatuses().Watch(options)
},
},
&corev1.ComponentStatus{},
&core_v1.ComponentStatus{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *componentStatusInformer) defaultInformer(client kubernetes.Interface, r
}
func (f *componentStatusInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.ComponentStatus{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.ComponentStatus{}, f.defaultInformer)
}
func (f *componentStatusInformer) Lister() v1.ComponentStatusLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewConfigMapInformer(client kubernetes.Interface, namespace string, resyncP
func NewFilteredConfigMapInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().ConfigMaps(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().ConfigMaps(namespace).Watch(options)
},
},
&corev1.ConfigMap{},
&core_v1.ConfigMap{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *configMapInformer) defaultInformer(client kubernetes.Interface, resyncP
}
func (f *configMapInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.ConfigMap{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.ConfigMap{}, f.defaultInformer)
}
func (f *configMapInformer) Lister() v1.ConfigMapLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewEndpointsInformer(client kubernetes.Interface, namespace string, resyncP
func NewFilteredEndpointsInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Endpoints(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Endpoints(namespace).Watch(options)
},
},
&corev1.Endpoints{},
&core_v1.Endpoints{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *endpointsInformer) defaultInformer(client kubernetes.Interface, resyncP
}
func (f *endpointsInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.Endpoints{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.Endpoints{}, f.defaultInformer)
}
func (f *endpointsInformer) Lister() v1.EndpointsLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewEventInformer(client kubernetes.Interface, namespace string, resyncPerio
func NewFilteredEventInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Events(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Events(namespace).Watch(options)
},
},
&corev1.Event{},
&core_v1.Event{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *eventInformer) defaultInformer(client kubernetes.Interface, resyncPerio
}
func (f *eventInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.Event{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.Event{}, f.defaultInformer)
}
func (f *eventInformer) Lister() v1.EventLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -57,20 +57,20 @@ func NewLimitRangeInformer(client kubernetes.Interface, namespace string, resync
func NewFilteredLimitRangeInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().LimitRanges(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().LimitRanges(namespace).Watch(options)
},
},
&corev1.LimitRange{},
&core_v1.LimitRange{},
resyncPeriod,
indexers,
)
@@ -81,7 +81,7 @@ func (f *limitRangeInformer) defaultInformer(client kubernetes.Interface, resync
}
func (f *limitRangeInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.LimitRange{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.LimitRange{}, f.defaultInformer)
}
func (f *limitRangeInformer) Lister() v1.LimitRangeLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -56,20 +56,20 @@ func NewNamespaceInformer(client kubernetes.Interface, resyncPeriod time.Duratio
func NewFilteredNamespaceInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Namespaces().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Namespaces().Watch(options)
},
},
&corev1.Namespace{},
&core_v1.Namespace{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *namespaceInformer) defaultInformer(client kubernetes.Interface, resyncP
}
func (f *namespaceInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.Namespace{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.Namespace{}, f.defaultInformer)
}
func (f *namespaceInformer) Lister() v1.NamespaceLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -56,20 +56,20 @@ func NewNodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, in
func NewFilteredNodeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Nodes().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Nodes().Watch(options)
},
},
&corev1.Node{},
&core_v1.Node{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *nodeInformer) defaultInformer(client kubernetes.Interface, resyncPeriod
}
func (f *nodeInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.Node{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.Node{}, f.defaultInformer)
}
func (f *nodeInformer) Lister() v1.NodeLister {

View File

@@ -21,8 +21,8 @@ package v1
import (
time "time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
core_v1 "k8s.io/api/core/v1"
meta_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"
@@ -56,20 +56,20 @@ func NewPersistentVolumeInformer(client kubernetes.Interface, resyncPeriod time.
func NewFilteredPersistentVolumeInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().PersistentVolumes().List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().PersistentVolumes().Watch(options)
},
},
&corev1.PersistentVolume{},
&core_v1.PersistentVolume{},
resyncPeriod,
indexers,
)
@@ -80,7 +80,7 @@ func (f *persistentVolumeInformer) defaultInformer(client kubernetes.Interface,
}
func (f *persistentVolumeInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&corev1.PersistentVolume{}, f.defaultInformer)
return f.factory.InformerFor(&core_v1.PersistentVolume{}, f.defaultInformer)
}
func (f *persistentVolumeInformer) Lister() v1.PersistentVolumeLister {

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