mirror of
https://github.com/kubernetes/client-go.git
synced 2026-05-15 19:53:50 +00:00
Compare commits
43 Commits
v0.26.2
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ef9fa9ab1 | ||
|
|
7adf9e8a99 | ||
|
|
fc39103f88 | ||
|
|
6ee0283fa4 | ||
|
|
76fabd5856 | ||
|
|
f981b01392 | ||
|
|
7d2c28896e | ||
|
|
7eeeef0f16 | ||
|
|
b625a191b9 | ||
|
|
0d6350fa4e | ||
|
|
7b6e8d8480 | ||
|
|
ee23718387 | ||
|
|
8429124260 | ||
|
|
5d715fe8c7 | ||
|
|
cea5ee961b | ||
|
|
c4c506ff2e | ||
|
|
cb4594adb3 | ||
|
|
5023c62056 | ||
|
|
a3a549a55a | ||
|
|
038b381bf6 | ||
|
|
cd83e43d17 | ||
|
|
dbfbc039f8 | ||
|
|
d72dec4966 | ||
|
|
a5144d412c | ||
|
|
d6f8d0460a | ||
|
|
2dd0093cac | ||
|
|
f3ae5cbd83 | ||
|
|
fffc68d58e | ||
|
|
5ebee1886e | ||
|
|
87720b3719 | ||
|
|
fc13749c0d | ||
|
|
f39ba12dc2 | ||
|
|
f538edfed7 | ||
|
|
5dbbc58c50 | ||
|
|
8190aa4d37 | ||
|
|
b667227efd | ||
|
|
30215cd5a1 | ||
|
|
ba3596940d | ||
|
|
97cf9cb9c2 | ||
|
|
fd7800c967 | ||
|
|
e7876b900b | ||
|
|
08d548e149 | ||
|
|
b1a83534de |
@@ -24,19 +24,36 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// StaleGroupVersionError encasulates failed GroupVersion marked "stale"
|
||||
// in the returned AggregatedDiscovery format.
|
||||
type StaleGroupVersionError struct {
|
||||
gv schema.GroupVersion
|
||||
}
|
||||
|
||||
func (s StaleGroupVersionError) Error() string {
|
||||
return fmt.Sprintf("stale GroupVersion discovery: %v", s.gv)
|
||||
}
|
||||
|
||||
// SplitGroupsAndResources transforms "aggregated" discovery top-level structure into
|
||||
// the previous "unaggregated" discovery groups and resources.
|
||||
func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList) (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList) {
|
||||
func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList) (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error) {
|
||||
// Aggregated group list will contain the entirety of discovery, including
|
||||
// groups, versions, and resources.
|
||||
// groups, versions, and resources. GroupVersions marked "stale" are failed.
|
||||
groups := []*metav1.APIGroup{}
|
||||
failedGVs := map[schema.GroupVersion]error{}
|
||||
resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{}
|
||||
for _, aggGroup := range aggregatedGroups.Items {
|
||||
group, resources := convertAPIGroup(aggGroup)
|
||||
group, resources, failed := convertAPIGroup(aggGroup)
|
||||
groups = append(groups, group)
|
||||
for gv, resourceList := range resources {
|
||||
resourcesByGV[gv] = resourceList
|
||||
}
|
||||
for gv, err := range failed {
|
||||
failedGVs[gv] = err
|
||||
}
|
||||
}
|
||||
// Transform slice of groups to group list before returning.
|
||||
groupList := &metav1.APIGroupList{}
|
||||
@@ -44,65 +61,96 @@ func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList
|
||||
for _, group := range groups {
|
||||
groupList.Groups = append(groupList.Groups, *group)
|
||||
}
|
||||
return groupList, resourcesByGV
|
||||
return groupList, resourcesByGV, failedGVs
|
||||
}
|
||||
|
||||
// convertAPIGroup tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup,
|
||||
// also returning the map of APIResourceList for resources within GroupVersions.
|
||||
func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (*metav1.APIGroup, map[schema.GroupVersion]*metav1.APIResourceList) {
|
||||
func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (
|
||||
*metav1.APIGroup,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error) {
|
||||
// Iterate through versions to convert to group and resources.
|
||||
group := &metav1.APIGroup{}
|
||||
gvResources := map[schema.GroupVersion]*metav1.APIResourceList{}
|
||||
failedGVs := map[schema.GroupVersion]error{}
|
||||
group.Name = g.ObjectMeta.Name
|
||||
for i, v := range g.Versions {
|
||||
version := metav1.GroupVersionForDiscovery{}
|
||||
for _, v := range g.Versions {
|
||||
gv := schema.GroupVersion{Group: g.Name, Version: v.Version}
|
||||
if v.Freshness == apidiscovery.DiscoveryFreshnessStale {
|
||||
failedGVs[gv] = StaleGroupVersionError{gv: gv}
|
||||
continue
|
||||
}
|
||||
version := metav1.GroupVersionForDiscovery{}
|
||||
version.GroupVersion = gv.String()
|
||||
version.Version = v.Version
|
||||
group.Versions = append(group.Versions, version)
|
||||
if i == 0 {
|
||||
// PreferredVersion is first non-stale Version
|
||||
if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) {
|
||||
group.PreferredVersion = version
|
||||
}
|
||||
resourceList := &metav1.APIResourceList{}
|
||||
resourceList.GroupVersion = gv.String()
|
||||
for _, r := range v.Resources {
|
||||
resource := convertAPIResource(r)
|
||||
resourceList.APIResources = append(resourceList.APIResources, resource)
|
||||
resource, err := convertAPIResource(r)
|
||||
if err == nil {
|
||||
resourceList.APIResources = append(resourceList.APIResources, resource)
|
||||
}
|
||||
// Subresources field in new format get transformed into full APIResources.
|
||||
// It is possible a partial result with an error was returned to be used
|
||||
// as the parent resource for the subresource.
|
||||
for _, subresource := range r.Subresources {
|
||||
sr := convertAPISubresource(resource, subresource)
|
||||
resourceList.APIResources = append(resourceList.APIResources, sr)
|
||||
sr, err := convertAPISubresource(resource, subresource)
|
||||
if err == nil {
|
||||
resourceList.APIResources = append(resourceList.APIResources, sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
gvResources[gv] = resourceList
|
||||
}
|
||||
return group, gvResources
|
||||
return group, gvResources, failedGVs
|
||||
}
|
||||
|
||||
// convertAPIResource tranforms a APIResourceDiscovery to an APIResource.
|
||||
func convertAPIResource(in apidiscovery.APIResourceDiscovery) metav1.APIResource {
|
||||
return metav1.APIResource{
|
||||
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
|
||||
// discovery, and the subresource MUST have the GVK.
|
||||
func convertAPIResource(in apidiscovery.APIResourceDiscovery) (metav1.APIResource, error) {
|
||||
result := metav1.APIResource{
|
||||
Name: in.Resource,
|
||||
SingularName: in.SingularResource,
|
||||
Namespaced: in.Scope == apidiscovery.ScopeNamespace,
|
||||
Group: in.ResponseKind.Group,
|
||||
Version: in.ResponseKind.Version,
|
||||
Kind: in.ResponseKind.Kind,
|
||||
Verbs: in.Verbs,
|
||||
ShortNames: in.ShortNames,
|
||||
Categories: in.Categories,
|
||||
}
|
||||
var err error
|
||||
if in.ResponseKind != nil && (*in.ResponseKind) != emptyKind {
|
||||
result.Group = in.ResponseKind.Group
|
||||
result.Version = in.ResponseKind.Version
|
||||
result.Kind = in.ResponseKind.Kind
|
||||
} else {
|
||||
err = fmt.Errorf("discovery resource %s missing GVK", in.Resource)
|
||||
}
|
||||
// Can return partial result with error, which can be the parent for a
|
||||
// subresource. Do not add this result to the returned discovery resources.
|
||||
return result, err
|
||||
}
|
||||
|
||||
// convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource.
|
||||
func convertAPISubresource(parent metav1.APIResource, in apidiscovery.APISubresourceDiscovery) metav1.APIResource {
|
||||
return metav1.APIResource{
|
||||
Name: fmt.Sprintf("%s/%s", parent.Name, in.Subresource),
|
||||
SingularName: parent.SingularName,
|
||||
Namespaced: parent.Namespaced,
|
||||
Group: in.ResponseKind.Group,
|
||||
Version: in.ResponseKind.Version,
|
||||
Kind: in.ResponseKind.Kind,
|
||||
Verbs: in.Verbs,
|
||||
func convertAPISubresource(parent metav1.APIResource, in apidiscovery.APISubresourceDiscovery) (metav1.APIResource, error) {
|
||||
result := metav1.APIResource{}
|
||||
if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
|
||||
return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource)
|
||||
}
|
||||
result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource)
|
||||
result.SingularName = parent.SingularName
|
||||
result.Namespaced = parent.Namespaced
|
||||
result.Group = in.ResponseKind.Group
|
||||
result.Version = in.ResponseKind.Version
|
||||
result.Kind = in.ResponseKind.Kind
|
||||
result.Verbs = in.Verbs
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
agg apidiscovery.APIGroupDiscoveryList
|
||||
expectedGroups metav1.APIGroupList
|
||||
expectedGVResources map[schema.GroupVersion]*metav1.APIResourceList
|
||||
expectedFailedGVs map[schema.GroupVersion]error
|
||||
}{
|
||||
{
|
||||
name: "Aggregated discovery: core/v1 group and pod resource",
|
||||
@@ -90,6 +91,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis",
|
||||
@@ -179,6 +181,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis",
|
||||
@@ -313,6 +316,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: multiple groups with cluster-scoped resources",
|
||||
@@ -447,6 +451,7 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery with single subresource",
|
||||
@@ -534,6 +539,146 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery with single subresource and parent missing 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 nil GVK for parent
|
||||
Resource: "*",
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
SingularResource: "",
|
||||
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 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",
|
||||
@@ -633,11 +778,185 @@ func TestSplitGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: single failed GV at /api",
|
||||
agg: apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "pods",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "services",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Service",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Single core Group/Version is stale, so no Version within Group.
|
||||
expectedGroups: metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{Name: ""}},
|
||||
},
|
||||
// Single core Group/Version is stale, so there are no expected resources.
|
||||
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{
|
||||
{Group: "", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "", Version: "v1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: single failed GV at /apis",
|
||||
agg: apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "statefulsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "StatefulSets",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Single apps/v1 Group/Version is stale, so no Version within Group.
|
||||
expectedGroups: metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{Name: "apps"}},
|
||||
},
|
||||
// Single apps/v1 Group/Version is stale, so there are no expected resources.
|
||||
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{
|
||||
{Group: "apps", Version: "v1"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/2 versions/1 failed GV at /apis",
|
||||
agg: apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
// Stale v2 should report failed GV.
|
||||
{
|
||||
Version: "v2",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "daemonsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v2",
|
||||
Kind: "DaemonSets",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Only apps/v1 is non-stale expected Group/Version
|
||||
expectedGroups: metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "apps",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{
|
||||
GroupVersion: "apps/v1",
|
||||
Version: "v1",
|
||||
},
|
||||
},
|
||||
// PreferredVersion must be apps/v1
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: "apps/v1",
|
||||
Version: "v1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Only apps/v1 resources expected.
|
||||
expectedGVResources: map[schema.GroupVersion]*metav1.APIResourceList{
|
||||
{Group: "apps", Version: "v1"}: {
|
||||
GroupVersion: "apps/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{
|
||||
Name: "deployments",
|
||||
Namespaced: true,
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFailedGVs: map[schema.GroupVersion]error{
|
||||
{Group: "apps", Version: "v2"}: StaleGroupVersionError{gv: schema.GroupVersion{Group: "apps", Version: "v2"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
apiGroups, resourcesByGV := SplitGroupsAndResources(test.agg)
|
||||
apiGroups, resourcesByGV, failedGVs := SplitGroupsAndResources(test.agg)
|
||||
assert.Equal(t, test.expectedFailedGVs, failedGVs)
|
||||
assert.Equal(t, test.expectedGroups, *apiGroups)
|
||||
assert.Equal(t, test.expectedGVResources, resourcesByGV)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"k8s.io/client-go/openapi"
|
||||
cachedopenapi "k8s.io/client-go/openapi/cached"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
@@ -61,6 +62,15 @@ var (
|
||||
ErrCacheNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// Server returning empty ResourceList for Group/Version.
|
||||
type emptyResponseError struct {
|
||||
gv string
|
||||
}
|
||||
|
||||
func (e *emptyResponseError) Error() string {
|
||||
return fmt.Sprintf("received empty response for: %s", e.gv)
|
||||
}
|
||||
|
||||
var _ discovery.CachedDiscoveryInterface = &memCacheClient{}
|
||||
|
||||
// isTransientConnectionError checks whether given error is "Connection refused" or
|
||||
@@ -103,7 +113,13 @@ func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*m
|
||||
if cachedVal.err != nil && isTransientError(cachedVal.err) {
|
||||
r, err := d.serverResourcesForGroupVersion(groupVersion)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
|
||||
// Don't log "empty response" as an error; it is a common response for metrics.
|
||||
if _, emptyErr := err.(*emptyResponseError); emptyErr {
|
||||
// Log at same verbosity as disk cache.
|
||||
klog.V(3).Infof("%v", err)
|
||||
} else {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
|
||||
}
|
||||
}
|
||||
cachedVal = &cacheEntry{r, err}
|
||||
d.groupToServerResources[groupVersion] = cachedVal
|
||||
@@ -120,32 +136,38 @@ func (d *memCacheClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*meta
|
||||
// GroupsAndMaybeResources returns the list of APIGroups, and possibly the map of group/version
|
||||
// to resources. The returned groups will never be nil, but the resources map can be nil
|
||||
// if there are no cached resources.
|
||||
func (d *memCacheClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {
|
||||
func (d *memCacheClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
if !d.cacheValid {
|
||||
if err := d.refreshLocked(); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
// Build the resourceList from the cache?
|
||||
var resourcesMap map[schema.GroupVersion]*metav1.APIResourceList
|
||||
var failedGVs map[schema.GroupVersion]error
|
||||
if d.receivedAggregatedDiscovery && len(d.groupToServerResources) > 0 {
|
||||
resourcesMap = map[schema.GroupVersion]*metav1.APIResourceList{}
|
||||
failedGVs = map[schema.GroupVersion]error{}
|
||||
for gv, cacheEntry := range d.groupToServerResources {
|
||||
groupVersion, err := schema.ParseGroupVersion(gv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse group version (%v): %v", gv, err)
|
||||
return nil, nil, nil, fmt.Errorf("failed to parse group version (%v): %v", gv, err)
|
||||
}
|
||||
if cacheEntry.err != nil {
|
||||
failedGVs[groupVersion] = cacheEntry.err
|
||||
} else {
|
||||
resourcesMap[groupVersion] = cacheEntry.resourceList
|
||||
}
|
||||
resourcesMap[groupVersion] = cacheEntry.resourceList
|
||||
}
|
||||
}
|
||||
return d.groupList, resourcesMap, nil
|
||||
return d.groupList, resourcesMap, failedGVs, nil
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
groups, _, err := d.GroupsAndMaybeResources()
|
||||
groups, _, _, err := d.GroupsAndMaybeResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -219,7 +241,8 @@ func (d *memCacheClient) refreshLocked() error {
|
||||
|
||||
if ad, ok := d.delegate.(discovery.AggregatedDiscoveryInterface); ok {
|
||||
var resources map[schema.GroupVersion]*metav1.APIResourceList
|
||||
gl, resources, err = ad.GroupsAndMaybeResources()
|
||||
var failedGVs map[schema.GroupVersion]error
|
||||
gl, resources, failedGVs, err = ad.GroupsAndMaybeResources()
|
||||
if resources != nil && err == nil {
|
||||
// Cache the resources.
|
||||
d.groupToServerResources = map[string]*cacheEntry{}
|
||||
@@ -227,6 +250,10 @@ func (d *memCacheClient) refreshLocked() error {
|
||||
for gv, resources := range resources {
|
||||
d.groupToServerResources[gv.String()] = &cacheEntry{resources, nil}
|
||||
}
|
||||
// Cache GroupVersion discovery errors
|
||||
for gv, err := range failedGVs {
|
||||
d.groupToServerResources[gv.String()] = &cacheEntry{nil, err}
|
||||
}
|
||||
d.receivedAggregatedDiscovery = true
|
||||
d.cacheValid = true
|
||||
return nil
|
||||
@@ -252,7 +279,13 @@ func (d *memCacheClient) refreshLocked() error {
|
||||
|
||||
r, err := d.serverResourcesForGroupVersion(gv)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", gv, err))
|
||||
// Don't log "empty response" as an error; it is a common response for metrics.
|
||||
if _, emptyErr := err.(*emptyResponseError); emptyErr {
|
||||
// Log at same verbosity as disk cache.
|
||||
klog.V(3).Infof("%v", err)
|
||||
} else {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", gv, err))
|
||||
}
|
||||
}
|
||||
|
||||
resultLock.Lock()
|
||||
@@ -274,7 +307,7 @@ func (d *memCacheClient) serverResourcesForGroupVersion(groupVersion string) (*m
|
||||
return r, err
|
||||
}
|
||||
if len(r.APIResources) == 0 {
|
||||
return r, fmt.Errorf("Got empty response for: %v", groupVersion)
|
||||
return r, &emptyResponseError{gv: groupVersion}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
errorsutil "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/sets"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/discovery/fake"
|
||||
@@ -599,10 +600,11 @@ func TestMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
groupToServerResources: map[string]*cacheEntry{},
|
||||
}
|
||||
assert.False(t, memClient.Fresh())
|
||||
apiGroupList, resourcesMap, err := memClient.GroupsAndMaybeResources()
|
||||
apiGroupList, resourcesMap, failedGVs, err := memClient.GroupsAndMaybeResources()
|
||||
require.NoError(t, err)
|
||||
// "Unaggregated" discovery always returns nil for resources.
|
||||
assert.Nil(t, resourcesMap)
|
||||
assert.True(t, len(failedGVs) == 0, "expected empty failed GroupVersions, got (%d)", len(failedGVs))
|
||||
assert.False(t, memClient.receivedAggregatedDiscovery)
|
||||
assert.True(t, memClient.Fresh())
|
||||
// Test the expected groups are returned for the aggregated format.
|
||||
@@ -618,7 +620,7 @@ func TestMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
// Invalidate the cache and retrieve the server groups and resources again.
|
||||
memClient.Invalidate()
|
||||
assert.False(t, memClient.Fresh())
|
||||
apiGroupList, resourcesMap, err = memClient.GroupsAndMaybeResources()
|
||||
apiGroupList, resourcesMap, _, err = memClient.GroupsAndMaybeResources()
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, resourcesMap)
|
||||
assert.False(t, memClient.receivedAggregatedDiscovery)
|
||||
@@ -638,6 +640,7 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
expectedGroupNames []string
|
||||
expectedGroupVersions []string
|
||||
expectedGVKs []string
|
||||
expectedFailedGVs []string
|
||||
}{
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/1 resources at /apis",
|
||||
@@ -694,9 +697,10 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
"/v1/Pod",
|
||||
"apps/v1/Deployment",
|
||||
},
|
||||
expectedFailedGVs: []string{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis",
|
||||
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources/1 stale GV at /apis",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
@@ -741,6 +745,7 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
// Stale Version is not included in discovery.
|
||||
Version: "v2",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
@@ -753,18 +758,19 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGroupNames: []string{"", "apps"},
|
||||
expectedGroupVersions: []string{"v1", "apps/v1", "apps/v2"},
|
||||
expectedGroupVersions: []string{"v1", "apps/v1"},
|
||||
expectedGVKs: []string{
|
||||
"/v1/Pod",
|
||||
"apps/v1/Deployment",
|
||||
"apps/v2/Deployment",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v2"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis",
|
||||
@@ -841,9 +847,10 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
"apps/v1/Deployment",
|
||||
"apps/v1/StatefulSet",
|
||||
},
|
||||
expectedFailedGVs: []string{},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources at /apis",
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources/1 stale GV at /apis",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
@@ -905,6 +912,31 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Stale version is not included in discovery.
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "statefulsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -949,9 +981,10 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
"batch/v1/Job",
|
||||
"batch/v1/CronJob",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v1beta1"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources at /apis",
|
||||
name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources/2 stale GV at /apis",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{},
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
@@ -960,6 +993,7 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
// Statel "v1" Version is not included in discovery.
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
@@ -982,6 +1016,30 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
{
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "statefulsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1013,18 +1071,35 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Stale Version is not included in discovery.
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "jobs",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1beta1",
|
||||
Kind: "Job",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGroupNames: []string{"apps", "batch"},
|
||||
expectedGroupVersions: []string{"apps/v1", "batch/v1"},
|
||||
expectedGroupVersions: []string{"apps/v1beta1", "batch/v1"},
|
||||
expectedGVKs: []string{
|
||||
"apps/v1/Deployment",
|
||||
"apps/v1/StatefulSet",
|
||||
"apps/v1beta1/Deployment",
|
||||
"apps/v1beta1/StatefulSet",
|
||||
"batch/v1/Job",
|
||||
"batch/v1/CronJob",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v1", "batch/v1beta1"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1054,7 +1129,7 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
groupToServerResources: map[string]*cacheEntry{},
|
||||
}
|
||||
assert.False(t, memClient.Fresh())
|
||||
apiGroupList, resourcesMap, err := memClient.GroupsAndMaybeResources()
|
||||
apiGroupList, resourcesMap, failedGVs, err := memClient.GroupsAndMaybeResources()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, memClient.receivedAggregatedDiscovery)
|
||||
assert.True(t, memClient.Fresh())
|
||||
@@ -1077,10 +1152,15 @@ func TestAggregatedMemCacheGroupsAndMaybeResources(t *testing.T) {
|
||||
actualGVKs := sets.NewString(groupVersionKinds(resources)...)
|
||||
assert.True(t, expectedGVKs.Equal(actualGVKs),
|
||||
"%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List())
|
||||
// Test the returned failed GroupVersions are correct.
|
||||
expectedFailedGVs := sets.NewString(test.expectedFailedGVs...)
|
||||
actualFailedGVs := sets.NewString(failedGroupVersions(failedGVs)...)
|
||||
assert.True(t, expectedFailedGVs.Equal(actualFailedGVs),
|
||||
"%s: Expected Failed GroupVersions (%s), got (%s)", test.name, expectedFailedGVs.List(), actualFailedGVs.List())
|
||||
// Invalidate the cache and retrieve the server groups again.
|
||||
memClient.Invalidate()
|
||||
assert.False(t, memClient.Fresh())
|
||||
apiGroupList, _, err = memClient.GroupsAndMaybeResources()
|
||||
apiGroupList, _, _, err = memClient.GroupsAndMaybeResources()
|
||||
require.NoError(t, err)
|
||||
// Test the expected groups are returned for the aggregated format.
|
||||
actualGroupNames = sets.NewString(groupNamesFromList(apiGroupList)...)
|
||||
@@ -1410,3 +1490,11 @@ func groupVersionKinds(resources []*metav1.APIResourceList) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func failedGroupVersions(gvs map[schema.GroupVersion]error) []string {
|
||||
result := []string{}
|
||||
for gv := range gvs {
|
||||
result = append(result, gv.String())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
@@ -58,8 +59,9 @@ const (
|
||||
defaultBurst = 300
|
||||
|
||||
AcceptV1 = runtime.ContentTypeJSON
|
||||
// Aggregated discovery content-type (currently v2beta1). NOTE: Currently, we are assuming the order
|
||||
// for "g", "v", and "as" from the server. We can only compare this string if we can make that assumption.
|
||||
// Aggregated discovery content-type (v2beta1). NOTE: content-type parameters
|
||||
// MUST be ordered (g, v, as) for server in "Accept" header (BUT we are resilient
|
||||
// to ordering when comparing returned values in "Content-Type" header).
|
||||
AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"
|
||||
// Prioritize aggregated discovery by placing first in the order of discovery accept types.
|
||||
acceptDiscoveryFormats = AcceptV2Beta1 + "," + AcceptV1
|
||||
@@ -86,7 +88,7 @@ type DiscoveryInterface interface {
|
||||
type AggregatedDiscoveryInterface interface {
|
||||
DiscoveryInterface
|
||||
|
||||
GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error)
|
||||
GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error, error)
|
||||
}
|
||||
|
||||
// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness.
|
||||
@@ -186,18 +188,23 @@ func apiVersionsToAPIGroup(apiVersions *metav1.APIVersions) (apiGroup metav1.API
|
||||
// and resources from /api and /apis (either aggregated or not). Legacy groups
|
||||
// must be ordered first. The server will either return both endpoints (/api, /apis)
|
||||
// as aggregated discovery format or legacy format. For safety, resources will only
|
||||
// be returned if both endpoints returned resources.
|
||||
func (d *DiscoveryClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {
|
||||
// be returned if both endpoints returned resources. Returned "failedGVs" can be
|
||||
// empty, but will only be nil in the case an error is returned.
|
||||
func (d *DiscoveryClient) GroupsAndMaybeResources() (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
// Legacy group ordered first (there is only one -- core/v1 group). Returned groups must
|
||||
// be non-nil, but it could be empty. Returned resources, apiResources map could be nil.
|
||||
groups, resources, err := d.downloadLegacy()
|
||||
groups, resources, failedGVs, err := d.downloadLegacy()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// Discovery groups and (possibly) resources downloaded from /apis.
|
||||
apiGroups, apiResources, aerr := d.downloadAPIs()
|
||||
apiGroups, apiResources, failedApisGVs, aerr := d.downloadAPIs()
|
||||
if aerr != nil {
|
||||
return nil, nil, aerr
|
||||
return nil, nil, nil, aerr
|
||||
}
|
||||
// Merge apis groups into the legacy groups.
|
||||
for _, group := range apiGroups.Groups {
|
||||
@@ -211,14 +218,23 @@ func (d *DiscoveryClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[s
|
||||
} else if resources != nil {
|
||||
resources = nil
|
||||
}
|
||||
return groups, resources, err
|
||||
// Merge failed GroupVersions from /api and /apis
|
||||
for gv, err := range failedApisGVs {
|
||||
failedGVs[gv] = err
|
||||
}
|
||||
return groups, resources, failedGVs, err
|
||||
}
|
||||
|
||||
// downloadLegacy returns the discovery groups and possibly resources
|
||||
// for the legacy v1 GVR at /api, or an error if one occurred. It is
|
||||
// possible for the resource map to be nil if the server returned
|
||||
// the unaggregated discovery.
|
||||
func (d *DiscoveryClient) downloadLegacy() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {
|
||||
// the unaggregated discovery. Returned "failedGVs" can be empty, but
|
||||
// will only be nil in the case of a returned error.
|
||||
func (d *DiscoveryClient) downloadLegacy() (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
accept := acceptDiscoveryFormats
|
||||
if d.UseLegacyDiscovery {
|
||||
accept = AcceptV1
|
||||
@@ -230,48 +246,55 @@ func (d *DiscoveryClient) downloadLegacy() (*metav1.APIGroupList, map[schema.Gro
|
||||
Do(context.TODO()).
|
||||
ContentType(&responseContentType).
|
||||
Raw()
|
||||
// Special error handling for 403 or 404 to be compatible with older v1.0 servers.
|
||||
// Return empty group list to be merged with /apis.
|
||||
if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
|
||||
return &metav1.APIGroupList{}, nil, nil
|
||||
apiGroupList := &metav1.APIGroupList{}
|
||||
failedGVs := map[schema.GroupVersion]error{}
|
||||
if err != nil {
|
||||
// Tolerate 404, since aggregated api servers can return it.
|
||||
if errors.IsNotFound(err) {
|
||||
// Return empty structures and no error.
|
||||
emptyGVMap := map[schema.GroupVersion]*metav1.APIResourceList{}
|
||||
return apiGroupList, emptyGVMap, failedGVs, nil
|
||||
} else {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
apiGroupList := &metav1.APIGroupList{}
|
||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||
// Switch on content-type server responded with: aggregated or unaggregated.
|
||||
switch responseContentType {
|
||||
case AcceptV1:
|
||||
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)
|
||||
default:
|
||||
// Default is unaggregated discovery v1.
|
||||
var v metav1.APIVersions
|
||||
err = json.Unmarshal(body, &v)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
apiGroup := metav1.APIGroup{}
|
||||
if len(v.Versions) != 0 {
|
||||
apiGroup = apiVersionsToAPIGroup(&v)
|
||||
}
|
||||
apiGroupList.Groups = []metav1.APIGroup{apiGroup}
|
||||
case AcceptV2Beta1:
|
||||
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
||||
err = json.Unmarshal(body, &aggregatedDiscovery)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
apiGroupList, resourcesByGV = SplitGroupsAndResources(aggregatedDiscovery)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)
|
||||
}
|
||||
|
||||
return apiGroupList, resourcesByGV, nil
|
||||
return apiGroupList, resourcesByGV, failedGVs, nil
|
||||
}
|
||||
|
||||
// downloadAPIs returns the discovery groups and (if aggregated format) the
|
||||
// discovery resources. The returned groups will always exist, but the
|
||||
// resources map may be nil.
|
||||
func (d *DiscoveryClient) downloadAPIs() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {
|
||||
// resources map may be nil. Returned "failedGVs" can be empty, but will
|
||||
// only be nil in the case of a returned error.
|
||||
func (d *DiscoveryClient) downloadAPIs() (
|
||||
*metav1.APIGroupList,
|
||||
map[schema.GroupVersion]*metav1.APIResourceList,
|
||||
map[schema.GroupVersion]error,
|
||||
error) {
|
||||
accept := acceptDiscoveryFormats
|
||||
if d.UseLegacyDiscovery {
|
||||
accept = AcceptV1
|
||||
@@ -283,42 +306,59 @@ func (d *DiscoveryClient) downloadAPIs() (*metav1.APIGroupList, map[schema.Group
|
||||
Do(context.TODO()).
|
||||
ContentType(&responseContentType).
|
||||
Raw()
|
||||
// Special error handling for 403 or 404 to be compatible with older v1.0 servers.
|
||||
// Return empty group list to be merged with /api.
|
||||
if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
|
||||
return &metav1.APIGroupList{}, nil, nil
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
apiGroupList := &metav1.APIGroupList{}
|
||||
failedGVs := map[schema.GroupVersion]error{}
|
||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||
// Switch on content-type server responded with: aggregated or unaggregated.
|
||||
switch responseContentType {
|
||||
case AcceptV1:
|
||||
err = json.Unmarshal(body, apiGroupList)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case AcceptV2Beta1:
|
||||
switch {
|
||||
case isV2Beta1ContentType(responseContentType):
|
||||
var aggregatedDiscovery apidiscovery.APIGroupDiscoveryList
|
||||
err = json.Unmarshal(body, &aggregatedDiscovery)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
apiGroupList, resourcesByGV = SplitGroupsAndResources(aggregatedDiscovery)
|
||||
apiGroupList, resourcesByGV, failedGVs = SplitGroupsAndResources(aggregatedDiscovery)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)
|
||||
// Default is unaggregated discovery v1.
|
||||
err = json.Unmarshal(body, apiGroupList)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return apiGroupList, resourcesByGV, nil
|
||||
return apiGroupList, resourcesByGV, failedGVs, nil
|
||||
}
|
||||
|
||||
// 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" = 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
|
||||
}
|
||||
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
|
||||
// preferred version.
|
||||
func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
groups, _, err := d.GroupsAndMaybeResources()
|
||||
groups, _, _, err := d.GroupsAndMaybeResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -341,8 +381,10 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
|
||||
}
|
||||
err = d.restClient.Get().AbsPath(url.String()).Do(context.TODO()).Into(resources)
|
||||
if err != nil {
|
||||
// ignore 403 or 404 error to be compatible with an v1.0 server.
|
||||
if groupVersion == "v1" && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
|
||||
// Tolerate core/v1 not found response by returning empty resource list;
|
||||
// this probably should not happen. But we should verify all callers are
|
||||
// not depending on this toleration before removal.
|
||||
if groupVersion == "v1" && errors.IsNotFound(err) {
|
||||
return resources, nil
|
||||
}
|
||||
return nil, err
|
||||
@@ -383,13 +425,14 @@ func IsGroupDiscoveryFailedError(err error) bool {
|
||||
func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
var sgs *metav1.APIGroupList
|
||||
var resources []*metav1.APIResourceList
|
||||
var failedGVs map[schema.GroupVersion]error
|
||||
var err error
|
||||
|
||||
// If the passed discovery object implements the wider AggregatedDiscoveryInterface,
|
||||
// then attempt to retrieve aggregated discovery with both groups and the resources.
|
||||
if ad, ok := d.(AggregatedDiscoveryInterface); ok {
|
||||
var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
|
||||
sgs, resourcesByGV, err = ad.GroupsAndMaybeResources()
|
||||
sgs, resourcesByGV, failedGVs, err = ad.GroupsAndMaybeResources()
|
||||
for _, resourceList := range resourcesByGV {
|
||||
resources = append(resources, resourceList)
|
||||
}
|
||||
@@ -404,8 +447,15 @@ func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*meta
|
||||
for i := range sgs.Groups {
|
||||
resultGroups = append(resultGroups, &sgs.Groups[i])
|
||||
}
|
||||
// resources is non-nil if aggregated discovery succeeded.
|
||||
if resources != nil {
|
||||
return resultGroups, resources, nil
|
||||
// Any stale Group/Versions returned by aggregated discovery
|
||||
// must be surfaced to the caller as failed Group/Versions.
|
||||
var ferr error
|
||||
if len(failedGVs) > 0 {
|
||||
ferr = &ErrGroupDiscoveryFailed{Groups: failedGVs}
|
||||
}
|
||||
return resultGroups, resources, ferr
|
||||
}
|
||||
|
||||
groupVersionResources, failedGroups := fetchGroupVersionResources(d, sgs)
|
||||
@@ -436,16 +486,18 @@ func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList,
|
||||
var err error
|
||||
|
||||
// If the passed discovery object implements the wider AggregatedDiscoveryInterface,
|
||||
// then it is attempt to retrieve both the groups and the resources.
|
||||
// then it is attempt to retrieve both the groups and the resources. "failedGroups"
|
||||
// are Group/Versions returned as stale in AggregatedDiscovery format.
|
||||
ad, ok := d.(AggregatedDiscoveryInterface)
|
||||
if ok {
|
||||
serverGroupList, groupVersionResources, err = ad.GroupsAndMaybeResources()
|
||||
serverGroupList, groupVersionResources, failedGroups, err = ad.GroupsAndMaybeResources()
|
||||
} else {
|
||||
serverGroupList, err = d.ServerGroups()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Non-aggregated discovery must fetch resources from Groups.
|
||||
if groupVersionResources == nil {
|
||||
groupVersionResources, failedGroups = fetchGroupVersionResources(d, serverGroupList)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,6 @@ func TestGetServerGroupsWithV1Server(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerGroups should not return an error even if server returns error at /api and /apis
|
||||
apiGroupList, err := client.ServerGroups()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -121,32 +120,49 @@ func TestGetServerGroupsWithV1Server(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerGroupsWithBrokenServer(t *testing.T) {
|
||||
for _, statusCode := range []int{http.StatusNotFound, http.StatusForbidden} {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(statusCode)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerGroups should not return an error even if server returns Not Found or Forbidden error at all end points
|
||||
apiGroupList, err := client.ServerGroups()
|
||||
func TestDiscoveryToleratesMissingCoreGroup(t *testing.T) {
|
||||
// Discovery tolerates 404 from /api. Aggregated api servers can do this.
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var obj interface{}
|
||||
switch req.URL.Path {
|
||||
case "/api":
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
case "/apis":
|
||||
obj = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "extensions",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "extensions/v1beta1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
groupVersions := metav1.ExtractGroupVersions(apiGroupList)
|
||||
if len(groupVersions) != 0 {
|
||||
t.Errorf("expected empty list, got: %q", groupVersions)
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerGroups should not return an error even if server returns 404 at /api.
|
||||
apiGroupList, err := client.ServerGroups()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
groupVersions := metav1.ExtractGroupVersions(apiGroupList)
|
||||
if !reflect.DeepEqual(groupVersions, []string{"extensions/v1beta1"}) {
|
||||
t.Errorf("expected: %q, got: %q", []string{"extensions/v1beta1"}, groupVersions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutIsSet(t *testing.T) {
|
||||
cfg := &restclient.Config{}
|
||||
setDiscoveryDefaults(cfg)
|
||||
assert.Equal(t, defaultTimeout, cfg.Timeout)
|
||||
}
|
||||
|
||||
func TestGetServerResourcesWithV1Server(t *testing.T) {
|
||||
func TestDiscoveryFailsWhenNonCoreGroupsMissing(t *testing.T) {
|
||||
// Discovery fails when /apis returns 404.
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var obj interface{}
|
||||
switch req.URL.Path {
|
||||
@@ -156,13 +172,12 @@ func TestGetServerResourcesWithV1Server(t *testing.T) {
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
default:
|
||||
case "/apis":
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected encoding error: %v", err)
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -171,17 +186,34 @@ func TestGetServerResourcesWithV1Server(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerResources should not return an error even if server returns error at /api/v1.
|
||||
_, serverResources, err := client.ServerGroupsAndResources()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
_, err := client.ServerGroups()
|
||||
if err == nil {
|
||||
t.Fatal("expected error, received none")
|
||||
}
|
||||
gvs := groupVersions(serverResources)
|
||||
if !sets.NewString(gvs...).Has("v1") {
|
||||
t.Errorf("missing v1 in resource list: %v", serverResources)
|
||||
}
|
||||
|
||||
func TestGetServerGroupsWithBrokenServer(t *testing.T) {
|
||||
// 404 Not Found errors because discovery at /apis returns an error.
|
||||
// 403 Forbidden errors because discovery at both /api and /apis returns error.
|
||||
for _, statusCode := range []int{http.StatusNotFound, http.StatusForbidden} {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(statusCode)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
_, err := client.ServerGroups()
|
||||
if err == nil {
|
||||
t.Fatal("expected error, received none")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutIsSet(t *testing.T) {
|
||||
cfg := &restclient.Config{}
|
||||
setDiscoveryDefaults(cfg)
|
||||
assert.Equal(t, defaultTimeout, cfg.Timeout)
|
||||
}
|
||||
|
||||
func TestGetServerResourcesForGroupVersion(t *testing.T) {
|
||||
stable := metav1.APIResourceList{
|
||||
GroupVersion: "v1",
|
||||
@@ -964,17 +996,33 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
expected map[schema.GroupVersionResource]struct{}
|
||||
}{
|
||||
{
|
||||
// Combines discovery for /api and /apis.
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/api/v1":
|
||||
list = &stable
|
||||
case "/api":
|
||||
list = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
case "/api/v1":
|
||||
list = &stable
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "batch",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "batch/v1", Version: "v1"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v1", Version: "v1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
case "/apis/batch/v1":
|
||||
list = &batchv1
|
||||
|
||||
default:
|
||||
t.Logf("unexpected request: %s", req.URL.Path)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -990,11 +1038,14 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
w.Write(output)
|
||||
},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "", Version: "v1", Resource: "pods"}: {},
|
||||
{Group: "", Version: "v1", Resource: "services"}: {},
|
||||
{Group: "", Version: "v1", Resource: "pods"}: {},
|
||||
{Group: "", Version: "v1", Resource: "services"}: {},
|
||||
{Group: "batch", Version: "v1", Resource: "jobs"}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Only return /apis (not legacy /api); does not error. 404 for legacy
|
||||
// core/v1 at /api is tolerated.
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
@@ -1349,8 +1400,9 @@ func TestAggregatedServerGroups(t *testing.T) {
|
||||
}
|
||||
output, err := json.Marshal(agg)
|
||||
require.NoError(t, err)
|
||||
// Content-type is "aggregated" discovery format.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
||||
// Content-Type is "aggregated" discovery format. Add extra parameter
|
||||
// to ensure we are resilient to these extra parameters.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
@@ -1384,6 +1436,7 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
expectedGroupNames []string
|
||||
expectedGroupVersions []string
|
||||
expectedGVKs []string
|
||||
expectedFailedGVs []string
|
||||
}{
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/1 resources at /apis",
|
||||
@@ -1512,6 +1565,78 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
"apps/v2/Deployment",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/1 resources at /api, 1 group/2 versions/1 resources at /apis",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "pods",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Version: "v2",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v2",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGroupNames: []string{"", "apps"},
|
||||
expectedGroupVersions: []string{"v1", "apps/v1"},
|
||||
expectedGVKs: []string{
|
||||
"/v1/Pod",
|
||||
"apps/v1/Deployment",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v2"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 1 group/2 resources at /apis",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
@@ -1552,6 +1677,31 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
// Stale "v2" version not included.
|
||||
{
|
||||
Version: "v2",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v2",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "statefulsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v2",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
@@ -1587,9 +1737,10 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
"apps/v1/Deployment",
|
||||
"apps/v1/StatefulSet",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v2"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources at /apis",
|
||||
name: "Aggregated discovery: 1 group/2 resources at /api, 2 group/2 resources/1 stale GV at /apis",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
@@ -1658,6 +1809,7 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
Name: "batch",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
// Stale Group/Version is not included
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
@@ -1680,21 +1832,46 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
{
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "jobs",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1beta1",
|
||||
Kind: "Job",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "cronjobs",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1beta1",
|
||||
Kind: "CronJob",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedGroupNames: []string{"", "apps", "batch"},
|
||||
expectedGroupVersions: []string{"v1", "apps/v1", "batch/v1"},
|
||||
expectedGroupVersions: []string{"v1", "apps/v1", "batch/v1beta1"},
|
||||
expectedGVKs: []string{
|
||||
"/v1/Pod",
|
||||
"/v1/Service",
|
||||
"apps/v1/Deployment",
|
||||
"apps/v1/StatefulSet",
|
||||
"batch/v1/Job",
|
||||
"batch/v1/CronJob",
|
||||
"batch/v1beta1/Job",
|
||||
"batch/v1beta1/CronJob",
|
||||
},
|
||||
expectedFailedGVs: []string{"batch/v1"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: /api returns nothing, 2 groups/2 resources at /apis",
|
||||
@@ -1759,6 +1936,31 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Stale "v1beta1" not included.
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "jobs",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1beta1",
|
||||
Kind: "Job",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "cronjobs",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "batch",
|
||||
Version: "v1beta1",
|
||||
Kind: "CronJob",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1771,6 +1973,7 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
"batch/v1/Job",
|
||||
"batch/v1/CronJob",
|
||||
},
|
||||
expectedFailedGVs: []string{"batch/v1beta1"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1788,15 +1991,24 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
}
|
||||
output, err := json.Marshal(agg)
|
||||
require.NoError(t, err)
|
||||
// Content-type is "aggregated" discovery format.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
||||
// Content-type is "aggregated" discovery format. Add extra parameter
|
||||
// to ensure we are resilient to these extra parameters.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
apiGroups, resources, err := client.ServerGroupsAndResources()
|
||||
require.NoError(t, err)
|
||||
if len(test.expectedFailedGVs) > 0 {
|
||||
require.Error(t, err)
|
||||
expectedFailedGVs := sets.NewString(test.expectedFailedGVs...)
|
||||
actualFailedGVs := sets.NewString(failedGroupVersions(err)...)
|
||||
assert.True(t, expectedFailedGVs.Equal(actualFailedGVs),
|
||||
"%s: Expected Failed GVs (%s), got (%s)", test.name, expectedFailedGVs, actualFailedGVs)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// Test the expected groups are returned for the aggregated format.
|
||||
expectedGroupNames := sets.NewString(test.expectedGroupNames...)
|
||||
actualGroupNames := sets.NewString(groupNames(apiGroups)...)
|
||||
@@ -1823,12 +2035,139 @@ func TestAggregatedServerGroupsAndResources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregatedServerGroupsAndResourcesWithErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
corev1 *apidiscovery.APIGroupDiscoveryList
|
||||
coreHttpStatus int
|
||||
apis *apidiscovery.APIGroupDiscoveryList
|
||||
apisHttpStatus int
|
||||
expectedGroups []string
|
||||
expectedResources []string
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "Aggregated Discovery: 404 for core/v1 is tolerated",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{},
|
||||
coreHttpStatus: http.StatusNotFound,
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "daemonsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "DaemonSet",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apisHttpStatus: http.StatusOK,
|
||||
expectedGroups: []string{"apps"},
|
||||
expectedResources: []string{"apps/v1/Deployment", "apps/v1/DaemonSet"},
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "Aggregated Discovery: 403 for core/v1 causes error",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{},
|
||||
coreHttpStatus: http.StatusForbidden,
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{},
|
||||
apisHttpStatus: http.StatusOK,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "Aggregated Discovery: 404 for /apis causes error",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{},
|
||||
coreHttpStatus: http.StatusOK,
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{},
|
||||
apisHttpStatus: http.StatusNotFound,
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "Aggregated Discovery: 403 for /apis causes error",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{},
|
||||
coreHttpStatus: http.StatusOK,
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{},
|
||||
apisHttpStatus: http.StatusForbidden,
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var agg *apidiscovery.APIGroupDiscoveryList
|
||||
var status int
|
||||
switch req.URL.Path {
|
||||
case "/api":
|
||||
agg = test.corev1
|
||||
status = test.coreHttpStatus
|
||||
case "/apis":
|
||||
agg = test.apis
|
||||
status = test.apisHttpStatus
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
output, err := json.Marshal(agg)
|
||||
require.NoError(t, err)
|
||||
// Content-type is "aggregated" discovery format. Add extra parameter
|
||||
// to ensure we are resilient to these extra parameters.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
w.Write(output)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
apiGroups, resources, err := client.ServerGroupsAndResources()
|
||||
if test.expectedErr {
|
||||
require.Error(t, err)
|
||||
require.Nil(t, apiGroups)
|
||||
require.Nil(t, resources)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
// First check the returned groups
|
||||
expectedGroups := sets.NewString(test.expectedGroups...)
|
||||
actualGroups := sets.NewString(groupNames(apiGroups)...)
|
||||
assert.True(t, expectedGroups.Equal(actualGroups),
|
||||
"%s: Expected GVKs (%s), got (%s)", test.name, expectedGroups.List(), actualGroups.List())
|
||||
// Next check the returned resources
|
||||
expectedGVKs := sets.NewString(test.expectedResources...)
|
||||
actualGVKs := sets.NewString(groupVersionKinds(resources)...)
|
||||
assert.True(t, expectedGVKs.Equal(actualGVKs),
|
||||
"%s: Expected GVKs (%s), got (%s)", test.name, expectedGVKs.List(), actualGVKs.List())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
corev1 *apidiscovery.APIGroupDiscoveryList
|
||||
apis *apidiscovery.APIGroupDiscoveryList
|
||||
expectedGVKs []string
|
||||
name string
|
||||
corev1 *apidiscovery.APIGroupDiscoveryList
|
||||
apis *apidiscovery.APIGroupDiscoveryList
|
||||
expectedGVKs []string
|
||||
expectedFailedGVs []string
|
||||
}{
|
||||
{
|
||||
name: "Aggregated discovery: basic corev1 and apps/v1 preferred resources returned",
|
||||
@@ -1954,6 +2293,78 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
"apps/v2/Deployment",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: stale Group/Version can not produce preferred version",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "pods",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apis: &apidiscovery.APIGroupDiscoveryList{
|
||||
Items: []apidiscovery.APIGroupDiscovery{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apps",
|
||||
},
|
||||
Versions: []apidiscovery.APIVersionDiscovery{
|
||||
// v2 is "stale", so it can not be "preferred".
|
||||
{
|
||||
Version: "v2",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v2",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
{
|
||||
Version: "v1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Only v1 resources from apps group; v2 would be preferred but it is "stale".
|
||||
expectedGVKs: []string{
|
||||
"/v1/Pod",
|
||||
"apps/v1/Deployment",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v2"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: preferred multiple resources from multiple group/versions",
|
||||
corev1: &apidiscovery.APIGroupDiscoveryList{
|
||||
@@ -2017,6 +2428,30 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "statefulsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
Freshness: apidiscovery.DiscoveryFreshnessStale,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2027,6 +2462,7 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
"apps/v1/Deployment",
|
||||
"apps/v1/StatefulSet",
|
||||
},
|
||||
expectedFailedGVs: []string{"apps/v1beta1"},
|
||||
},
|
||||
{
|
||||
name: "Aggregated discovery: resources from multiple preferred group versions at /apis",
|
||||
@@ -2091,6 +2527,30 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Not included because "v1" is preferred.
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
Resource: "deployments",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
{
|
||||
Resource: "statefulsets",
|
||||
ResponseKind: &metav1.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1beta1",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
Scope: apidiscovery.ScopeNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -2228,6 +2688,7 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
// Not included, since "v1" is preferred.
|
||||
Version: "v1beta1",
|
||||
Resources: []apidiscovery.APIResourceDiscovery{
|
||||
{
|
||||
@@ -2280,15 +2741,24 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
}
|
||||
output, err := json.Marshal(agg)
|
||||
require.NoError(t, err)
|
||||
// Content-type is "aggregated" discovery format.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1)
|
||||
// Content-type is "aggregated" discovery format. Add extra parameter
|
||||
// to ensure we are resilient to these extra parameters.
|
||||
w.Header().Set("Content-Type", AcceptV2Beta1+"; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
resources, err := client.ServerPreferredResources()
|
||||
require.NoError(t, err)
|
||||
if len(test.expectedFailedGVs) > 0 {
|
||||
require.Error(t, err)
|
||||
expectedFailedGVs := sets.NewString(test.expectedFailedGVs...)
|
||||
actualFailedGVs := sets.NewString(failedGroupVersions(err)...)
|
||||
assert.True(t, expectedFailedGVs.Equal(actualFailedGVs),
|
||||
"%s: Expected Failed GVs (%s), got (%s)", test.name, expectedFailedGVs, actualFailedGVs)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// Test the expected preferred GVKs are returned from the aggregated discovery.
|
||||
expectedGVKs := sets.NewString(test.expectedGVKs...)
|
||||
actualGVKs := sets.NewString(groupVersionKinds(resources)...)
|
||||
@@ -2297,6 +2767,58 @@ func TestAggregatedServerPreferredResources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoveryContentTypeVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
contentType string
|
||||
isV2Beta1 bool
|
||||
}{
|
||||
{
|
||||
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList",
|
||||
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",
|
||||
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",
|
||||
isV2Beta1: true,
|
||||
},
|
||||
{
|
||||
// Ignores extra parameter "charset=utf-8"
|
||||
contentType: "application/json; g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList;charset=utf-8",
|
||||
isV2Beta1: true,
|
||||
},
|
||||
{
|
||||
contentType: "application/json",
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "application/json; charset=UTF-8",
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "text/json",
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "text/html",
|
||||
isV2Beta1: false,
|
||||
},
|
||||
{
|
||||
contentType: "",
|
||||
isV2Beta1: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
isV2Beta1 := isV2Beta1ContentType(test.contentType)
|
||||
assert.Equal(t, test.isV2Beta1, isV2Beta1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseLegacyDiscovery(t *testing.T) {
|
||||
// Default client sends aggregated discovery accept format (first) as well as legacy format.
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -2370,3 +2892,15 @@ func groupVersionKinds(resources []*metav1.APIResourceList) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func failedGroupVersions(err error) []string {
|
||||
result := []string{}
|
||||
ferr, ok := err.(*ErrGroupDiscoveryFailed)
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
for gv := range ferr.Groups {
|
||||
result = append(result, gv.String())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -65,6 +65,22 @@ func TestServerSupportsVersion(t *testing.T) {
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) },
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "Status 403 Forbidden for core/v1 group returns error and is unsupported",
|
||||
requiredVersion: schema.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"/version1", v1.SchemeGroupVersion.String()},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), "unknown") },
|
||||
statusCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "Status 404 Not Found for core/v1 group returns empty and is unsupported",
|
||||
requiredVersion: schema.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"/version1", v1.SchemeGroupVersion.String()},
|
||||
expectErr: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "server could not find the requested resource")
|
||||
},
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "connection refused error",
|
||||
serverVersions: []string{"version1"},
|
||||
@@ -72,11 +88,6 @@ func TestServerSupportsVersion(t *testing.T) {
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), "connection refused") },
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "discovery fails due to 404 Not Found errors and thus serverVersions is empty, use requested GroupVersion",
|
||||
requiredVersion: schema.GroupVersion{Version: "version1"},
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
29
go.mod
29
go.mod
@@ -9,23 +9,23 @@ require (
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/golang/protobuf v1.5.4
|
||||
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/uuid v1.1.2
|
||||
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.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
|
||||
golang.org/x/term v0.5.0
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
||||
google.golang.org/protobuf v1.28.1
|
||||
k8s.io/api v0.0.0-20230215103918-5fd8a44fa3de
|
||||
k8s.io/apimachinery v0.0.0-20230215102121-53ecdf01b997
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/term v0.13.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
k8s.io/api v0.0.0-20240307174302-4143d6bf2723
|
||||
k8s.io/apimachinery v0.0.0-20240307172550-48b9387ff76a
|
||||
k8s.io/klog/v2 v2.80.1
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d
|
||||
@@ -49,16 +49,11 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.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
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230215103918-5fd8a44fa3de
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230215102121-53ecdf01b997
|
||||
)
|
||||
|
||||
317
go.sum
317
go.sum
@@ -1,45 +1,8 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -48,15 +11,10 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
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=
|
||||
@@ -71,36 +29,22 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/
|
||||
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=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
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=
|
||||
@@ -109,43 +53,23 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -178,287 +102,104 @@ 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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -470,25 +211,17 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.0.0-20230215103918-5fd8a44fa3de h1:qSZiw9Qed4asmzSO2BUK7AC+ZvLJYvnJMPzYyXcg8Gw=
|
||||
k8s.io/api v0.0.0-20230215103918-5fd8a44fa3de/go.mod h1:hf9uiKf2s1FXvyGkg/nAkwMb35yQaTZrvdCtquvBpAE=
|
||||
k8s.io/apimachinery v0.0.0-20230215102121-53ecdf01b997 h1:ZbxFKMGLvUrVMRxcfkGr9rVsPCERc+NhrdqpGY2XaCo=
|
||||
k8s.io/apimachinery v0.0.0-20230215102121-53ecdf01b997/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
||||
k8s.io/api v0.0.0-20240307174302-4143d6bf2723 h1:1t7OvP8PBn6zTSEufGLW2Up0bWzKji0nruUg+Zp6ugI=
|
||||
k8s.io/api v0.0.0-20240307174302-4143d6bf2723/go.mod h1:u1xz9wvtKoENxBxHuLCmlKKSzWQ109/rNmrAcpAIuDs=
|
||||
k8s.io/apimachinery v0.0.0-20240307172550-48b9387ff76a h1:X5uUiPni3SqlQhT9RxdXOrg2iuJaK9ehBCWwbkbhCGM=
|
||||
k8s.io/apimachinery v0.0.0-20240307172550-48b9387ff76a/go.mod h1:O/uIhIOWuy6ndHqQ6qbkjD7OgeMhVtlk8+Z66ZcmJQc=
|
||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
|
||||
@@ -19,6 +19,7 @@ package openapi
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
@@ -58,7 +59,11 @@ func (c *client) Paths() (map[string]GroupVersion, error) {
|
||||
// Create GroupVersions for each element of the result
|
||||
result := map[string]GroupVersion{}
|
||||
for k, v := range discoMap.Paths {
|
||||
result[k] = newGroupVersion(c, v)
|
||||
// If the server returned a URL rooted at /openapi/v3, preserve any additional client-side prefix.
|
||||
// If the server returned a URL not rooted at /openapi/v3, treat it as an actual server-relative URL.
|
||||
// See https://github.com/kubernetes/kubernetes/issues/117463 for details
|
||||
useClientPrefix := strings.HasPrefix(v.ServerRelativeURL, "/openapi/v3")
|
||||
result[k] = newGroupVersion(c, v, useClientPrefix)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package openapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
)
|
||||
@@ -29,18 +30,41 @@ type GroupVersion interface {
|
||||
}
|
||||
|
||||
type groupversion struct {
|
||||
client *client
|
||||
item handler3.OpenAPIV3DiscoveryGroupVersion
|
||||
client *client
|
||||
item handler3.OpenAPIV3DiscoveryGroupVersion
|
||||
useClientPrefix bool
|
||||
}
|
||||
|
||||
func newGroupVersion(client *client, item handler3.OpenAPIV3DiscoveryGroupVersion) *groupversion {
|
||||
return &groupversion{client: client, item: item}
|
||||
func newGroupVersion(client *client, item handler3.OpenAPIV3DiscoveryGroupVersion, useClientPrefix bool) *groupversion {
|
||||
return &groupversion{client: client, item: item, useClientPrefix: useClientPrefix}
|
||||
}
|
||||
|
||||
func (g *groupversion) Schema(contentType string) ([]byte, error) {
|
||||
return g.client.restClient.Get().
|
||||
RequestURI(g.item.ServerRelativeURL).
|
||||
SetHeader("Accept", contentType).
|
||||
Do(context.TODO()).
|
||||
Raw()
|
||||
if !g.useClientPrefix {
|
||||
return g.client.restClient.Get().
|
||||
RequestURI(g.item.ServerRelativeURL).
|
||||
SetHeader("Accept", contentType).
|
||||
Do(context.TODO()).
|
||||
Raw()
|
||||
}
|
||||
|
||||
locator, err := url.Parse(g.item.ServerRelativeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := g.client.restClient.Get().
|
||||
AbsPath(locator.Path).
|
||||
SetHeader("Accept", contentType)
|
||||
|
||||
// Other than root endpoints(openapiv3/apis), resources have hash query parameter to support etags.
|
||||
// However, absPath does not support handling query parameters internally,
|
||||
// so that hash query parameter is added manually
|
||||
for k, value := range locator.Query() {
|
||||
for _, v := range value {
|
||||
path.Param(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return path.Do(context.TODO()).Raw()
|
||||
}
|
||||
|
||||
106
openapi/groupversion_test.go
Normal file
106
openapi/groupversion_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func TestGroupVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prefix string
|
||||
serverReturnsPrefix bool
|
||||
}{
|
||||
{
|
||||
name: "no prefix",
|
||||
prefix: "",
|
||||
serverReturnsPrefix: false,
|
||||
},
|
||||
{
|
||||
name: "prefix not in discovery",
|
||||
prefix: "/test-endpoint",
|
||||
serverReturnsPrefix: false,
|
||||
},
|
||||
{
|
||||
name: "prefix in discovery",
|
||||
prefix: "/test-endpoint",
|
||||
serverReturnsPrefix: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.URL.Path == test.prefix+"/openapi/v3/apis/apps/v1" && r.URL.RawQuery == "hash=014fbff9a07c":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"openapi":"3.0.0","info":{"title":"Kubernetes","version":"unversioned"}}`))
|
||||
case r.URL.Path == test.prefix+"/openapi/v3":
|
||||
// return root content
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if test.serverReturnsPrefix {
|
||||
w.Write([]byte(fmt.Sprintf(`{"paths":{"apis/apps/v1":{"serverRelativeURL":"%s/openapi/v3/apis/apps/v1?hash=014fbff9a07c"}}}`, test.prefix)))
|
||||
} else {
|
||||
w.Write([]byte(`{"paths":{"apis/apps/v1":{"serverRelativeURL":"/openapi/v3/apis/apps/v1?hash=014fbff9a07c"}}}`))
|
||||
}
|
||||
default:
|
||||
t.Errorf("unexpected request: %s", r.URL.String())
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c, err := rest.RESTClientFor(&rest.Config{
|
||||
Host: server.URL + test.prefix,
|
||||
ContentConfig: rest.ContentConfig{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
GroupVersion: &appsv1.SchemeGroupVersion,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error occurred: %v", err)
|
||||
}
|
||||
|
||||
openapiClient := NewClient(c)
|
||||
paths, err := openapiClient.Paths()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error occurred: %v", err)
|
||||
}
|
||||
schema, err := paths["apis/apps/v1"].Schema(runtime.ContentTypeJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error occurred: %v", err)
|
||||
}
|
||||
expectedResult := `{"openapi":"3.0.0","info":{"title":"Kubernetes","version":"unversioned"}}`
|
||||
if string(schema) != expectedResult {
|
||||
t.Fatalf("unexpected result actual: %s expected: %s", string(schema), expectedResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
22
tools/cache/controller.go
vendored
22
tools/cache/controller.go
vendored
@@ -353,17 +353,6 @@ func NewIndexerInformer(
|
||||
return clientState, newInformer(lw, objType, resyncPeriod, h, clientState, nil)
|
||||
}
|
||||
|
||||
// TransformFunc allows for transforming an object before it will be processed
|
||||
// and put into the controller cache and before the corresponding handlers will
|
||||
// be called on it.
|
||||
// TransformFunc (similarly to ResourceEventHandler functions) should be able
|
||||
// to correctly handle the tombstone of type cache.DeletedFinalStateUnknown
|
||||
//
|
||||
// The most common usage pattern is to clean-up some parts of the object to
|
||||
// reduce component memory usage if a given component doesn't care about them.
|
||||
// given controller doesn't care for them
|
||||
type TransformFunc func(interface{}) (interface{}, error)
|
||||
|
||||
// NewTransformingInformer returns a Store and a controller for populating
|
||||
// the store while also providing event notifications. You should only used
|
||||
// the returned Store for Get/List operations; Add/Modify/Deletes will cause
|
||||
@@ -411,19 +400,11 @@ func processDeltas(
|
||||
// Object which receives event notifications from the given deltas
|
||||
handler ResourceEventHandler,
|
||||
clientState Store,
|
||||
transformer TransformFunc,
|
||||
deltas Deltas,
|
||||
) error {
|
||||
// from oldest to newest
|
||||
for _, d := range deltas {
|
||||
obj := d.Object
|
||||
if transformer != nil {
|
||||
var err error
|
||||
obj, err = transformer(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch d.Type {
|
||||
case Sync, Replaced, Added, Updated:
|
||||
@@ -475,6 +456,7 @@ func newInformer(
|
||||
fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: clientState,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
Transformer: transformer,
|
||||
})
|
||||
|
||||
cfg := &Config{
|
||||
@@ -486,7 +468,7 @@ func newInformer(
|
||||
|
||||
Process: func(obj interface{}) error {
|
||||
if deltas, ok := obj.(Deltas); ok {
|
||||
return processDeltas(h, clientState, transformer, deltas)
|
||||
return processDeltas(h, clientState, deltas)
|
||||
}
|
||||
return errors.New("object given as Process argument is not Deltas")
|
||||
},
|
||||
|
||||
4
tools/cache/controller_test.go
vendored
4
tools/cache/controller_test.go
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
fcache "k8s.io/client-go/tools/cache/testing"
|
||||
|
||||
"github.com/google/gofuzz"
|
||||
fuzz "github.com/google/gofuzz"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
|
||||
135
tools/cache/delta_fifo.go
vendored
135
tools/cache/delta_fifo.go
vendored
@@ -51,6 +51,10 @@ type DeltaFIFOOptions struct {
|
||||
// When true, `Replaced` events will be sent for items passed to a Replace() call.
|
||||
// When false, `Sync` events will be sent instead.
|
||||
EmitDeltaTypeReplaced bool
|
||||
|
||||
// If set, will be called for objects before enqueueing them. Please
|
||||
// see the comment on TransformFunc for details.
|
||||
Transformer TransformFunc
|
||||
}
|
||||
|
||||
// DeltaFIFO is like FIFO, but differs in two ways. One is that the
|
||||
@@ -129,8 +133,32 @@ type DeltaFIFO struct {
|
||||
// emitDeltaTypeReplaced is whether to emit the Replaced or Sync
|
||||
// DeltaType when Replace() is called (to preserve backwards compat).
|
||||
emitDeltaTypeReplaced bool
|
||||
|
||||
// Called with every object if non-nil.
|
||||
transformer TransformFunc
|
||||
}
|
||||
|
||||
// TransformFunc allows for transforming an object before it will be processed.
|
||||
// TransformFunc (similarly to ResourceEventHandler functions) should be able
|
||||
// to correctly handle the tombstone of type cache.DeletedFinalStateUnknown.
|
||||
//
|
||||
// New in v1.27: In such cases, the contained object will already have gone
|
||||
// through the transform object separately (when it was added / updated prior
|
||||
// to the delete), so the TransformFunc can likely safely ignore such objects
|
||||
// (i.e., just return the input object).
|
||||
//
|
||||
// The most common usage pattern is to clean-up some parts of the object to
|
||||
// reduce component memory usage if a given component doesn't care about them.
|
||||
//
|
||||
// New in v1.27: unless the object is a DeletedFinalStateUnknown, TransformFunc
|
||||
// sees the object before any other actor, and it is now safe to mutate the
|
||||
// object in place instead of making a copy.
|
||||
//
|
||||
// Note that TransformFunc is called while inserting objects into the
|
||||
// notification queue and is therefore extremely performance sensitive; please
|
||||
// do not do anything that will take a long time.
|
||||
type TransformFunc func(interface{}) (interface{}, error)
|
||||
|
||||
// DeltaType is the type of a change (addition, deletion, etc)
|
||||
type DeltaType string
|
||||
|
||||
@@ -227,6 +255,7 @@ func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO {
|
||||
knownObjects: opts.KnownObjects,
|
||||
|
||||
emitDeltaTypeReplaced: opts.EmitDeltaTypeReplaced,
|
||||
transformer: opts.Transformer,
|
||||
}
|
||||
f.cond.L = &f.lock
|
||||
return f
|
||||
@@ -411,6 +440,21 @@ func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) err
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
|
||||
// Every object comes through this code path once, so this is a good
|
||||
// place to call the transform func. If obj is a
|
||||
// DeletedFinalStateUnknown tombstone, then the containted inner object
|
||||
// will already have gone through the transformer, but we document that
|
||||
// this can happen. In cases involving Replace(), such an object can
|
||||
// come through multiple times.
|
||||
if f.transformer != nil {
|
||||
var err error
|
||||
obj, err = f.transformer(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
oldDeltas := f.items[id]
|
||||
newDeltas := append(oldDeltas, Delta{actionType, obj})
|
||||
newDeltas = dedupDeltas(newDeltas)
|
||||
@@ -566,12 +610,11 @@ func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {
|
||||
// using the Sync or Replace DeltaType and then (2) it does some deletions.
|
||||
// In particular: for every pre-existing key K that is not the key of
|
||||
// an object in `list` there is the effect of
|
||||
// `Delete(DeletedFinalStateUnknown{K, O})` where O is current object
|
||||
// of K. If `f.knownObjects == nil` then the pre-existing keys are
|
||||
// those in `f.items` and the current object of K is the `.Newest()`
|
||||
// of the Deltas associated with K. Otherwise the pre-existing keys
|
||||
// are those listed by `f.knownObjects` and the current object of K is
|
||||
// what `f.knownObjects.GetByKey(K)` returns.
|
||||
// `Delete(DeletedFinalStateUnknown{K, O})` where O is the latest known
|
||||
// object of K. The pre-existing keys are those in the union set of the keys in
|
||||
// `f.items` and `f.knownObjects` (if not nil). The last known object for key K is
|
||||
// the one present in the last delta in `f.items`. If there is no delta for K
|
||||
// in `f.items`, it is the object in `f.knownObjects`
|
||||
func (f *DeltaFIFO) Replace(list []interface{}, _ string) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
@@ -595,56 +638,54 @@ func (f *DeltaFIFO) Replace(list []interface{}, _ string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if f.knownObjects == nil {
|
||||
// Do deletion detection against our own list.
|
||||
queuedDeletions := 0
|
||||
for k, oldItem := range f.items {
|
||||
// Do deletion detection against objects in the queue
|
||||
queuedDeletions := 0
|
||||
for k, oldItem := range f.items {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
// Delete pre-existing items not in the new list.
|
||||
// This could happen if watch deletion event was missed while
|
||||
// disconnected from apiserver.
|
||||
var deletedObj interface{}
|
||||
if n := oldItem.Newest(); n != nil {
|
||||
deletedObj = n.Object
|
||||
|
||||
// if the previous object is a DeletedFinalStateUnknown, we have to extract the actual Object
|
||||
if d, ok := deletedObj.(DeletedFinalStateUnknown); ok {
|
||||
deletedObj = d.Obj
|
||||
}
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if f.knownObjects != nil {
|
||||
// Detect deletions for objects not present in the queue, but present in KnownObjects
|
||||
knownKeys := f.knownObjects.ListKeys()
|
||||
for _, k := range knownKeys {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
// Delete pre-existing items not in the new list.
|
||||
// This could happen if watch deletion event was missed while
|
||||
// disconnected from apiserver.
|
||||
var deletedObj interface{}
|
||||
if n := oldItem.Newest(); n != nil {
|
||||
deletedObj = n.Object
|
||||
if len(f.items[k]) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
deletedObj, exists, err := f.knownObjects.GetByKey(k)
|
||||
if err != nil {
|
||||
deletedObj = nil
|
||||
klog.Errorf("Unexpected error %v during lookup of key %v, placing DeleteFinalStateUnknown marker without object", err, k)
|
||||
} else if !exists {
|
||||
deletedObj = nil
|
||||
klog.Infof("Key %v does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", k)
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.populated {
|
||||
f.populated = true
|
||||
// While there shouldn't be any queued deletions in the initial
|
||||
// population of the queue, it's better to be on the safe side.
|
||||
f.initialPopulationCount = keys.Len() + queuedDeletions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detect deletions not already in the queue.
|
||||
knownKeys := f.knownObjects.ListKeys()
|
||||
queuedDeletions := 0
|
||||
for _, k := range knownKeys {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
deletedObj, exists, err := f.knownObjects.GetByKey(k)
|
||||
if err != nil {
|
||||
deletedObj = nil
|
||||
klog.Errorf("Unexpected error %v during lookup of key %v, placing DeleteFinalStateUnknown marker without object", err, k)
|
||||
} else if !exists {
|
||||
deletedObj = nil
|
||||
klog.Infof("Key %v does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", k)
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.populated {
|
||||
|
||||
269
tools/cache/delta_fifo_test.go
vendored
269
tools/cache/delta_fifo_test.go
vendored
@@ -121,6 +121,130 @@ func TestDeltaFIFO_replaceWithDeleteDeltaIn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeltaFIFOW_ReplaceMakesDeletionsForObjectsOnlyInQueue(t *testing.T) {
|
||||
obj := mkFifoObj("foo", 2)
|
||||
objV2 := mkFifoObj("foo", 3)
|
||||
table := []struct {
|
||||
name string
|
||||
operations func(f *DeltaFIFO)
|
||||
expectedDeltas Deltas
|
||||
}{
|
||||
{
|
||||
name: "Added object should be deleted on Replace",
|
||||
operations: func(f *DeltaFIFO) {
|
||||
f.Add(obj)
|
||||
f.Replace([]interface{}{}, "0")
|
||||
},
|
||||
expectedDeltas: Deltas{
|
||||
{Added, obj},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "foo", Obj: obj}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Replaced object should have only a single Delete",
|
||||
operations: func(f *DeltaFIFO) {
|
||||
f.emitDeltaTypeReplaced = true
|
||||
f.Add(obj)
|
||||
f.Replace([]interface{}{obj}, "0")
|
||||
f.Replace([]interface{}{}, "0")
|
||||
},
|
||||
expectedDeltas: Deltas{
|
||||
{Added, obj},
|
||||
{Replaced, obj},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "foo", Obj: obj}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Deleted object should have only a single Delete",
|
||||
operations: func(f *DeltaFIFO) {
|
||||
f.Add(obj)
|
||||
f.Delete(obj)
|
||||
f.Replace([]interface{}{}, "0")
|
||||
},
|
||||
expectedDeltas: Deltas{
|
||||
{Added, obj},
|
||||
{Deleted, obj},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Synced objects should have a single delete",
|
||||
operations: func(f *DeltaFIFO) {
|
||||
f.Add(obj)
|
||||
f.Replace([]interface{}{obj}, "0")
|
||||
f.Replace([]interface{}{obj}, "0")
|
||||
f.Replace([]interface{}{}, "0")
|
||||
},
|
||||
expectedDeltas: Deltas{
|
||||
{Added, obj},
|
||||
{Sync, obj},
|
||||
{Sync, obj},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "foo", Obj: obj}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Added objects should have a single delete on multiple Replaces",
|
||||
operations: func(f *DeltaFIFO) {
|
||||
f.Add(obj)
|
||||
f.Replace([]interface{}{}, "0")
|
||||
f.Replace([]interface{}{}, "1")
|
||||
},
|
||||
expectedDeltas: Deltas{
|
||||
{Added, obj},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "foo", Obj: obj}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Added and deleted and added object should be deleted",
|
||||
operations: func(f *DeltaFIFO) {
|
||||
f.Add(obj)
|
||||
f.Delete(obj)
|
||||
f.Add(objV2)
|
||||
f.Replace([]interface{}{}, "0")
|
||||
},
|
||||
expectedDeltas: Deltas{
|
||||
{Added, obj},
|
||||
{Deleted, obj},
|
||||
{Added, objV2},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "foo", Obj: objV2}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range table {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test with a DeltaFIFO with a backing KnownObjects
|
||||
fWithKnownObjects := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: testFifoObjectKeyFunc,
|
||||
KnownObjects: literalListerGetter(func() []testFifoObject {
|
||||
return []testFifoObject{}
|
||||
}),
|
||||
})
|
||||
tt.operations(fWithKnownObjects)
|
||||
actualDeltasWithKnownObjects := Pop(fWithKnownObjects)
|
||||
if !reflect.DeepEqual(tt.expectedDeltas, actualDeltasWithKnownObjects) {
|
||||
t.Errorf("expected %#v, got %#v", tt.expectedDeltas, actualDeltasWithKnownObjects)
|
||||
}
|
||||
if len(fWithKnownObjects.items) != 0 {
|
||||
t.Errorf("expected no extra deltas (empty map), got %#v", fWithKnownObjects.items)
|
||||
}
|
||||
|
||||
// Test with a DeltaFIFO without a backing KnownObjects
|
||||
fWithoutKnownObjects := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: testFifoObjectKeyFunc,
|
||||
})
|
||||
tt.operations(fWithoutKnownObjects)
|
||||
actualDeltasWithoutKnownObjects := Pop(fWithoutKnownObjects)
|
||||
if !reflect.DeepEqual(tt.expectedDeltas, actualDeltasWithoutKnownObjects) {
|
||||
t.Errorf("expected %#v, got %#v", tt.expectedDeltas, actualDeltasWithoutKnownObjects)
|
||||
}
|
||||
if len(fWithoutKnownObjects.items) != 0 {
|
||||
t.Errorf("expected no extra deltas (empty map), got %#v", fWithoutKnownObjects.items)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeltaFIFO_requeueOnPop(t *testing.T) {
|
||||
f := NewDeltaFIFOWithOptions(DeltaFIFOOptions{KeyFunction: testFifoObjectKeyFunc})
|
||||
|
||||
@@ -203,6 +327,88 @@ func TestDeltaFIFO_addUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type rvAndXfrm struct {
|
||||
rv int
|
||||
xfrm int
|
||||
}
|
||||
|
||||
func TestDeltaFIFO_transformer(t *testing.T) {
|
||||
mk := func(name string, rv int) testFifoObject {
|
||||
return mkFifoObj(name, &rvAndXfrm{rv, 0})
|
||||
}
|
||||
xfrm := TransformFunc(func(obj interface{}) (interface{}, error) {
|
||||
switch v := obj.(type) {
|
||||
case testFifoObject:
|
||||
v.val.(*rvAndXfrm).xfrm++
|
||||
case DeletedFinalStateUnknown:
|
||||
if x := v.Obj.(testFifoObject).val.(*rvAndXfrm).xfrm; x != 1 {
|
||||
return nil, fmt.Errorf("object has been transformed wrong number of times: %#v", obj)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
return obj, nil
|
||||
})
|
||||
|
||||
must := func(err error) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
f := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: testFifoObjectKeyFunc,
|
||||
Transformer: xfrm,
|
||||
})
|
||||
must(f.Add(mk("foo", 10)))
|
||||
must(f.Add(mk("bar", 11)))
|
||||
must(f.Update(mk("foo", 12)))
|
||||
must(f.Delete(mk("foo", 15)))
|
||||
must(f.Replace([]interface{}{}, ""))
|
||||
must(f.Add(mk("bar", 16)))
|
||||
must(f.Replace([]interface{}{}, ""))
|
||||
|
||||
// Should be empty
|
||||
if e, a := []string{"foo", "bar"}, f.ListKeys(); !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %+v, got %+v", e, a)
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
obj, err := f.Pop(func(o interface{}) error { return nil })
|
||||
if err != nil {
|
||||
t.Fatalf("got nothing on try %v?", i)
|
||||
}
|
||||
obj = obj.(Deltas).Newest().Object
|
||||
switch v := obj.(type) {
|
||||
case testFifoObject:
|
||||
if v.name != "foo" {
|
||||
t.Errorf("expected regular deletion of foo, got %q", v.name)
|
||||
}
|
||||
rx := v.val.(*rvAndXfrm)
|
||||
if rx.rv != 15 {
|
||||
t.Errorf("expected last message, got %#v", obj)
|
||||
}
|
||||
if rx.xfrm != 1 {
|
||||
t.Errorf("obj %v transformed wrong number of times.", obj)
|
||||
}
|
||||
case DeletedFinalStateUnknown:
|
||||
tf := v.Obj.(testFifoObject)
|
||||
rx := tf.val.(*rvAndXfrm)
|
||||
if tf.name != "bar" {
|
||||
t.Errorf("expected tombstone deletion of bar, got %q", tf.name)
|
||||
}
|
||||
if rx.rv != 16 {
|
||||
t.Errorf("expected last message, got %#v", obj)
|
||||
}
|
||||
if rx.xfrm != 1 {
|
||||
t.Errorf("tombstoned obj %v transformed wrong number of times.", obj)
|
||||
}
|
||||
default:
|
||||
t.Errorf("unknown item %#v", obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeltaFIFO_enqueueingNoLister(t *testing.T) {
|
||||
f := NewDeltaFIFOWithOptions(DeltaFIFOOptions{KeyFunction: testFifoObjectKeyFunc})
|
||||
f.Add(mkFifoObj("foo", 10))
|
||||
@@ -371,7 +577,7 @@ func TestDeltaFIFO_ReplaceMakesDeletions(t *testing.T) {
|
||||
|
||||
expectedList = []Deltas{
|
||||
{{Added, mkFifoObj("baz", 10)},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "baz", Obj: mkFifoObj("baz", 7)}}},
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "baz", Obj: mkFifoObj("baz", 10)}}},
|
||||
{{Sync, mkFifoObj("foo", 5)}},
|
||||
// Since "bar" didn't have a delete event and wasn't in the Replace list
|
||||
// it should get a tombstone key with the right Obj.
|
||||
@@ -385,6 +591,67 @@ func TestDeltaFIFO_ReplaceMakesDeletions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Now try deleting and recreating the object in the queue, then delete it by a Replace call
|
||||
f = NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: testFifoObjectKeyFunc,
|
||||
KnownObjects: literalListerGetter(func() []testFifoObject {
|
||||
return []testFifoObject{mkFifoObj("foo", 5), mkFifoObj("bar", 6), mkFifoObj("baz", 7)}
|
||||
}),
|
||||
})
|
||||
f.Delete(mkFifoObj("bar", 6))
|
||||
f.Add(mkFifoObj("bar", 100))
|
||||
f.Replace([]interface{}{mkFifoObj("foo", 5)}, "0")
|
||||
|
||||
expectedList = []Deltas{
|
||||
{
|
||||
{Deleted, mkFifoObj("bar", 6)},
|
||||
{Added, mkFifoObj("bar", 100)},
|
||||
// Since "bar" has a newer object in the queue than in the state,
|
||||
// it should get a tombstone key with the latest object from the queue
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "bar", Obj: mkFifoObj("bar", 100)}},
|
||||
},
|
||||
{{Sync, mkFifoObj("foo", 5)}},
|
||||
{{Deleted, DeletedFinalStateUnknown{Key: "baz", Obj: mkFifoObj("baz", 7)}}},
|
||||
}
|
||||
|
||||
for _, expected := range expectedList {
|
||||
cur := Pop(f).(Deltas)
|
||||
if e, a := expected, cur; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// Now try syncing it first to ensure the delete use the latest version
|
||||
f = NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: testFifoObjectKeyFunc,
|
||||
KnownObjects: literalListerGetter(func() []testFifoObject {
|
||||
return []testFifoObject{mkFifoObj("foo", 5), mkFifoObj("bar", 6), mkFifoObj("baz", 7)}
|
||||
}),
|
||||
})
|
||||
f.Replace([]interface{}{mkFifoObj("bar", 100), mkFifoObj("foo", 5)}, "0")
|
||||
f.Replace([]interface{}{mkFifoObj("foo", 5)}, "0")
|
||||
|
||||
expectedList = []Deltas{
|
||||
{
|
||||
{Sync, mkFifoObj("bar", 100)},
|
||||
// Since "bar" didn't have a delete event and wasn't in the Replace list
|
||||
// it should get a tombstone key with the right Obj.
|
||||
{Deleted, DeletedFinalStateUnknown{Key: "bar", Obj: mkFifoObj("bar", 100)}},
|
||||
},
|
||||
{
|
||||
{Sync, mkFifoObj("foo", 5)},
|
||||
{Sync, mkFifoObj("foo", 5)},
|
||||
},
|
||||
{{Deleted, DeletedFinalStateUnknown{Key: "baz", Obj: mkFifoObj("baz", 7)}}},
|
||||
}
|
||||
|
||||
for _, expected := range expectedList {
|
||||
cur := Pop(f).(Deltas)
|
||||
if e, a := expected, cur; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// Now try starting without an explicit KeyListerGetter
|
||||
f = NewDeltaFIFOWithOptions(DeltaFIFOOptions{KeyFunction: testFifoObjectKeyFunc})
|
||||
f.Add(mkFifoObj("baz", 10))
|
||||
|
||||
8
tools/cache/shared_informer.go
vendored
8
tools/cache/shared_informer.go
vendored
@@ -198,10 +198,7 @@ type SharedInformer interface {
|
||||
//
|
||||
// Must be set before starting the informer.
|
||||
//
|
||||
// Note: Since the object given to the handler may be already shared with
|
||||
// other goroutines, it is advisable to copy the object being
|
||||
// transform before mutating it at all and returning the copy to prevent
|
||||
// data races.
|
||||
// Please see the comment on TransformFunc for more details.
|
||||
SetTransform(handler TransformFunc) error
|
||||
|
||||
// IsStopped reports whether the informer has already been stopped.
|
||||
@@ -422,6 +419,7 @@ func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
|
||||
fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: s.indexer,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
Transformer: s.transform,
|
||||
})
|
||||
|
||||
cfg := &Config{
|
||||
@@ -585,7 +583,7 @@ func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
|
||||
defer s.blockDeltas.Unlock()
|
||||
|
||||
if deltas, ok := obj.(Deltas); ok {
|
||||
return processDeltas(s, s.indexer, s.transform, deltas)
|
||||
return processDeltas(s, s.indexer, deltas)
|
||||
}
|
||||
return errors.New("object given as Process argument is not Deltas")
|
||||
}
|
||||
|
||||
5
tools/cache/shared_informer_test.go
vendored
5
tools/cache/shared_informer_test.go
vendored
@@ -395,9 +395,8 @@ func TestSharedInformerTransformer(t *testing.T) {
|
||||
name := pod.GetName()
|
||||
|
||||
if upper := strings.ToUpper(name); upper != name {
|
||||
copied := pod.DeepCopyObject().(*v1.Pod)
|
||||
copied.SetName(upper)
|
||||
return copied, nil
|
||||
pod.SetName(upper)
|
||||
return pod, nil
|
||||
}
|
||||
}
|
||||
return obj, nil
|
||||
|
||||
@@ -181,22 +181,24 @@ func (e *eventBroadcasterImpl) recordToSink(event *eventsv1.Event, clock clock.C
|
||||
return nil
|
||||
}
|
||||
isomorphicEvent.Series = &eventsv1.EventSeries{
|
||||
Count: 1,
|
||||
Count: 2,
|
||||
LastObservedTime: metav1.MicroTime{Time: clock.Now()},
|
||||
}
|
||||
return isomorphicEvent
|
||||
// Make a copy of the Event to make sure that recording it
|
||||
// doesn't mess with the object stored in cache.
|
||||
return isomorphicEvent.DeepCopy()
|
||||
}
|
||||
e.eventCache[eventKey] = eventCopy
|
||||
return eventCopy
|
||||
// Make a copy of the Event to make sure that recording it doesn't
|
||||
// mess with the object stored in cache.
|
||||
return eventCopy.DeepCopy()
|
||||
}()
|
||||
if evToRecord != nil {
|
||||
recordedEvent := e.attemptRecording(evToRecord)
|
||||
if recordedEvent != nil {
|
||||
recordedEventKey := getKey(recordedEvent)
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.eventCache[recordedEventKey] = recordedEvent
|
||||
}
|
||||
// TODO: Add a metric counting the number of recording attempts
|
||||
e.attemptRecording(evToRecord)
|
||||
// We don't want the new recorded Event to be reflected in the
|
||||
// client's cache because server-side mutations could mess with the
|
||||
// aggregation mechanism used by the client.
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -248,6 +250,14 @@ func recordEvent(sink EventSink, event *eventsv1.Event) (*eventsv1.Event, bool)
|
||||
return nil, false
|
||||
case *errors.StatusError:
|
||||
if errors.IsAlreadyExists(err) {
|
||||
// If we tried to create an Event from an EventSerie, it means that
|
||||
// the original Patch request failed because the Event we were
|
||||
// trying to patch didn't exist. If the creation failed because the
|
||||
// Event now exists, it is safe to retry. This occurs when a new
|
||||
// Event is emitted twice in a very short period of time.
|
||||
if isEventSeries {
|
||||
return nil, true
|
||||
}
|
||||
klog.V(5).Infof("Server rejected event '%#v': '%v' (will not retry!)", event, err)
|
||||
} else {
|
||||
klog.Errorf("Server rejected event '%#v': '%v' (will not retry!)", event, err)
|
||||
|
||||
103
tools/events/event_broadcaster_test.go
Normal file
103
tools/events/event_broadcaster_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func TestRecordEventToSink(t *testing.T) {
|
||||
nonIsomorphicEvent := eventsv1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Series: nil,
|
||||
}
|
||||
|
||||
isomorphicEvent := *nonIsomorphicEvent.DeepCopy()
|
||||
isomorphicEvent.Series = &eventsv1.EventSeries{Count: 2}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
eventsToRecord []eventsv1.Event
|
||||
expectedRecordedEvent eventsv1.Event
|
||||
}{
|
||||
{
|
||||
name: "record one Event",
|
||||
eventsToRecord: []eventsv1.Event{
|
||||
nonIsomorphicEvent,
|
||||
},
|
||||
expectedRecordedEvent: nonIsomorphicEvent,
|
||||
},
|
||||
{
|
||||
name: "record one Event followed by an isomorphic one",
|
||||
eventsToRecord: []eventsv1.Event{
|
||||
nonIsomorphicEvent,
|
||||
isomorphicEvent,
|
||||
},
|
||||
expectedRecordedEvent: isomorphicEvent,
|
||||
},
|
||||
{
|
||||
name: "record one isomorphic Event before the original",
|
||||
eventsToRecord: []eventsv1.Event{
|
||||
isomorphicEvent,
|
||||
nonIsomorphicEvent,
|
||||
},
|
||||
expectedRecordedEvent: isomorphicEvent,
|
||||
},
|
||||
{
|
||||
name: "record one isomorphic Event without one already existing",
|
||||
eventsToRecord: []eventsv1.Event{
|
||||
isomorphicEvent,
|
||||
},
|
||||
expectedRecordedEvent: isomorphicEvent,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
kubeClient := fake.NewSimpleClientset()
|
||||
eventSink := &EventSinkImpl{Interface: kubeClient.EventsV1()}
|
||||
|
||||
for _, ev := range tc.eventsToRecord {
|
||||
recordEvent(eventSink, &ev)
|
||||
}
|
||||
|
||||
recordedEvents, err := kubeClient.EventsV1().Events(metav1.NamespaceDefault).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("expected to be able to list Events from fake client")
|
||||
}
|
||||
|
||||
if len(recordedEvents.Items) != 1 {
|
||||
t.Errorf("expected one Event to be recorded, found: %d", len(recordedEvents.Items))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8sruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
fake "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
ref "k8s.io/client-go/tools/reference"
|
||||
@@ -106,7 +108,7 @@ func TestEventSeriesf(t *testing.T) {
|
||||
nonIsomorphicEvent := expectedEvent.DeepCopy()
|
||||
nonIsomorphicEvent.Action = "stopped"
|
||||
|
||||
expectedEvent.Series = &eventsv1.EventSeries{Count: 1}
|
||||
expectedEvent.Series = &eventsv1.EventSeries{Count: 2}
|
||||
table := []struct {
|
||||
regarding k8sruntime.Object
|
||||
related k8sruntime.Object
|
||||
@@ -185,6 +187,44 @@ func TestEventSeriesf(t *testing.T) {
|
||||
close(stopCh)
|
||||
}
|
||||
|
||||
// TestEventSeriesWithEventSinkImplRace verifies that when Events are emitted to
|
||||
// an EventSink consecutively there is no data race. This test is meant to be
|
||||
// run with the `-race` option.
|
||||
func TestEventSeriesWithEventSinkImplRace(t *testing.T) {
|
||||
kubeClient := fake.NewSimpleClientset()
|
||||
|
||||
eventSink := &EventSinkImpl{Interface: kubeClient.EventsV1()}
|
||||
eventBroadcaster := NewBroadcaster(eventSink)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
eventBroadcaster.StartRecordingToSink(stopCh)
|
||||
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "test")
|
||||
|
||||
recorder.Eventf(&v1.ObjectReference{}, nil, v1.EventTypeNormal, "reason", "action", "", "")
|
||||
recorder.Eventf(&v1.ObjectReference{}, nil, v1.EventTypeNormal, "reason", "action", "", "")
|
||||
|
||||
err := wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (done bool, err error) {
|
||||
events, err := kubeClient.EventsV1().Events(metav1.NamespaceDefault).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(events.Items) != 1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if events.Items[0].Series == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("expected that 2 identical Eventf calls would result in the creation of an Event with a Serie")
|
||||
}
|
||||
}
|
||||
|
||||
func validateEvent(messagePrefix string, expectedUpdate bool, actualEvent *eventsv1.Event, expectedEvent *eventsv1.Event, t *testing.T) {
|
||||
recvEvent := *actualEvent
|
||||
|
||||
|
||||
@@ -344,6 +344,9 @@ func (recorder *recorderImpl) generateEvent(object runtime.Object, annotations m
|
||||
event := recorder.makeEvent(ref, annotations, eventtype, reason, message)
|
||||
event.Source = recorder.source
|
||||
|
||||
event.ReportingInstance = recorder.source.Host
|
||||
event.ReportingController = recorder.source.Component
|
||||
|
||||
// NOTE: events should be a non-blocking operation, but we also need to not
|
||||
// put this in a goroutine, otherwise we'll race to write to a closed channel
|
||||
// when we go to shut down this broadcaster. Just drop events if we get overloaded,
|
||||
|
||||
@@ -178,11 +178,12 @@ func TestEventf(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[2]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -205,11 +206,12 @@ func TestEventf(t *testing.T) {
|
||||
UID: "bar",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
Reason: "Killed",
|
||||
Message: "some other verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Killed",
|
||||
Message: "some other verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -233,11 +235,12 @@ func TestEventf(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[2]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: true,
|
||||
@@ -261,11 +264,12 @@ func TestEventf(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[3]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -289,11 +293,12 @@ func TestEventf(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[2]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 3,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 3,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: true,
|
||||
@@ -317,11 +322,12 @@ func TestEventf(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[3]",
|
||||
},
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -345,11 +351,12 @@ func TestEventf(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[3]",
|
||||
},
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
|
||||
expectUpdate: true,
|
||||
@@ -697,11 +704,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[2]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -724,11 +732,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
UID: "bar",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
Reason: "Killed",
|
||||
Message: "some other verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Killed",
|
||||
Message: "some other verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -752,11 +761,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[2]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: true,
|
||||
@@ -780,11 +790,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[3]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -808,11 +819,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[2]",
|
||||
},
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 3,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Started",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 3,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`,
|
||||
expectUpdate: true,
|
||||
@@ -836,11 +848,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[3]",
|
||||
},
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 1,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
|
||||
expectUpdate: false,
|
||||
@@ -864,11 +877,12 @@ func TestMultiSinkCache(t *testing.T) {
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.containers[3]",
|
||||
},
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
Reason: "Stopped",
|
||||
Message: "some verbose message: 1",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
ReportingController: "eventTest",
|
||||
Count: 2,
|
||||
Type: v1.EventTypeNormal,
|
||||
},
|
||||
expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`,
|
||||
expectUpdate: true,
|
||||
|
||||
@@ -115,6 +115,9 @@ func validateEvent(messagePrefix string, actualEvent *v1.Event, expectedEvent *v
|
||||
// Temp clear time stamps for comparison because actual values don't matter for comparison
|
||||
recvEvent.FirstTimestamp = expectedEvent.FirstTimestamp
|
||||
recvEvent.LastTimestamp = expectedEvent.LastTimestamp
|
||||
|
||||
recvEvent.ReportingController = expectedEvent.ReportingController
|
||||
|
||||
// Check that name has the right prefix.
|
||||
if n, en := recvEvent.Name, expectedEvent.Name; !strings.HasPrefix(n, en) {
|
||||
t.Errorf("%v - Name '%v' does not contain prefix '%v'", messagePrefix, n, en)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
@@ -44,6 +45,7 @@ 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
|
||||
@@ -57,14 +59,24 @@ type AltNames struct {
|
||||
// NewSelfSignedCACert creates a CA certificate
|
||||
func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
|
||||
now := time.Now()
|
||||
// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
|
||||
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
|
||||
if err != nil {
|
||||
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: new(big.Int).SetInt64(0),
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: cfg.CommonName,
|
||||
Organization: cfg.Organization,
|
||||
},
|
||||
DNSNames: []string{cfg.CommonName},
|
||||
NotBefore: now.UTC(),
|
||||
NotBefore: notBefore,
|
||||
NotAfter: now.Add(duration365d * 10).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
@@ -116,9 +128,14 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
|
||||
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
serial = new(big.Int).Add(serial, big.NewInt(1))
|
||||
caTemplate := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: fmt.Sprintf("%s-ca@%d", host, time.Now().Unix()),
|
||||
},
|
||||
@@ -144,9 +161,14 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
|
||||
serial, err = cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
serial = new(big.Int).Add(serial, big.NewInt(1))
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user