mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-19 16:25:23 +00:00
Compare commits
98 Commits
kubernetes
...
v0.28.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bc0b31a81 | ||
|
|
cf830e3cb3 | ||
|
|
f21df6e02d | ||
|
|
6c7d1bc996 | ||
|
|
c670796be1 | ||
|
|
260c0c5584 | ||
|
|
7a7ea20936 | ||
|
|
151a5919db | ||
|
|
6fbb0bd42d | ||
|
|
328d0fb9ee | ||
|
|
17a70dc433 | ||
|
|
4cb373f7ca | ||
|
|
3195e36899 | ||
|
|
bae10246dd | ||
|
|
2a5f18df73 | ||
|
|
0011bf6b0b | ||
|
|
84dc0417b2 | ||
|
|
a9b2f9e9eb | ||
|
|
f454763642 | ||
|
|
9e63bf0275 | ||
|
|
c2105c1815 | ||
|
|
9cbecd3bd0 | ||
|
|
73d5348976 | ||
|
|
578f82a568 | ||
|
|
435f839a9e | ||
|
|
79d967dc52 | ||
|
|
2c19e72cd0 | ||
|
|
d400a4ae88 | ||
|
|
81fe15039a | ||
|
|
c1b43e3ea9 | ||
|
|
2b18fa2c7a | ||
|
|
f5cc996aa0 | ||
|
|
54dbcc99c2 | ||
|
|
719a53871d | ||
|
|
23a9e82799 | ||
|
|
aa75d3bd59 | ||
|
|
7cf3125431 | ||
|
|
783d0d3362 | ||
|
|
a3b4cd32e5 | ||
|
|
bbdc95deee | ||
|
|
383ccb06d0 | ||
|
|
bea472626f | ||
|
|
d8abacd71f | ||
|
|
1513f87e7a | ||
|
|
54190dfee5 | ||
|
|
71d8553562 | ||
|
|
8009187f26 | ||
|
|
a4fdfdae72 | ||
|
|
18ffb5c67f | ||
|
|
0a0ddf8b6b | ||
|
|
f4ef3f8afe | ||
|
|
224050a5b5 | ||
|
|
10f4062eb6 | ||
|
|
757bbef10a | ||
|
|
337feac705 | ||
|
|
741627a2eb | ||
|
|
fa6d692ff2 | ||
|
|
3b78b11d95 | ||
|
|
fc96cec338 | ||
|
|
f775857c5d | ||
|
|
68394bf465 | ||
|
|
6aab78468d | ||
|
|
5d5619d392 | ||
|
|
6c514ab3ff | ||
|
|
1ec6a37b5c | ||
|
|
5c4e72cc66 | ||
|
|
27bbe76535 | ||
|
|
b26f379fe1 | ||
|
|
d2b87849d5 | ||
|
|
e846cbee0d | ||
|
|
a9d165e169 | ||
|
|
bc930747ec | ||
|
|
861f50a667 | ||
|
|
e367d8e4c2 | ||
|
|
061a00296e | ||
|
|
23557e107c | ||
|
|
c84a3771c0 | ||
|
|
34d738fb5d | ||
|
|
1aeffb0799 | ||
|
|
1dc92da663 | ||
|
|
4e89310f6e | ||
|
|
8b888b0027 | ||
|
|
46f963913c | ||
|
|
ef522cc423 | ||
|
|
12beb34ceb | ||
|
|
12553015e2 | ||
|
|
c4339eeca9 | ||
|
|
7114041b4f | ||
|
|
8005e0d28b | ||
|
|
20bcfef42d | ||
|
|
04ef61f72b | ||
|
|
8301a60862 | ||
|
|
42799afb70 | ||
|
|
20433f9965 | ||
|
|
9ed804b6d5 | ||
|
|
db3650d98f | ||
|
|
d3003795b3 | ||
|
|
0536a4e40c |
6
OWNERS
6
OWNERS
@@ -3,21 +3,23 @@
|
||||
approvers:
|
||||
- caesarxuchao
|
||||
- deads2k
|
||||
- lavalamp
|
||||
- liggitt
|
||||
- smarterclayton
|
||||
- sttts
|
||||
- yliaog
|
||||
- jpbetz
|
||||
reviewers:
|
||||
- aojea
|
||||
- apelisse
|
||||
- caesarxuchao
|
||||
- deads2k
|
||||
- jpbetz
|
||||
- lavalamp
|
||||
- liggitt
|
||||
- soltysh
|
||||
- sttts
|
||||
- yliaog
|
||||
- jpbetz
|
||||
labels:
|
||||
- sig/api-machinery
|
||||
emeritus_approvers:
|
||||
- lavalamp
|
||||
|
||||
@@ -32,8 +32,7 @@ import (
|
||||
type NetworkPolicyApplyConfiguration struct {
|
||||
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||
Spec *NetworkPolicySpecApplyConfiguration `json:"spec,omitempty"`
|
||||
Status *NetworkPolicyStatusApplyConfiguration `json:"status,omitempty"`
|
||||
Spec *NetworkPolicySpecApplyConfiguration `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkPolicy constructs an declarative configuration of the NetworkPolicy type for use with
|
||||
@@ -248,11 +247,3 @@ func (b *NetworkPolicyApplyConfiguration) WithSpec(value *NetworkPolicySpecApply
|
||||
b.Spec = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStatus sets the Status field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Status field is set to the value of the last call.
|
||||
func (b *NetworkPolicyApplyConfiguration) WithStatus(value *NetworkPolicyStatusApplyConfiguration) *NetworkPolicyApplyConfiguration {
|
||||
b.Status = value
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -1,48 +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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
|
||||
)
|
||||
|
||||
// NetworkPolicyStatusApplyConfiguration represents an declarative configuration of the NetworkPolicyStatus type for use
|
||||
// with apply.
|
||||
type NetworkPolicyStatusApplyConfiguration struct {
|
||||
Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkPolicyStatusApplyConfiguration constructs an declarative configuration of the NetworkPolicyStatus type for use with
|
||||
// apply.
|
||||
func NetworkPolicyStatus() *NetworkPolicyStatusApplyConfiguration {
|
||||
return &NetworkPolicyStatusApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithConditions adds the given value to the Conditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Conditions field.
|
||||
func (b *NetworkPolicyStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *NetworkPolicyStatusApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithConditions")
|
||||
}
|
||||
b.Conditions = append(b.Conditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -8343,10 +8343,6 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
namedType: io.k8s.api.extensions.v1beta1.NetworkPolicySpec
|
||||
default: {}
|
||||
- name: status
|
||||
type:
|
||||
namedType: io.k8s.api.extensions.v1beta1.NetworkPolicyStatus
|
||||
default: {}
|
||||
- name: io.k8s.api.extensions.v1beta1.NetworkPolicyEgressRule
|
||||
map:
|
||||
fields:
|
||||
@@ -8426,17 +8422,6 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
elementType:
|
||||
scalar: string
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.extensions.v1beta1.NetworkPolicyStatus
|
||||
map:
|
||||
fields:
|
||||
- name: conditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- type
|
||||
- name: io.k8s.api.extensions.v1beta1.ReplicaSet
|
||||
map:
|
||||
fields:
|
||||
@@ -10087,10 +10072,6 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
namedType: io.k8s.api.networking.v1.NetworkPolicySpec
|
||||
default: {}
|
||||
- name: status
|
||||
type:
|
||||
namedType: io.k8s.api.networking.v1.NetworkPolicyStatus
|
||||
default: {}
|
||||
- name: io.k8s.api.networking.v1.NetworkPolicyEgressRule
|
||||
map:
|
||||
fields:
|
||||
@@ -10170,17 +10151,6 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
elementType:
|
||||
scalar: string
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.networking.v1.NetworkPolicyStatus
|
||||
map:
|
||||
fields:
|
||||
- name: conditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- type
|
||||
- name: io.k8s.api.networking.v1.ServiceBackendPort
|
||||
map:
|
||||
fields:
|
||||
|
||||
@@ -32,8 +32,7 @@ import (
|
||||
type NetworkPolicyApplyConfiguration struct {
|
||||
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||
Spec *NetworkPolicySpecApplyConfiguration `json:"spec,omitempty"`
|
||||
Status *NetworkPolicyStatusApplyConfiguration `json:"status,omitempty"`
|
||||
Spec *NetworkPolicySpecApplyConfiguration `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkPolicy constructs an declarative configuration of the NetworkPolicy type for use with
|
||||
@@ -248,11 +247,3 @@ func (b *NetworkPolicyApplyConfiguration) WithSpec(value *NetworkPolicySpecApply
|
||||
b.Spec = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStatus sets the Status field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Status field is set to the value of the last call.
|
||||
func (b *NetworkPolicyApplyConfiguration) WithStatus(value *NetworkPolicyStatusApplyConfiguration) *NetworkPolicyApplyConfiguration {
|
||||
b.Status = value
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -1,48 +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 applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
|
||||
)
|
||||
|
||||
// NetworkPolicyStatusApplyConfiguration represents an declarative configuration of the NetworkPolicyStatus type for use
|
||||
// with apply.
|
||||
type NetworkPolicyStatusApplyConfiguration struct {
|
||||
Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkPolicyStatusApplyConfiguration constructs an declarative configuration of the NetworkPolicyStatus type for use with
|
||||
// apply.
|
||||
func NetworkPolicyStatus() *NetworkPolicyStatusApplyConfiguration {
|
||||
return &NetworkPolicyStatusApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithConditions adds the given value to the Conditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Conditions field.
|
||||
func (b *NetworkPolicyStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *NetworkPolicyStatusApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithConditions")
|
||||
}
|
||||
b.Conditions = append(b.Conditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -1019,8 +1019,6 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationsextensionsv1beta1.NetworkPolicyPortApplyConfiguration{}
|
||||
case extensionsv1beta1.SchemeGroupVersion.WithKind("NetworkPolicySpec"):
|
||||
return &applyconfigurationsextensionsv1beta1.NetworkPolicySpecApplyConfiguration{}
|
||||
case extensionsv1beta1.SchemeGroupVersion.WithKind("NetworkPolicyStatus"):
|
||||
return &applyconfigurationsextensionsv1beta1.NetworkPolicyStatusApplyConfiguration{}
|
||||
case extensionsv1beta1.SchemeGroupVersion.WithKind("ReplicaSet"):
|
||||
return &applyconfigurationsextensionsv1beta1.ReplicaSetApplyConfiguration{}
|
||||
case extensionsv1beta1.SchemeGroupVersion.WithKind("ReplicaSetCondition"):
|
||||
@@ -1293,8 +1291,6 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationsnetworkingv1.NetworkPolicyPortApplyConfiguration{}
|
||||
case networkingv1.SchemeGroupVersion.WithKind("NetworkPolicySpec"):
|
||||
return &applyconfigurationsnetworkingv1.NetworkPolicySpecApplyConfiguration{}
|
||||
case networkingv1.SchemeGroupVersion.WithKind("NetworkPolicyStatus"):
|
||||
return &applyconfigurationsnetworkingv1.NetworkPolicyStatusApplyConfiguration{}
|
||||
case networkingv1.SchemeGroupVersion.WithKind("ServiceBackendPort"):
|
||||
return &applyconfigurationsnetworkingv1.ServiceBackendPortApplyConfiguration{}
|
||||
|
||||
|
||||
@@ -111,8 +111,6 @@ func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (
|
||||
return group, gvResources, failedGVs
|
||||
}
|
||||
|
||||
var emptyKind = metav1.GroupVersionKind{}
|
||||
|
||||
// convertAPIResource tranforms a APIResourceDiscovery to an APIResource. We are
|
||||
// resilient to missing GVK, since this resource might be the parent resource
|
||||
// for a subresource. If the parent is missing a GVK, it is not returned in
|
||||
@@ -127,7 +125,7 @@ func convertAPIResource(in apidiscovery.APIResourceDiscovery) (metav1.APIResourc
|
||||
Categories: in.Categories,
|
||||
}
|
||||
var err error
|
||||
if in.ResponseKind != nil && (*in.ResponseKind) != emptyKind {
|
||||
if in.ResponseKind != nil {
|
||||
result.Group = in.ResponseKind.Group
|
||||
result.Version = in.ResponseKind.Version
|
||||
result.Kind = in.ResponseKind.Kind
|
||||
@@ -142,7 +140,7 @@ func convertAPIResource(in apidiscovery.APIResourceDiscovery) (metav1.APIResourc
|
||||
// convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource.
|
||||
func convertAPISubresource(parent metav1.APIResource, in apidiscovery.APISubresourceDiscovery) (metav1.APIResource, error) {
|
||||
result := metav1.APIResource{}
|
||||
if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
|
||||
if in.ResponseKind == nil {
|
||||
return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource)
|
||||
}
|
||||
result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource)
|
||||
|
||||
@@ -610,76 +610,6 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery with single subresource and parent empty GVK",
|
||||
agg: apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "external.metrics.k8s.io",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
// resilient to empty GVK for parent
|
||||
Resource: "*",
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
SingularResource: "",
|
||||
ResponseKind: &metav1.GroupVersionKind{},
|
||||
Subresources: []apidiscovery.APISubresourceDiscovery{
|
||||
{
|
||||
Subresource: "other-external-metric",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Kind: "MetricValueList",
|
||||
},
|
||||
Verbs: []string{"get"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGroups: metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "external.metrics.k8s.io",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{
|
||||
GroupVersion: "external.metrics.k8s.io/v1beta1",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: "external.metrics.k8s.io/v1beta1",
|
||||
Version: "v1beta1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{
|
||||
{Group: "external.metrics.k8s.io", Version: "v1beta1"}: {
|
||||
GroupVersion: "external.metrics.k8s.io/v1beta1",
|
||||
APIResources: []metav1.APIResource{
|
||||
// Since parent GVK was nil, it is NOT returned--only the subresource.
|
||||
{
|
||||
Name: "*/other-external-metric",
|
||||
SingularName: "",
|
||||
Namespaced: true,
|
||||
Group: "",
|
||||
Version: "",
|
||||
Kind: "MetricValueList",
|
||||
Verbs: []string{"get"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery with multiple subresources",
|
||||
agg: apidiscovery.APIGroupDiscoveryList{
|
||||
|
||||
@@ -67,9 +67,6 @@ const (
|
||||
acceptDiscoveryFormats = AcceptV2Beta1 + "," + AcceptV1
|
||||
)
|
||||
|
||||
// Aggregated discovery content-type GVK.
|
||||
var v2Beta1GVK = schema.GroupVersionKind{Group: "apidiscovery.k8s.io", Version: "v2beta1", Kind: "APIGroupDiscoveryList"}
|
||||
|
||||
// DiscoveryInterface holds the methods that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryInterface interface {
|
||||
@@ -263,15 +260,16 @@ func (d *DiscoveryClient) downloadLegacy() (
|
||||
}
|
||||
|
||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||
// Based on the content-type server responded with: aggregated or unaggregated.
|
||||
if isGVK, _ := ContentTypeIsGVK(responseContentType, v2Beta1GVK); isGVK {
|
||||
// Switch on content-type server responded with: aggregated or unaggregated.
|
||||
switch {
|
||||
case isV2Beta1ContentType(responseContentType):
|
||||
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
||||
err = json.Unmarshal(body, &aggregatedDiscovery)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
||||
} else {
|
||||
default:
|
||||
// Default is unaggregated discovery v1.
|
||||
var v metav1.APIVersions
|
||||
err = json.Unmarshal(body, &v)
|
||||
@@ -315,15 +313,16 @@ func (d *DiscoveryClient) downloadAPIs() (
|
||||
apiGroupList := &metav1.APIGroupList{}
|
||||
failedGVs := map[schema.GroupVersion]error{}
|
||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||
// Based on the content-type server responded with: aggregated or unaggregated.
|
||||
if isGVK, _ := ContentTypeIsGVK(responseContentType, v2Beta1GVK); isGVK {
|
||||
// Switch on content-type server responded with: aggregated or unaggregated.
|
||||
switch {
|
||||
case isV2Beta1ContentType(responseContentType):
|
||||
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
||||
err = json.Unmarshal(body, &aggregatedDiscovery)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
||||
} else {
|
||||
default:
|
||||
// Default is unaggregated discovery v1.
|
||||
err = json.Unmarshal(body, apiGroupList)
|
||||
if err != nil {
|
||||
@@ -334,29 +333,26 @@ func (d *DiscoveryClient) downloadAPIs() (
|
||||
return apiGroupList, resourcesByGV, failedGVs, nil
|
||||
}
|
||||
|
||||
// ContentTypeIsGVK checks of the content-type string is both
|
||||
// "application/json" and matches the provided GVK. An error
|
||||
// is returned if the content type string is malformed.
|
||||
// isV2Beta1ContentType checks of the content-type string is both
|
||||
// "application/json" and contains the v2beta1 content-type params.
|
||||
// NOTE: This function is resilient to the ordering of the
|
||||
// content-type parameters, as well as parameters added by
|
||||
// intermediaries such as proxies or gateways. Examples:
|
||||
//
|
||||
// ("application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList", {apidiscovery.k8s.io, v2beta1, APIGroupDiscoveryList}) = (true, nil)
|
||||
// ("application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io", {apidiscovery.k8s.io, v2beta1, APIGroupDiscoveryList}) = (true, nil)
|
||||
// ("application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io;charset=utf-8", {apidiscovery.k8s.io, v2beta1, APIGroupDiscoveryList}) = (true, nil)
|
||||
// ("application/json", any GVK) = (false, nil)
|
||||
// ("application/json; charset=UTF-8", any GVK) = (false, nil)
|
||||
// ("malformed content type string", any GVK) = (false, error)
|
||||
func ContentTypeIsGVK(contentType string, gvk schema.GroupVersionKind) (bool, error) {
|
||||
// "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList" = true
|
||||
// "application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io" = true
|
||||
// "application/json; as=APIGroupDiscoveryList;v=v2beta1;g=apidiscovery.k8s.io;charset=utf-8" = true
|
||||
// "application/json" = false
|
||||
// "application/json; charset=UTF-8" = false
|
||||
func isV2Beta1ContentType(contentType string) bool {
|
||||
base, params, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
gvkMatch := runtime.ContentTypeJSON == base &&
|
||||
params["g"] == gvk.Group &&
|
||||
params["v"] == gvk.Version &&
|
||||
params["as"] == gvk.Kind
|
||||
return gvkMatch, nil
|
||||
return runtime.ContentTypeJSON == base &&
|
||||
params["g"] == "apidiscovery.k8s.io" &&
|
||||
params["v"] == "v2beta1" &&
|
||||
params["as"] == "APIGroupDiscoveryList"
|
||||
}
|
||||
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/openapi"
|
||||
@@ -437,7 +436,7 @@ func TestGetServerResourcesForGroupVersion(t *testing.T) {
|
||||
"extensions/v1beta10",
|
||||
}
|
||||
if !reflect.DeepEqual(expectedGroupVersions, serverGroupVersions) {
|
||||
t.Errorf("unexpected group versions: %v", diff.ObjectReflectDiff(expectedGroupVersions, serverGroupVersions))
|
||||
t.Errorf("unexpected group versions: %v", cmp.Diff(expectedGroupVersions, serverGroupVersions))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2763,76 +2762,54 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDiscoveryContentTypeVersion(t *testing.T) {
|
||||
v2beta1 := schema.GroupVersionKind{Group: "apidiscovery.k8s.io", Version: "v2beta1", Kind: "APIGroupDiscoveryList"}
|
||||
tests := []struct {
|
||||
contentType string
|
||||
gvk schema.GroupVersionKind
|
||||
match bool
|
||||
expectErr bool
|
||||
isV2Beta1 bool
|
||||
}{
|
||||
{
|
||||
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList",
|
||||
gvk: v2beta1,
|
||||
match: true,
|
||||
expectErr: false,
|
||||
isV2Beta1: true,
|
||||
},
|
||||
{
|
||||
// content-type parameters are not in correct order, but comparison ignores order.
|
||||
contentType: "application/json; v=v2beta1;as=APIGroupDiscoveryList;g=apidiscovery.k8s.io",
|
||||
gvk: v2beta1,
|
||||
match: true,
|
||||
expectErr: false,
|
||||
isV2Beta1: true,
|
||||
},
|
||||
{
|
||||
// content-type parameters are not in correct order, but comparison ignores order.
|
||||
contentType: "application/json; as=APIGroupDiscoveryList;g=apidiscovery.k8s.io;v=v2beta1",
|
||||
gvk: v2beta1,
|
||||
match: true,
|
||||
expectErr: false,
|
||||
isV2Beta1: true,
|
||||
},
|
||||
{
|
||||
// Ignores extra parameter "charset=utf-8"
|
||||
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList;charset=utf-8",
|
||||
gvk: v2beta1,
|
||||
match: true,
|
||||
expectErr: false,
|
||||
isV2Beta1: true,
|
||||
},
|
||||
{
|
||||
contentType: "application/json",
|
||||
gvk: v2beta1,
|
||||
match: false,
|
||||
expectErr: false,
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
gvk: v2beta1,
|
||||
match: false,
|
||||
expectErr: false,
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "text/json",
|
||||
gvk: v2beta1,
|
||||
match: false,
|
||||
expectErr: false,
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "text/html",
|
||||
gvk: v2beta1,
|
||||
match: false,
|
||||
expectErr: false,
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "",
|
||||
gvk: v2beta1,
|
||||
match: false,
|
||||
expectErr: true,
|
||||
isV2Beta1: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
match, err := ContentTypeIsGVK(test.contentType, test.gvk)
|
||||
assert.Equal(t, test.expectErr, err != nil)
|
||||
assert.Equal(t, test.match, match)
|
||||
isV2Beta1 := isV2Beta1ContentType(test.contentType)
|
||||
assert.Equal(t, test.isV2Beta1, isV2Beta1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"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"
|
||||
@@ -118,7 +118,7 @@ func TestFilteredDynamicSharedInformerFactory(t *testing.T) {
|
||||
t.Errorf("informer received an object for namespace %s when watching namespace %s", ts.ns, ts.informNS)
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
|
||||
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
|
||||
t.Fatalf("%v", cmp.Diff(testObject, objFromInformer))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if ts.ns == ts.informNS {
|
||||
@@ -239,7 +239,7 @@ func TestDynamicSharedInformerFactory(t *testing.T) {
|
||||
select {
|
||||
case objFromInformer := <-informerReciveObjectCh:
|
||||
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
|
||||
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
|
||||
t.Fatalf("%v", cmp.Diff(testObject, objFromInformer))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Errorf("tested informer haven't received an object, waited %v", timeout)
|
||||
|
||||
@@ -20,11 +20,11 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"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"
|
||||
)
|
||||
@@ -90,7 +90,7 @@ func TestNamespaceGetMethod(t *testing.T) {
|
||||
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))
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, cmp.Diff(test.expectedObject, actualObject))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -188,7 +188,7 @@ func TestListerGetMethod(t *testing.T) {
|
||||
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))
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, cmp.Diff(test.expectedObject, actualObject))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -245,7 +245,7 @@ func assertListOrDie(expected, actual []*unstructured.Unstructured, t *testing.T
|
||||
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))
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", expectedObject, actualObject, cmp.Diff(expectedObject, actualObject))
|
||||
}
|
||||
found = true
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -80,7 +79,7 @@ func TestGet(t *testing.T) {
|
||||
},
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(get, expected) {
|
||||
t.Fatal(diff.ObjectGoPrintDiff(expected, get))
|
||||
t.Fatal(cmp.Diff(expected, get))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ func TestListDecoding(t *testing.T) {
|
||||
Items: []unstructured.Unstructured{},
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(list, expectedList) {
|
||||
t.Fatal(diff.ObjectGoPrintDiff(expectedList, list))
|
||||
t.Fatal(cmp.Diff(expectedList, list))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +116,7 @@ func TestGetDecoding(t *testing.T) {
|
||||
},
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(get, expectedObj) {
|
||||
t.Fatal(diff.ObjectGoPrintDiff(expectedObj, get))
|
||||
t.Fatal(cmp.Diff(expectedObj, get))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +144,7 @@ func TestList(t *testing.T) {
|
||||
*newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
|
||||
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
|
||||
t.Fatal(cmp.Diff(expected, listFirst.Items))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +188,7 @@ func Test_ListKind(t *testing.T) {
|
||||
},
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(listFirst, expectedList) {
|
||||
t.Fatal(diff.ObjectGoPrintDiff(expectedList, listFirst))
|
||||
t.Fatal(cmp.Diff(expectedList, listFirst))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +241,7 @@ func (tc *patchTestCase) verifyResult(result *unstructured.Unstructured) error {
|
||||
return nil
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
|
||||
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
|
||||
return fmt.Errorf("unexpected diff in received object: %s", cmp.Diff(tc.expectedPatchedObject, result))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
38
go.mod
38
go.mod
@@ -5,38 +5,38 @@ module k8s.io/client-go
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/gnostic v0.5.7-v3refs
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/gofuzz v1.1.0
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
|
||||
github.com/imdario/mergo v0.3.6
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/term v0.18.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/term v0.7.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
k8s.io/api v0.27.14
|
||||
k8s.io/apimachinery v0.27.14
|
||||
k8s.io/klog/v2 v2.90.1
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
||||
google.golang.org/protobuf v1.30.0
|
||||
k8s.io/api v0.28.0-alpha.1
|
||||
k8s.io/apimachinery v0.28.0-alpha.1
|
||||
k8s.io/klog/v2 v2.100.1
|
||||
k8s.io/kube-openapi v0.0.0-20230524182850-78281498afbb
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.1 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
@@ -48,12 +48,18 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/gomega v1.27.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.28.0-alpha.1
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.28.0-alpha.1
|
||||
)
|
||||
|
||||
70
go.sum
70
go.sum
@@ -15,15 +15,15 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
|
||||
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -41,8 +41,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
@@ -55,8 +55,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -89,8 +89,9 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=
|
||||
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
|
||||
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -98,7 +99,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
@@ -109,8 +110,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -131,11 +133,11 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -145,15 +147,15 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -164,7 +166,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -190,8 +192,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -208,16 +210,16 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.27.14 h1:/oKAF9HiSB47polol2Ji2TaFnC400JK57jSPUXY5MzU=
|
||||
k8s.io/api v0.27.14/go.mod h1:Jekhd9Kyo2CsmJlYbqZPXNwIxiHvyGJCdp0X56yDyvU=
|
||||
k8s.io/apimachinery v0.27.14 h1:jAIGvPbvAg4XJysK7JPFa6DdjTR6vts4/p4Q6ZrcQ+4=
|
||||
k8s.io/apimachinery v0.27.14/go.mod h1:TWo+8wOIz3CytsrlI9k/LBWXLRr9dqf5hRSCbbggMAg=
|
||||
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
|
||||
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/api v0.28.0-alpha.1 h1:3bUmrW1e4qXoeCVhF/ujVmV23lVBVeDEvv28ESdSvWY=
|
||||
k8s.io/api v0.28.0-alpha.1/go.mod h1:DDfdvx9eX3kJcFxYr/xa56cxGM9W0ShN+0Jlyl7Fmug=
|
||||
k8s.io/apimachinery v0.28.0-alpha.1 h1:meWzVdzwTLuBAvXwv6MHgSnkCFggcBq2bzziCdaLACY=
|
||||
k8s.io/apimachinery v0.28.0-alpha.1/go.mod h1:qCTDst7QeP2n3JDxbpuJSTTaAxalvFKMTN0Lga1+0zU=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20230524182850-78281498afbb h1:uV7fQu8Ldz5z8jAjwKQ0qftj249dTMzwlPEGhNIO4Ug=
|
||||
k8s.io/kube-openapi v0.0.0-20230524182850-78281498afbb/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
|
||||
@@ -184,7 +184,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref
|
||||
return res
|
||||
}
|
||||
|
||||
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
|
||||
// InformerFor returns the SharedIndexInformer for obj using an internal
|
||||
// client.
|
||||
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
|
||||
f.lock.Lock()
|
||||
@@ -257,7 +257,7 @@ type SharedInformerFactory interface {
|
||||
// ForResource gives generic access to a shared informer of the matching type.
|
||||
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
|
||||
|
||||
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
|
||||
// InformerFor returns the SharedIndexInformer for obj using an internal
|
||||
// client.
|
||||
InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
type AuthenticationV1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
SelfSubjectReviewsGetter
|
||||
TokenReviewsGetter
|
||||
}
|
||||
|
||||
@@ -36,6 +37,10 @@ type AuthenticationV1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *AuthenticationV1Client) SelfSubjectReviews() SelfSubjectReviewInterface {
|
||||
return newSelfSubjectReviews(c)
|
||||
}
|
||||
|
||||
func (c *AuthenticationV1Client) TokenReviews() TokenReviewInterface {
|
||||
return newTokenReviews(c)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ type FakeAuthenticationV1 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeAuthenticationV1) SelfSubjectReviews() v1.SelfSubjectReviewInterface {
|
||||
return &FakeSelfSubjectReviews{c}
|
||||
}
|
||||
|
||||
func (c *FakeAuthenticationV1) TokenReviews() v1.TokenReviewInterface {
|
||||
return &FakeTokenReviews{c}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeSelfSubjectReviews implements SelfSubjectReviewInterface
|
||||
type FakeSelfSubjectReviews struct {
|
||||
Fake *FakeAuthenticationV1
|
||||
}
|
||||
|
||||
var selfsubjectreviewsResource = v1.SchemeGroupVersion.WithResource("selfsubjectreviews")
|
||||
|
||||
var selfsubjectreviewsKind = v1.SchemeGroupVersion.WithKind("SelfSubjectReview")
|
||||
|
||||
// Create takes the representation of a selfSubjectReview and creates it. Returns the server's representation of the selfSubjectReview, and an error, if there is any.
|
||||
func (c *FakeSelfSubjectReviews) Create(ctx context.Context, selfSubjectReview *v1.SelfSubjectReview, opts metav1.CreateOptions) (result *v1.SelfSubjectReview, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootCreateAction(selfsubjectreviewsResource, selfSubjectReview), &v1.SelfSubjectReview{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1.SelfSubjectReview), err
|
||||
}
|
||||
@@ -18,4 +18,6 @@ limitations under the License.
|
||||
|
||||
package v1
|
||||
|
||||
type SelfSubjectReviewExpansion interface{}
|
||||
|
||||
type TokenReviewExpansion interface{}
|
||||
|
||||
64
kubernetes/typed/authentication/v1/selfsubjectreview.go
Normal file
64
kubernetes/typed/authentication/v1/selfsubjectreview.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// SelfSubjectReviewsGetter has a method to return a SelfSubjectReviewInterface.
|
||||
// A group's client should implement this interface.
|
||||
type SelfSubjectReviewsGetter interface {
|
||||
SelfSubjectReviews() SelfSubjectReviewInterface
|
||||
}
|
||||
|
||||
// SelfSubjectReviewInterface has methods to work with SelfSubjectReview resources.
|
||||
type SelfSubjectReviewInterface interface {
|
||||
Create(ctx context.Context, selfSubjectReview *v1.SelfSubjectReview, opts metav1.CreateOptions) (*v1.SelfSubjectReview, error)
|
||||
SelfSubjectReviewExpansion
|
||||
}
|
||||
|
||||
// selfSubjectReviews implements SelfSubjectReviewInterface
|
||||
type selfSubjectReviews struct {
|
||||
client rest.Interface
|
||||
}
|
||||
|
||||
// newSelfSubjectReviews returns a SelfSubjectReviews
|
||||
func newSelfSubjectReviews(c *AuthenticationV1Client) *selfSubjectReviews {
|
||||
return &selfSubjectReviews{
|
||||
client: c.RESTClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// Create takes the representation of a selfSubjectReview and creates it. Returns the server's representation of the selfSubjectReview, and an error, if there is any.
|
||||
func (c *selfSubjectReviews) Create(ctx context.Context, selfSubjectReview *v1.SelfSubjectReview, opts metav1.CreateOptions) (result *v1.SelfSubjectReview, err error) {
|
||||
result = &v1.SelfSubjectReview{}
|
||||
err = c.client.Post().
|
||||
Resource("selfsubjectreviews").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(selfSubjectReview).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
@@ -104,18 +104,6 @@ func (c *FakeNetworkPolicies) Update(ctx context.Context, networkPolicy *v1beta1
|
||||
return obj.(*v1beta1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeNetworkPolicies) UpdateStatus(ctx context.Context, networkPolicy *v1beta1.NetworkPolicy, opts v1.UpdateOptions) (*v1beta1.NetworkPolicy, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateSubresourceAction(networkpoliciesResource, "status", c.ns, networkPolicy), &v1beta1.NetworkPolicy{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1beta1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
// Delete takes name of the networkPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeNetworkPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
@@ -164,26 +152,3 @@ func (c *FakeNetworkPolicies) Apply(ctx context.Context, networkPolicy *extensio
|
||||
}
|
||||
return obj.(*v1beta1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
// ApplyStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
|
||||
func (c *FakeNetworkPolicies) ApplyStatus(ctx context.Context, networkPolicy *extensionsv1beta1.NetworkPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1beta1.NetworkPolicy, err error) {
|
||||
if networkPolicy == nil {
|
||||
return nil, fmt.Errorf("networkPolicy provided to Apply must not be nil")
|
||||
}
|
||||
data, err := json.Marshal(networkPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := networkPolicy.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("networkPolicy.Name must be provided to Apply")
|
||||
}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(networkpoliciesResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1beta1.NetworkPolicy{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1beta1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ type NetworkPoliciesGetter interface {
|
||||
type NetworkPolicyInterface interface {
|
||||
Create(ctx context.Context, networkPolicy *v1beta1.NetworkPolicy, opts v1.CreateOptions) (*v1beta1.NetworkPolicy, error)
|
||||
Update(ctx context.Context, networkPolicy *v1beta1.NetworkPolicy, opts v1.UpdateOptions) (*v1beta1.NetworkPolicy, error)
|
||||
UpdateStatus(ctx context.Context, networkPolicy *v1beta1.NetworkPolicy, opts v1.UpdateOptions) (*v1beta1.NetworkPolicy, error)
|
||||
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.NetworkPolicy, error)
|
||||
@@ -51,7 +50,6 @@ type NetworkPolicyInterface interface {
|
||||
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.NetworkPolicy, err error)
|
||||
Apply(ctx context.Context, networkPolicy *extensionsv1beta1.NetworkPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1beta1.NetworkPolicy, err error)
|
||||
ApplyStatus(ctx context.Context, networkPolicy *extensionsv1beta1.NetworkPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1beta1.NetworkPolicy, err error)
|
||||
NetworkPolicyExpansion
|
||||
}
|
||||
|
||||
@@ -141,22 +139,6 @@ func (c *networkPolicies) Update(ctx context.Context, networkPolicy *v1beta1.Net
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *networkPolicies) UpdateStatus(ctx context.Context, networkPolicy *v1beta1.NetworkPolicy, opts v1.UpdateOptions) (result *v1beta1.NetworkPolicy, err error) {
|
||||
result = &v1beta1.NetworkPolicy{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("networkpolicies").
|
||||
Name(networkPolicy.Name).
|
||||
SubResource("status").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(networkPolicy).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the networkPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *networkPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
@@ -224,33 +206,3 @@ func (c *networkPolicies) Apply(ctx context.Context, networkPolicy *extensionsv1
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplyStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
|
||||
func (c *networkPolicies) ApplyStatus(ctx context.Context, networkPolicy *extensionsv1beta1.NetworkPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1beta1.NetworkPolicy, err error) {
|
||||
if networkPolicy == nil {
|
||||
return nil, fmt.Errorf("networkPolicy provided to Apply must not be nil")
|
||||
}
|
||||
patchOpts := opts.ToPatchOptions()
|
||||
data, err := json.Marshal(networkPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := networkPolicy.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("networkPolicy.Name must be provided to Apply")
|
||||
}
|
||||
|
||||
result = &v1beta1.NetworkPolicy{}
|
||||
err = c.client.Patch(types.ApplyPatchType).
|
||||
Namespace(c.ns).
|
||||
Resource("networkpolicies").
|
||||
Name(*name).
|
||||
SubResource("status").
|
||||
VersionedParams(&patchOpts, scheme.ParameterCodec).
|
||||
Body(data).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,18 +104,6 @@ func (c *FakeNetworkPolicies) Update(ctx context.Context, networkPolicy *v1.Netw
|
||||
return obj.(*v1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeNetworkPolicies) UpdateStatus(ctx context.Context, networkPolicy *v1.NetworkPolicy, opts metav1.UpdateOptions) (*v1.NetworkPolicy, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateSubresourceAction(networkpoliciesResource, "status", c.ns, networkPolicy), &v1.NetworkPolicy{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
// Delete takes name of the networkPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeNetworkPolicies) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
@@ -164,26 +152,3 @@ func (c *FakeNetworkPolicies) Apply(ctx context.Context, networkPolicy *networki
|
||||
}
|
||||
return obj.(*v1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
// ApplyStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
|
||||
func (c *FakeNetworkPolicies) ApplyStatus(ctx context.Context, networkPolicy *networkingv1.NetworkPolicyApplyConfiguration, opts metav1.ApplyOptions) (result *v1.NetworkPolicy, err error) {
|
||||
if networkPolicy == nil {
|
||||
return nil, fmt.Errorf("networkPolicy provided to Apply must not be nil")
|
||||
}
|
||||
data, err := json.Marshal(networkPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := networkPolicy.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("networkPolicy.Name must be provided to Apply")
|
||||
}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(networkpoliciesResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1.NetworkPolicy{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1.NetworkPolicy), err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ type NetworkPoliciesGetter interface {
|
||||
type NetworkPolicyInterface interface {
|
||||
Create(ctx context.Context, networkPolicy *v1.NetworkPolicy, opts metav1.CreateOptions) (*v1.NetworkPolicy, error)
|
||||
Update(ctx context.Context, networkPolicy *v1.NetworkPolicy, opts metav1.UpdateOptions) (*v1.NetworkPolicy, error)
|
||||
UpdateStatus(ctx context.Context, networkPolicy *v1.NetworkPolicy, opts metav1.UpdateOptions) (*v1.NetworkPolicy, error)
|
||||
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.NetworkPolicy, error)
|
||||
@@ -51,7 +50,6 @@ type NetworkPolicyInterface interface {
|
||||
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.NetworkPolicy, err error)
|
||||
Apply(ctx context.Context, networkPolicy *networkingv1.NetworkPolicyApplyConfiguration, opts metav1.ApplyOptions) (result *v1.NetworkPolicy, err error)
|
||||
ApplyStatus(ctx context.Context, networkPolicy *networkingv1.NetworkPolicyApplyConfiguration, opts metav1.ApplyOptions) (result *v1.NetworkPolicy, err error)
|
||||
NetworkPolicyExpansion
|
||||
}
|
||||
|
||||
@@ -141,22 +139,6 @@ func (c *networkPolicies) Update(ctx context.Context, networkPolicy *v1.NetworkP
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *networkPolicies) UpdateStatus(ctx context.Context, networkPolicy *v1.NetworkPolicy, opts metav1.UpdateOptions) (result *v1.NetworkPolicy, err error) {
|
||||
result = &v1.NetworkPolicy{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("networkpolicies").
|
||||
Name(networkPolicy.Name).
|
||||
SubResource("status").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(networkPolicy).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the networkPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *networkPolicies) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
@@ -224,33 +206,3 @@ func (c *networkPolicies) Apply(ctx context.Context, networkPolicy *networkingv1
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplyStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
|
||||
func (c *networkPolicies) ApplyStatus(ctx context.Context, networkPolicy *networkingv1.NetworkPolicyApplyConfiguration, opts metav1.ApplyOptions) (result *v1.NetworkPolicy, err error) {
|
||||
if networkPolicy == nil {
|
||||
return nil, fmt.Errorf("networkPolicy provided to Apply must not be nil")
|
||||
}
|
||||
patchOpts := opts.ToPatchOptions()
|
||||
data, err := json.Marshal(networkPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := networkPolicy.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("networkPolicy.Name must be provided to Apply")
|
||||
}
|
||||
|
||||
result = &v1.NetworkPolicy{}
|
||||
err = c.client.Patch(types.ApplyPatchType).
|
||||
Namespace(c.ns).
|
||||
Resource("networkpolicies").
|
||||
Name(*name).
|
||||
SubResource("status").
|
||||
VersionedParams(&patchOpts, scheme.ParameterCodec).
|
||||
Body(data).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,10 +23,9 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -39,7 +38,7 @@ func TestListTimeout(t *testing.T) {
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Client: manualfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Query().Get("timeout") != "21s" {
|
||||
t.Fatal(spew.Sdump(req.URL.Query()))
|
||||
t.Fatal(dump.Pretty(req.URL.Query()))
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Body: io.NopCloser(&bytes.Buffer{})}, nil
|
||||
}),
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -79,7 +79,7 @@ func TestList(t *testing.T) {
|
||||
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
|
||||
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
|
||||
t.Fatal(cmp.Diff(expected, listFirst.Items))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (tc *patchTestCase) verifyResult(result *metav1.PartialObjectMetadata) erro
|
||||
return nil
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
|
||||
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
|
||||
return fmt.Errorf("unexpected diff in received object: %s", cmp.Diff(tc.expectedPatchedObject, result))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestClient(t *testing.T) {
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(expect, obj) {
|
||||
t.Fatal(diff.ObjectReflectDiff(expect, obj))
|
||||
t.Fatal(cmp.Diff(expect, obj))
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -146,7 +146,7 @@ func TestClient(t *testing.T) {
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(expect, objs.Items) {
|
||||
t.Fatal(diff.ObjectReflectDiff(expect, objs.Items))
|
||||
t.Fatal(cmp.Diff(expect, objs.Items))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,13 +22,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/metadata/fake"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
@@ -147,7 +147,7 @@ func TestMetadataSharedInformerFactory(t *testing.T) {
|
||||
select {
|
||||
case objFromInformer := <-informerReciveObjectCh:
|
||||
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
|
||||
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
|
||||
t.Fatalf("%v", cmp.Diff(testObject, objFromInformer))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Errorf("tested informer haven't received an object, waited %v", timeout)
|
||||
|
||||
@@ -20,11 +20,11 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestNamespaceGetMethod(t *testing.T) {
|
||||
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))
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, cmp.Diff(test.expectedObject, actualObject))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func TestListerGetMethod(t *testing.T) {
|
||||
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))
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, cmp.Diff(test.expectedObject, actualObject))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func assertListOrDie(expected, actual []*metav1.PartialObjectMetadata, t *testin
|
||||
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))
|
||||
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", expectedObject, actualObject, cmp.Diff(expectedObject, actualObject))
|
||||
}
|
||||
found = true
|
||||
}
|
||||
|
||||
@@ -19,33 +19,35 @@ package openapitest
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/openapi"
|
||||
)
|
||||
|
||||
//go:embed testdata/*_openapi.json
|
||||
var f embed.FS
|
||||
var embedded embed.FS
|
||||
|
||||
// NewFileClient returns a test client implementing the openapi.Client
|
||||
// interface, which serves a subset of hard-coded GroupVersion
|
||||
// Open API V3 specifications files. The subset of specifications is
|
||||
// located in the "testdata" subdirectory.
|
||||
func NewFileClient(t *testing.T) openapi.Client {
|
||||
if t == nil {
|
||||
panic("non-nil testing.T required; this package is only for use in tests")
|
||||
// interface, which serves Open API V3 specifications files from the
|
||||
// given path, as prepared in `api/openapi-spec/v3`.
|
||||
func NewFileClient(path string) openapi.Client {
|
||||
return &fileClient{f: os.DirFS(path)}
|
||||
}
|
||||
|
||||
// NewEmbeddedFileClient returns a test client that uses the embedded
|
||||
// `testdata` openapi files.
|
||||
func NewEmbeddedFileClient() openapi.Client {
|
||||
f, err := fs.Sub(embedded, "testdata")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &fileClient{t: t}
|
||||
return &fileClient{f: f}
|
||||
}
|
||||
|
||||
type fileClient struct {
|
||||
t *testing.T
|
||||
init sync.Once
|
||||
paths map[string]openapi.GroupVersion
|
||||
err error
|
||||
f fs.FS
|
||||
}
|
||||
|
||||
// fileClient implements the openapi.Client interface.
|
||||
@@ -60,29 +62,23 @@ var _ openapi.Client = &fileClient{}
|
||||
//
|
||||
// The file contents are read only once. All files must parse correctly
|
||||
// into an api path, or an error is returned.
|
||||
func (t *fileClient) Paths() (map[string]openapi.GroupVersion, error) {
|
||||
t.init.Do(func() {
|
||||
t.paths = map[string]openapi.GroupVersion{}
|
||||
entries, err := f.ReadDir("testdata")
|
||||
if err != nil {
|
||||
t.err = err
|
||||
t.t.Error(err)
|
||||
}
|
||||
for _, e := range entries {
|
||||
// this reverses the transformation done in hack/update-openapi-spec.sh
|
||||
path := strings.ReplaceAll(strings.TrimSuffix(e.Name(), "_openapi.json"), "__", "/")
|
||||
t.paths[path] = &fileGroupVersion{t: t.t, filename: filepath.Join("testdata", e.Name())}
|
||||
}
|
||||
})
|
||||
return t.paths, t.err
|
||||
func (f *fileClient) Paths() (map[string]openapi.GroupVersion, error) {
|
||||
paths := map[string]openapi.GroupVersion{}
|
||||
entries, err := fs.ReadDir(f.f, ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range entries {
|
||||
// this reverses the transformation done in hack/update-openapi-spec.sh
|
||||
path := strings.ReplaceAll(strings.TrimSuffix(e.Name(), "_openapi.json"), "__", "/")
|
||||
paths[path] = &fileGroupVersion{f: f.f, filename: e.Name()}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
type fileGroupVersion struct {
|
||||
t *testing.T
|
||||
init sync.Once
|
||||
f fs.FS
|
||||
filename string
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// fileGroupVersion implements the openapi.GroupVersion interface.
|
||||
@@ -91,17 +87,10 @@ var _ openapi.GroupVersion = &fileGroupVersion{}
|
||||
// Schema returns the OpenAPI V3 specification for the GroupVersion as
|
||||
// unstructured bytes, or an error if the contentType is not
|
||||
// "application/json" or there is an error reading the spec file. The
|
||||
// file is read only once. The embedded file is located in the "testdata"
|
||||
// subdirectory.
|
||||
func (t *fileGroupVersion) Schema(contentType string) ([]byte, error) {
|
||||
// file is read only once.
|
||||
func (f *fileGroupVersion) Schema(contentType string) ([]byte, error) {
|
||||
if contentType != "application/json" {
|
||||
return nil, errors.New("openapitest only supports 'application/json' contentType")
|
||||
}
|
||||
t.init.Do(func() {
|
||||
t.data, t.err = f.ReadFile(t.filename)
|
||||
if t.err != nil {
|
||||
t.t.Error(t.err)
|
||||
}
|
||||
})
|
||||
return t.data, t.err
|
||||
return fs.ReadFile(f.f, f.filename)
|
||||
}
|
||||
|
||||
@@ -14,16 +14,61 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package openapitest
|
||||
package openapitest_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/openapi/openapitest"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
kjson "sigs.k8s.io/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpenAPITest(t *testing.T) {
|
||||
client := NewFileClient(t)
|
||||
func TestOpenAPIEmbeddedTest(t *testing.T) {
|
||||
client := openapitest.NewEmbeddedFileClient()
|
||||
|
||||
// make sure we get paths
|
||||
paths, err := client.Paths()
|
||||
if err != nil {
|
||||
t.Fatalf("error fetching paths: %v", err)
|
||||
}
|
||||
if len(paths) == 0 {
|
||||
t.Error("empty paths")
|
||||
}
|
||||
|
||||
// spot check specific paths
|
||||
expectedPaths := []string{
|
||||
"api/v1",
|
||||
"apis/apps/v1",
|
||||
"apis/batch/v1",
|
||||
"apis/networking.k8s.io/v1alpha1",
|
||||
"apis/discovery.k8s.io/v1",
|
||||
}
|
||||
for _, p := range expectedPaths {
|
||||
if _, ok := paths[p]; !ok {
|
||||
t.Fatalf("expected %s", p)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure all paths can load
|
||||
for path, gv := range paths {
|
||||
data, err := gv.Schema("application/json")
|
||||
if err != nil {
|
||||
t.Fatalf("error reading schema for %v: %v", path, err)
|
||||
}
|
||||
o := &spec3.OpenAPI{}
|
||||
stricterrs, err := kjson.UnmarshalStrict(data, o)
|
||||
if err != nil {
|
||||
t.Fatalf("error unmarshaling schema for %v: %v", path, err)
|
||||
}
|
||||
if len(stricterrs) > 0 {
|
||||
t.Fatalf("strict errors unmarshaling schema for %v: %v", path, stricterrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAPITest(t *testing.T) {
|
||||
client := openapitest.NewFileClient("testdata")
|
||||
|
||||
// make sure we get paths
|
||||
paths, err := client.Paths()
|
||||
|
||||
48
openapi/typeconverter.go
Normal file
48
openapi/typeconverter.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2023 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 openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
func NewTypeConverter(client Client, preserveUnknownFields bool) (managedfields.TypeConverter, error) {
|
||||
spec := map[string]*spec.Schema{}
|
||||
paths, err := client.Paths()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list paths: %w", err)
|
||||
}
|
||||
for _, gv := range paths {
|
||||
s, err := gv.Schema("application/json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download schema: %w", err)
|
||||
}
|
||||
var openapi spec3.OpenAPI
|
||||
if err := json.Unmarshal(s, &openapi); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse schema: %w", err)
|
||||
}
|
||||
for k, v := range openapi.Components.Schemas {
|
||||
spec[k] = v
|
||||
}
|
||||
}
|
||||
return managedfields.NewTypeConverter(spec, preserveUnknownFields)
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func TestOpenAPIV3Root_GVSpec(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
client := openapitest.NewFileClient(t)
|
||||
client := openapitest.NewEmbeddedFileClient()
|
||||
root := NewRoot(client)
|
||||
gvSpec, err := root.GVSpec(test.gv)
|
||||
if test.err != nil {
|
||||
@@ -209,8 +209,7 @@ func TestOpenAPIV3Root_GVSpecAsMap(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
client := openapitest.NewFileClient(t)
|
||||
root := NewRoot(client)
|
||||
root := NewRoot(openapitest.NewEmbeddedFileClient())
|
||||
gvSpecAsMap, err := root.GVSpecAsMap(test.gv)
|
||||
if test.err != nil {
|
||||
assert.True(t, reflect.DeepEqual(test.err, err))
|
||||
|
||||
@@ -32,12 +32,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"golang.org/x/term"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/client-go/pkg/apis/clientauthentication"
|
||||
"k8s.io/client-go/pkg/apis/clientauthentication/install"
|
||||
@@ -81,8 +81,6 @@ func newCache() *cache {
|
||||
return &cache{m: make(map[string]*Authenticator)}
|
||||
}
|
||||
|
||||
var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "}
|
||||
|
||||
func cacheKey(conf *api.ExecConfig, cluster *clientauthentication.Cluster) string {
|
||||
key := struct {
|
||||
conf *api.ExecConfig
|
||||
@@ -91,7 +89,7 @@ func cacheKey(conf *api.ExecConfig, cluster *clientauthentication.Cluster) strin
|
||||
conf: conf,
|
||||
cluster: cluster,
|
||||
}
|
||||
return spewConfig.Sprint(key)
|
||||
return dump.Pretty(key)
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -340,8 +339,8 @@ func TestCreateBackoffManager(t *testing.T) {
|
||||
theUrl, _ := url.Parse("http://localhost")
|
||||
|
||||
// 1 second base backoff + duration of 2 seconds -> exponential backoff for requests.
|
||||
os.Setenv(envBackoffBase, "1")
|
||||
os.Setenv(envBackoffDuration, "2")
|
||||
t.Setenv(envBackoffBase, "1")
|
||||
t.Setenv(envBackoffDuration, "2")
|
||||
backoff := readExpBackoffConfig()
|
||||
backoff.UpdateBackoff(theUrl, nil, 500)
|
||||
backoff.UpdateBackoff(theUrl, nil, 500)
|
||||
@@ -350,8 +349,8 @@ func TestCreateBackoffManager(t *testing.T) {
|
||||
}
|
||||
|
||||
// 0 duration -> no backoff.
|
||||
os.Setenv(envBackoffBase, "1")
|
||||
os.Setenv(envBackoffDuration, "0")
|
||||
t.Setenv(envBackoffBase, "1")
|
||||
t.Setenv(envBackoffDuration, "0")
|
||||
backoff.UpdateBackoff(theUrl, nil, 500)
|
||||
backoff.UpdateBackoff(theUrl, nil, 500)
|
||||
backoff = readExpBackoffConfig()
|
||||
@@ -360,8 +359,8 @@ func TestCreateBackoffManager(t *testing.T) {
|
||||
}
|
||||
|
||||
// No env -> No backoff.
|
||||
os.Setenv(envBackoffBase, "")
|
||||
os.Setenv(envBackoffDuration, "")
|
||||
t.Setenv(envBackoffBase, "")
|
||||
t.Setenv(envBackoffDuration, "")
|
||||
backoff = readExpBackoffConfig()
|
||||
backoff.UpdateBackoff(theUrl, nil, 500)
|
||||
backoff.UpdateBackoff(theUrl, nil, 500)
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -80,23 +79,15 @@ func newLB(t *testing.T, serverURL string) *tcpLB {
|
||||
return &lb
|
||||
}
|
||||
|
||||
func setEnv(key, value string) func() {
|
||||
originalValue := os.Getenv(key)
|
||||
os.Setenv(key, value)
|
||||
return func() {
|
||||
os.Setenv(key, originalValue)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
readIdleTimeout int = 1
|
||||
pingTimeout int = 1
|
||||
)
|
||||
|
||||
func TestReconnectBrokenTCP(t *testing.T) {
|
||||
defer setEnv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", strconv.Itoa(readIdleTimeout))()
|
||||
defer setEnv("HTTP2_PING_TIMEOUT_SECONDS", strconv.Itoa(pingTimeout))()
|
||||
defer setEnv("DISABLE_HTTP2", "")()
|
||||
t.Setenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", strconv.Itoa(readIdleTimeout))
|
||||
t.Setenv("HTTP2_PING_TIMEOUT_SECONDS", strconv.Itoa(pingTimeout))
|
||||
t.Setenv("DISABLE_HTTP2", "")
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %s", r.Proto)
|
||||
}))
|
||||
|
||||
@@ -45,7 +45,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
@@ -323,7 +322,7 @@ func TestRequestBody(t *testing.T) {
|
||||
}
|
||||
|
||||
// test error set when failing to read file
|
||||
f, err := os.CreateTemp("", "test")
|
||||
f, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp file")
|
||||
}
|
||||
@@ -925,22 +924,22 @@ func TestTransformUnstructuredError(t *testing.T) {
|
||||
expect = err
|
||||
}
|
||||
if !reflect.DeepEqual(expect, transformed) {
|
||||
t.Errorf("unexpected Error(): %s", diff.ObjectReflectDiff(expect, transformed))
|
||||
t.Errorf("unexpected Error(): %s", cmp.Diff(expect, transformed))
|
||||
}
|
||||
|
||||
// verify result.Get properly transforms the error
|
||||
if _, err := result.Get(); !reflect.DeepEqual(expect, err) {
|
||||
t.Errorf("unexpected error on Get(): %s", diff.ObjectReflectDiff(expect, err))
|
||||
t.Errorf("unexpected error on Get(): %s", cmp.Diff(expect, err))
|
||||
}
|
||||
|
||||
// verify result.Into properly handles the error
|
||||
if err := result.Into(&v1.Pod{}); !reflect.DeepEqual(expect, err) {
|
||||
t.Errorf("unexpected error on Into(): %s", diff.ObjectReflectDiff(expect, err))
|
||||
t.Errorf("unexpected error on Into(): %s", cmp.Diff(expect, err))
|
||||
}
|
||||
|
||||
// verify result.Raw leaves the error in the untransformed state
|
||||
if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) {
|
||||
t.Errorf("unexpected error on Raw(): %s", diff.ObjectReflectDiff(expect, err))
|
||||
t.Errorf("unexpected error on Raw(): %s", cmp.Diff(expect, err))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1220,7 +1219,7 @@ func TestRequestWatch(t *testing.T) {
|
||||
t.Fatalf("Watch closed early, %d/%d read", i, len(testCase.Expect))
|
||||
}
|
||||
if !reflect.DeepEqual(evt, out) {
|
||||
t.Fatalf("Event %d does not match: %s", i, diff.ObjectReflectDiff(evt, out))
|
||||
t.Fatalf("Event %d does not match: %s", i, cmp.Diff(evt, out))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1466,7 +1465,7 @@ func TestDoRequestNewWay(t *testing.T) {
|
||||
expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
|
||||
Protocol: "TCP",
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
TargetPort: intstr.FromInt32(12345),
|
||||
}}}}
|
||||
expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
@@ -1709,7 +1708,7 @@ func TestDoRequestNewWayReader(t *testing.T) {
|
||||
expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
|
||||
Protocol: "TCP",
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
TargetPort: intstr.FromInt32(12345),
|
||||
}}}}
|
||||
expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
@@ -1748,7 +1747,7 @@ func TestDoRequestNewWayObj(t *testing.T) {
|
||||
expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
|
||||
Protocol: "TCP",
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
TargetPort: intstr.FromInt32(12345),
|
||||
}}}}
|
||||
expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
@@ -1803,7 +1802,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
|
||||
expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
|
||||
Protocol: "TCP",
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
TargetPort: intstr.FromInt32(12345),
|
||||
}}}}
|
||||
expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
@@ -1848,7 +1847,7 @@ func TestWasCreated(t *testing.T) {
|
||||
expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
|
||||
Protocol: "TCP",
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
TargetPort: intstr.FromInt32(12345),
|
||||
}}}}
|
||||
expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"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"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
. "k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/openapi"
|
||||
@@ -364,7 +364,7 @@ func TestGetAPIGroupResources(t *testing.T) {
|
||||
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))
|
||||
t.Errorf("unexpected result:\nexpected = %s\ngot = %s", dump.Pretty(test.expected), dump.Pretty(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
4
tools/cache/OWNERS
vendored
4
tools/cache/OWNERS
vendored
@@ -2,7 +2,6 @@
|
||||
|
||||
approvers:
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
@@ -11,7 +10,6 @@ approvers:
|
||||
- ncdc
|
||||
reviewers:
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
@@ -26,3 +24,5 @@ reviewers:
|
||||
- dims
|
||||
- ingvagabund
|
||||
- ncdc
|
||||
emeritus_approvers:
|
||||
- lavalamp
|
||||
|
||||
4
tools/cache/controller.go
vendored
4
tools/cache/controller.go
vendored
@@ -18,7 +18,6 @@ package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -148,9 +147,6 @@ func (c *controller) Run(stopCh <-chan struct{}) {
|
||||
if c.config.WatchErrorHandler != nil {
|
||||
r.watchErrorHandler = c.config.WatchErrorHandler
|
||||
}
|
||||
if s := os.Getenv("ENABLE_CLIENT_GO_WATCH_LIST_ALPHA"); len(s) > 0 {
|
||||
r.UseWatchList = true
|
||||
}
|
||||
|
||||
c.reflectorMutex.Lock()
|
||||
c.reflector = r
|
||||
|
||||
65
tools/cache/object-names.go
vendored
Normal file
65
tools/cache/object-names.go
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// ObjectName is a reference to an object of some implicit kind
|
||||
type ObjectName struct {
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewObjectName constructs a new one
|
||||
func NewObjectName(namespace, name string) ObjectName {
|
||||
return ObjectName{Namespace: namespace, Name: name}
|
||||
}
|
||||
|
||||
// Parts is the inverse of the constructor
|
||||
func (objName ObjectName) Parts() (namespace, name string) {
|
||||
return objName.Namespace, objName.Name
|
||||
}
|
||||
|
||||
// String returns the standard string encoding,
|
||||
// which is designed to match the historical behavior of MetaNamespaceKeyFunc.
|
||||
// Note this behavior is different from the String method of types.NamespacedName.
|
||||
func (objName ObjectName) String() string {
|
||||
if len(objName.Namespace) > 0 {
|
||||
return objName.Namespace + "/" + objName.Name
|
||||
}
|
||||
return objName.Name
|
||||
}
|
||||
|
||||
// ParseObjectName tries to parse the standard encoding
|
||||
func ParseObjectName(str string) (ObjectName, error) {
|
||||
var objName ObjectName
|
||||
var err error
|
||||
objName.Namespace, objName.Name, err = SplitMetaNamespaceKey(str)
|
||||
return objName, err
|
||||
}
|
||||
|
||||
// NamespacedNameAsObjectName rebrands the given NamespacedName as an ObjectName
|
||||
func NamespacedNameAsObjectName(nn types.NamespacedName) ObjectName {
|
||||
return NewObjectName(nn.Namespace, nn.Name)
|
||||
}
|
||||
|
||||
// AsNamespacedName rebrands as a NamespacedName
|
||||
func (objName ObjectName) AsNamespacedName() types.NamespacedName {
|
||||
return types.NamespacedName{Namespace: objName.Namespace, Name: objName.Name}
|
||||
}
|
||||
59
tools/cache/object-names_test.go
vendored
Normal file
59
tools/cache/object-names_test.go
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestObjectNames(t *testing.T) {
|
||||
chars := "abcdefghi/"
|
||||
for count := 1; count <= 100; count++ {
|
||||
var encodedB strings.Builder
|
||||
for index := 0; index < 10; index++ {
|
||||
encodedB.WriteByte(chars[rand.Intn(len(chars))])
|
||||
}
|
||||
encodedS := encodedB.String()
|
||||
parts := strings.Split(encodedS, "/")
|
||||
on, err := ParseObjectName(encodedS)
|
||||
expectError := len(parts) > 2
|
||||
if expectError != (err != nil) {
|
||||
t.Errorf("Wrong error; expected=%v, got=%v", expectError, err)
|
||||
}
|
||||
if expectError || err != nil {
|
||||
continue
|
||||
}
|
||||
var expectedObjectName ObjectName
|
||||
if len(parts) == 2 {
|
||||
expectedObjectName = ObjectName{Namespace: parts[0], Name: parts[1]}
|
||||
} else {
|
||||
expectedObjectName = ObjectName{Name: encodedS}
|
||||
}
|
||||
if on != expectedObjectName {
|
||||
t.Errorf("Parse failed, expected=%+v, got=%+v", expectedObjectName, on)
|
||||
}
|
||||
recoded := on.String()
|
||||
if encodedS[0] == '/' {
|
||||
recoded = "/" + recoded
|
||||
}
|
||||
if encodedS != recoded {
|
||||
t.Errorf("Parse().String() was not identity, original=%q, final=%q", encodedS, recoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
30
tools/cache/reflector.go
vendored
30
tools/cache/reflector.go
vendored
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -69,9 +70,7 @@ type Reflector struct {
|
||||
listerWatcher ListerWatcher
|
||||
// backoff manages backoff of ListWatch
|
||||
backoffManager wait.BackoffManager
|
||||
// initConnBackoffManager manages backoff the initial connection with the Watch call of ListAndWatch.
|
||||
initConnBackoffManager wait.BackoffManager
|
||||
resyncPeriod time.Duration
|
||||
resyncPeriod time.Duration
|
||||
// clock allows tests to manipulate time
|
||||
clock clock.Clock
|
||||
// paginatedResult defines whether pagination should be forced for list calls.
|
||||
@@ -220,11 +219,10 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store S
|
||||
// We used to make the call every 1sec (1 QPS), the goal here is to achieve ~98% traffic reduction when
|
||||
// API server is not healthy. With these parameters, backoff will stop at [30,60) sec interval which is
|
||||
// 0.22 QPS. If we don't backoff for 2min, assume API server is healthy and we reset the backoff.
|
||||
backoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, reflectorClock),
|
||||
initConnBackoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, reflectorClock),
|
||||
clock: reflectorClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
expectedType: reflect.TypeOf(expectedType),
|
||||
backoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, reflectorClock),
|
||||
clock: reflectorClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
expectedType: reflect.TypeOf(expectedType),
|
||||
}
|
||||
|
||||
if r.name == "" {
|
||||
@@ -239,6 +237,10 @@ func NewReflectorWithOptions(lw ListerWatcher, expectedType interface{}, store S
|
||||
r.expectedGVK = getExpectedGVKFromObject(expectedType)
|
||||
}
|
||||
|
||||
if s := os.Getenv("ENABLE_CLIENT_GO_WATCH_LIST_ALPHA"); len(s) > 0 {
|
||||
r.UseWatchList = true
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -420,7 +422,7 @@ func (r *Reflector) watch(w watch.Interface, stopCh <-chan struct{}, resyncerrc
|
||||
select {
|
||||
case <-stopCh:
|
||||
return nil
|
||||
case <-r.initConnBackoffManager.Backoff().C():
|
||||
case <-r.backoffManager.Backoff().C():
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -446,7 +448,7 @@ func (r *Reflector) watch(w watch.Interface, stopCh <-chan struct{}, resyncerrc
|
||||
select {
|
||||
case <-stopCh:
|
||||
return nil
|
||||
case <-r.initConnBackoffManager.Backoff().C():
|
||||
case <-r.backoffManager.Backoff().C():
|
||||
continue
|
||||
}
|
||||
case apierrors.IsInternalError(err) && retry.ShouldRetry():
|
||||
@@ -508,7 +510,7 @@ func (r *Reflector) list(stopCh <-chan struct{}) error {
|
||||
pager.PageSize = 0
|
||||
}
|
||||
|
||||
list, paginatedResult, err = pager.List(context.Background(), options)
|
||||
list, paginatedResult, err = pager.ListWithAlloc(context.Background(), options)
|
||||
if isExpiredError(err) || isTooLargeResourceVersionError(err) {
|
||||
r.setIsLastSyncResourceVersionUnavailable(true)
|
||||
// Retry immediately if the resource version used to list is unavailable.
|
||||
@@ -517,7 +519,7 @@ func (r *Reflector) list(stopCh <-chan struct{}) error {
|
||||
// resource version it is listing at is expired or the cache may not yet be synced to the provided
|
||||
// resource version. So we need to fallback to resourceVersion="" in all to recover and ensure
|
||||
// the reflector makes forward progress.
|
||||
list, paginatedResult, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
|
||||
list, paginatedResult, err = pager.ListWithAlloc(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
|
||||
}
|
||||
close(listCh)
|
||||
}()
|
||||
@@ -555,7 +557,7 @@ func (r *Reflector) list(stopCh <-chan struct{}) error {
|
||||
}
|
||||
resourceVersion = listMetaInterface.GetResourceVersion()
|
||||
initTrace.Step("Resource version extracted")
|
||||
items, err := meta.ExtractList(list)
|
||||
items, err := meta.ExtractListWithAlloc(list)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to understand list result %#v (%v)", list, err)
|
||||
}
|
||||
@@ -599,7 +601,7 @@ func (r *Reflector) watchList(stopCh <-chan struct{}) (watch.Interface, error) {
|
||||
isErrorRetriableWithSideEffectsFn := func(err error) bool {
|
||||
if canRetry := isWatchErrorRetriable(err); canRetry {
|
||||
klog.V(2).Infof("%s: watch-list of %v returned %v - backing off", r.name, r.typeDescription, err)
|
||||
<-r.initConnBackoffManager.Backoff().C()
|
||||
<-r.backoffManager.Backoff().C()
|
||||
return true
|
||||
}
|
||||
if isExpiredError(err) || isTooLargeResourceVersionError(err) {
|
||||
|
||||
531
tools/cache/reflector_test.go
vendored
531
tools/cache/reflector_test.go
vendored
@@ -17,10 +17,12 @@ limitations under the License.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
goruntime "runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -28,10 +30,13 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
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/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/utils/clock"
|
||||
@@ -411,12 +416,12 @@ func TestReflectorListAndWatchInitConnBackoff(t *testing.T) {
|
||||
},
|
||||
}
|
||||
r := &Reflector{
|
||||
name: "test-reflector",
|
||||
listerWatcher: lw,
|
||||
store: NewFIFO(MetaNamespaceKeyFunc),
|
||||
initConnBackoffManager: bm,
|
||||
clock: fakeClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
name: "test-reflector",
|
||||
listerWatcher: lw,
|
||||
store: NewFIFO(MetaNamespaceKeyFunc),
|
||||
backoffManager: bm,
|
||||
clock: fakeClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
}
|
||||
start := fakeClock.Now()
|
||||
err := r.ListAndWatch(stopCh)
|
||||
@@ -471,12 +476,12 @@ func TestBackoffOnTooManyRequests(t *testing.T) {
|
||||
}
|
||||
|
||||
r := &Reflector{
|
||||
name: "test-reflector",
|
||||
listerWatcher: lw,
|
||||
store: NewFIFO(MetaNamespaceKeyFunc),
|
||||
initConnBackoffManager: bm,
|
||||
clock: clock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
name: "test-reflector",
|
||||
listerWatcher: lw,
|
||||
store: NewFIFO(MetaNamespaceKeyFunc),
|
||||
backoffManager: bm,
|
||||
clock: clock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
@@ -540,12 +545,12 @@ func TestRetryInternalError(t *testing.T) {
|
||||
}
|
||||
|
||||
r := &Reflector{
|
||||
name: "test-reflector",
|
||||
listerWatcher: lw,
|
||||
store: NewFIFO(MetaNamespaceKeyFunc),
|
||||
initConnBackoffManager: bm,
|
||||
clock: fakeClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
name: "test-reflector",
|
||||
listerWatcher: lw,
|
||||
store: NewFIFO(MetaNamespaceKeyFunc),
|
||||
backoffManager: bm,
|
||||
clock: fakeClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
}
|
||||
|
||||
r.MaxInternalErrorRetryDuration = tc.maxInternalDuration
|
||||
@@ -1119,3 +1124,493 @@ func TestReflectorResourceVersionUpdate(t *testing.T) {
|
||||
t.Errorf("Expected series of resource version updates of %#v but got: %#v", expectedRVs, s.resourceVersions)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
fakeItemsNum = 100
|
||||
exemptObjectIndex = fakeItemsNum / 4
|
||||
pageNum = 3
|
||||
)
|
||||
|
||||
func getPodListItems(start int, numItems int) (string, string, *v1.PodList) {
|
||||
out := &v1.PodList{
|
||||
Items: make([]v1.Pod, numItems),
|
||||
}
|
||||
|
||||
for i := 0; i < numItems; i++ {
|
||||
|
||||
out.Items[i] = v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("pod-%d", i+start),
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"label-key-1": "label-value-1",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"annotations-key-1": "annotations-value-1",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Overhead: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("3"),
|
||||
v1.ResourceMemory: resource.MustParse("8"),
|
||||
},
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "quux",
|
||||
},
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{MatchExpressions: []v1.NodeSelectorRequirement{{Key: `foo`}}},
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
|
||||
{Preference: v1.NodeSelectorTerm{MatchExpressions: []v1.NodeSelectorRequirement{{Key: `foo`}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologySpreadConstraints: []v1.TopologySpreadConstraint{
|
||||
{TopologyKey: `foo`},
|
||||
},
|
||||
HostAliases: []v1.HostAlias{
|
||||
{IP: "1.1.1.1"},
|
||||
{IP: "2.2.2.2"},
|
||||
},
|
||||
ImagePullSecrets: []v1.LocalObjectReference{
|
||||
{Name: "secret1"},
|
||||
{Name: "secret2"},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "foobar",
|
||||
Image: "alpine",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
|
||||
},
|
||||
Limits: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foobar2",
|
||||
Image: "alpine",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("4"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"),
|
||||
},
|
||||
Limits: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("8"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
InitContainers: []v1.Container{
|
||||
{
|
||||
Name: "small-init",
|
||||
Image: "alpine",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
|
||||
},
|
||||
Limits: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "big-init",
|
||||
Image: "alpine",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("40"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"),
|
||||
},
|
||||
Limits: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse("80"),
|
||||
v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Hostname: fmt.Sprintf("node-%d", i),
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
ContainerID: "docker://numbers",
|
||||
Image: "alpine",
|
||||
Name: "foobar",
|
||||
Ready: false,
|
||||
},
|
||||
{
|
||||
ContainerID: "docker://numbers",
|
||||
Image: "alpine",
|
||||
Name: "foobar2",
|
||||
Ready: false,
|
||||
},
|
||||
},
|
||||
InitContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
ContainerID: "docker://numbers",
|
||||
Image: "alpine",
|
||||
Name: "small-init",
|
||||
Ready: false,
|
||||
},
|
||||
{
|
||||
ContainerID: "docker://numbers",
|
||||
Image: "alpine",
|
||||
Name: "big-init",
|
||||
Ready: false,
|
||||
},
|
||||
},
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.PodScheduled,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: "successfully",
|
||||
Message: "sync pod successfully",
|
||||
LastProbeTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out.Items[0].GetName(), out.Items[exemptObjectIndex].GetName(), out
|
||||
}
|
||||
|
||||
func getConfigmapListItems(start int, numItems int) (string, string, *v1.ConfigMapList) {
|
||||
out := &v1.ConfigMapList{
|
||||
Items: make([]v1.ConfigMap, numItems),
|
||||
}
|
||||
|
||||
for i := 0; i < numItems; i++ {
|
||||
out.Items[i] = v1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("cm-%d", i+start),
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"label-key-1": "label-value-1",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"annotations-key-1": "annotations-value-1",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"data-1": "value-1",
|
||||
"data-2": "value-2",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out.Items[0].GetName(), out.Items[exemptObjectIndex].GetName(), out
|
||||
}
|
||||
|
||||
type TestPagingPodsLW struct {
|
||||
totalPageCount int
|
||||
fetchedPageCount int
|
||||
|
||||
detectedObjectNameList []string
|
||||
exemptObjectNameList []string
|
||||
}
|
||||
|
||||
func newPageTestLW(totalPageNum int) *TestPagingPodsLW {
|
||||
return &TestPagingPodsLW{
|
||||
totalPageCount: totalPageNum,
|
||||
fetchedPageCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TestPagingPodsLW) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
firstPodName, exemptPodName, list := getPodListItems(t.fetchedPageCount*fakeItemsNum, fakeItemsNum)
|
||||
t.detectedObjectNameList = append(t.detectedObjectNameList, firstPodName)
|
||||
t.exemptObjectNameList = append(t.exemptObjectNameList, exemptPodName)
|
||||
t.fetchedPageCount++
|
||||
if t.fetchedPageCount >= t.totalPageCount {
|
||||
return list, nil
|
||||
}
|
||||
list.SetContinue("true")
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (t *TestPagingPodsLW) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestReflectorListExtract(t *testing.T) {
|
||||
store := NewStore(func(obj interface{}) (string, error) {
|
||||
pod, ok := obj.(*v1.Pod)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("expect *v1.Pod, but got %T", obj)
|
||||
}
|
||||
return pod.GetName(), nil
|
||||
})
|
||||
|
||||
lw := newPageTestLW(5)
|
||||
reflector := NewReflector(lw, &v1.Pod{}, store, 0)
|
||||
reflector.WatchListPageSize = fakeItemsNum
|
||||
|
||||
// execute list to fill store
|
||||
stopCh := make(chan struct{})
|
||||
if err := reflector.list(stopCh); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We will not delete exemptPod,
|
||||
// in order to see if the existence of this Pod causes other Pods that are not used to be unable to properly clear.
|
||||
for _, podName := range lw.exemptObjectNameList {
|
||||
_, exist, err := store.GetByKey(podName)
|
||||
if err != nil || !exist {
|
||||
t.Fatalf("%s should exist in pod store", podName)
|
||||
}
|
||||
}
|
||||
|
||||
// we will pay attention to whether the memory occupied by the first Pod is released
|
||||
// Golang's can only be SetFinalizer for the first element of the array,
|
||||
// so pod-0 will be the object of our attention
|
||||
detectedPodAlreadyBeCleared := make(chan struct{}, len(lw.detectedObjectNameList))
|
||||
|
||||
for _, firstPodName := range lw.detectedObjectNameList {
|
||||
_, exist, err := store.GetByKey(firstPodName)
|
||||
if err != nil || !exist {
|
||||
t.Fatalf("%s should exist in pod store", firstPodName)
|
||||
}
|
||||
firstPod, exist, err := store.GetByKey(firstPodName)
|
||||
if err != nil || !exist {
|
||||
t.Fatalf("%s should exist in pod store", firstPodName)
|
||||
}
|
||||
goruntime.SetFinalizer(firstPod, func(obj interface{}) {
|
||||
t.Logf("%s already be gc\n", obj.(*v1.Pod).GetName())
|
||||
detectedPodAlreadyBeCleared <- struct{}{}
|
||||
})
|
||||
}
|
||||
|
||||
storedObjectKeys := store.ListKeys()
|
||||
for _, k := range storedObjectKeys {
|
||||
// delete all Pods except the exempted Pods.
|
||||
if sets.NewString(lw.exemptObjectNameList...).Has(k) {
|
||||
continue
|
||||
}
|
||||
obj, exist, err := store.GetByKey(k)
|
||||
if err != nil || !exist {
|
||||
t.Fatalf("%s should exist in pod store", k)
|
||||
}
|
||||
|
||||
if err := store.Delete(obj); err != nil {
|
||||
t.Fatalf("delete object: %v", err)
|
||||
}
|
||||
goruntime.GC()
|
||||
}
|
||||
|
||||
clearedNum := 0
|
||||
for {
|
||||
select {
|
||||
case <-detectedPodAlreadyBeCleared:
|
||||
clearedNum++
|
||||
if clearedNum == len(lw.detectedObjectNameList) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExtractList(b *testing.B) {
|
||||
_, _, podList := getPodListItems(0, fakeItemsNum)
|
||||
_, _, configMapList := getConfigmapListItems(0, fakeItemsNum)
|
||||
tests := []struct {
|
||||
name string
|
||||
list runtime.Object
|
||||
}{
|
||||
{
|
||||
name: "PodList",
|
||||
list: podList,
|
||||
},
|
||||
{
|
||||
name: "ConfigMapList",
|
||||
list: configMapList,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := meta.ExtractList(tc.list)
|
||||
if err != nil {
|
||||
b.Errorf("extract list: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEachListItem(b *testing.B) {
|
||||
_, _, podList := getPodListItems(0, fakeItemsNum)
|
||||
_, _, configMapList := getConfigmapListItems(0, fakeItemsNum)
|
||||
tests := []struct {
|
||||
name string
|
||||
list runtime.Object
|
||||
}{
|
||||
{
|
||||
name: "PodList",
|
||||
list: podList,
|
||||
},
|
||||
{
|
||||
name: "ConfigMapList",
|
||||
list: configMapList,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := meta.EachListItem(tc.list, func(object runtime.Object) error {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
b.Errorf("each list: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExtractListWithAlloc(b *testing.B) {
|
||||
_, _, podList := getPodListItems(0, fakeItemsNum)
|
||||
_, _, configMapList := getConfigmapListItems(0, fakeItemsNum)
|
||||
tests := []struct {
|
||||
name string
|
||||
list runtime.Object
|
||||
}{
|
||||
{
|
||||
name: "PodList",
|
||||
list: podList,
|
||||
},
|
||||
{
|
||||
name: "ConfigMapList",
|
||||
list: configMapList,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := meta.ExtractListWithAlloc(tc.list)
|
||||
if err != nil {
|
||||
b.Errorf("extract list with alloc: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEachListItemWithAlloc(b *testing.B) {
|
||||
_, _, podList := getPodListItems(0, fakeItemsNum)
|
||||
_, _, configMapList := getConfigmapListItems(0, fakeItemsNum)
|
||||
tests := []struct {
|
||||
name string
|
||||
list runtime.Object
|
||||
}{
|
||||
{
|
||||
name: "PodList",
|
||||
list: podList,
|
||||
},
|
||||
{
|
||||
name: "ConfigMapList",
|
||||
list: configMapList,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := meta.EachListItemWithAlloc(tc.list, func(object runtime.Object) error {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
b.Errorf("each list with alloc: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReflectorList(b *testing.B) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
store := NewStore(func(obj interface{}) (string, error) {
|
||||
o, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return o.GetName(), nil
|
||||
})
|
||||
|
||||
_, _, podList := getPodListItems(0, fakeItemsNum)
|
||||
_, _, configMapList := getConfigmapListItems(0, fakeItemsNum)
|
||||
tests := []struct {
|
||||
name string
|
||||
sample func() interface{}
|
||||
list runtime.Object
|
||||
}{
|
||||
{
|
||||
name: "PodList",
|
||||
sample: func() interface{} {
|
||||
return v1.Pod{}
|
||||
},
|
||||
list: podList,
|
||||
},
|
||||
{
|
||||
name: "ConfigMapList",
|
||||
sample: func() interface{} {
|
||||
return v1.ConfigMap{}
|
||||
},
|
||||
list: configMapList,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
|
||||
sample := tc.sample()
|
||||
reflector := NewReflector(newPageTestLW(pageNum), &sample, store, 0)
|
||||
reflector.WatchListPageSize = fakeItemsNum
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := reflector.list(ctx.Done())
|
||||
if err != nil {
|
||||
b.Fatalf("reflect list: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
31
tools/cache/store.go
vendored
31
tools/cache/store.go
vendored
@@ -21,6 +21,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Store is a generic object storage and processing interface. A
|
||||
@@ -99,20 +100,38 @@ type ExplicitKey string
|
||||
// The key uses the format <namespace>/<name> unless <namespace> is empty, then
|
||||
// it's just <name>.
|
||||
//
|
||||
// TODO: replace key-as-string with a key-as-struct so that this
|
||||
// packing/unpacking won't be necessary.
|
||||
// Clients that want a structured alternative can use ObjectToName or MetaObjectToName.
|
||||
// Note: this would not be a client that wants a key for a Store because those are
|
||||
// necessarily strings.
|
||||
//
|
||||
// TODO maybe some day?: change Store to be keyed differently
|
||||
func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
|
||||
if key, ok := obj.(ExplicitKey); ok {
|
||||
return string(key), nil
|
||||
}
|
||||
objName, err := ObjectToName(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return objName.String(), nil
|
||||
}
|
||||
|
||||
// ObjectToName returns the structured name for the given object,
|
||||
// if indeed it can be viewed as a metav1.Object.
|
||||
func ObjectToName(obj interface{}) (ObjectName, error) {
|
||||
meta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("object has no meta: %v", err)
|
||||
return ObjectName{}, fmt.Errorf("object has no meta: %v", err)
|
||||
}
|
||||
if len(meta.GetNamespace()) > 0 {
|
||||
return meta.GetNamespace() + "/" + meta.GetName(), nil
|
||||
return MetaObjectToName(meta), nil
|
||||
}
|
||||
|
||||
// MetaObjectToName returns the structured name for the given object
|
||||
func MetaObjectToName(obj metav1.Object) ObjectName {
|
||||
if len(obj.GetNamespace()) > 0 {
|
||||
return ObjectName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
|
||||
}
|
||||
return meta.GetName(), nil
|
||||
return ObjectName{Namespace: "", Name: obj.GetName()}
|
||||
}
|
||||
|
||||
// SplitMetaNamespaceKey returns the namespace and name that
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -53,11 +55,9 @@ func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caConte
|
||||
|
||||
func TestMinifySuccess(t *testing.T) {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, certFile, keyFile, caFile)
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
|
||||
@@ -89,11 +89,9 @@ func TestMinifySuccess(t *testing.T) {
|
||||
|
||||
func TestMinifyMissingContext(t *testing.T) {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, certFile, keyFile, caFile)
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
mutatingConfig.CurrentContext = "missing"
|
||||
@@ -107,11 +105,9 @@ func TestMinifyMissingContext(t *testing.T) {
|
||||
|
||||
func TestMinifyMissingCluster(t *testing.T) {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, certFile, keyFile, caFile)
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
delete(mutatingConfig.Clusters, mutatingConfig.Contexts[mutatingConfig.CurrentContext].Cluster)
|
||||
@@ -125,11 +121,9 @@ func TestMinifyMissingCluster(t *testing.T) {
|
||||
|
||||
func TestMinifyMissingAuthInfo(t *testing.T) {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, certFile, keyFile, caFile)
|
||||
|
||||
mutatingConfig := newMergedConfig(certFile.Name(), "cert", keyFile.Name(), "key", caFile.Name(), "ca", t)
|
||||
delete(mutatingConfig.AuthInfos, mutatingConfig.Contexts[mutatingConfig.CurrentContext].AuthInfo)
|
||||
@@ -143,11 +137,9 @@ func TestMinifyMissingAuthInfo(t *testing.T) {
|
||||
|
||||
func TestFlattenSuccess(t *testing.T) {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, certFile, keyFile, caFile)
|
||||
|
||||
certData := "cert"
|
||||
keyData := "key"
|
||||
@@ -208,11 +200,9 @@ func TestFlattenSuccess(t *testing.T) {
|
||||
|
||||
func Example_minifyAndShorten() {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(&testing.T{}, certFile, keyFile, caFile)
|
||||
|
||||
certData := "cert"
|
||||
keyData := "key"
|
||||
@@ -228,19 +218,16 @@ func Example_minifyAndShorten() {
|
||||
// Output:
|
||||
// clusters:
|
||||
// cow-cluster:
|
||||
// LocationOfOrigin: ""
|
||||
// certificate-authority-data: DATA+OMITTED
|
||||
// server: http://cow.org:8080
|
||||
// contexts:
|
||||
// federal-context:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: cow-cluster
|
||||
// user: red-user
|
||||
// current-context: federal-context
|
||||
// preferences: {}
|
||||
// users:
|
||||
// red-user:
|
||||
// LocationOfOrigin: ""
|
||||
// client-certificate-data: DATA+OMITTED
|
||||
// client-key-data: DATA+OMITTED
|
||||
// token: REDACTED
|
||||
@@ -248,11 +235,9 @@ func Example_minifyAndShorten() {
|
||||
|
||||
func TestShortenSuccess(t *testing.T) {
|
||||
certFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(certFile.Name())
|
||||
keyFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(keyFile.Name())
|
||||
caFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(caFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, certFile, keyFile, caFile)
|
||||
|
||||
certData := "cert"
|
||||
keyData := "key"
|
||||
|
||||
@@ -67,7 +67,7 @@ type Preferences struct {
|
||||
type Cluster struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
LocationOfOrigin string
|
||||
LocationOfOrigin string `json:"-"`
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// TLSServerName is used to check server certificate. If TLSServerName is empty, the hostname used to contact the server is used.
|
||||
@@ -107,7 +107,7 @@ type Cluster struct {
|
||||
type AuthInfo struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
LocationOfOrigin string
|
||||
LocationOfOrigin string `json:"-"`
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
@@ -159,7 +159,7 @@ type AuthInfo struct {
|
||||
type Context struct {
|
||||
// LocationOfOrigin indicates where this object came from. It is used for round tripping config post-merge, but never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
LocationOfOrigin string
|
||||
LocationOfOrigin string `json:"-"`
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
@@ -252,7 +252,7 @@ type ExecConfig struct {
|
||||
// recommended as one of the prime benefits of exec plugins is that no secrets need
|
||||
// to be stored directly in the kubeconfig.
|
||||
// +k8s:conversion-gen=false
|
||||
Config runtime.Object
|
||||
Config runtime.Object `json:"-"`
|
||||
|
||||
// InteractiveMode determines this plugin's relationship with standard input. Valid
|
||||
// values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this
|
||||
@@ -264,7 +264,7 @@ type ExecConfig struct {
|
||||
// client.authentication.k8s.io/v1beta1, then this field is optional and defaults
|
||||
// to "IfAvailable" when unset. Otherwise, this field is required.
|
||||
// +optional
|
||||
InteractiveMode ExecInteractiveMode
|
||||
InteractiveMode ExecInteractiveMode `json:"interactiveMode,omitempty"`
|
||||
|
||||
// StdinUnavailable indicates whether the exec authenticator can pass standard
|
||||
// input through to this exec plugin. For example, a higher level entity might be using
|
||||
@@ -272,14 +272,14 @@ type ExecConfig struct {
|
||||
// plugin to use standard input. This is kept here in order to keep all of the exec configuration
|
||||
// together, but it is never serialized.
|
||||
// +k8s:conversion-gen=false
|
||||
StdinUnavailable bool
|
||||
StdinUnavailable bool `json:"-"`
|
||||
|
||||
// StdinUnavailableMessage is an optional message to be displayed when the exec authenticator
|
||||
// cannot successfully run this exec plugin because it needs to use standard input and
|
||||
// StdinUnavailable is true. For example, a process that is already using standard input to
|
||||
// read user instructions might set this to "used by my-program to read user instructions".
|
||||
// +k8s:conversion-gen=false
|
||||
StdinUnavailableMessage string
|
||||
StdinUnavailableMessage string `json:"-"`
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = new(ExecConfig)
|
||||
|
||||
@@ -94,26 +94,21 @@ func Example_ofOptionsConfig() {
|
||||
// Output:
|
||||
// clusters:
|
||||
// alfa:
|
||||
// LocationOfOrigin: ""
|
||||
// certificate-authority: path/to/my/cert-ca-filename
|
||||
// disable-compression: true
|
||||
// insecure-skip-tls-verify: true
|
||||
// server: https://alfa.org:8080
|
||||
// bravo:
|
||||
// LocationOfOrigin: ""
|
||||
// server: https://bravo.org:8080
|
||||
// contexts:
|
||||
// alfa-as-black-mage:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: alfa
|
||||
// namespace: zulu
|
||||
// user: black-mage-via-auth-provider
|
||||
// alfa-as-white-mage:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: alfa
|
||||
// user: white-mage-via-cert
|
||||
// bravo-as-black-mage:
|
||||
// LocationOfOrigin: ""
|
||||
// cluster: bravo
|
||||
// namespace: yankee
|
||||
// user: black-mage-via-auth-provider
|
||||
@@ -122,17 +117,14 @@ func Example_ofOptionsConfig() {
|
||||
// colors: true
|
||||
// users:
|
||||
// black-mage-via-auth-provider:
|
||||
// LocationOfOrigin: ""
|
||||
// auth-provider:
|
||||
// config:
|
||||
// foo: bar
|
||||
// token: s3cr3t-t0k3n
|
||||
// name: gcp
|
||||
// red-mage-via-token:
|
||||
// LocationOfOrigin: ""
|
||||
// token: my-secret-token
|
||||
// white-mage-via-cert:
|
||||
// LocationOfOrigin: ""
|
||||
// client-certificate: path/to/my/client-cert-filename
|
||||
// client-key: path/to/my/client-key-filename
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -177,7 +179,7 @@ func TestCAOverridesCAData(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tempfile: %v", err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
defer utiltesting.CloseAndRemove(t, file)
|
||||
|
||||
config := createCAValidTestConfig()
|
||||
clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{
|
||||
@@ -312,8 +314,7 @@ func TestModifyContext(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.Remove(tempPath.Name())
|
||||
|
||||
defer utiltesting.CloseAndRemove(t, tempPath)
|
||||
pathOptions := NewDefaultPathOptions()
|
||||
config := createValidTestConfig()
|
||||
|
||||
@@ -498,7 +499,7 @@ func TestBasicTokenFile(t *testing.T) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
defer utiltesting.CloseAndRemove(t, f)
|
||||
if err := os.WriteFile(f.Name(), []byte(token), 0644); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -534,7 +535,7 @@ func TestPrecedenceTokenFile(t *testing.T) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
defer utiltesting.CloseAndRemove(t, f)
|
||||
if err := os.WriteFile(f.Name(), []byte(token), 0644); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -923,7 +924,7 @@ users:
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, tmpfile)
|
||||
if err := os.WriteFile(tmpfile.Name(), []byte(content), 0666); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -128,6 +128,28 @@ type ClientConfigLoadingRules struct {
|
||||
// WarnIfAllMissing indicates whether the configuration files pointed by KUBECONFIG environment variable are present or not.
|
||||
// In case of missing files, it warns the user about the missing files.
|
||||
WarnIfAllMissing bool
|
||||
|
||||
// Warner is the warning log callback to use in case of missing files.
|
||||
Warner WarningHandler
|
||||
}
|
||||
|
||||
// WarningHandler allows to set the logging function to use
|
||||
type WarningHandler func(error)
|
||||
|
||||
func (handler WarningHandler) Warn(err error) {
|
||||
if handler == nil {
|
||||
klog.V(1).Info(err)
|
||||
} else {
|
||||
handler(err)
|
||||
}
|
||||
}
|
||||
|
||||
type MissingConfigError struct {
|
||||
Missing []string
|
||||
}
|
||||
|
||||
func (c MissingConfigError) Error() string {
|
||||
return fmt.Sprintf("Config not found: %s", strings.Join(c.Missing, ", "))
|
||||
}
|
||||
|
||||
// ClientConfigLoadingRules implements the ClientConfigLoader interface.
|
||||
@@ -219,7 +241,7 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
|
||||
}
|
||||
|
||||
if rules.WarnIfAllMissing && len(missingList) > 0 && len(kubeconfigs) == 0 {
|
||||
klog.Warningf("Config not found: %s", strings.Join(missingList, ", "))
|
||||
rules.Warner.Warn(MissingConfigError{Missing: missingList})
|
||||
}
|
||||
|
||||
// first merge all of our maps
|
||||
|
||||
@@ -18,6 +18,7 @@ package clientcmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -26,12 +27,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -120,19 +124,82 @@ func TestNonExistentCommandLineFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToleratingMissingFiles(t *testing.T) {
|
||||
envVarValue := "bogus"
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
Precedence: []string{"bogus1", "bogus2", "bogus3"},
|
||||
Precedence: []string{"bogus1", "bogus2", "bogus3"},
|
||||
WarnIfAllMissing: true,
|
||||
Warner: func(err error) { klog.Warning(err) },
|
||||
}
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
klog.LogToStderr(false)
|
||||
klog.SetOutput(buffer)
|
||||
|
||||
_, err := loadingRules.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
klog.Flush()
|
||||
expectedLog := fmt.Sprintf("Config not found: %s", envVarValue)
|
||||
if !strings.Contains(buffer.String(), expectedLog) {
|
||||
t.Fatalf("expected log: \"%s\"", expectedLog)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarningMissingFiles(t *testing.T) {
|
||||
envVarValue := "bogus"
|
||||
os.Setenv(RecommendedConfigPathEnvVar, envVarValue)
|
||||
loadingRules := NewDefaultClientConfigLoadingRules()
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
flags := &flag.FlagSet{}
|
||||
klog.InitFlags(flags)
|
||||
flags.Set("v", "1")
|
||||
klog.LogToStderr(false)
|
||||
klog.SetOutput(buffer)
|
||||
|
||||
_, err := loadingRules.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
klog.Flush()
|
||||
|
||||
expectedLog := fmt.Sprintf("Config not found: %s", envVarValue)
|
||||
if !strings.Contains(buffer.String(), expectedLog) {
|
||||
t.Fatalf("expected log: \"%s\"", expectedLog)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoWarningMissingFiles(t *testing.T) {
|
||||
envVarValue := "bogus"
|
||||
os.Setenv(RecommendedConfigPathEnvVar, envVarValue)
|
||||
loadingRules := NewDefaultClientConfigLoadingRules()
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
flags := &flag.FlagSet{}
|
||||
klog.InitFlags(flags)
|
||||
flags.Set("v", "0")
|
||||
klog.LogToStderr(false)
|
||||
klog.SetOutput(buffer)
|
||||
|
||||
_, err := loadingRules.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
klog.Flush()
|
||||
|
||||
logNotExpected := fmt.Sprintf("Config not found: %s", envVarValue)
|
||||
if strings.Contains(buffer.String(), logNotExpected) {
|
||||
t.Fatalf("log not expected: \"%s\"", logNotExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorReadingFile(t *testing.T) {
|
||||
commandLineFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, commandLineFile)
|
||||
|
||||
if err := os.WriteFile(commandLineFile.Name(), []byte("bogus value"), 0644); err != nil {
|
||||
t.Fatalf("Error creating tempfile: %v", err)
|
||||
@@ -173,9 +240,8 @@ func TestErrorReadingNonFile(t *testing.T) {
|
||||
|
||||
func TestConflictingCurrentContext(t *testing.T) {
|
||||
commandLineFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, commandLineFile, envVarFile)
|
||||
|
||||
mockCommandLineConfig := clientcmdapi.Config{
|
||||
CurrentContext: "any-context-value",
|
||||
@@ -248,13 +314,13 @@ preferences: {}
|
||||
users: null
|
||||
`)
|
||||
if !bytes.Equal(expected, data) {
|
||||
t.Error(diff.ObjectReflectDiff(string(expected), string(data)))
|
||||
t.Error(cmp.Diff(string(expected), string(data)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadingEmptyMaps(t *testing.T) {
|
||||
configFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, configFile)
|
||||
|
||||
mockConfig := clientcmdapi.Config{
|
||||
CurrentContext: "any-context-value",
|
||||
@@ -280,7 +346,7 @@ func TestLoadingEmptyMaps(t *testing.T) {
|
||||
|
||||
func TestDuplicateClusterName(t *testing.T) {
|
||||
configFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, configFile)
|
||||
|
||||
err := os.WriteFile(configFile.Name(), []byte(`
|
||||
kind: Config
|
||||
@@ -322,7 +388,7 @@ users:
|
||||
|
||||
func TestDuplicateContextName(t *testing.T) {
|
||||
configFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, configFile)
|
||||
|
||||
err := os.WriteFile(configFile.Name(), []byte(`
|
||||
kind: Config
|
||||
@@ -364,7 +430,7 @@ users:
|
||||
|
||||
func TestDuplicateUserName(t *testing.T) {
|
||||
configFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, configFile)
|
||||
|
||||
err := os.WriteFile(configFile.Name(), []byte(`
|
||||
kind: Config
|
||||
@@ -404,7 +470,7 @@ users:
|
||||
|
||||
func TestDuplicateExtensionName(t *testing.T) {
|
||||
configFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(configFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, configFile)
|
||||
|
||||
err := os.WriteFile(configFile.Name(), []byte(`
|
||||
kind: Config
|
||||
@@ -560,7 +626,7 @@ func TestResolveRelativePaths(t *testing.T) {
|
||||
|
||||
func TestMigratingFile(t *testing.T) {
|
||||
sourceFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(sourceFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, sourceFile)
|
||||
destinationFile, _ := os.CreateTemp("", "")
|
||||
// delete the file so that we'll write to it
|
||||
os.Remove(destinationFile.Name())
|
||||
@@ -574,9 +640,8 @@ func TestMigratingFile(t *testing.T) {
|
||||
if _, err := loadingRules.Load(); err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
// the load should have recreated this file
|
||||
defer os.Remove(destinationFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, destinationFile)
|
||||
|
||||
sourceContent, err := os.ReadFile(sourceFile.Name())
|
||||
if err != nil {
|
||||
@@ -594,9 +659,8 @@ func TestMigratingFile(t *testing.T) {
|
||||
|
||||
func TestMigratingFileLeaveExistingFileAlone(t *testing.T) {
|
||||
sourceFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(sourceFile.Name())
|
||||
destinationFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(destinationFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, sourceFile, destinationFile)
|
||||
|
||||
WriteToFile(testConfigAlfa, sourceFile.Name())
|
||||
|
||||
@@ -622,7 +686,7 @@ func TestMigratingFileSourceMissingSkip(t *testing.T) {
|
||||
sourceFilename := "some-missing-file"
|
||||
destinationFile, _ := os.CreateTemp("", "")
|
||||
// delete the file so that we'll write to it
|
||||
os.Remove(destinationFile.Name())
|
||||
utiltesting.CloseAndRemove(t, destinationFile)
|
||||
|
||||
loadingRules := ClientConfigLoadingRules{
|
||||
MigrationRules: map[string]string{destinationFile.Name(): sourceFilename},
|
||||
@@ -639,7 +703,7 @@ func TestMigratingFileSourceMissingSkip(t *testing.T) {
|
||||
|
||||
func TestFileLocking(t *testing.T) {
|
||||
f, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(f.Name())
|
||||
defer utiltesting.CloseAndRemove(t, f)
|
||||
|
||||
err := lockFile(f.Name())
|
||||
if err != nil {
|
||||
@@ -655,9 +719,8 @@ func TestFileLocking(t *testing.T) {
|
||||
|
||||
func Example_noMergingOnExplicitPaths() {
|
||||
commandLineFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
defer utiltesting.CloseAndRemove(&testing.T{}, commandLineFile, envVarFile)
|
||||
|
||||
WriteToFile(testConfigAlfa, commandLineFile.Name())
|
||||
WriteToFile(testConfigConflictAlfa, envVarFile.Name())
|
||||
@@ -704,9 +767,8 @@ func Example_noMergingOnExplicitPaths() {
|
||||
|
||||
func Example_mergingSomeWithConflict() {
|
||||
commandLineFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
defer utiltesting.CloseAndRemove(&testing.T{}, commandLineFile, envVarFile)
|
||||
|
||||
WriteToFile(testConfigAlfa, commandLineFile.Name())
|
||||
WriteToFile(testConfigConflictAlfa, envVarFile.Name())
|
||||
@@ -760,13 +822,10 @@ func Example_mergingSomeWithConflict() {
|
||||
|
||||
func Example_mergingEverythingNoConflicts() {
|
||||
commandLineFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(commandLineFile.Name())
|
||||
envVarFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(envVarFile.Name())
|
||||
currentDirFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(currentDirFile.Name())
|
||||
homeDirFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(homeDirFile.Name())
|
||||
defer utiltesting.CloseAndRemove(&testing.T{}, commandLineFile, envVarFile, currentDirFile, homeDirFile)
|
||||
|
||||
WriteToFile(testConfigAlfa, commandLineFile.Name())
|
||||
WriteToFile(testConfigBravo, envVarFile.Name())
|
||||
@@ -897,12 +956,9 @@ func TestLoadingGetLoadingPrecedence(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
defer os.Setenv("KUBECONFIG", kubeconfig)
|
||||
|
||||
for name, test := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
os.Setenv("KUBECONFIG", test.env)
|
||||
t.Setenv("KUBECONFIG", test.env)
|
||||
rules := test.rules
|
||||
if rules == nil {
|
||||
rules = NewDefaultClientConfigLoadingRules()
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
@@ -296,7 +298,7 @@ func TestValidateCleanClusterInfo(t *testing.T) {
|
||||
|
||||
func TestValidateCleanWithCAClusterInfo(t *testing.T) {
|
||||
tempFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, tempFile)
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters["clean"] = &clientcmdapi.Cluster{
|
||||
@@ -339,7 +341,7 @@ func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
|
||||
|
||||
func TestValidateCertDataOverridesFiles(t *testing.T) {
|
||||
tempFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, tempFile)
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
@@ -359,7 +361,7 @@ func TestValidateCertDataOverridesFiles(t *testing.T) {
|
||||
|
||||
func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
|
||||
tempFile, _ := os.CreateTemp("", "")
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer utiltesting.CloseAndRemove(t, tempFile)
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
|
||||
@@ -748,37 +750,31 @@ func TestErrConfigurationInvalidWithErrorsIs(t *testing.T) {
|
||||
err error
|
||||
matchAgainst error
|
||||
expectMatch bool
|
||||
}{
|
||||
{
|
||||
name: "no match",
|
||||
err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")},
|
||||
matchAgainst: fmt.Errorf("no entry %s", "here"),
|
||||
},
|
||||
{
|
||||
name: "match via .Is()",
|
||||
err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}},
|
||||
matchAgainst: errors.New("unauthorized"),
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match via equality",
|
||||
err: errConfigurationInvalid{errors.New("err"), someError{}},
|
||||
matchAgainst: someError{},
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match via nested aggregate",
|
||||
err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}},
|
||||
matchAgainst: someError{},
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match via wrapped aggregate",
|
||||
err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}),
|
||||
matchAgainst: someError{},
|
||||
expectMatch: true,
|
||||
},
|
||||
}
|
||||
}{{
|
||||
name: "no match",
|
||||
err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")},
|
||||
matchAgainst: fmt.Errorf("no entry %s", "here"),
|
||||
}, {
|
||||
name: "match via .Is()",
|
||||
err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}},
|
||||
matchAgainst: errors.New("unauthorized"),
|
||||
expectMatch: true,
|
||||
}, {
|
||||
name: "match via equality",
|
||||
err: errConfigurationInvalid{errors.New("err"), someError{}},
|
||||
matchAgainst: someError{},
|
||||
expectMatch: true,
|
||||
}, {
|
||||
name: "match via nested aggregate",
|
||||
err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}},
|
||||
matchAgainst: someError{},
|
||||
expectMatch: true,
|
||||
}, {
|
||||
name: "match via wrapped aggregate",
|
||||
err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}),
|
||||
matchAgainst: someError{},
|
||||
expectMatch: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestRecordEventToSink(t *testing.T) {
|
||||
|
||||
recordedEvent := recordedEvents.Items[0]
|
||||
if !reflect.DeepEqual(recordedEvent, tc.expectedRecordedEvent) {
|
||||
t.Errorf("expected to have recorded Event: %#+v, got: %#+v\n diff: %s", tc.expectedRecordedEvent, recordedEvent, diff.ObjectReflectDiff(tc.expectedRecordedEvent, recordedEvent))
|
||||
t.Errorf("expected to have recorded Event: %#+v, got: %#+v\n diff: %s", tc.expectedRecordedEvent, recordedEvent, cmp.Diff(tc.expectedRecordedEvent, recordedEvent))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,6 +99,11 @@ func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {
|
||||
if lec.Lock == nil {
|
||||
return nil, fmt.Errorf("Lock must not be nil.")
|
||||
}
|
||||
id := lec.Lock.Identity()
|
||||
if id == "" {
|
||||
return nil, fmt.Errorf("Lock identity is empty")
|
||||
}
|
||||
|
||||
le := LeaderElector{
|
||||
config: lec,
|
||||
clock: clock.RealClock{},
|
||||
|
||||
@@ -24,19 +24,21 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
fakeclient "k8s.io/client-go/testing"
|
||||
rl "k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createLockObject(t *testing.T, objectType, namespace, name string, record *rl.LeaderElectionRecord) (obj runtime.Object) {
|
||||
@@ -371,551 +373,30 @@ func TestLeaseSpecToLeaderElectionRecordRoundTrip(t *testing.T) {
|
||||
newSpec := rl.LeaderElectionRecordToLeaseSpec(oldRecord)
|
||||
|
||||
if !equality.Semantic.DeepEqual(oldSpec, newSpec) {
|
||||
t.Errorf("diff: %v", diff.ObjectReflectDiff(oldSpec, newSpec))
|
||||
t.Errorf("diff: %v", cmp.Diff(oldSpec, newSpec))
|
||||
}
|
||||
|
||||
newRecord := rl.LeaseSpecToLeaderElectionRecord(&newSpec)
|
||||
|
||||
if !equality.Semantic.DeepEqual(oldRecord, newRecord) {
|
||||
t.Errorf("diff: %v", diff.ObjectReflectDiff(oldRecord, newRecord))
|
||||
t.Errorf("diff: %v", cmp.Diff(oldRecord, newRecord))
|
||||
}
|
||||
}
|
||||
|
||||
func multiLockType(t *testing.T, objectType string) (primaryType, secondaryType string) {
|
||||
switch objectType {
|
||||
case rl.EndpointsLeasesResourceLock:
|
||||
return "endpoints", rl.LeasesResourceLock
|
||||
case rl.ConfigMapsLeasesResourceLock:
|
||||
return "configmaps", rl.LeasesResourceLock
|
||||
default:
|
||||
t.Fatal("unexpected objType:" + objectType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetRawRecordOrDie(t *testing.T, objectType string, ler rl.LeaderElectionRecord) (ret []byte) {
|
||||
var err error
|
||||
switch objectType {
|
||||
case "endpoints", "configmaps", "leases":
|
||||
case "leases":
|
||||
ret, err = json.Marshal(ler)
|
||||
if err != nil {
|
||||
t.Fatalf("lock %s get raw record %v failed: %v", objectType, ler, err)
|
||||
}
|
||||
case "endpointsleases", "configmapsleases":
|
||||
recordBytes, err := json.Marshal(ler)
|
||||
if err != nil {
|
||||
t.Fatalf("lock %s get raw record %v failed: %v", objectType, ler, err)
|
||||
}
|
||||
ret = rl.ConcatRawRecord(recordBytes, recordBytes)
|
||||
default:
|
||||
t.Fatal("unexpected objType:" + objectType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testTryAcquireOrRenewMultiLock(t *testing.T, objectType string) {
|
||||
clock := clock.RealClock{}
|
||||
future := clock.Now().Add(1000 * time.Hour)
|
||||
past := clock.Now().Add(-1000 * time.Hour)
|
||||
primaryType, secondaryType := multiLockType(t, objectType)
|
||||
tests := []struct {
|
||||
name string
|
||||
observedRecord rl.LeaderElectionRecord
|
||||
observedRawRecord []byte
|
||||
observedTime time.Time
|
||||
reactors []Reactor
|
||||
expectedEvents []string
|
||||
|
||||
expectSuccess bool
|
||||
transitionLeader bool
|
||||
outHolder string
|
||||
}{
|
||||
{
|
||||
name: "acquire from no object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "create",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.CreateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "create",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.CreateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
expectSuccess: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
{
|
||||
name: "acquire from unled old object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "create",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.CreateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
expectSuccess: true,
|
||||
transitionLeader: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
{
|
||||
name: "acquire from unled transition object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
expectSuccess: true,
|
||||
transitionLeader: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
{
|
||||
name: "acquire from led, unack old object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "create",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.CreateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, primaryType, rl.LeaderElectionRecord{HolderIdentity: "bing"}),
|
||||
observedTime: past,
|
||||
|
||||
expectSuccess: true,
|
||||
transitionLeader: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
{
|
||||
name: "acquire from led, unack transition object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "bing"}),
|
||||
observedTime: past,
|
||||
|
||||
expectSuccess: true,
|
||||
transitionLeader: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
{
|
||||
name: "acquire from conflict led, ack transition object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "bing"}),
|
||||
observedTime: future,
|
||||
|
||||
expectSuccess: false,
|
||||
outHolder: rl.UnknownLeader,
|
||||
},
|
||||
{
|
||||
name: "acquire from led, unack unknown object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader},
|
||||
observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: rl.UnknownLeader}),
|
||||
observedTime: past,
|
||||
|
||||
expectSuccess: true,
|
||||
transitionLeader: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
{
|
||||
name: "don't acquire from led, ack old object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, nil, errors.NewNotFound(action.(fakeclient.GetAction).GetResource().GroupResource(), action.(fakeclient.GetAction).GetName())
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, primaryType, rl.LeaderElectionRecord{HolderIdentity: "bing"}),
|
||||
observedTime: future,
|
||||
|
||||
expectSuccess: false,
|
||||
outHolder: "bing",
|
||||
},
|
||||
{
|
||||
name: "don't acquire from led, acked new object, observe new record",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, secondaryType, rl.LeaderElectionRecord{HolderIdentity: "bing"}),
|
||||
observedTime: future,
|
||||
|
||||
expectSuccess: false,
|
||||
outHolder: rl.UnknownLeader,
|
||||
},
|
||||
{
|
||||
name: "don't acquire from led, acked new object, observe transition record",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "bing"}), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "bing"}),
|
||||
observedTime: future,
|
||||
|
||||
expectSuccess: false,
|
||||
outHolder: "bing",
|
||||
},
|
||||
{
|
||||
name: "renew already required object",
|
||||
reactors: []Reactor{
|
||||
{
|
||||
verb: "get",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, primaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: primaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "get",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, createLockObject(t, secondaryType, action.GetNamespace(), action.(fakeclient.GetAction).GetName(), &rl.LeaderElectionRecord{HolderIdentity: "baz"}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
verb: "update",
|
||||
objectType: secondaryType,
|
||||
reaction: func(action fakeclient.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, action.(fakeclient.UpdateAction).GetObject(), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"},
|
||||
observedRawRecord: GetRawRecordOrDie(t, objectType, rl.LeaderElectionRecord{HolderIdentity: "baz"}),
|
||||
observedTime: future,
|
||||
|
||||
expectSuccess: true,
|
||||
outHolder: "baz",
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
test := &tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// OnNewLeader is called async so we have to wait for it.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
var reportedLeader string
|
||||
|
||||
recorder := record.NewFakeRecorder(100)
|
||||
resourceLockConfig := rl.ResourceLockConfig{
|
||||
Identity: "baz",
|
||||
EventRecorder: recorder,
|
||||
}
|
||||
c := &fake.Clientset{}
|
||||
for _, reactor := range test.reactors {
|
||||
c.AddReactor(reactor.verb, reactor.objectType, reactor.reaction)
|
||||
}
|
||||
c.AddReactor("*", "*", func(action fakeclient.Action) (bool, runtime.Object, error) {
|
||||
t.Errorf("unreachable action. testclient called too many times: %+v", action)
|
||||
return true, nil, fmt.Errorf("unreachable action")
|
||||
})
|
||||
|
||||
lock, err := rl.New(objectType, "foo", "bar", c.CoreV1(), c.CoordinationV1(), resourceLockConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create lock: %v", err)
|
||||
}
|
||||
|
||||
lec := LeaderElectionConfig{
|
||||
Lock: lock,
|
||||
LeaseDuration: 10 * time.Second,
|
||||
Callbacks: LeaderCallbacks{
|
||||
OnNewLeader: func(l string) {
|
||||
defer wg.Done()
|
||||
reportedLeader = l
|
||||
},
|
||||
},
|
||||
}
|
||||
le := &LeaderElector{
|
||||
config: lec,
|
||||
observedRecord: test.observedRecord,
|
||||
observedRawRecord: test.observedRawRecord,
|
||||
observedTime: test.observedTime,
|
||||
clock: clock,
|
||||
}
|
||||
if test.expectSuccess != le.tryAcquireOrRenew(context.Background()) {
|
||||
t.Errorf("unexpected result of tryAcquireOrRenew: [succeeded=%v]", !test.expectSuccess)
|
||||
}
|
||||
|
||||
le.observedRecord.AcquireTime = metav1.Time{}
|
||||
le.observedRecord.RenewTime = metav1.Time{}
|
||||
if le.observedRecord.HolderIdentity != test.outHolder {
|
||||
t.Errorf("expected holder:\n\t%+v\ngot:\n\t%+v", test.outHolder, le.observedRecord.HolderIdentity)
|
||||
}
|
||||
if len(test.reactors) != len(c.Actions()) {
|
||||
t.Errorf("wrong number of api interactions")
|
||||
}
|
||||
if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 {
|
||||
t.Errorf("leader should have transitioned but did not")
|
||||
}
|
||||
if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 {
|
||||
t.Errorf("leader should not have transitioned but did")
|
||||
}
|
||||
|
||||
le.maybeReportTransition()
|
||||
wg.Wait()
|
||||
if reportedLeader != test.outHolder {
|
||||
t.Errorf("reported leader was not the new leader. expected %q, got %q", test.outHolder, reportedLeader)
|
||||
}
|
||||
assertEqualEvents(t, test.expectedEvents, recorder.Events)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Will test leader election using endpointsleases as the resource
|
||||
func TestTryAcquireOrRenewEndpointsLeases(t *testing.T) {
|
||||
testTryAcquireOrRenewMultiLock(t, "endpointsleases")
|
||||
}
|
||||
|
||||
// Will test leader election using configmapsleases as the resource
|
||||
func TestTryAcquireOrRenewConfigMapsLeases(t *testing.T) {
|
||||
testTryAcquireOrRenewMultiLock(t, "configmapsleases")
|
||||
}
|
||||
|
||||
func testReleaseLease(t *testing.T, objectType string) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1274,6 +755,41 @@ func testReleaseOnCancellation(t *testing.T, objectType string) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeaderElectionConfigValidation(t *testing.T) {
|
||||
resourceLockConfig := rl.ResourceLockConfig{
|
||||
Identity: "baz",
|
||||
}
|
||||
|
||||
lock := &rl.LeaseLock{
|
||||
LockConfig: resourceLockConfig,
|
||||
}
|
||||
|
||||
lec := LeaderElectionConfig{
|
||||
Lock: lock,
|
||||
LeaseDuration: 15 * time.Second,
|
||||
RenewDeadline: 2 * time.Second,
|
||||
RetryPeriod: 1 * time.Second,
|
||||
|
||||
ReleaseOnCancel: true,
|
||||
|
||||
Callbacks: LeaderCallbacks{
|
||||
OnNewLeader: func(identity string) {},
|
||||
OnStoppedLeading: func() {},
|
||||
OnStartedLeading: func(context.Context) {},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := NewLeaderElector(lec)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Invalid lock identity
|
||||
resourceLockConfig.Identity = ""
|
||||
lock.LockConfig = resourceLockConfig
|
||||
lec.Lock = lock
|
||||
_, err = NewLeaderElector(lec)
|
||||
assert.Error(t, err, fmt.Errorf("Lock identity is empty"))
|
||||
}
|
||||
|
||||
func assertEqualEvents(t *testing.T, expected []string, actual <-chan string) {
|
||||
c := time.After(wait.ForeverTestTimeout)
|
||||
for _, e := range expected {
|
||||
|
||||
@@ -1,126 +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 resourcelock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
// TODO: This is almost a exact replica of Endpoints lock.
|
||||
// going forwards as we self host more and more components
|
||||
// and use ConfigMaps as the means to pass that configuration
|
||||
// data we will likely move to deprecate the Endpoints lock.
|
||||
|
||||
type configMapLock struct {
|
||||
// ConfigMapMeta should contain a Name and a Namespace of a
|
||||
// ConfigMapMeta object that the LeaderElector will attempt to lead.
|
||||
ConfigMapMeta metav1.ObjectMeta
|
||||
Client corev1client.ConfigMapsGetter
|
||||
LockConfig ResourceLockConfig
|
||||
cm *v1.ConfigMap
|
||||
}
|
||||
|
||||
// Get returns the election record from a ConfigMap Annotation
|
||||
func (cml *configMapLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
|
||||
var record LeaderElectionRecord
|
||||
cm, err := cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Get(ctx, cml.ConfigMapMeta.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cml.cm = cm
|
||||
if cml.cm.Annotations == nil {
|
||||
cml.cm.Annotations = make(map[string]string)
|
||||
}
|
||||
recordStr, found := cml.cm.Annotations[LeaderElectionRecordAnnotationKey]
|
||||
recordBytes := []byte(recordStr)
|
||||
if found {
|
||||
if err := json.Unmarshal(recordBytes, &record); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return &record, recordBytes, nil
|
||||
}
|
||||
|
||||
// Create attempts to create a LeaderElectionRecord annotation
|
||||
func (cml *configMapLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
|
||||
recordBytes, err := json.Marshal(ler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Create(ctx, &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cml.ConfigMapMeta.Name,
|
||||
Namespace: cml.ConfigMapMeta.Namespace,
|
||||
Annotations: map[string]string{
|
||||
LeaderElectionRecordAnnotationKey: string(recordBytes),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Update will update an existing annotation on a given resource.
|
||||
func (cml *configMapLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
|
||||
if cml.cm == nil {
|
||||
return errors.New("configmap not initialized, call get or create first")
|
||||
}
|
||||
recordBytes, err := json.Marshal(ler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cml.cm.Annotations == nil {
|
||||
cml.cm.Annotations = make(map[string]string)
|
||||
}
|
||||
cml.cm.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
|
||||
cm, err := cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(ctx, cml.cm, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cml.cm = cm
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordEvent in leader election while adding meta-data
|
||||
func (cml *configMapLock) RecordEvent(s string) {
|
||||
if cml.LockConfig.EventRecorder == nil {
|
||||
return
|
||||
}
|
||||
events := fmt.Sprintf("%v %v", cml.LockConfig.Identity, s)
|
||||
subject := &v1.ConfigMap{ObjectMeta: cml.cm.ObjectMeta}
|
||||
// Populate the type meta, so we don't have to get it from the schema
|
||||
subject.Kind = "ConfigMap"
|
||||
subject.APIVersion = v1.SchemeGroupVersion.String()
|
||||
cml.LockConfig.EventRecorder.Eventf(subject, v1.EventTypeNormal, "LeaderElection", events)
|
||||
}
|
||||
|
||||
// Describe is used to convert details on current resource lock
|
||||
// into a string
|
||||
func (cml *configMapLock) Describe() string {
|
||||
return fmt.Sprintf("%v/%v", cml.ConfigMapMeta.Namespace, cml.ConfigMapMeta.Name)
|
||||
}
|
||||
|
||||
// Identity returns the Identity of the lock
|
||||
func (cml *configMapLock) Identity() string {
|
||||
return cml.LockConfig.Identity
|
||||
}
|
||||
@@ -1,121 +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 resourcelock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
)
|
||||
|
||||
type endpointsLock struct {
|
||||
// EndpointsMeta should contain a Name and a Namespace of an
|
||||
// Endpoints object that the LeaderElector will attempt to lead.
|
||||
EndpointsMeta metav1.ObjectMeta
|
||||
Client corev1client.EndpointsGetter
|
||||
LockConfig ResourceLockConfig
|
||||
e *v1.Endpoints
|
||||
}
|
||||
|
||||
// Get returns the election record from a Endpoints Annotation
|
||||
func (el *endpointsLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
|
||||
var record LeaderElectionRecord
|
||||
ep, err := el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(ctx, el.EndpointsMeta.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
el.e = ep
|
||||
if el.e.Annotations == nil {
|
||||
el.e.Annotations = make(map[string]string)
|
||||
}
|
||||
recordStr, found := el.e.Annotations[LeaderElectionRecordAnnotationKey]
|
||||
recordBytes := []byte(recordStr)
|
||||
if found {
|
||||
if err := json.Unmarshal(recordBytes, &record); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return &record, recordBytes, nil
|
||||
}
|
||||
|
||||
// Create attempts to create a LeaderElectionRecord annotation
|
||||
func (el *endpointsLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
|
||||
recordBytes, err := json.Marshal(ler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Create(ctx, &v1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: el.EndpointsMeta.Name,
|
||||
Namespace: el.EndpointsMeta.Namespace,
|
||||
Annotations: map[string]string{
|
||||
LeaderElectionRecordAnnotationKey: string(recordBytes),
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Update will update and existing annotation on a given resource.
|
||||
func (el *endpointsLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
|
||||
if el.e == nil {
|
||||
return errors.New("endpoint not initialized, call get or create first")
|
||||
}
|
||||
recordBytes, err := json.Marshal(ler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if el.e.Annotations == nil {
|
||||
el.e.Annotations = make(map[string]string)
|
||||
}
|
||||
el.e.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
|
||||
e, err := el.Client.Endpoints(el.EndpointsMeta.Namespace).Update(ctx, el.e, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
el.e = e
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordEvent in leader election while adding meta-data
|
||||
func (el *endpointsLock) RecordEvent(s string) {
|
||||
if el.LockConfig.EventRecorder == nil {
|
||||
return
|
||||
}
|
||||
events := fmt.Sprintf("%v %v", el.LockConfig.Identity, s)
|
||||
subject := &v1.Endpoints{ObjectMeta: el.e.ObjectMeta}
|
||||
// Populate the type meta, so we don't have to get it from the schema
|
||||
subject.Kind = "Endpoints"
|
||||
subject.APIVersion = v1.SchemeGroupVersion.String()
|
||||
el.LockConfig.EventRecorder.Eventf(subject, v1.EventTypeNormal, "LeaderElection", events)
|
||||
}
|
||||
|
||||
// Describe is used to convert details on current resource lock
|
||||
// into a string
|
||||
func (el *endpointsLock) Describe() string {
|
||||
return fmt.Sprintf("%v/%v", el.EndpointsMeta.Namespace, el.EndpointsMeta.Name)
|
||||
}
|
||||
|
||||
// Identity returns the Identity of the lock
|
||||
func (el *endpointsLock) Identity() string {
|
||||
return el.LockConfig.Identity
|
||||
}
|
||||
@@ -34,7 +34,7 @@ const (
|
||||
endpointsResourceLock = "endpoints"
|
||||
configMapsResourceLock = "configmaps"
|
||||
LeasesResourceLock = "leases"
|
||||
// When using EndpointsLeasesResourceLock, you need to ensure that
|
||||
// When using endpointsLeasesResourceLock, you need to ensure that
|
||||
// API Priority & Fairness is configured with non-default flow-schema
|
||||
// that will catch the necessary operations on leader-election related
|
||||
// endpoint objects.
|
||||
@@ -67,8 +67,8 @@ const (
|
||||
// serviceAccount:
|
||||
// name: '*'
|
||||
// namespace: kube-system
|
||||
EndpointsLeasesResourceLock = "endpointsleases"
|
||||
// When using ConfigMapsLeasesResourceLock, you need to ensure that
|
||||
endpointsLeasesResourceLock = "endpointsleases"
|
||||
// When using configMapsLeasesResourceLock, you need to ensure that
|
||||
// API Priority & Fairness is configured with non-default flow-schema
|
||||
// that will catch the necessary operations on leader-election related
|
||||
// configmap objects.
|
||||
@@ -101,7 +101,7 @@ const (
|
||||
// serviceAccount:
|
||||
// name: '*'
|
||||
// namespace: kube-system
|
||||
ConfigMapsLeasesResourceLock = "configmapsleases"
|
||||
configMapsLeasesResourceLock = "configmapsleases"
|
||||
)
|
||||
|
||||
// LeaderElectionRecord is the record that is stored in the leader election annotation.
|
||||
@@ -164,22 +164,6 @@ type Interface interface {
|
||||
|
||||
// Manufacture will create a lock of a given type according to the input parameters
|
||||
func New(lockType string, ns string, name string, coreClient corev1.CoreV1Interface, coordinationClient coordinationv1.CoordinationV1Interface, rlc ResourceLockConfig) (Interface, error) {
|
||||
endpointsLock := &endpointsLock{
|
||||
EndpointsMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
Client: coreClient,
|
||||
LockConfig: rlc,
|
||||
}
|
||||
configmapLock := &configMapLock{
|
||||
ConfigMapMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
Client: coreClient,
|
||||
LockConfig: rlc,
|
||||
}
|
||||
leaseLock := &LeaseLock{
|
||||
LeaseMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
@@ -190,21 +174,15 @@ func New(lockType string, ns string, name string, coreClient corev1.CoreV1Interf
|
||||
}
|
||||
switch lockType {
|
||||
case endpointsResourceLock:
|
||||
return nil, fmt.Errorf("endpoints lock is removed, migrate to %s", EndpointsLeasesResourceLock)
|
||||
return nil, fmt.Errorf("endpoints lock is removed, migrate to %s (using version v0.27.x)", endpointsLeasesResourceLock)
|
||||
case configMapsResourceLock:
|
||||
return nil, fmt.Errorf("configmaps lock is removed, migrate to %s", ConfigMapsLeasesResourceLock)
|
||||
return nil, fmt.Errorf("configmaps lock is removed, migrate to %s (using version v0.27.x)", configMapsLeasesResourceLock)
|
||||
case LeasesResourceLock:
|
||||
return leaseLock, nil
|
||||
case EndpointsLeasesResourceLock:
|
||||
return &MultiLock{
|
||||
Primary: endpointsLock,
|
||||
Secondary: leaseLock,
|
||||
}, nil
|
||||
case ConfigMapsLeasesResourceLock:
|
||||
return &MultiLock{
|
||||
Primary: configmapLock,
|
||||
Secondary: leaseLock,
|
||||
}, nil
|
||||
case endpointsLeasesResourceLock:
|
||||
return nil, fmt.Errorf("endpointsleases lock is removed, migrate to %s", LeasesResourceLock)
|
||||
case configMapsLeasesResourceLock:
|
||||
return nil, fmt.Errorf("configmapsleases lock is removed, migrated to %s", LeasesResourceLock)
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid lock-type %s", lockType)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,17 @@ type RetryMetric interface {
|
||||
IncrementRetry(ctx context.Context, code string, method string, host string)
|
||||
}
|
||||
|
||||
// TransportCacheMetric shows the number of entries in the internal transport cache
|
||||
type TransportCacheMetric interface {
|
||||
Observe(value int)
|
||||
}
|
||||
|
||||
// TransportCreateCallsMetric counts the number of times a transport is created
|
||||
// partitioned by the result of the cache: hit, miss, uncacheable
|
||||
type TransportCreateCallsMetric interface {
|
||||
Increment(result string)
|
||||
}
|
||||
|
||||
var (
|
||||
// ClientCertExpiry is the expiry time of a client certificate
|
||||
ClientCertExpiry ExpiryMetric = noopExpiry{}
|
||||
@@ -85,6 +96,12 @@ var (
|
||||
// RequestRetry is the retry metric that tracks the number of
|
||||
// retries sent to the server.
|
||||
RequestRetry RetryMetric = noopRetry{}
|
||||
// TransportCacheEntries is the metric that tracks the number of entries in the
|
||||
// internal transport cache.
|
||||
TransportCacheEntries TransportCacheMetric = noopTransportCache{}
|
||||
// TransportCreateCalls is the metric that counts the number of times a new transport
|
||||
// is created
|
||||
TransportCreateCalls TransportCreateCallsMetric = noopTransportCreateCalls{}
|
||||
)
|
||||
|
||||
// RegisterOpts contains all the metrics to register. Metrics may be nil.
|
||||
@@ -98,6 +115,8 @@ type RegisterOpts struct {
|
||||
RequestResult ResultMetric
|
||||
ExecPluginCalls CallsMetric
|
||||
RequestRetry RetryMetric
|
||||
TransportCacheEntries TransportCacheMetric
|
||||
TransportCreateCalls TransportCreateCallsMetric
|
||||
}
|
||||
|
||||
// Register registers metrics for the rest client to use. This can
|
||||
@@ -131,6 +150,12 @@ func Register(opts RegisterOpts) {
|
||||
if opts.RequestRetry != nil {
|
||||
RequestRetry = opts.RequestRetry
|
||||
}
|
||||
if opts.TransportCacheEntries != nil {
|
||||
TransportCacheEntries = opts.TransportCacheEntries
|
||||
}
|
||||
if opts.TransportCreateCalls != nil {
|
||||
TransportCreateCalls = opts.TransportCreateCalls
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,3 +186,11 @@ func (noopCalls) Increment(int, string) {}
|
||||
type noopRetry struct{}
|
||||
|
||||
func (noopRetry) IncrementRetry(context.Context, string, string, string) {}
|
||||
|
||||
type noopTransportCache struct{}
|
||||
|
||||
func (noopTransportCache) Observe(int) {}
|
||||
|
||||
type noopTransportCreateCalls struct{}
|
||||
|
||||
func (noopTransportCreateCalls) Increment(string) {}
|
||||
|
||||
@@ -73,7 +73,23 @@ func New(fn ListPageFunc) *ListPager {
|
||||
// List returns a single list object, but attempts to retrieve smaller chunks from the
|
||||
// server to reduce the impact on the server. If the chunk attempt fails, it will load
|
||||
// the full list instead. The Limit field on options, if unset, will default to the page size.
|
||||
//
|
||||
// If items in the returned list are retained for different durations, and you want to avoid
|
||||
// retaining the whole slice returned by p.PageFn as long as any item is referenced,
|
||||
// use ListWithAlloc instead.
|
||||
func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runtime.Object, bool, error) {
|
||||
return p.list(ctx, options, false)
|
||||
}
|
||||
|
||||
// ListWithAlloc works like List, but avoids retaining references to the items slice returned by p.PageFn.
|
||||
// It does this by making a shallow copy of non-pointer items in the slice returned by p.PageFn.
|
||||
//
|
||||
// If the items in the returned list are not retained, or are retained for the same duration, use List instead for memory efficiency.
|
||||
func (p *ListPager) ListWithAlloc(ctx context.Context, options metav1.ListOptions) (runtime.Object, bool, error) {
|
||||
return p.list(ctx, options, true)
|
||||
}
|
||||
|
||||
func (p *ListPager) list(ctx context.Context, options metav1.ListOptions, allocNew bool) (runtime.Object, bool, error) {
|
||||
if options.Limit == 0 {
|
||||
options.Limit = p.PageSize
|
||||
}
|
||||
@@ -123,7 +139,11 @@ func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runti
|
||||
list.ResourceVersion = m.GetResourceVersion()
|
||||
list.SelfLink = m.GetSelfLink()
|
||||
}
|
||||
if err := meta.EachListItem(obj, func(obj runtime.Object) error {
|
||||
eachListItemFunc := meta.EachListItem
|
||||
if allocNew {
|
||||
eachListItemFunc = meta.EachListItemWithAlloc
|
||||
}
|
||||
if err := eachListItemFunc(obj, func(obj runtime.Object) error {
|
||||
list.Items = append(list.Items, obj)
|
||||
return nil
|
||||
}); err != nil {
|
||||
@@ -156,12 +176,26 @@ func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runti
|
||||
//
|
||||
// Items are retrieved in chunks from the server to reduce the impact on the server with up to
|
||||
// ListPager.PageBufferSize chunks buffered concurrently in the background.
|
||||
//
|
||||
// If items passed to fn are retained for different durations, and you want to avoid
|
||||
// retaining the whole slice returned by p.PageFn as long as any item is referenced,
|
||||
// use EachListItemWithAlloc instead.
|
||||
func (p *ListPager) EachListItem(ctx context.Context, options metav1.ListOptions, fn func(obj runtime.Object) error) error {
|
||||
return p.eachListChunkBuffered(ctx, options, func(obj runtime.Object) error {
|
||||
return meta.EachListItem(obj, fn)
|
||||
})
|
||||
}
|
||||
|
||||
// EachListItemWithAlloc works like EachListItem, but avoids retaining references to the items slice returned by p.PageFn.
|
||||
// It does this by making a shallow copy of non-pointer items in the slice returned by p.PageFn.
|
||||
//
|
||||
// If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency.
|
||||
func (p *ListPager) EachListItemWithAlloc(ctx context.Context, options metav1.ListOptions, fn func(obj runtime.Object) error) error {
|
||||
return p.eachListChunkBuffered(ctx, options, func(obj runtime.Object) error {
|
||||
return meta.EachListItemWithAlloc(obj, fn)
|
||||
})
|
||||
}
|
||||
|
||||
// eachListChunkBuffered fetches runtimeObject list chunks using this ListPager and invokes fn on
|
||||
// each list chunk. If fn returns an error, processing stops and that error is returned. If fn does
|
||||
// not return an error, any error encountered while retrieving the list from the server is
|
||||
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
testclocks "k8s.io/utils/clock/testing"
|
||||
)
|
||||
|
||||
@@ -124,7 +124,7 @@ func validateEvent(messagePrefix string, actualEvent *v1.Event, expectedEvent *v
|
||||
}
|
||||
recvEvent.Name = expectedEvent.Name
|
||||
if e, a := expectedEvent, &recvEvent; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%v - diff: %s", messagePrefix, diff.ObjectGoPrintDiff(e, a))
|
||||
t.Errorf("%v - diff: %s", messagePrefix, cmp.Diff(e, a))
|
||||
}
|
||||
recvEvent.FirstTimestamp = actualFirstTimestamp
|
||||
recvEvent.LastTimestamp = actualLastTimestamp
|
||||
|
||||
@@ -17,10 +17,13 @@ limitations under the License.
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -365,3 +368,61 @@ func TestStreamExitsAfterConnectionIsClosed(t *testing.T) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamRandomData(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var stdin, stdout bytes.Buffer
|
||||
ctx, err := createHTTPStreams(w, req, &StreamOptions{
|
||||
Stdin: &stdin,
|
||||
Stdout: &stdout,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("error on createHTTPStreams: %v", err)
|
||||
return
|
||||
}
|
||||
defer ctx.conn.Close()
|
||||
|
||||
io.Copy(ctx.stdoutStream, ctx.stdinStream)
|
||||
}))
|
||||
|
||||
defer server.Close()
|
||||
|
||||
uri, _ := url.Parse(server.URL)
|
||||
exec, err := NewSPDYExecutor(&rest.Config{Host: uri.Host}, "POST", uri)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
randomData := make([]byte, 1024*1024)
|
||||
if _, err := rand.Read(randomData); err != nil {
|
||||
t.Errorf("unexpected error reading random data: %v", err)
|
||||
}
|
||||
var stdout bytes.Buffer
|
||||
options := &StreamOptions{
|
||||
Stdin: bytes.NewReader(randomData),
|
||||
Stdout: &stdout,
|
||||
}
|
||||
errorChan := make(chan error)
|
||||
go func() {
|
||||
errorChan <- exec.StreamWithContext(context.Background(), *options)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Fatalf("expect stream to be closed after connection is closed.")
|
||||
case err := <-errorChan:
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(bytes.NewReader(stdout.Bytes()))
|
||||
if err != nil {
|
||||
t.Errorf("error reading the stream: %v", err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(randomData, data) {
|
||||
t.Errorf("unexpected data received: %d sent: %d", len(data), len(randomData))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "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/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
testcore "k8s.io/client-go/testing"
|
||||
@@ -260,7 +260,7 @@ func TestNewInformerWatcher(t *testing.T) {
|
||||
sort.Sort(byEventTypeAndName(result))
|
||||
|
||||
if !reflect.DeepEqual(expected, result) {
|
||||
t.Error(spew.Errorf("\nexpected: %#v,\ngot: %#v,\ndiff: %s", expected, result, diff.ObjectReflectDiff(expected, result)))
|
||||
t.Errorf("\nexpected: %s,\ngot: %s,\ndiff: %s", dump.Pretty(expected), dump.Pretty(result), cmp.Diff(expected, result))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ func TestInformerWatcherDeletedFinalStateUnknown(t *testing.T) {
|
||||
return retval, nil
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
w := watch.NewFake()
|
||||
w := watch.NewRaceFreeFake()
|
||||
if options.ResourceVersion == "1" {
|
||||
go func() {
|
||||
// Close with a "Gone" error when trying to start a watch from the first list
|
||||
@@ -315,6 +315,7 @@ func TestInformerWatcherDeletedFinalStateUnknown(t *testing.T) {
|
||||
},
|
||||
}
|
||||
_, _, w, done := NewIndexerInformerWatcher(lw, &corev1.Secret{})
|
||||
defer w.Stop()
|
||||
|
||||
// Expect secret add
|
||||
select {
|
||||
|
||||
@@ -24,10 +24,9 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
@@ -191,7 +190,7 @@ func (rw *RetryWatcher) doReceive() (bool, time.Duration) {
|
||||
errObject := apierrors.FromObject(event.Object)
|
||||
statusErr, ok := errObject.(*apierrors.StatusError)
|
||||
if !ok {
|
||||
klog.Error(spew.Sprintf("Received an error which is not *metav1.Status but %#+v", event.Object))
|
||||
klog.Error(fmt.Sprintf("Received an error which is not *metav1.Status but %s", dump.Pretty(event.Object)))
|
||||
// Retry unknown errors
|
||||
return false, 0
|
||||
}
|
||||
@@ -220,7 +219,7 @@ func (rw *RetryWatcher) doReceive() (bool, time.Duration) {
|
||||
|
||||
// Log here so we have a record of hitting the unexpected error
|
||||
// and we can whitelist some error codes if we missed any that are expected.
|
||||
klog.V(5).Info(spew.Sprintf("Retrying after unexpected error: %#+v", event.Object))
|
||||
klog.V(5).Info(fmt.Sprintf("Retrying after unexpected error: %s", dump.Pretty(event.Object)))
|
||||
|
||||
// Retry
|
||||
return false, statusDelay
|
||||
|
||||
@@ -26,13 +26,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
apierrors "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/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/dump"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
@@ -530,7 +530,7 @@ func TestRetryWatcher(t *testing.T) {
|
||||
for i := 0; i < len(tc.expected); i++ {
|
||||
event, ok := <-watcher.ResultChan()
|
||||
if !ok {
|
||||
t.Error(spew.Errorf("expected event %#+v, but channel is closed"), tc.expected[i])
|
||||
t.Errorf("expected event %s, but channel is closed", dump.Pretty(tc.expected[i]))
|
||||
break
|
||||
}
|
||||
|
||||
@@ -544,7 +544,7 @@ func TestRetryWatcher(t *testing.T) {
|
||||
select {
|
||||
case event, ok := <-watcher.ResultChan():
|
||||
if ok {
|
||||
t.Error(spew.Errorf("Unexpected event received after reading all the expected ones: %#+v", event))
|
||||
t.Errorf("Unexpected event received after reading all the expected ones: %s", dump.Pretty(event))
|
||||
}
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
break
|
||||
@@ -564,7 +564,7 @@ func TestRetryWatcher(t *testing.T) {
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.expected, got) {
|
||||
t.Fatal(spew.Errorf("expected %#+v, got %#+v;\ndiff: %s", tc.expected, got, diff.ObjectReflectDiff(tc.expected, got)))
|
||||
t.Fatalf("expected %s, got %s;\ndiff: %s", dump.Pretty(tc.expected), dump.Pretty(got), cmp.Diff(tc.expected, got))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/metrics"
|
||||
)
|
||||
|
||||
// TlsTransportCache caches TLS http.RoundTrippers different configurations. The
|
||||
@@ -80,11 +81,16 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
|
||||
// Ensure we only create a single transport for the given TLS options
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
defer metrics.TransportCacheEntries.Observe(len(c.transports))
|
||||
|
||||
// See if we already have a custom transport for this config
|
||||
if t, ok := c.transports[key]; ok {
|
||||
metrics.TransportCreateCalls.Increment("hit")
|
||||
return t, nil
|
||||
}
|
||||
metrics.TransportCreateCalls.Increment("miss")
|
||||
} else {
|
||||
metrics.TransportCreateCalls.Increment("uncacheable")
|
||||
}
|
||||
|
||||
// Get the TLS options for this client config
|
||||
|
||||
@@ -45,7 +45,6 @@ type Config struct {
|
||||
Organization []string
|
||||
AltNames AltNames
|
||||
Usages []x509.ExtKeyUsage
|
||||
NotBefore time.Time
|
||||
}
|
||||
|
||||
// AltNames contains the domain names and IP addresses that will be added
|
||||
@@ -65,10 +64,6 @@ func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, erro
|
||||
return nil, err
|
||||
}
|
||||
serial = new(big.Int).Add(serial, big.NewInt(1))
|
||||
notBefore := now.UTC()
|
||||
if !cfg.NotBefore.IsZero() {
|
||||
notBefore = cfg.NotBefore.UTC()
|
||||
}
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
@@ -76,7 +71,7 @@ func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, erro
|
||||
Organization: cfg.Organization,
|
||||
},
|
||||
DNSNames: []string{cfg.CommonName},
|
||||
NotBefore: notBefore,
|
||||
NotBefore: now.UTC(),
|
||||
NotAfter: now.Add(duration365d * 10).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
|
||||
40
util/testing/remove_file.go
Normal file
40
util/testing/remove_file.go
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2023 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 testing
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// CloseAndRemove is a helper to close and remove test file.
|
||||
func CloseAndRemove(t *testing.T, files ...*os.File) {
|
||||
t.Helper()
|
||||
// We should close it first before remove a file, it's not only a good practice,
|
||||
// but also can avoid failed file removing on Windows OS.
|
||||
for _, f := range files {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("Error closing %s: %v", f.Name(), err)
|
||||
}
|
||||
if err := os.Remove(f.Name()); err != nil {
|
||||
t.Fatalf("Error removing %s: %v", f.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user