From 2eb41a9db7ba56658bcdc6242851aa1df4bf60b1 Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Mon, 3 Oct 2022 16:12:11 -0500 Subject: [PATCH 01/36] Only return changed counts, and send initial count Three main changes: - When a count changes, only send that count. This makes the count socket significantly more usable - The count buffer will now send a count right away. This allows a message to launch wihtout an inital buffered wait period - The count buffer now sends messages every 5 seconds instead of 1 --- pkg/resources/counts/buffer.go | 53 +++++++++++++++++++++++++--------- pkg/resources/counts/counts.go | 22 +++++++------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/pkg/resources/counts/buffer.go b/pkg/resources/counts/buffer.go index 2552c5d..0f97d14 100644 --- a/pkg/resources/counts/buffer.go +++ b/pkg/resources/counts/buffer.go @@ -6,35 +6,60 @@ import ( "github.com/rancher/apiserver/pkg/types" ) -func buffer(c chan types.APIEvent) chan types.APIEvent { +// debounceDuration determines how long events will be held before they are sent to the consumer +var debounceDuration = 5 * time.Second + +// countsBuffer creates an APIEvent channel with a buffered response time (i.e. replies are only sent once every second) +func countsBuffer(c chan Count) chan types.APIEvent { result := make(chan types.APIEvent) go func() { defer close(result) - debounce(result, c) + debounceCounts(result, c) }() return result } -func debounce(result, input chan types.APIEvent) { - t := time.NewTicker(time.Second) +// debounceCounts converts counts from an input channel into an APIEvent, and updates the result channel at a reduced pace +func debounceCounts(result chan types.APIEvent, input chan Count) { + // counts aren't a critical value. To avoid excess UI processing, only send updates after debounceDuration has elapsed + t := time.NewTicker(debounceDuration) defer t.Stop() - var ( - lastEvent *types.APIEvent - ) + var currentCount *Count + + firstCount, fOk := <-input + if fOk { + // send a count immediately or we will have to wait a second for the first update + result <- toAPIEvent(firstCount) + } for { select { - case event, ok := <-input: - if ok { - lastEvent = &event - } else { + case count, ok := <-input: + if !ok { return } + if currentCount == nil { + currentCount = &count + } else { + itemCounts := count.Counts + for id, itemCount := range itemCounts { + // our current count will be outdated in comparison with anything in the new events + currentCount.Counts[id] = itemCount + } + } case <-t.C: - if lastEvent != nil { - result <- *lastEvent - lastEvent = nil + if currentCount != nil { + result <- toAPIEvent(*currentCount) + currentCount = nil } } } } + +func toAPIEvent(count Count) types.APIEvent { + return types.APIEvent{ + Name: "resource.change", + ResourceType: "counts", + Object: toAPIObject(count), + } +} diff --git a/pkg/resources/counts/counts.go b/pkg/resources/counts/counts.go index a44d44b..47ce8ec 100644 --- a/pkg/resources/counts/counts.go +++ b/pkg/resources/counts/counts.go @@ -24,6 +24,7 @@ var ( } ) +// Register registers a new count schema. This schema isn't a true resource but instead returns counts for other resources func Register(schemas *types.APISchemas, ccache clustercache.ClusterCache) { schemas.MustImportAndCustomize(Count{}, func(schema *types.APISchema) { schema.CollectionMethods = []string{http.MethodGet} @@ -110,9 +111,10 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP }, nil } +// Watch creates a watch for the Counts schema. This returns only the counts which have changed since the watch was established func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) { var ( - result = make(chan types.APIEvent, 100) + result = make(chan Count, 100) counts map[string]ItemCount gvkToSchema = map[schema2.GroupVersionKind]*types.APISchema{} countLock sync.Mutex @@ -178,18 +180,13 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. } counts[schema.ID] = itemCount - countsCopy := map[string]ItemCount{} - for k, v := range counts { - countsCopy[k] = *v.DeepCopy() + changedCount := map[string]ItemCount{ + schema.ID: itemCount, } - result <- types.APIEvent{ - Name: "resource.change", - ResourceType: "counts", - Object: toAPIObject(Count{ - ID: "count", - Counts: countsCopy, - }), + result <- Count{ + ID: "count", + Counts: changedCount, } return nil @@ -205,7 +202,8 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. return onChange(false, gvk, key, obj, nil) }) - return buffer(result), nil + // buffer the counts so that we don't spam the consumer with constant updates + return countsBuffer(result), nil } func (s *Store) schemasToWatch(apiOp *types.APIRequest) (result []*types.APISchema) { From 5cb45b9dcd76b47c826fba28591f1f3232ae6959 Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Mon, 3 Oct 2022 16:19:31 -0500 Subject: [PATCH 02/36] Adding test for sending counts on diffs --- pkg/resources/counts/buffer_test.go | 111 ++++++++++ pkg/resources/counts/counts_test.go | 304 ++++++++++++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 pkg/resources/counts/buffer_test.go create mode 100644 pkg/resources/counts/counts_test.go diff --git a/pkg/resources/counts/buffer_test.go b/pkg/resources/counts/buffer_test.go new file mode 100644 index 0000000..3219af4 --- /dev/null +++ b/pkg/resources/counts/buffer_test.go @@ -0,0 +1,111 @@ +package counts + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/rancher/apiserver/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_countsBuffer(t *testing.T) { + tests := []struct { + name string + numInputEvents int + overrideInput map[int]int // events whose count we should override. Don't include an event >= numInputEvents + }{ + { + name: "test basic input", + numInputEvents: 1, + }, + { + name: "test basic multiple input", + numInputEvents: 3, + }, + { + name: "test basic input which is overriden by later events", + numInputEvents: 3, + overrideInput: map[int]int{ + 1: 17, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + debounceDuration = 10 * time.Millisecond + countsChannel := make(chan Count, 100) + outputChannel := countsBuffer(countsChannel) + + countsChannel <- Count{ + ID: "count", + Counts: map[string]ItemCount{"test": createItemCount(1)}, + } + + // first event is not buffered, so we expect to receive it quicker than the debounce + _, err := receiveWithTimeout(outputChannel, time.Millisecond*1) + assert.NoError(t, err, "Expected first event to be received quickly") + + // stream our standard count events + for i := 0; i < test.numInputEvents; i++ { + countsChannel <- Count{ + ID: "count", + Counts: map[string]ItemCount{strconv.Itoa(i): createItemCount(1)}, + } + } + + // stream any overrides, if applicable + for key, value := range test.overrideInput { + countsChannel <- Count{ + ID: "count", + Counts: map[string]ItemCount{strconv.Itoa(key): createItemCount(value)}, + } + } + + // due to complexities of cycle calculation, give a slight delay for the event to actually stream + output, err := receiveWithTimeout(outputChannel, debounceDuration+time.Millisecond*1) + assert.NoError(t, err, "did not expect an error when receiving value from channel") + outputCount := output.Object.Object.(Count) + assert.Len(t, outputCount.Counts, test.numInputEvents) + for outputID, outputItem := range outputCount.Counts { + outputIdx, err := strconv.Atoi(outputID) + assert.NoError(t, err, "couldn't convert output idx") + nsTotal := 0 + for _, nsSummary := range outputItem.Namespaces { + nsTotal += nsSummary.Count + } + if outputOverride, ok := test.overrideInput[outputIdx]; ok { + assert.Equal(t, outputOverride, outputItem.Summary.Count, "expected overridden output count to be most recent value") + assert.Equal(t, outputOverride, nsTotal, "expected overridden output namespace count to be most recent value") + } else { + assert.Equal(t, 1, outputItem.Summary.Count, "expected non-overridden output count to be 1") + assert.Equal(t, 1, nsTotal, "expected non-overridden output namespace count to be 1") + } + } + }) + } +} + +// receiveWithTimeout tries to get a value from input within duration. Returns an error if no input was received during that period +func receiveWithTimeout(input chan types.APIEvent, duration time.Duration) (*types.APIEvent, error) { + select { + case value := <-input: + return &value, nil + case <-time.After(duration): + return nil, fmt.Errorf("timeout error, no value recieved after %f seconds", duration.Seconds()) + } +} + +func createItemCount(countTotal int) ItemCount { + return ItemCount{ + Summary: Summary{ + Count: countTotal, + }, + Namespaces: map[string]Summary{ + "test": { + Count: countTotal, + }, + }, + } +} diff --git a/pkg/resources/counts/counts_test.go b/pkg/resources/counts/counts_test.go new file mode 100644 index 0000000..bf7567e --- /dev/null +++ b/pkg/resources/counts/counts_test.go @@ -0,0 +1,304 @@ +package counts_test + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/rancher/apiserver/pkg/server" + "github.com/rancher/apiserver/pkg/store/empty" + "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/steve/pkg/accesscontrol" + "github.com/rancher/steve/pkg/attributes" + "github.com/rancher/steve/pkg/clustercache" + "github.com/rancher/steve/pkg/resources/counts" + "github.com/rancher/steve/pkg/schema" + "github.com/rancher/wrangler/pkg/schemas" + "github.com/rancher/wrangler/pkg/summary" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + schema2 "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + testGroup = "test.k8s.io" + testVersion = "v1" + testResource = "testCRD" + testNotUsedResource = "testNotUsedCRD" + testNewResource = "testNewCRD" +) + +func TestWatch(t *testing.T) { + tests := []struct { + name string + event string // the event to send, can be "add", "remove", or "change" + newSchema bool + countsForSchema int + errDesired bool + }{ + { + name: "add of known schema", + event: "add", + newSchema: false, + countsForSchema: 2, + errDesired: false, + }, + { + name: "add of unknown schema", + event: "add", + newSchema: true, + countsForSchema: 0, + errDesired: true, + }, + { + name: "change of known schema", + event: "change", + newSchema: false, + countsForSchema: 0, + errDesired: true, + }, + { + name: "change of unknown schema", + event: "change", + newSchema: true, + countsForSchema: 0, + errDesired: true, + }, + { + name: "remove of known schema", + event: "remove", + newSchema: false, + countsForSchema: 0, + errDesired: false, + }, + { + name: "remove of unknown schema", + event: "remove", + newSchema: true, + countsForSchema: 0, + errDesired: true, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + testSchema := makeSchema(testResource) + testNotUsedSchema := makeSchema(testNotUsedResource) + testNewSchema := makeSchema(testNewResource) + addGenericPermissionsToSchema(testSchema, "list") + addGenericPermissionsToSchema(testNotUsedSchema, "list") + testSchemas := types.EmptyAPISchemas() + testSchemas.MustAddSchema(*testSchema) + testSchemas.MustAddSchema(*testNotUsedSchema) + testOp := &types.APIRequest{ + Schemas: testSchemas, + AccessControl: &server.SchemaBasedAccess{}, + Request: &http.Request{}, + } + fakeCache := NewFakeClusterCache() + gvk := attributes.GVK(testSchema) + newGVK := attributes.GVK(testNewSchema) + fakeCache.AddSummaryObj(makeSummarizedObject(gvk, "testName1", "testNs", "1")) + counts.Register(testSchemas, fakeCache) + + // next, get the channel our results will be delivered on + countSchema := testSchemas.LookupSchema("count") + // channel will stream our events after we call the handlers to simulate/add/remove/change events + resChannel, err := countSchema.Store.Watch(testOp, nil, types.WatchRequest{}) + assert.NoError(t, err, "got an error when trying to watch counts, did not expect one") + + // call the handlers, triggering the update to receive the event + if test.event == "add" { + var summarizedObject *summary.SummarizedObject + var testGVK schema2.GroupVersionKind + if test.newSchema { + summarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "1") + testGVK = newGVK + } else { + summarizedObject = makeSummarizedObject(gvk, "testName2", "testNs", "2") + testGVK = gvk + } + err = fakeCache.addHandler(testGVK, "n/a", summarizedObject) + assert.NoError(t, err, "did not expect error when calling add method") + } else if test.event == "change" { + var summarizedObject *summary.SummarizedObject + var testGVK schema2.GroupVersionKind + var changedSummarizedObject *summary.SummarizedObject + if test.newSchema { + summarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "1") + changedSummarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "2") + testGVK = newGVK + } else { + summarizedObject = makeSummarizedObject(gvk, "testName1", "testNs", "2") + changedSummarizedObject = makeSummarizedObject(gvk, "testName1", "testNs", "3") + testGVK = gvk + } + err = fakeCache.changeHandler(testGVK, "n/a", changedSummarizedObject, summarizedObject) + assert.NoError(t, err, "did not expect error when calling change method") + } else if test.event == "remove" { + var summarizedObject *summary.SummarizedObject + var testGVK schema2.GroupVersionKind + if test.newSchema { + summarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "2") + testGVK = newGVK + } else { + summarizedObject = makeSummarizedObject(gvk, "testName1", "testNs", "2") + testGVK = gvk + } + err = fakeCache.removeHandler(testGVK, "n/a", summarizedObject) + assert.NoError(t, err, "did not expect error when calling add method") + } else { + assert.Failf(t, "unexpected event", "%s is not one of the allowed values of add, change, remove", test.event) + } + // need to call the event handler to force the event to stream + outputCount, err := receiveWithTimeout(resChannel, 100*time.Millisecond) + if test.errDesired { + assert.Errorf(t, err, "expected no value from channel, but got one %+v", outputCount) + } else { + assert.NoError(t, err, "got an error when attempting to get a value from the result channel") + assert.NotNilf(t, outputCount, "expected a new count value, did not get one") + count := outputCount.Object.Object.(counts.Count) + assert.Len(t, count.Counts, 1, "only expected one count event") + itemCount, ok := count.Counts[testResource] + assert.True(t, ok, "expected an item count for %s", testResource) + assert.Equal(t, test.countsForSchema, itemCount.Summary.Count, "expected counts to be correct") + } + }) + } +} + +// receiveWithTimeout tries to get a value from input within duration. Returns an error if no input was received during that period +func receiveWithTimeout(input chan types.APIEvent, duration time.Duration) (*types.APIEvent, error) { + select { + case value := <-input: + return &value, nil + case <-time.After(duration): + return nil, fmt.Errorf("timeout error, no value recieved after %f seconds", duration.Seconds()) + } +} + +// addGenericPermissions grants the specified verb for all namespaces and all resourceNames +func addGenericPermissionsToSchema(schema *types.APISchema, verb string) { + if verb == "create" { + schema.CollectionMethods = append(schema.CollectionMethods, http.MethodPost) + } else if verb == "get" { + schema.ResourceMethods = append(schema.ResourceMethods, http.MethodGet) + } else if verb == "list" || verb == "watch" { + // list and watch use the same permission checks, so we handle in one case + schema.CollectionMethods = append(schema.CollectionMethods, http.MethodGet, http.MethodPost) + } else if verb == "update" { + schema.ResourceMethods = append(schema.ResourceMethods, http.MethodPut) + } else if verb == "delete" { + schema.ResourceMethods = append(schema.ResourceMethods, http.MethodDelete) + } else { + panic(fmt.Sprintf("Can't add generic permissions for verb %s", verb)) + } + currentAccess := schema.Attributes["access"].(accesscontrol.AccessListByVerb) + currentAccess[verb] = []accesscontrol.Access{ + { + Namespace: "*", + ResourceName: "*", + }, + } +} + +func makeSchema(resourceType string) *types.APISchema { + return &types.APISchema{ + Schema: &schemas.Schema{ + ID: resourceType, + CollectionMethods: []string{}, + ResourceMethods: []string{}, + ResourceFields: map[string]schemas.Field{ + "name": {Type: "string"}, + "value": {Type: "string"}, + }, + Attributes: map[string]interface{}{ + "group": testGroup, + "version": testVersion, + "kind": resourceType, + "resource": resourceType, + "verbs": []string{"get", "list", "watch", "delete", "update", "create"}, + "access": accesscontrol.AccessListByVerb{}, + }, + }, + Store: &empty.Store{}, + } +} + +type fakeClusterCache struct { + summarizedObjects []*summary.SummarizedObject + addHandler clustercache.Handler + removeHandler clustercache.Handler + changeHandler clustercache.ChangeHandler +} + +func NewFakeClusterCache() *fakeClusterCache { + return &fakeClusterCache{ + summarizedObjects: []*summary.SummarizedObject{}, + addHandler: nil, + removeHandler: nil, + changeHandler: nil, + } +} + +func (f *fakeClusterCache) Get(gvk schema2.GroupVersionKind, namespace, name string) (interface{}, bool, error) { + return nil, false, nil +} + +func (f *fakeClusterCache) List(gvk schema2.GroupVersionKind) []interface{} { + var retList []interface{} + for _, summaryObj := range f.summarizedObjects { + if summaryObj.GroupVersionKind() != gvk { + // only list the summary objects for the provided gvk + continue + } + retList = append(retList, summaryObj) + } + return retList +} + +func (f *fakeClusterCache) OnAdd(ctx context.Context, handler clustercache.Handler) { + f.addHandler = handler +} + +func (f *fakeClusterCache) OnRemove(ctx context.Context, handler clustercache.Handler) { + f.removeHandler = handler +} + +func (f *fakeClusterCache) OnChange(ctx context.Context, handler clustercache.ChangeHandler) { + f.changeHandler = handler +} + +func (f *fakeClusterCache) OnSchemas(schemas *schema.Collection) error { + return nil +} + +func (f *fakeClusterCache) AddSummaryObj(summaryObj *summary.SummarizedObject) { + f.summarizedObjects = append(f.summarizedObjects, summaryObj) +} + +func makeSummarizedObject(gvk schema2.GroupVersionKind, name string, namespace string, version string) *summary.SummarizedObject { + apiVersion, kind := gvk.ToAPIVersionAndKind() + return &summary.SummarizedObject{ + Summary: summary.Summary{ + State: "", + Error: false, + Transitioning: false, + }, + PartialObjectMetadata: metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: apiVersion, + Kind: kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + ResourceVersion: version, // any non-zero value should work here. 0 seems to have specific meaning for counts + }, + }, + } +} From 7312fe9ef3602787889118324555fe3a8941b889 Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Fri, 14 Oct 2022 08:52:00 -0500 Subject: [PATCH 03/36] Bump go to version 1.19 --- Dockerfile | 5 ++-- go.mod | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- go.sum | 6 ----- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48df490..e6974cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax = docker/dockerfile:experimental -FROM golang:1.13.4 as build +FROM registry.suse.com/bci/golang:1.19 as build COPY go.mod go.sum main.go /src/ COPY pkg /src/pkg/ #RUN --mount=type=cache,target=/root/.cache/go-build \ @@ -7,8 +7,7 @@ RUN \ cd /src && \ CGO_ENABLED=0 go build -ldflags "-extldflags -static -s" -o /steve -FROM alpine -RUN apk -U --no-cache add ca-certificates +FROM registry.suse.com/bci/bci-micro:15.4.15.1 COPY --from=build /steve /usr/bin/steve # Hack to make golang do files,dns search order ENV LOCALDOMAIN="" diff --git a/go.mod b/go.mod index f7b33fe..33e6432 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/rancher/steve -go 1.13 +go 1.19 replace ( github.com/crewjam/saml => github.com/rancher/saml v0.0.0-20180713225824-ce1532152fde @@ -14,7 +14,6 @@ require ( github.com/adrg/xdg v0.3.1 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 - github.com/imdario/mergo v0.3.8 // indirect github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 @@ -38,3 +37,76 @@ require ( k8s.io/kube-aggregator v0.24.0 k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 ) + +require ( + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful v2.9.5+incompatible // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.5 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/imdario/mergo v0.3.8 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.opentelemetry.io/contrib v0.20.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect + go.opentelemetry.io/otel v0.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect + go.opentelemetry.io/otel/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect + go.opentelemetry.io/otel/trace v0.20.0 // indirect + go.opentelemetry.io/proto/otlp v0.7.0 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect + google.golang.org/grpc v1.40.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + k8s.io/component-base v0.24.0 // indirect + k8s.io/klog/v2 v2.60.1 // indirect + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect + sigs.k8s.io/cli-utils v0.16.0 // indirect + sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) diff --git a/go.sum b/go.sum index bdd8bc4..9b8c4d3 100644 --- a/go.sum +++ b/go.sum @@ -89,14 +89,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -129,7 +127,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -590,7 +587,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uY github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -787,7 +783,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1294,7 +1289,6 @@ sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5 sigs.k8s.io/kustomize/kyaml v0.4.0/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= From 7e4a51bda007a5ebbdf07a4e03cff6337893d928 Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Fri, 14 Oct 2022 15:02:53 -0500 Subject: [PATCH 04/36] Adding build/test phase for CI --- .drone.yml | 33 +++++++++++++++++++++++++++++++-- Makefile | 6 ++++++ scripts/build-bin.sh | 3 +++ scripts/test.sh | 3 +++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 scripts/build-bin.sh create mode 100644 scripts/test.sh diff --git a/.drone.yml b/.drone.yml index 4420ccb..ae9db17 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,6 +8,35 @@ steps: settings: api_key: from_secret: FOSSA_API_KEY + when: + instance: + include: + - drone-publish.rancher.io + exclude: + - drone-pr.rancher.io +--- +kind: pipeline +name: build + +steps: + - name: build + image: registry.suse.com/bci/golang:1.19 + commands: + - make build-bin when: - instance: - - drone-publish.rancher.io + event: + - push + - pull_request +--- +kind: pipeline +name: test + +steps: + - name: test + image: registry.suse.com/bci/golang:1.19 + commands: + - make test + when: + event: + - push + - pull_request \ No newline at end of file diff --git a/Makefile b/Makefile index e5d9982..5dc381c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,14 @@ build: docker build -t steve . +build-bin: + bash scripts/build-bin.sh + run: build docker run $(DOCKER_ARGS) --rm -p 8989:9080 -it -v ${HOME}/.kube:/root/.kube steve --https-listen-port 0 run-host: build docker run $(DOCKER_ARGS) --net=host --uts=host --rm -it -v ${HOME}/.kube:/root/.kube steve --kubeconfig /root/.kube/config --http-listen-port 8989 --https-listen-port 0 + +test: + bash scripts/test.sh \ No newline at end of file diff --git a/scripts/build-bin.sh b/scripts/build-bin.sh new file mode 100644 index 0000000..52994fe --- /dev/null +++ b/scripts/build-bin.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +CGO_ENABLED=0 go build -ldflags "-extldflags -static -s" -o ./bin/steve \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100644 index 0000000..f9143f7 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +go test ./... \ No newline at end of file From b73cc57b204fd1924b4b24b07622458a6dbef67b Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Fri, 14 Oct 2022 15:21:17 -0500 Subject: [PATCH 05/36] Adding validate phase to the CI Adds a validate phase to the CI which runs a linter. Also fixes linter issues discovered during the initial run --- .drone.yml | 16 ++++++ .golangci.json | 67 ++++++++++++++++++++++++ Makefile | 5 +- pkg/auth/cli/webhookcli.go | 2 +- pkg/auth/filter.go | 2 +- pkg/podimpersonation/podimpersonation.go | 8 +-- pkg/resources/counts/counts.go | 4 +- pkg/schema/converter/crd.go | 2 +- pkg/schema/converter/k8stonorman.go | 2 +- pkg/schema/factory.go | 6 +-- pkg/server/handler/handlers.go | 5 -- pkg/stores/metrics/metrics_store.go | 2 +- pkg/stores/partition/parallel.go | 19 ++++--- pkg/stores/proxy/rbac_store.go | 13 +++-- scripts/validate.sh | 12 +++++ 15 files changed, 128 insertions(+), 37 deletions(-) create mode 100644 .golangci.json create mode 100644 scripts/validate.sh diff --git a/.drone.yml b/.drone.yml index ae9db17..0902f3a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -29,6 +29,22 @@ steps: - pull_request --- kind: pipeline +name: validate + +steps: + - name: validate + image: registry.suse.com/bci/bci-base:15.4 + commands: + - zypper in -y go=1.19 git tar gzip make + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.49.0 + - mv ./bin/golangci-lint /usr/local/bin/golangci-lint + - make validate + when: + event: + - push + - pull_request +--- +kind: pipeline name: test steps: diff --git a/.golangci.json b/.golangci.json new file mode 100644 index 0000000..b998d60 --- /dev/null +++ b/.golangci.json @@ -0,0 +1,67 @@ +{ + "linters": { + "disable-all": true, + "enable": [ + "govet", + "revive", + "goimports", + "misspell", + "ineffassign", + "gofmt" + ] + }, + "linters-settings": { + "govet": { + "check-shadowing": false + }, + "gofmt": { + "simplify": false + } + }, + "run": { + "skip-dirs": [ + "vendor", + "tests", + "pkg/client", + "pkg/generated" + ], + "tests": false, + "timeout": "10m" + }, + "issues": { + "exclude-rules": [ + { + "linters": "govet", + "text": "^(nilness|structtag)" + }, + { + "path":"pkg/apis/management.cattle.io/v3/globaldns_types.go", + "text":".*lobalDns.*" + }, + { + "path": "pkg/apis/management.cattle.io/v3/zz_generated_register.go", + "text":".*lobalDns.*" + }, + { + "path":"pkg/apis/management.cattle.io/v3/zz_generated_list_types.go", + "text":".*lobalDns.*" + }, + { + "linters": "revive", + "text": "should have comment" + }, + { + "linters": "revive", + "text": "should be of the form" + }, + { + "linters": "revive", + "text": "by other packages, and that stutters" + }, + { + "linters": "typecheck", + "text": "imported but not used as apierrors" + } + ] + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index 5dc381c..5f2ebc1 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,7 @@ run-host: build docker run $(DOCKER_ARGS) --net=host --uts=host --rm -it -v ${HOME}/.kube:/root/.kube steve --kubeconfig /root/.kube/config --http-listen-port 8989 --https-listen-port 0 test: - bash scripts/test.sh \ No newline at end of file + bash scripts/test.sh + +validate: + bash scripts/validate.sh \ No newline at end of file diff --git a/pkg/auth/cli/webhookcli.go b/pkg/auth/cli/webhookcli.go index e3d9c4d..8ead8ae 100644 --- a/pkg/auth/cli/webhookcli.go +++ b/pkg/auth/cli/webhookcli.go @@ -1,12 +1,12 @@ package cli import ( - "k8s.io/client-go/tools/clientcmd" "os" "time" "github.com/rancher/steve/pkg/auth" "github.com/urfave/cli" + "k8s.io/client-go/tools/clientcmd" ) type WebhookConfig struct { diff --git a/pkg/auth/filter.go b/pkg/auth/filter.go index 472366a..26cfb74 100644 --- a/pkg/auth/filter.go +++ b/pkg/auth/filter.go @@ -2,7 +2,6 @@ package auth import ( "io/ioutil" - "k8s.io/client-go/rest" "net/http" "strings" "time" @@ -13,6 +12,7 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/transport" diff --git a/pkg/podimpersonation/podimpersonation.go b/pkg/podimpersonation/podimpersonation.go index 30e3ced..01b8216 100644 --- a/pkg/podimpersonation/podimpersonation.go +++ b/pkg/podimpersonation/podimpersonation.go @@ -128,10 +128,10 @@ type PodOptions struct { // CreatePod will create a pod with a service account that impersonates as user. Corresponding // ClusterRoles, ClusterRoleBindings, and ServiceAccounts will be create. // IMPORTANT NOTES: -// 1. To ensure this is used securely the namespace assigned to the pod must be a dedicated -// namespace used only for the purpose of running impersonated pods. This is to ensure -// proper protection for the service accounts created. -// 2. The pod must KUBECONFIG env var set to where you expect the kubeconfig to reside +// 1. To ensure this is used securely the namespace assigned to the pod must be a dedicated +// namespace used only for the purpose of running impersonated pods. This is to ensure +// proper protection for the service accounts created. +// 2. The pod must KUBECONFIG env var set to where you expect the kubeconfig to reside func (s *PodImpersonation) CreatePod(ctx context.Context, user user.Info, pod *v1.Pod, podOptions *PodOptions) (*v1.Pod, error) { if podOptions == nil { podOptions = &PodOptions{} diff --git a/pkg/resources/counts/counts.go b/pkg/resources/counts/counts.go index a44d44b..aa51b13 100644 --- a/pkg/resources/counts/counts.go +++ b/pkg/resources/counts/counts.go @@ -280,7 +280,7 @@ func removeSummary(counts Summary, summary summary.Summary) Summary { if counts.States == nil { counts.States = map[string]int{} } - counts.States[simpleState(summary)] -= 1 + counts.States[simpleState(summary)]-- } return counts } @@ -297,7 +297,7 @@ func addSummary(counts Summary, summary summary.Summary) Summary { if counts.States == nil { counts.States = map[string]int{} } - counts.States[simpleState(summary)] += 1 + counts.States[simpleState(summary)]++ } return counts } diff --git a/pkg/schema/converter/crd.go b/pkg/schema/converter/crd.go index 1e02145..874f8a6 100644 --- a/pkg/schema/converter/crd.go +++ b/pkg/schema/converter/crd.go @@ -6,7 +6,7 @@ import ( "github.com/rancher/steve/pkg/schema/table" apiextv1 "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io/v1" "github.com/rancher/wrangler/pkg/schemas" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) diff --git a/pkg/schema/converter/k8stonorman.go b/pkg/schema/converter/k8stonorman.go index cba8e10..1c3c941 100644 --- a/pkg/schema/converter/k8stonorman.go +++ b/pkg/schema/converter/k8stonorman.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/rancher/apiserver/pkg/types" - "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io/v1" + v1 "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" ) diff --git a/pkg/schema/factory.go b/pkg/schema/factory.go index 5f7ebd9..b6f0e4b 100644 --- a/pkg/schema/factory.go +++ b/pkg/schema/factory.go @@ -41,11 +41,11 @@ func (c *Collection) Schemas(user user.Info) (*types.APISchemas, error) { func (c *Collection) removeOldRecords(access *accesscontrol.AccessSet, user user.Info) { current, ok := c.userCache.Get(user.GetName()) if ok { - currentId, cOk := current.(string) - if cOk && currentId != access.ID { + currentID, cOk := current.(string) + if cOk && currentID != access.ID { // we only want to keep around one record per user. If our current access record is invalid, purge the //record of it from the cache, so we don't keep duplicates - c.purgeUserRecords(currentId) + c.purgeUserRecords(currentID) c.userCache.Remove(user.GetName()) } } diff --git a/pkg/server/handler/handlers.go b/pkg/server/handler/handlers.go index 0b4fa3e..79cfe5b 100644 --- a/pkg/server/handler/handlers.go +++ b/pkg/server/handler/handlers.go @@ -9,11 +9,6 @@ import ( func k8sAPI(sf schema.Factory, apiOp *types.APIRequest) { vars := mux.Vars(apiOp.Request) - group := vars["group"] - if group == "core" { - group = "" - } - apiOp.Name = vars["name"] apiOp.Type = vars["type"] diff --git a/pkg/stores/metrics/metrics_store.go b/pkg/stores/metrics/metrics_store.go index 1e316a1..6a434c4 100644 --- a/pkg/stores/metrics/metrics_store.go +++ b/pkg/stores/metrics/metrics_store.go @@ -63,4 +63,4 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. apiEvent, err := s.Store.Watch(apiOp, schema, w) m.RecordProxyStoreResponseTime(err, float64(time.Since(storeStart).Milliseconds())) return apiEvent, err -} \ No newline at end of file +} diff --git a/pkg/stores/partition/parallel.go b/pkg/stores/partition/parallel.go index e1901d3..1b77486 100644 --- a/pkg/stores/partition/parallel.go +++ b/pkg/stores/partition/parallel.go @@ -170,17 +170,16 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l } capacity = 0 return nil - } else { - result <- list.Objects - capacity -= len(list.Objects) - if list.Continue == "" { - return nil - } - // loop again and get more data - state.Continue = list.Continue - state.PartitionName = partition.Name() - state.Offset = 0 } + result <- list.Objects + capacity -= len(list.Objects) + if list.Continue == "" { + return nil + } + // loop again and get more data + state.Continue = list.Continue + state.PartitionName = partition.Name() + state.Offset = 0 } }) } diff --git a/pkg/stores/proxy/rbac_store.go b/pkg/stores/proxy/rbac_store.go index a5fc416..31c73bc 100644 --- a/pkg/stores/proxy/rbac_store.go +++ b/pkg/stores/proxy/rbac_store.go @@ -166,14 +166,13 @@ func isPassthroughUnconstrained(apiOp *types.APIRequest, schema *types.APISchema if apiOp.Namespace != "" { if resources[apiOp.Namespace].All { return nil, true - } else { - return []partition.Partition{ - Partition{ - Namespace: apiOp.Namespace, - Names: resources[apiOp.Namespace].Names, - }, - }, false } + return []partition.Partition{ + Partition{ + Namespace: apiOp.Namespace, + Names: resources[apiOp.Namespace].Names, + }, + }, false } var result []partition.Partition diff --git a/scripts/validate.sh b/scripts/validate.sh new file mode 100644 index 0000000..257ac7e --- /dev/null +++ b/scripts/validate.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e +golangci-lint run +go mod tidy +go mod verify +unclean=$(git status --porcelain --untracked-files=no) +if [ -n "$unclean" ]; then + echo "Encountered dirty repo!"; + echo "$unclean"; + exit 1; +fi From 95da447d9016afba34f064f2f64d851984d9015c Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 10 Oct 2022 11:49:05 -0700 Subject: [PATCH 06/36] cleanup: remove duplicate apiserver import Remove duplicate import and make aliasing of other apiserver imports consistent throughout steve. --- pkg/accesscontrol/access_control.go | 4 ++-- pkg/schema/collection.go | 4 ++-- pkg/server/handler/apiserver.go | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/accesscontrol/access_control.go b/pkg/accesscontrol/access_control.go index fb91a32..391cc7e 100644 --- a/pkg/accesscontrol/access_control.go +++ b/pkg/accesscontrol/access_control.go @@ -1,7 +1,7 @@ package accesscontrol import ( - "github.com/rancher/apiserver/pkg/server" + apiserver "github.com/rancher/apiserver/pkg/server" "github.com/rancher/apiserver/pkg/types" "github.com/rancher/steve/pkg/attributes" "github.com/rancher/wrangler/pkg/kv" @@ -9,7 +9,7 @@ import ( ) type AccessControl struct { - server.SchemaBasedAccess + apiserver.SchemaBasedAccess } func NewAccessControl() *AccessControl { diff --git a/pkg/schema/collection.go b/pkg/schema/collection.go index 48f23ea..405b495 100644 --- a/pkg/schema/collection.go +++ b/pkg/schema/collection.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/rancher/apiserver/pkg/server" + apiserver "github.com/rancher/apiserver/pkg/server" "github.com/rancher/apiserver/pkg/types" "github.com/rancher/steve/pkg/accesscontrol" "github.com/rancher/steve/pkg/attributes" @@ -55,7 +55,7 @@ type Template struct { StoreFactory func(types.Store) types.Store } -func WrapServer(factory Factory, server *server.Server) http.Handler { +func WrapServer(factory Factory, server *apiserver.Server) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { user, ok := request.UserFrom(req.Context()) if !ok { diff --git a/pkg/server/handler/apiserver.go b/pkg/server/handler/apiserver.go index aaf1a65..e0333ac 100644 --- a/pkg/server/handler/apiserver.go +++ b/pkg/server/handler/apiserver.go @@ -3,7 +3,6 @@ package handler import ( "net/http" - "github.com/rancher/apiserver/pkg/server" apiserver "github.com/rancher/apiserver/pkg/server" "github.com/rancher/apiserver/pkg/types" "github.com/rancher/apiserver/pkg/urlbuilder" @@ -26,7 +25,7 @@ func New(cfg *rest.Config, sf schema.Factory, authMiddleware auth.Middleware, ne a := &apiServer{ sf: sf, - server: server.DefaultAPIServer(), + server: apiserver.DefaultAPIServer(), } a.server.AccessControl = accesscontrol.NewAccessControl() @@ -55,7 +54,7 @@ func New(cfg *rest.Config, sf schema.Factory, authMiddleware auth.Middleware, ne type apiServer struct { sf schema.Factory - server *server.Server + server *apiserver.Server } func (a *apiServer) common(rw http.ResponseWriter, req *http.Request) (*types.APIRequest, bool) { From 18afa8868fd561c16cb27afff46ded529cab65cc Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 10 Oct 2022 11:50:11 -0700 Subject: [PATCH 07/36] cleanup: Remove unused function HandlerFromConfig was not used in steve or rancher. --- pkg/proxy/proxy.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 942644d..0006e5f 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -5,7 +5,6 @@ import ( "net/url" "strings" - "github.com/rancher/wrangler/pkg/kubeconfig" "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apiserver/pkg/authentication/user" @@ -14,17 +13,6 @@ import ( "k8s.io/client-go/transport" ) -// Mostly copied from "kubectl proxy" code -func HandlerFromConfig(prefix, kubeConfig string) (http.Handler, error) { - loader := kubeconfig.GetInteractiveClientConfig(kubeConfig) - cfg, err := loader.ClientConfig() - if err != nil { - return nil, err - } - - return Handler(prefix, cfg) -} - func ImpersonatingHandler(prefix string, cfg *rest.Config) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { impersonate(rw, req, prefix, cfg) From 70ea2828610cf8db956e1a89934458420d5d63f5 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 10 Oct 2022 11:52:26 -0700 Subject: [PATCH 08/36] cleanup: Fix API root header The header defined in RFC 9110[1] is "Accept", not "Accepts". This change corrects the route filter. Since this API is not documented and the header was plainly incorrect, no attempt is made at backwards compatibility. [1] https://www.rfc-editor.org/rfc/rfc9110.html#name-accept --- pkg/server/router/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 8ad7e75..87ca48f 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -22,7 +22,7 @@ func Routes(h Handlers) http.Handler { m.StrictSlash(true) m.Use(urlbuilder.RedirectRewrite) - m.Path("/").Handler(h.APIRoot).HeadersRegexp("Accepts", ".*json.*") + m.Path("/").Handler(h.APIRoot).HeadersRegexp("Accept", ".*json.*") m.Path("/{name:v1}").Handler(h.APIRoot) m.Path("/v1/{type}").Handler(h.K8sResource) From 475f311d0bf35d581f1d262eb6896402a0a90dcf Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 10 Oct 2022 12:00:10 -0700 Subject: [PATCH 09/36] cleanup: Remove unused switchstore store Usage of the switchstore store was removed when the clusters resource was removed in dcea1db. --- pkg/stores/switchstore/store.go | 59 --------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 pkg/stores/switchstore/store.go diff --git a/pkg/stores/switchstore/store.go b/pkg/stores/switchstore/store.go deleted file mode 100644 index 95dfb31..0000000 --- a/pkg/stores/switchstore/store.go +++ /dev/null @@ -1,59 +0,0 @@ -package switchstore - -import ( - "github.com/rancher/apiserver/pkg/types" -) - -type StorePicker func(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (types.Store, error) - -type Store struct { - Picker StorePicker -} - -func (e *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { - s, err := e.Picker(apiOp, schema, "delete", id) - if err != nil { - return types.APIObject{}, err - } - return s.Delete(apiOp, schema, id) -} - -func (e *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { - s, err := e.Picker(apiOp, schema, "get", id) - if err != nil { - return types.APIObject{}, err - } - return s.ByID(apiOp, schema, id) -} - -func (e *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { - s, err := e.Picker(apiOp, schema, "list", "") - if err != nil { - return types.APIObjectList{}, err - } - return s.List(apiOp, schema) -} - -func (e *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { - s, err := e.Picker(apiOp, schema, "create", "") - if err != nil { - return types.APIObject{}, err - } - return s.Create(apiOp, schema, data) -} - -func (e *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) { - s, err := e.Picker(apiOp, schema, "update", id) - if err != nil { - return types.APIObject{}, err - } - return s.Update(apiOp, schema, data, id) -} - -func (e *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { - s, err := e.Picker(apiOp, schema, "watch", "") - if err != nil { - return nil, err - } - return s.Watch(apiOp, schema, wr) -} From ea61c2187aba3b8c1a16fede2a021fcaf8d780ab Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 11 Oct 2022 12:28:51 -0700 Subject: [PATCH 10/36] cleanup: Remove unused namespace constraint The use of AddNamespaceConstraint was removed in e35b8304 of rancher/rancher, so there is no possibility of there being a namespace constraint in the request context. Remove the unused function and the unused codepath from the rbac store. --- pkg/stores/proxy/rbac_store.go | 45 ++-------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/pkg/stores/proxy/rbac_store.go b/pkg/stores/proxy/rbac_store.go index 31c73bc..30e6b80 100644 --- a/pkg/stores/proxy/rbac_store.go +++ b/pkg/stores/proxy/rbac_store.go @@ -1,9 +1,7 @@ package proxy import ( - "context" "fmt" - "net/http" "sort" "github.com/rancher/apiserver/pkg/types" @@ -20,19 +18,6 @@ var ( } ) -type filterKey struct{} - -func AddNamespaceConstraint(req *http.Request, names ...string) *http.Request { - set := sets.NewString(names...) - ctx := context.WithValue(req.Context(), filterKey{}, set) - return req.WithContext(ctx) -} - -func getNamespaceConstraint(req *http.Request) (sets.String, bool) { - set, ok := req.Context().Value(filterKey{}).(sets.String) - return set, ok -} - type Partition struct { Namespace string All bool @@ -128,35 +113,9 @@ func (b *byNameOrNamespaceStore) Watch(apiOp *types.APIRequest, schema *types.AP return b.Store.WatchNames(apiOp, schema, wr, b.partition.Names) } +// isPassthrough determines whether a request can be passed through directly to the underlying store +// or if the results need to be partitioned by namespace and name based on the requester's access. func isPassthrough(apiOp *types.APIRequest, schema *types.APISchema, verb string) ([]partition.Partition, bool) { - partitions, passthrough := isPassthroughUnconstrained(apiOp, schema, verb) - namespaces, ok := getNamespaceConstraint(apiOp.Request) - if !ok { - return partitions, passthrough - } - - var result []partition.Partition - - if passthrough { - for namespace := range namespaces { - result = append(result, Partition{ - Namespace: namespace, - All: true, - }) - } - return result, false - } - - for _, partition := range partitions { - if namespaces.Has(partition.Name()) { - result = append(result, partition) - } - } - - return result, false -} - -func isPassthroughUnconstrained(apiOp *types.APIRequest, schema *types.APISchema, verb string) ([]partition.Partition, bool) { accessListByVerb, _ := attributes.Access(schema).(accesscontrol.AccessListByVerb) if accessListByVerb.All(verb) { return nil, true From 72ab913c4c9dc9551e121ee62e37ccedd9600802 Mon Sep 17 00:00:00 2001 From: Kevin Joiner Date: Thu, 20 Oct 2022 00:16:01 -0400 Subject: [PATCH 11/36] Updates Schemas watch logic. Updated logic used to decide when and what schema events are sent during a watch. --- go.mod | 1 + go.sum | 1 + pkg/accesscontrol/access_store.go | 2 + pkg/accesscontrol/fake/AccessSetLookup.go | 62 +++ pkg/resources/schemas/template.go | 87 ++-- pkg/resources/schemas/template_test.go | 491 ++++++++++++++++++++++ pkg/schema/collection.go | 9 - pkg/schema/factory.go | 11 + pkg/schema/fake/factory.go | 110 +++++ 9 files changed, 738 insertions(+), 36 deletions(-) create mode 100644 pkg/accesscontrol/fake/AccessSetLookup.go create mode 100644 pkg/resources/schemas/template_test.go create mode 100644 pkg/schema/fake/factory.go diff --git a/go.mod b/go.mod index 33e6432..f2ee838 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ replace ( require ( github.com/adrg/xdg v0.3.1 + github.com/golang/mock v1.5.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/pborman/uuid v1.2.0 diff --git a/go.sum b/go.sum index 9b8c4d3..7a89a88 100644 --- a/go.sum +++ b/go.sum @@ -278,6 +278,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/pkg/accesscontrol/access_store.go b/pkg/accesscontrol/access_store.go index 5e51f97..562edf0 100644 --- a/pkg/accesscontrol/access_store.go +++ b/pkg/accesscontrol/access_store.go @@ -12,6 +12,8 @@ import ( "k8s.io/apiserver/pkg/authentication/user" ) +//go:generate mockgen --build_flags=--mod=mod -package fake -destination fake/AccessSetLookup.go "github.com/rancher/steve/pkg/accesscontrol" AccessSetLookup + type AccessSetLookup interface { AccessFor(user user.Info) *AccessSet PurgeUserData(id string) diff --git a/pkg/accesscontrol/fake/AccessSetLookup.go b/pkg/accesscontrol/fake/AccessSetLookup.go new file mode 100644 index 0000000..4164429 --- /dev/null +++ b/pkg/accesscontrol/fake/AccessSetLookup.go @@ -0,0 +1,62 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/rancher/steve/pkg/accesscontrol (interfaces: AccessSetLookup) + +// Package fake is a generated GoMock package. +package fake + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + accesscontrol "github.com/rancher/steve/pkg/accesscontrol" + user "k8s.io/apiserver/pkg/authentication/user" +) + +// MockAccessSetLookup is a mock of AccessSetLookup interface. +type MockAccessSetLookup struct { + ctrl *gomock.Controller + recorder *MockAccessSetLookupMockRecorder +} + +// MockAccessSetLookupMockRecorder is the mock recorder for MockAccessSetLookup. +type MockAccessSetLookupMockRecorder struct { + mock *MockAccessSetLookup +} + +// NewMockAccessSetLookup creates a new mock instance. +func NewMockAccessSetLookup(ctrl *gomock.Controller) *MockAccessSetLookup { + mock := &MockAccessSetLookup{ctrl: ctrl} + mock.recorder = &MockAccessSetLookupMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccessSetLookup) EXPECT() *MockAccessSetLookupMockRecorder { + return m.recorder +} + +// AccessFor mocks base method. +func (m *MockAccessSetLookup) AccessFor(arg0 user.Info) *accesscontrol.AccessSet { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AccessFor", arg0) + ret0, _ := ret[0].(*accesscontrol.AccessSet) + return ret0 +} + +// AccessFor indicates an expected call of AccessFor. +func (mr *MockAccessSetLookupMockRecorder) AccessFor(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessFor", reflect.TypeOf((*MockAccessSetLookup)(nil).AccessFor), arg0) +} + +// PurgeUserData mocks base method. +func (m *MockAccessSetLookup) PurgeUserData(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "PurgeUserData", arg0) +} + +// PurgeUserData indicates an expected call of PurgeUserData. +func (mr *MockAccessSetLookupMockRecorder) PurgeUserData(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurgeUserData", reflect.TypeOf((*MockAccessSetLookup)(nil).PurgeUserData), arg0) +} diff --git a/pkg/resources/schemas/template.go b/pkg/resources/schemas/template.go index 50b7bb8..99c47df 100644 --- a/pkg/resources/schemas/template.go +++ b/pkg/resources/schemas/template.go @@ -1,13 +1,13 @@ +// Package schemas handles streaming schema updates and changes. package schemas import ( "context" + "fmt" "sync" "time" "github.com/rancher/apiserver/pkg/builtin" - "k8s.io/apimachinery/pkg/api/equality" - schemastore "github.com/rancher/apiserver/pkg/store/schema" "github.com/rancher/apiserver/pkg/types" "github.com/rancher/steve/pkg/accesscontrol" @@ -15,10 +15,12 @@ import ( "github.com/rancher/wrangler/pkg/broadcast" "github.com/rancher/wrangler/pkg/schemas/validation" "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/endpoints/request" ) +// SetupWatcher create a new schema.Store for tracking schema changes func SetupWatcher(ctx context.Context, schemas *types.APISchemas, asl accesscontrol.AccessSetLookup, factory schema.Factory) { // one instance shared with all stores notifier := schemaChangeNotifier(ctx, factory) @@ -34,6 +36,7 @@ func SetupWatcher(ctx context.Context, schemas *types.APISchemas, asl accesscont schemas.AddSchema(schema) } +// Store hold information for watching updates to schemas type Store struct { types.Store @@ -42,14 +45,16 @@ type Store struct { schemaChangeNotify func(context.Context) (chan interface{}, error) } -func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) { +// Watch will return a APIevent channel that tracks changes to schemas for a user in a given APIRequest. +// Changes will be returned until Done is closed on the context in the given APIRequest. +func (s *Store) Watch(apiOp *types.APIRequest, _ *types.APISchema, _ types.WatchRequest) (chan types.APIEvent, error) { user, ok := request.UserFrom(apiOp.Request.Context()) if !ok { return nil, validation.Unauthorized } wg := sync.WaitGroup{} - wg.Add(2) + wg.Add(1) result := make(chan types.APIEvent) go func() { @@ -57,30 +62,38 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. close(result) }() - go func() { - defer wg.Done() - c, err := s.schemaChangeNotify(apiOp.Context()) - if err != nil { - return - } - schemas, err := s.sf.Schemas(user) - if err != nil { - logrus.Errorf("failed to generate schemas for user %v: %v", user, err) - return - } - for range c { - schemas = s.sendSchemas(result, apiOp, user, schemas) - } - }() + schemas, err := s.sf.Schemas(user) + if err != nil { + return nil, fmt.Errorf("failed to generate schemas for user '%v': %w", user, err) + } + + // Create child contexts that allows us to cancel both change notifications routines. + notifyCtx, notifyCancel := context.WithCancel(apiOp.Context()) + + schemaChangeSignal, err := s.schemaChangeNotify(notifyCtx) + if err != nil { + notifyCancel() + return nil, fmt.Errorf("failed to start schema change notifications: %w", err) + } + + userChangeSignal := s.userChangeNotify(notifyCtx, user) go func() { + defer notifyCancel() defer wg.Done() - schemas, err := s.sf.Schemas(user) - if err != nil { - logrus.Errorf("failed to generate schemas for notify user %v: %v", user, err) - return - } - for range s.userChangeNotify(apiOp.Context(), user) { + + // For each change notification send schema updates onto the result channel. + for { + select { + case _, ok := <-schemaChangeSignal: + if !ok { + return + } + case _, ok := <-userChangeSignal: + if !ok { + return + } + } schemas = s.sendSchemas(result, apiOp, user, schemas) } }() @@ -88,7 +101,9 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. return result, nil } +// sendSchemas will send APIEvents onto the provided result channel based on detected changes in the schemas for the provided users. func (s *Store) sendSchemas(result chan types.APIEvent, apiOp *types.APIRequest, user user.Info, oldSchemas *types.APISchemas) *types.APISchemas { + // get the current schemas for a user schemas, err := s.sf.Schemas(user) if err != nil { logrus.Errorf("failed to get schemas for %v: %v", user, err) @@ -96,9 +111,15 @@ func (s *Store) sendSchemas(result chan types.APIEvent, apiOp *types.APIRequest, } inNewSchemas := map[string]bool{} - for _, apiObject := range schemastore.FilterSchemas(apiOp, schemas.Schemas).Objects { + + // Convert the schemas for the given user to a flat list of APIObjects. + apiObjects := schemastore.FilterSchemas(apiOp, schemas.Schemas).Objects + for i := range apiObjects { + apiObject := apiObjects[i] inNewSchemas[apiObject.ID] = true eventName := types.ChangeAPIEvent + + // Check to see if the schema represented by the current APIObject exist in the oldSchemas. if oldSchema := oldSchemas.LookupSchema(apiObject.ID); oldSchema == nil { eventName = types.CreateAPIEvent } else { @@ -106,10 +127,15 @@ func (s *Store) sendSchemas(result chan types.APIEvent, apiOp *types.APIRequest, oldSchemaCopy := oldSchema.Schema.DeepCopy() newSchemaCopy.Mapper = nil oldSchemaCopy.Mapper = nil + + // APIObjects are intentionally stripped of access information. Thus we will remove the field when comparing changes. + delete(oldSchemaCopy.Attributes, "access") if equality.Semantic.DeepEqual(newSchemaCopy, oldSchemaCopy) { continue } } + + // Send the new or modified schema as an APIObject on the APIEvent channel. result <- types.APIEvent{ Name: eventName, ResourceType: "schema", @@ -117,7 +143,10 @@ func (s *Store) sendSchemas(result chan types.APIEvent, apiOp *types.APIRequest, } } - for _, oldSchema := range schemastore.FilterSchemas(apiOp, oldSchemas.Schemas).Objects { + // Identify all of the oldSchema APIObjects that have been removed and send Remove APIEvents. + oldSchemaObjs := schemastore.FilterSchemas(apiOp, oldSchemas.Schemas).Objects + for i := range oldSchemaObjs { + oldSchema := oldSchemaObjs[i] if inNewSchemas[oldSchema.ID] { continue } @@ -131,6 +160,9 @@ func (s *Store) sendSchemas(result chan types.APIEvent, apiOp *types.APIRequest, return schemas } +// userChangeNotify gets the provided users AccessSet every 2 seconds. +// If the AccessSet has changed the caller is notified via an empty struct sent on the returned channel. +// If the given context is finished then the returned channel will be closed. func (s *Store) userChangeNotify(ctx context.Context, user user.Info) chan interface{} { as := s.asl.AccessFor(user) result := make(chan interface{}) @@ -154,6 +186,7 @@ func (s *Store) userChangeNotify(ctx context.Context, user user.Info) chan inter return result } +// schemaChangeNotifier returns a channel that is used to signal OnChange was called for the provided factory. func schemaChangeNotifier(ctx context.Context, factory schema.Factory) func(ctx context.Context) (chan interface{}, error) { notify := make(chan interface{}) bcast := &broadcast.Broadcaster{} diff --git a/pkg/resources/schemas/template_test.go b/pkg/resources/schemas/template_test.go new file mode 100644 index 0000000..8ce53b3 --- /dev/null +++ b/pkg/resources/schemas/template_test.go @@ -0,0 +1,491 @@ +// Package schemas handles streaming schema updates and changes. +package schemas_test + +import ( + "context" + "encoding/json" + "net/http/httptest" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/steve/pkg/accesscontrol" + acfake "github.com/rancher/steve/pkg/accesscontrol/fake" + "github.com/rancher/steve/pkg/attributes" + "github.com/rancher/steve/pkg/resources/schemas" + schemafake "github.com/rancher/steve/pkg/schema/fake" + v1schema "github.com/rancher/wrangler/pkg/schemas" + "github.com/stretchr/testify/assert" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/request" +) + +var setupTimeout = time.Millisecond * 50 + +const resourceType = "schemas" + +func Test_WatchChangeDetection(t *testing.T) { + ctrl := gomock.NewController(t) + asl := acfake.NewMockAccessSetLookup(ctrl) + userInfo := &user.DefaultInfo{ + Name: "test", + UID: "test", + Groups: nil, + Extra: nil, + } + accessSet := &accesscontrol.AccessSet{} + // always return the same empty accessSet for the test user + asl.EXPECT().AccessFor(userInfo).Return(accessSet).AnyTimes() + + req := httptest.NewRequest("GET", "/", nil) + + type testValues struct { + expectedChanges []types.APIEvent + mockFactory *schemafake.MockFactory + eventsReady chan struct{} + } + tests := []struct { + name string + setup func(*gomock.Controller) testValues + }{ + { + name: "Schemas have no change", + setup: func(ctrl *gomock.Controller) testValues { + factory := schemafake.NewMockFactory(ctrl) + eventsReady := make(chan struct{}) + baseSchemas := types.EmptyAPISchemas() + updateSchemas := types.EmptyAPISchemas() + + testSchema := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "pod", + PluralName: "pods", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + + // initial schemas + baseSchemas.AddSchema(testSchema) + factory.EXPECT().Schemas(userInfo).Return(baseSchemas, nil) + + updateSchemas.AddSchema(testSchema) + + // return updated schemas for the second request + factory.EXPECT().Schemas(userInfo).DoAndReturn(func(_ user.Info) (*types.APISchemas, error) { + // signal that initial Schemas were called + close(eventsReady) + return updateSchemas, nil + }) + + expectedEvents := []types.APIEvent{} + return testValues{expectedEvents, factory, eventsReady} + }, + }, + { + name: "New schema is added to schemas.", + setup: func(ctrl *gomock.Controller) testValues { + factory := schemafake.NewMockFactory(ctrl) + eventsReady := make(chan struct{}) + baseSchemas := types.EmptyAPISchemas() + updateSchemas := types.EmptyAPISchemas() + + testSchema := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "pod", + PluralName: "pods", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + testSchemaNew := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "secret", + PluralName: "secrets", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + baseSchemas.AddSchema(testSchema) + // initial schemas + factory.EXPECT().Schemas(userInfo).Return(baseSchemas, nil) + + updateSchemas.AddSchema(testSchema) + updateSchemas.AddSchema((testSchemaNew)) + + // return updated schemas for the second request + factory.EXPECT().Schemas(userInfo).DoAndReturn(func(_ user.Info) (*types.APISchemas, error) { + // signal that initial Schemas were called + close(eventsReady) + return updateSchemas, nil + }) + + expectedEvents := []types.APIEvent{ + { + Name: types.CreateAPIEvent, + ResourceType: "schema", + Object: types.APIObject{ + Type: resourceType, + ID: testSchemaNew.ID, + Object: &testSchemaNew, + }, + }, + } + return testValues{expectedEvents, factory, eventsReady} + }, + }, + { + name: "Schema is deleted from schemas.", + setup: func(ctrl *gomock.Controller) testValues { + factory := schemafake.NewMockFactory(ctrl) + eventsReady := make(chan struct{}) + baseSchemas := types.EmptyAPISchemas() + updateSchemas := types.EmptyAPISchemas() + + testSchema := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "pod", + PluralName: "pods", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + testSchemaToDelete := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "secret", + PluralName: "secrets", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + baseSchemas.AddSchema(testSchema) + baseSchemas.AddSchema(testSchemaToDelete) + // initial schemas + factory.EXPECT().Schemas(userInfo).Return(baseSchemas, nil) + + updateSchemas.AddSchema(testSchema) + + // return updated schemas for the second request + factory.EXPECT().Schemas(userInfo).DoAndReturn(func(_ user.Info) (*types.APISchemas, error) { + // signal that initial Schemas were called + close(eventsReady) + return updateSchemas, nil + }) + + expectedEvents := []types.APIEvent{ + { + Name: types.RemoveAPIEvent, + ResourceType: "schema", + Object: types.APIObject{ + Type: resourceType, + ID: testSchemaToDelete.ID, + Object: &testSchemaToDelete, + }, + }, + } + return testValues{expectedEvents, factory, eventsReady} + }, + }, + { + name: "Empty Schemas", + setup: func(ctrl *gomock.Controller) testValues { + factory := schemafake.NewMockFactory(ctrl) + eventsReady := make(chan struct{}) + + // initial schemas + factory.EXPECT().Schemas(userInfo).Return(types.EmptyAPISchemas(), nil) + + // return updated schemas for the second request + factory.EXPECT().Schemas(userInfo).DoAndReturn(func(_ user.Info) (*types.APISchemas, error) { + // signal that initial Schemas were called + close(eventsReady) + return types.EmptyAPISchemas(), nil + }) + + return testValues{nil, factory, eventsReady} + }, + }, + { + name: "Schema kind attribute is updated", + setup: func(ctrl *gomock.Controller) testValues { + factory := schemafake.NewMockFactory(ctrl) + eventsReady := make(chan struct{}) + baseSchemas := types.EmptyAPISchemas() + updateSchemas := types.EmptyAPISchemas() + + testSchema := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "pod", + PluralName: "pods", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + baseSchemas.AddSchema(testSchema) + // initial schemas + factory.EXPECT().Schemas(userInfo).Return(baseSchemas, nil) + + // add kind attribute + attributes.SetKind(&testSchema, "newKind") + updateSchemas.AddSchema(testSchema) + + // return updated schemas for the second request + factory.EXPECT().Schemas(userInfo).DoAndReturn(func(_ user.Info) (*types.APISchemas, error) { + // signal that initial Schemas were called + close(eventsReady) + return updateSchemas, nil + }) + + expectedEvents := []types.APIEvent{ + { + Name: types.ChangeAPIEvent, + ResourceType: "schema", + Object: types.APIObject{ + Type: resourceType, + ID: testSchema.ID, + Object: &testSchema, + }, + }, + } + return testValues{expectedEvents, factory, eventsReady} + }, + }, + { + name: "Schema access attribute is updated", + setup: func(ctrl *gomock.Controller) testValues { + factory := schemafake.NewMockFactory(ctrl) + eventsReady := make(chan struct{}) + baseSchemas := types.EmptyAPISchemas() + updateSchemas := types.EmptyAPISchemas() + + testSchema := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "pod", + PluralName: "pods", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + baseSchemas.AddSchema(testSchema) + // initial schemas + factory.EXPECT().Schemas(userInfo).Return(baseSchemas, nil) + + // add access attribute + attributes.SetAccess(&testSchema, map[string]string{"List": "*"}) + updateSchemas.AddSchema(testSchema) + + // return updated schemas for the second request + factory.EXPECT().Schemas(userInfo).DoAndReturn(func(_ user.Info) (*types.APISchemas, error) { + // signal that schemas were requested + close(eventsReady) + return updateSchemas, nil + }) + + expectedEvents := []types.APIEvent{} + return testValues{expectedEvents, factory, eventsReady} + }, + }, + } + + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + t.Parallel() + // create new context for the test user + testCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + apiOp := &types.APIRequest{ + Request: req.WithContext(request.WithUser(testCtx, userInfo)), + } + + // create test factory + ctrl := gomock.NewController(t) + values := test.setup(ctrl) + + // store onChange cb use to trigger the notifier that will be set in schemas.SetupWatcher(..) + var onChangeCB func() + values.mockFactory.EXPECT().OnChange(gomock.AssignableToTypeOf(testCtx), gomock.AssignableToTypeOf(onChangeCB)). + Do(func(_ context.Context, cb func()) { + onChangeCB = cb + }) + + baseSchemas := types.EmptyAPISchemas() + + // create a new store and add it to baseSchemas + schemas.SetupWatcher(testCtx, baseSchemas, asl, values.mockFactory) + schema := baseSchemas.LookupSchema(resourceType) + + // Start watching + resultChan, err := schema.Store.Watch(apiOp, nil, types.WatchRequest{}) + assert.NoError(t, err, "Unexpected error starting Watch") + + // wait for the store's go routines to start watching for onChange events + time.Sleep(setupTimeout) + + // trigger watch notification that fetches new schemas + onChangeCB() + + select { + case <-values.eventsReady: + // New schema was requested now we sleep to give time for watcher to send events + time.Sleep(setupTimeout) + case <-time.After(setupTimeout): + // When we continue here then the test will fail due to missing mock calls not being called. + } + + // verify correct results are sent + hasExpectedResults(t, values.expectedChanges, resultChan, setupTimeout) + }) + } +} + +func Test_AccessSetAndChangeSignal(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + asl := acfake.NewMockAccessSetLookup(ctrl) + userInfo := &user.DefaultInfo{ + Name: "test", + UID: "test", + Groups: nil, + Extra: nil, + } + accessSet := &accesscontrol.AccessSet{} + changedSet := &accesscontrol.AccessSet{ID: "1"} + + // return access set with ID "" the first time then "1" for subsequent request + gomock.InOrder( + asl.EXPECT().AccessFor(userInfo).Return(accessSet), + asl.EXPECT().AccessFor(userInfo).Return(changedSet).AnyTimes(), + ) + + req := httptest.NewRequest("GET", "/", nil) + + factory := schemafake.NewMockFactory(ctrl) + baseSchemas := types.EmptyAPISchemas() + onChangeUpdateSchemas := types.EmptyAPISchemas() + userAccessUpdateSchemas := types.EmptyAPISchemas() + + testSchema := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "pod", + PluralName: "pods", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + + // initial schemas + baseSchemas.AddSchema(testSchema) + factory.EXPECT().Schemas(userInfo).Return(baseSchemas, nil) + + testSchemaNew := types.APISchema{ + Schema: &v1schema.Schema{ + ID: "secret", + PluralName: "secrets", + CollectionMethods: []string{"GET"}, + ResourceMethods: []string{"GET"}, + }, + } + onChangeUpdateSchemas.AddSchema(testSchema) + onChangeUpdateSchemas.AddSchema((testSchemaNew)) + + // return updated schemas with new schemas added + factory.EXPECT().Schemas(userInfo).Return(onChangeUpdateSchemas, nil) + + userAccessUpdateSchemas.AddSchema(testSchema) + + // return updated schemas with new schemas removed + factory.EXPECT().Schemas(userInfo).Return(userAccessUpdateSchemas, nil) + + expectedEvents := []types.APIEvent{ + { + Name: types.CreateAPIEvent, + ResourceType: "schema", + Object: types.APIObject{ + Type: resourceType, + ID: testSchemaNew.ID, + Object: &testSchemaNew, + }, + }, + { + Name: types.RemoveAPIEvent, + ResourceType: "schema", + Object: types.APIObject{ + Type: resourceType, + ID: testSchemaNew.ID, + Object: &testSchemaNew, + }, + }, + } + // create new context for the test user + testCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + apiOp := &types.APIRequest{ + Request: req.WithContext(request.WithUser(testCtx, userInfo)), + } + + // store onChange cb use to trigger the notifier that will be set in schemas.SetupWatcher(..) + var onChangeCB func() + factory.EXPECT().OnChange(gomock.AssignableToTypeOf(testCtx), gomock.AssignableToTypeOf(onChangeCB)). + Do(func(_ context.Context, cb func()) { + onChangeCB = cb + }) + + watcherSchema := types.EmptyAPISchemas() + + // create a new store and add it to watcherSchema + schemas.SetupWatcher(testCtx, watcherSchema, asl, factory) + schema := watcherSchema.LookupSchema(resourceType) + + // Start watching + resultChan, err := schema.Store.Watch(apiOp, nil, types.WatchRequest{}) + assert.NoError(t, err, "Unexpected error starting Watch") + + // wait for the store's go routines to start watching for onChange events + time.Sleep(setupTimeout) + + // trigger watch notification that fetches new schemas + onChangeCB() + + // wait for user access set to be checked (2 seconds) + time.Sleep(time.Millisecond * 2100) + + // verify correct results are sent + hasExpectedResults(t, expectedEvents, resultChan, setupTimeout) + +} + +// hasExpectedResults verifies the list of expected apiEvents are all received from the provided channel. +func hasExpectedResults(t *testing.T, expectedEvents []types.APIEvent, resultChan chan types.APIEvent, timeout time.Duration) { + t.Helper() + numEventsSent := 0 + for { + select { + case event, ok := <-resultChan: + if !ok { + if numEventsSent == len(expectedEvents) { + // we got everything we expect + return + } + assert.Fail(t, "result channel unexpectedly closed") + } + if numEventsSent >= len(expectedEvents) { + assert.Failf(t, "too many events", "received unexpected events on channel %+v", event) + return + } + eventJSON, err := json.Marshal(event) + assert.NoError(t, err, "failed to marshal new event") + expectedJSON, err := json.Marshal(event) + assert.NoError(t, err, "failed to marshal expected event") + assert.JSONEq(t, string(expectedJSON), string(eventJSON), "incorrect event received") + + case <-time.After(timeout): + if numEventsSent != len(expectedEvents) { + assert.Fail(t, "timeout waiting for results") + } + return + } + numEventsSent++ + } +} diff --git a/pkg/schema/collection.go b/pkg/schema/collection.go index 48f23ea..d24cae3 100644 --- a/pkg/schema/collection.go +++ b/pkg/schema/collection.go @@ -14,18 +14,9 @@ import ( "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/cache" - "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/endpoints/request" ) -type Factory interface { - Schemas(user user.Info) (*types.APISchemas, error) - ByGVR(gvr schema.GroupVersionResource) string - ByGVK(gvr schema.GroupVersionKind) string - OnChange(ctx context.Context, cb func()) - AddTemplate(template ...Template) -} - type Collection struct { toSync int32 baseSchema *types.APISchemas diff --git a/pkg/schema/factory.go b/pkg/schema/factory.go index b6f0e4b..b3fbe4a 100644 --- a/pkg/schema/factory.go +++ b/pkg/schema/factory.go @@ -1,6 +1,8 @@ package schema +//go:generate mockgen --build_flags=--mod=mod -package fake -destination fake/factory.go "github.com/rancher/steve/pkg/schema" Factory import ( + "context" "fmt" "net/http" "time" @@ -9,9 +11,18 @@ import ( "github.com/rancher/apiserver/pkg/types" "github.com/rancher/steve/pkg/accesscontrol" "github.com/rancher/steve/pkg/attributes" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authentication/user" ) +type Factory interface { + Schemas(user user.Info) (*types.APISchemas, error) + ByGVR(gvr schema.GroupVersionResource) string + ByGVK(gvr schema.GroupVersionKind) string + OnChange(ctx context.Context, cb func()) + AddTemplate(template ...Template) +} + func newSchemas() (*types.APISchemas, error) { apiSchemas := types.EmptyAPISchemas() if err := apiSchemas.AddSchemas(builtin.Schemas); err != nil { diff --git a/pkg/schema/fake/factory.go b/pkg/schema/fake/factory.go new file mode 100644 index 0000000..9f7d7e7 --- /dev/null +++ b/pkg/schema/fake/factory.go @@ -0,0 +1,110 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/rancher/steve/pkg/schema (interfaces: Factory) + +// Package fake is a generated GoMock package. +package fake + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + types "github.com/rancher/apiserver/pkg/types" + schema "github.com/rancher/steve/pkg/schema" + schema0 "k8s.io/apimachinery/pkg/runtime/schema" + user "k8s.io/apiserver/pkg/authentication/user" +) + +// MockFactory is a mock of Factory interface. +type MockFactory struct { + ctrl *gomock.Controller + recorder *MockFactoryMockRecorder +} + +// MockFactoryMockRecorder is the mock recorder for MockFactory. +type MockFactoryMockRecorder struct { + mock *MockFactory +} + +// NewMockFactory creates a new mock instance. +func NewMockFactory(ctrl *gomock.Controller) *MockFactory { + mock := &MockFactory{ctrl: ctrl} + mock.recorder = &MockFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFactory) EXPECT() *MockFactoryMockRecorder { + return m.recorder +} + +// AddTemplate mocks base method. +func (m *MockFactory) AddTemplate(arg0 ...schema.Template) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "AddTemplate", varargs...) +} + +// AddTemplate indicates an expected call of AddTemplate. +func (mr *MockFactoryMockRecorder) AddTemplate(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTemplate", reflect.TypeOf((*MockFactory)(nil).AddTemplate), arg0...) +} + +// ByGVK mocks base method. +func (m *MockFactory) ByGVK(arg0 schema0.GroupVersionKind) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ByGVK", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// ByGVK indicates an expected call of ByGVK. +func (mr *MockFactoryMockRecorder) ByGVK(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByGVK", reflect.TypeOf((*MockFactory)(nil).ByGVK), arg0) +} + +// ByGVR mocks base method. +func (m *MockFactory) ByGVR(arg0 schema0.GroupVersionResource) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ByGVR", arg0) + ret0, _ := ret[0].(string) + return ret0 +} + +// ByGVR indicates an expected call of ByGVR. +func (mr *MockFactoryMockRecorder) ByGVR(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByGVR", reflect.TypeOf((*MockFactory)(nil).ByGVR), arg0) +} + +// OnChange mocks base method. +func (m *MockFactory) OnChange(arg0 context.Context, arg1 func()) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OnChange", arg0, arg1) +} + +// OnChange indicates an expected call of OnChange. +func (mr *MockFactoryMockRecorder) OnChange(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnChange", reflect.TypeOf((*MockFactory)(nil).OnChange), arg0, arg1) +} + +// Schemas mocks base method. +func (m *MockFactory) Schemas(arg0 user.Info) (*types.APISchemas, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Schemas", arg0) + ret0, _ := ret[0].(*types.APISchemas) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Schemas indicates an expected call of Schemas. +func (mr *MockFactoryMockRecorder) Schemas(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Schemas", reflect.TypeOf((*MockFactory)(nil).Schemas), arg0) +} From 1f2f28e0e224d5fd0d5b84ec754de3c4e89fd3fa Mon Sep 17 00:00:00 2001 From: Kevin Joiner Date: Thu, 27 Oct 2022 22:13:21 -0400 Subject: [PATCH 12/36] Add go generate to CI --- .drone.yml | 35 ++++++++++++++++++----------------- scripts/validate.sh | 7 ++++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0902f3a..fe090d8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,17 +3,17 @@ kind: pipeline name: fossa steps: -- name: fossa - image: rancher/drone-fossa:latest - settings: - api_key: - from_secret: FOSSA_API_KEY - when: - instance: - include: - - drone-publish.rancher.io - exclude: - - drone-pr.rancher.io + - name: fossa + image: rancher/drone-fossa:latest + settings: + api_key: + from_secret: FOSSA_API_KEY + when: + instance: + include: + - drone-publish.rancher.io + exclude: + - drone-pr.rancher.io --- kind: pipeline name: build @@ -22,11 +22,11 @@ steps: - name: build image: registry.suse.com/bci/golang:1.19 commands: - - make build-bin + - make build-bin when: event: - - push - - pull_request + - push + - pull_request --- kind: pipeline name: validate @@ -38,6 +38,7 @@ steps: - zypper in -y go=1.19 git tar gzip make - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.49.0 - mv ./bin/golangci-lint /usr/local/bin/golangci-lint + - GOBIN=/usr/local/bin go install github.com/golang/mock/mockgen@v1.6.0 - make validate when: event: @@ -51,8 +52,8 @@ steps: - name: test image: registry.suse.com/bci/golang:1.19 commands: - - make test + - make test when: event: - - push - - pull_request \ No newline at end of file + - push + - pull_request diff --git a/scripts/validate.sh b/scripts/validate.sh index 257ac7e..4687315 100644 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -1,12 +1,13 @@ #!/bin/bash set -e +go generate ./.. golangci-lint run go mod tidy go mod verify unclean=$(git status --porcelain --untracked-files=no) if [ -n "$unclean" ]; then - echo "Encountered dirty repo!"; - echo "$unclean"; - exit 1; + echo "Encountered dirty repo!" + echo "$unclean" + exit 1 fi From b16d502fc79b8071f7dc56e8a77c6086a97e8a6f Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 14 Oct 2022 16:17:32 -0700 Subject: [PATCH 13/36] docs: Add docs for partition and proxy store --- pkg/stores/partition/parallel.go | 64 +++++++++++++++++++++++++------ pkg/stores/partition/store.go | 20 ++++++++-- pkg/stores/proxy/error_wrapper.go | 8 +++- pkg/stores/proxy/proxy_store.go | 22 +++++++++++ pkg/stores/proxy/rbac_store.go | 11 ++++++ pkg/stores/proxy/watch_refresh.go | 2 + 6 files changed, 111 insertions(+), 16 deletions(-) diff --git a/pkg/stores/partition/parallel.go b/pkg/stores/partition/parallel.go index 1b77486..4e9f3e5 100644 --- a/pkg/stores/partition/parallel.go +++ b/pkg/stores/partition/parallel.go @@ -10,29 +10,42 @@ import ( "golang.org/x/sync/semaphore" ) +// Partition represents a named grouping of kubernetes resources, +// such as by namespace or a set of names. type Partition interface { Name() string } +// ParallelPartitionLister defines how a set of partitions will be queried. type ParallelPartitionLister struct { - Lister PartitionLister + // Lister is the lister method for a single partition. + Lister PartitionLister + + // Concurrency is the weight of the semaphore. Concurrency int64 - Partitions []Partition - state *listState - revision string - err error + + // Partitions is the set of partitions that will be concurrently queried. + Partitions []Partition + + state *listState + revision string + err error } +// PartitionLister lists objects for one partition. type PartitionLister func(ctx context.Context, partition Partition, cont string, revision string, limit int) (types.APIObjectList, error) +// Err returns the latest error encountered. func (p *ParallelPartitionLister) Err() error { return p.err } +// Revision returns the revision for the current list state. func (p *ParallelPartitionLister) Revision() string { return p.revision } +// Continue returns the encoded continue token based on the current list state. func (p *ParallelPartitionLister) Continue() string { if p.state == nil { return "" @@ -56,6 +69,9 @@ func indexOrZero(partitions []Partition, name string) int { return 0 } +// List returns a stream of objects up to the requested limit. +// If the continue token is not empty, it decodes it and returns the stream +// starting at the indicated marker. func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume string) (<-chan []types.APIObject, error) { var state listState if resume != "" { @@ -77,14 +93,33 @@ func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume st return result, nil } +// listState is a representation of the continuation point for a partial list. +// It is encoded as the continue token in the returned response. type listState struct { - Revision string `json:"r,omitempty"` + // Revision is the resourceVersion for the List object. + Revision string `json:"r,omitempty"` + + // PartitionName is the name of the partition. PartitionName string `json:"p,omitempty"` - Continue string `json:"c,omitempty"` - Offset int `json:"o,omitempty"` - Limit int `json:"l,omitempty"` + + // Continue is the continue token returned from Kubernetes for a partially filled list request. + // It is a subfield of the continue token returned from steve. + Continue string `json:"c,omitempty"` + + // Offset is the offset from the start of the list within the partition to begin the result list. + Offset int `json:"o,omitempty"` + + // Limit is the maximum number of items from all partitions to return in the result. + Limit int `json:"l,omitempty"` } +// feeder spawns a goroutine to list resources in each partition and feeds the +// results, in order by partition index, into a channel. +// If the sum of the results from all partitions (by namespaces or names) is +// greater than the limit parameter from the user request or the default of +// 100000, the result is truncated and a continue token is generated that +// indicates the partition and offset for the client to start on in the next +// request. func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, limit int, result chan []types.APIObject) { var ( sem = semaphore.NewWeighted(p.Concurrency) @@ -116,6 +151,7 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l // setup a linked list of channel to control insertion order last = next + // state.Revision is decoded from the continue token, there won't be a revision on the first request. if state.Revision == "" { // don't have a revision yet so grab all tickets to set a revision tickets = 3 @@ -125,7 +161,7 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l break } - // make state local + // make state local for this partition state := state eg.Go(func() error { defer sem.Release(tickets) @@ -154,10 +190,13 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l p.revision = list.Revision } + // We have already seen the first objects in the list, truncate up to the offset. if state.PartitionName == partition.Name() && state.Offset > 0 && state.Offset < len(list.Objects) { list.Objects = list.Objects[state.Offset:] } + // Case 1: the capacity has been reached across all goroutines but the list is still only partial, + // so save the state so that the next page can be requested later. if len(list.Objects) > capacity { result <- list.Objects[:capacity] // save state to redo this list at this offset @@ -173,10 +212,13 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l } result <- list.Objects capacity -= len(list.Objects) + // Case 2: all objects have been returned, we are done. if list.Continue == "" { return nil } - // loop again and get more data + // Case 3: we started at an offset and truncated the list to skip the objects up to the offset. + // We're not yet up to capacity and have not retrieved every object, + // so loop again and get more data. state.Continue = list.Continue state.PartitionName = partition.Name() state.Offset = 0 diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index 547eec1..4bb5340 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -9,12 +9,16 @@ import ( "golang.org/x/sync/errgroup" ) +const defaultLimit = 100000 + +// Partitioner is an interface for interacting with partitions. type Partitioner interface { Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (Partition, error) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]Partition, error) Store(apiOp *types.APIRequest, partition Partition) (types.Store, error) } +// Store implements types.Store for partitions. type Store struct { Partitioner Partitioner } @@ -28,6 +32,7 @@ func (s *Store) getStore(apiOp *types.APIRequest, schema *types.APISchema, verb, return s.Partitioner.Store(apiOp, p) } +// Delete deletes an object from a store. func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { target, err := s.getStore(apiOp, schema, "delete", id) if err != nil { @@ -37,6 +42,7 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri return target.Delete(apiOp, schema, id) } +// ByID looks up a single object by its ID. func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { target, err := s.getStore(apiOp, schema, "get", id) if err != nil { @@ -69,12 +75,14 @@ func (s *Store) listPartition(ctx context.Context, apiOp *types.APIRequest, sche return store.List(req, schema) } +// List returns a list of objects across all applicable partitions. +// If pagination parameters are used, it returns a segment of the list. func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { var ( result types.APIObjectList ) - paritions, err := s.Partitioner.All(apiOp, schema, "list", "") + partitions, err := s.Partitioner.All(apiOp, schema, "list", "") if err != nil { return result, err } @@ -84,7 +92,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP return s.listPartition(ctx, apiOp, schema, partition, cont, revision, limit) }, Concurrency: 3, - Partitions: paritions, + Partitions: partitions, } resume := apiOp.Request.URL.Query().Get("continue") @@ -104,6 +112,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP return result, lister.Err() } +// Create creates a single object in the store. func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { target, err := s.getStore(apiOp, schema, "create", "") if err != nil { @@ -113,6 +122,7 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data ty return target.Create(apiOp, schema, data) } +// Update updates a single object in the store. func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) { target, err := s.getStore(apiOp, schema, "update", id) if err != nil { @@ -122,6 +132,7 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data ty return target.Update(apiOp, schema, data, id) } +// Watch returns a channel of events for a list or resource. func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { partitions, err := s.Partitioner.All(apiOp, schema, "watch", wr.ID) if err != nil { @@ -164,6 +175,9 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types return response, nil } +// getLimit extracts the limit parameter from the request or sets a default of 100000. +// Since a default is always set, this implies that clients must always be +// aware that the list may be incomplete. func getLimit(req *http.Request) int { limitString := req.URL.Query().Get("limit") limit, err := strconv.Atoi(limitString) @@ -171,7 +185,7 @@ func getLimit(req *http.Request) int { limit = 0 } if limit <= 0 { - limit = 100000 + limit = defaultLimit } return limit } diff --git a/pkg/stores/proxy/error_wrapper.go b/pkg/stores/proxy/error_wrapper.go index 4aa9fa4..01af8d1 100644 --- a/pkg/stores/proxy/error_wrapper.go +++ b/pkg/stores/proxy/error_wrapper.go @@ -11,34 +11,38 @@ type errorStore struct { types.Store } +// ByID looks up a single object by its ID. func (e *errorStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { data, err := e.Store.ByID(apiOp, schema, id) return data, translateError(err) } +// List returns a list of resources. func (e *errorStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { data, err := e.Store.List(apiOp, schema) return data, translateError(err) } +// Create creates a single object in the store. func (e *errorStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { data, err := e.Store.Create(apiOp, schema, data) return data, translateError(err) - } +// Update updates a single object in the store. func (e *errorStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) { data, err := e.Store.Update(apiOp, schema, data, id) return data, translateError(err) - } +// Delete deletes an object from a store. func (e *errorStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { data, err := e.Store.Delete(apiOp, schema, id) return data, translateError(err) } +// Watch returns a channel of events for a list or resource. func (e *errorStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { data, err := e.Store.Watch(apiOp, schema, wr) return data, translateError(err) diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index ce9fd99..8299740 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -46,6 +46,7 @@ func init() { metav1.AddToGroupVersion(paramScheme, metav1.SchemeGroupVersion) } +// ClientGetter is a dynamic kubernetes client factory. type ClientGetter interface { IsImpersonating() bool K8sInterface(ctx *types.APIRequest) (kubernetes.Interface, error) @@ -59,15 +60,18 @@ type ClientGetter interface { TableAdminClientForWatch(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) } +// RelationshipNotifier is an interface for handling wrangler summary.Relationship events. type RelationshipNotifier interface { OnInboundRelationshipChange(ctx context.Context, schema *types.APISchema, namespace string) <-chan *summary.Relationship } +// Store implements types.Store directly on top of kubernetes. type Store struct { clientGetter ClientGetter notifier RelationshipNotifier } +// NewProxyStore returns a wrapped types.Store. func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, lookup accesscontrol.AccessSetLookup) types.Store { return &errorStore{ Store: &WatchRefresh{ @@ -84,6 +88,7 @@ func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, loo } } +// ByID looks up a single object by its ID. func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { result, err := s.byID(apiOp, schema, apiOp.Namespace, id) return toAPI(schema, result), err @@ -219,6 +224,12 @@ func tableToObjects(obj map[string]interface{}) []unstructured.Unstructured { return result } +// ByNames filters a list of objects by an allowed set of names. +// In plain kubernetes, if a user has permission to 'list' or 'watch' a defined set of resource names, +// performing the list or watch will result in a Forbidden error, because the user does not have permission +// to list *all* resources. +// With this filter, the request can be performed successfully, and only the allowed resources will +// be returned in the list. func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names sets.String) (types.APIObjectList, error) { if apiOp.Namespace == "*" { // This happens when you grant namespaced objects with "get" by name in a clusterrolebinding. We will treat @@ -247,6 +258,7 @@ func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names return objs, nil } +// List returns a list of resources. func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { client, err := s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace) if err != nil { @@ -360,6 +372,12 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt return } +// WatchNames returns a channel of events filtered by an allowed set of names. +// In plain kubernetes, if a user has permission to 'list' or 'watch' a defined set of resource names, +// performing the list or watch will result in a Forbidden error, because the user does not have permission +// to list *all* resources. +// With this filter, the request can be performed successfully, and only the allowed resources will +// be returned in watch. func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, names sets.String) (chan types.APIEvent, error) { adminClient, err := s.clientGetter.TableAdminClientForWatch(apiOp, schema, apiOp.Namespace) if err != nil { @@ -383,6 +401,7 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t return result, nil } +// Watch returns a channel of events for a list or resource. func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) { client, err := s.clientGetter.TableClientForWatch(apiOp, schema, apiOp.Namespace) if err != nil { @@ -428,6 +447,7 @@ func (s *Store) toAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, et return event } +// Create creates a single object in the store. func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject) (types.APIObject, error) { var ( resp *unstructured.Unstructured @@ -468,6 +488,7 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params return apiObject, err } +// Update updates a single object in the store. func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject, id string) (types.APIObject, error) { var ( err error @@ -535,6 +556,7 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params return toAPI(schema, resp), nil } +// Delete deletes an object from a store. func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { opts := metav1.DeleteOptions{} if err := decodeParams(apiOp, &opts); err != nil { diff --git a/pkg/stores/proxy/rbac_store.go b/pkg/stores/proxy/rbac_store.go index 30e6b80..1562560 100644 --- a/pkg/stores/proxy/rbac_store.go +++ b/pkg/stores/proxy/rbac_store.go @@ -18,6 +18,7 @@ var ( } ) +// Partition is an implementation of the partition.Partition interface that uses RBAC to determine how a set of resources should be segregated and accessed. type Partition struct { Namespace string All bool @@ -25,14 +26,18 @@ type Partition struct { Names sets.String } +// Name returns the name of the partition, which for this type is the namespace. func (p Partition) Name() string { return p.Namespace } +// rbacPartitioner is an implementation of the partition.Partioner interface. type rbacPartitioner struct { proxyStore *Store } +// Lookup returns the default passthrough partition which is used only for retrieving single resources. +// Listing or watching resources require custom partitions. func (p *rbacPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (partition.Partition, error) { switch verb { case "create": @@ -48,6 +53,9 @@ func (p *rbacPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchem } } +// All returns a slice of partitions applicable to the API schema and the user's access level. +// For watching individual resources or for blanket access permissions, it returns the passthrough partition. +// For more granular permissions, it returns a slice of partitions matching an allowed namespace or resource names. func (p *rbacPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]partition.Partition, error) { switch verb { case "list": @@ -77,6 +85,7 @@ func (p *rbacPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, } } +// Store returns a proxy Store suited to listing and watching resources by partition. func (p *rbacPartitioner) Store(apiOp *types.APIRequest, partition partition.Partition) (types.Store, error) { return &byNameOrNamespaceStore{ Store: p.proxyStore, @@ -89,6 +98,7 @@ type byNameOrNamespaceStore struct { partition Partition } +// List returns a list of resources by partition. func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { if b.partition.Passthrough { return b.Store.List(apiOp, schema) @@ -101,6 +111,7 @@ func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.API return b.Store.ByNames(apiOp, schema, b.partition.Names) } +// Watch returns a channel of resources by partition. func (b *byNameOrNamespaceStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { if b.partition.Passthrough { return b.Store.Watch(apiOp, schema, wr) diff --git a/pkg/stores/proxy/watch_refresh.go b/pkg/stores/proxy/watch_refresh.go index 7674a16..b6e1929 100644 --- a/pkg/stores/proxy/watch_refresh.go +++ b/pkg/stores/proxy/watch_refresh.go @@ -9,11 +9,13 @@ import ( "k8s.io/apiserver/pkg/endpoints/request" ) +// WatchRefresh implements types.Store with awareness of changes to the requester's access. type WatchRefresh struct { types.Store asl accesscontrol.AccessSetLookup } +// Watch performs a watch request which halts if the user's access level changes. func (w *WatchRefresh) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { user, ok := request.UserFrom(apiOp.Context()) if !ok { From 3df95d23efbba395bff2b3cf5fbee360d854c6d3 Mon Sep 17 00:00:00 2001 From: Jiaqi Luo <6218999+jiaqiluo@users.noreply.github.com> Date: Mon, 28 Nov 2022 16:19:49 -0700 Subject: [PATCH 14/36] bump k8s and rancher modules to support 1.25 --- go.mod | 68 ++++----- go.sum | 427 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 255 insertions(+), 240 deletions(-) diff --git a/go.mod b/go.mod index f2ee838..fe621a3 100644 --- a/go.mod +++ b/go.mod @@ -7,36 +7,36 @@ replace ( github.com/knative/pkg => github.com/rancher/pkg v0.0.0-20190514055449-b30ab9de040e github.com/matryer/moq => github.com/rancher/moq v0.0.0-20190404221404-ee5226d43009 - k8s.io/client-go => github.com/rancher/client-go v1.24.0-rancher1 + k8s.io/client-go => github.com/rancher/client-go v1.25.4-rancher1 ) require ( github.com/adrg/xdg v0.3.1 - github.com/golang/mock v1.5.0 + github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 - github.com/rancher/apiserver v0.0.0-20210922180056-297b6df8d714 + github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076 github.com/rancher/dynamiclistener v0.2.1-0.20200714201033-9c1939da3af9 github.com/rancher/kubernetes-provider-detector v0.1.2 - github.com/rancher/norman v0.0.0-20210423002317-8e6ffc77a819 + github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a - github.com/rancher/wrangler v0.8.11-0.20211214201934-f5aa5d9f2e81 + github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1 github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.0 github.com/urfave/cli v1.22.2 github.com/urfave/cli/v2 v2.1.1 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - k8s.io/api v0.24.0 - k8s.io/apiextensions-apiserver v0.24.0 - k8s.io/apimachinery v0.24.0 - k8s.io/apiserver v0.24.0 + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 + k8s.io/api v0.25.4 + k8s.io/apiextensions-apiserver v0.25.4 + k8s.io/apimachinery v0.25.4 + k8s.io/apiserver v0.25.4 k8s.io/client-go v12.0.0+incompatible k8s.io/klog v1.0.0 - k8s.io/kube-aggregator v0.24.0 - k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 + k8s.io/kube-aggregator v0.25.4 + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 ) require ( @@ -47,22 +47,22 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful v2.9.5+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-logr/logr v1.2.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.5 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/imdario/mergo v0.3.8 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect @@ -75,7 +75,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08 // indirect + github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.opentelemetry.io/contrib v0.20.0 // indirect @@ -88,26 +88,26 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect go.opentelemetry.io/otel/trace v0.20.0 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect - golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect + golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect - google.golang.org/grpc v1.40.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.47.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/component-base v0.24.0 // indirect - k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect - sigs.k8s.io/cli-utils v0.16.0 // indirect - sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/component-base v0.25.4 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 // indirect + sigs.k8s.io/cli-utils v0.27.0 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 7a89a88..94d760a 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,13 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= 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= @@ -37,14 +44,16 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl 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/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -53,7 +62,6 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YH github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -70,9 +78,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -89,7 +95,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -107,16 +115,18 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -135,7 +145,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -150,12 +159,11 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.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= @@ -163,10 +171,12 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= @@ -174,11 +184,10 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -198,10 +207,11 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -237,12 +247,10 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= @@ -253,19 +261,18 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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= @@ -278,10 +285,10 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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= @@ -300,14 +307,11 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 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/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= 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/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= -github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -320,15 +324,17 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/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/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 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= @@ -340,32 +346,38 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/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/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= @@ -385,7 +397,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 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/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -396,9 +407,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -406,7 +416,6 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -425,7 +434,6 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL 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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -440,6 +448,7 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -448,12 +457,11 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/maruel/panicparse v0.0.0-20171209025017-c0182c169410/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= -github.com/maruel/ut v1.0.0/go.mod h1:I68ffiAt5qre9obEVTy7S2/fj2dJku2NYLvzPuY0gqE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -468,18 +476,16 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -489,47 +495,51 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -543,7 +553,6 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= @@ -552,7 +561,6 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -561,30 +569,24 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= -github.com/rancher/apiserver v0.0.0-20210922180056-297b6df8d714 h1:DJPC3bY+yy8Ne4ge2FYs7k6J0CWERTL47hlKXTCgPYs= -github.com/rancher/apiserver v0.0.0-20210922180056-297b6df8d714/go.mod h1:8W0EwaR9dH5NDFw6mpAX437D0q+EZqKWbZyX71+z2WI= -github.com/rancher/client-go v1.24.0-rancher1 h1:3Hr+QgZRbTo3RF8evVwGd8hn2ZFO6UMUMnRth2CbJcI= -github.com/rancher/client-go v1.24.0-rancher1/go.mod h1:J+jC4WE19J7G2gTyKYvlGroIAdDgUb/Gsr3wyf2SOCQ= +github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076 h1:wS95KbXFI1QOVQr3Tz+qyOJ9iia1ITCnjsapxJyI/9U= +github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= +github.com/rancher/client-go v1.25.4-rancher1 h1:9MlBC8QbgngUkhNzMR8rZmmCIj6WNRHFOnYiwC2Kty4= +github.com/rancher/client-go v1.25.4-rancher1/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= github.com/rancher/dynamiclistener v0.2.1-0.20200714201033-9c1939da3af9 h1:Mo5mPXi7k/TgzMcUIuDpbNxiX2bYh68+yEpaur5Nx80= github.com/rancher/dynamiclistener v0.2.1-0.20200714201033-9c1939da3af9/go.mod h1:qr0QfhwzcVCR+Ao9WyfnE+jmOpfEAdRhXtNOZGJ3nCQ= github.com/rancher/kubernetes-provider-detector v0.1.2 h1:iFfmmcZiGya6s3cS4Qxksyqqw5hPbbIDHgKJ2Y44XKM= github.com/rancher/kubernetes-provider-detector v0.1.2/go.mod h1:ypuJS7kP7rUiAn330xG46mj+Nhvym05GM8NqMVekpH0= github.com/rancher/lasso v0.0.0-20200515155337-a34e1e26ad91/go.mod h1:G6Vv2aj6xB2YjTVagmu4NkhBvbE8nBcGykHRENH6arI= -github.com/rancher/lasso v0.0.0-20200820172840-0e4cc0ef5cb0/go.mod h1:OhBBBO1pBwYp0hacWdnvSGOj+XE9yMLOLnaypIlic18= -github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08 h1:NxR8Fh0eE7/5/5Zvlog9B5NVjWKqBSb1WYMUF7/IE5c= -github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08/go.mod h1:9qZd/S8DqWzfKtjKGgSoHqGEByYmUE3qRaBaaAHwfEM= -github.com/rancher/moq v0.0.0-20190404221404-ee5226d43009/go.mod h1:wpITyDPTi/Na5h73XkbuEf2AP9fbgrIGqqxVzFhYD6U= -github.com/rancher/norman v0.0.0-20210423002317-8e6ffc77a819 h1:K3pICsdBbzOHoOyWdEjCxd8vApLpk8qwBI5VNCQQsM0= -github.com/rancher/norman v0.0.0-20210423002317-8e6ffc77a819/go.mod h1:hhnf77V2lmZD7cvUqi4vTBpIs3KpHNL/AmuN0MqEClI= +github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c h1:RKGa+6plIHqyfBcC+lnCs3oe7A8wSFkUPBtqWBAi/1E= +github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c/go.mod h1:Qewwr/xGzhtG7WCxhubrKZrdAobv5yqAIuHbDoxThZQ= +github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 h1:+Oob+DG+SZoX8hnKBWpZfVwxx6K84hMou9nbBOTux7w= +github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99/go.mod h1:zpv7z4ySYL5LlEBKEPf/xf3cjx837/J2i/wHpT43viE= github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a h1:Go8MpBEeZCR0yV1ylu2/KjJBvpYomIezU58pejYCtgk= github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a/go.mod h1:vq3LvyOFnLcwMiCE1KdW3foPd6g5kAjZOtOb7JqGHck= -github.com/rancher/wrangler v0.6.1/go.mod h1:L4HtjPeX8iqLgsxfJgz+JjKMcX2q3qbRXSeTlC/CSd4= github.com/rancher/wrangler v0.6.2-0.20200714200521-c61fae623942/go.mod h1:8LdIqAQPHysxNlHqmKbUiDIx9ULt9IHUauh9aOnr67k= -github.com/rancher/wrangler v0.6.2-0.20200820173016-2068de651106/go.mod h1:iKqQcYs4YSDjsme52OZtQU4jHPmLlIiM93aj2c8c/W8= -github.com/rancher/wrangler v0.8.11-0.20211214201934-f5aa5d9f2e81 h1:V6K7b1xEclCmDwfZFYhXHubtTLVpkvYFN+CGOIgIT1M= -github.com/rancher/wrangler v0.8.11-0.20211214201934-f5aa5d9f2e81/go.mod h1:Lte9WjPtGYxYacIWeiS9qawvu2R4NujFU9xuXWJvc/0= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1 h1:Rnrc4yx1Pkqoto7/n6GT68tk5up/SLuan1uSYXACpoM= +github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1/go.mod h1:+GbeNyk8mbdOdMSdLIqvz+0JQ/2PhIb4ufMOcmHLIJQ= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -605,7 +607,6 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -614,12 +615,14 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -628,19 +631,22 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spyzhov/ajson v0.4.2/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA= 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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -654,9 +660,7 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -665,17 +669,15 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= @@ -712,19 +714,14 @@ go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52l go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= @@ -742,14 +739,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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= @@ -771,7 +767,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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= @@ -785,9 +780,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -809,7 +803,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL 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-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -837,12 +830,16 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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= @@ -854,7 +851,11 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -867,10 +868,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -917,12 +918,14 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -933,22 +936,31 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/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= @@ -962,6 +974,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb 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-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -970,7 +983,6 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/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= @@ -989,7 +1001,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/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-20191017205301-920acffc3e65/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1023,22 +1034,22 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 h1:hI3jKY4Hpf63ns040onEbB3dAkR/H/P83hw1TG8dD3Y= -golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= 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= @@ -1060,6 +1071,14 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= 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= @@ -1101,7 +1120,6 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1111,10 +1129,23 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 h1:Et6SkiuvnBn+SgrSYXs/BrUpGB4mbdwt4R3vaPIlicA= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= 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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1137,9 +1168,15 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -1152,8 +1189,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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= @@ -1163,17 +1201,15 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1183,12 +1219,11 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= @@ -1199,105 +1234,85 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= -k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M= -k8s.io/api v0.0.0-20220420164651-0bf1867dde52/go.mod h1:qOGElvkvG4iusrwS28JSJgPofbMSCv5PWe0AD3boQGQ= -k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= -k8s.io/api v0.24.0 h1:J0hann2hfxWr1hinZIDefw7Q96wmCBx6SSB8IY0MdDg= -k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I= -k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= -k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= +k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= +k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= +k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= -k8s.io/apiextensions-apiserver v0.24.0 h1:JfgFqbA8gKJ/uDT++feAqk9jBIwNnL9YGdQvaI9DLtY= -k8s.io/apiextensions-apiserver v0.24.0/go.mod h1:iuVe4aEpe6827lvO6yWQVxiPSpPoSKVjkq+MIdg84cM= -k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= -k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= -k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= -k8s.io/apimachinery v0.0.0-20220331225401-97e5df2d0258/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= +k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= +k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= -k8s.io/apimachinery v0.24.0 h1:ydFCyC/DjCvFCHK5OPMKBlxayQytB8pxy8YQInd5UyQ= -k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= -k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= +k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= +k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= -k8s.io/apiserver v0.24.0 h1:GR7kGsjOMfilRvlG3Stxv/3uz/ryvJ/aZXc5pqdsNV0= -k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA= -k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY= -k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= -k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= -k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao= -k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= +k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo= +k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0= +k8s.io/cli-runtime v0.22.2/go.mod h1:tkm2YeORFpbgQHEK/igqttvPTRIHFRz5kATlw53zlMI= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= -k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c= -k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= +k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= -k8s.io/component-base v0.24.0 h1:h5jieHZQoHrY/lHG+HyrSbJeyfuitheBvqvKwKHVC0g= -k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA= +k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= +k8s.io/component-base v0.25.4 h1:n1bjg9Yt+G1C0WnIDJmg2fo6wbEU1UGMRiQSjmj7hNQ= +k8s.io/component-base v0.25.4/go.mod h1:nnZJU8OP13PJEm6/p5V2ztgX2oyteIaAGKGMYb2L2cY= +k8s.io/component-helpers v0.22.2/go.mod h1:+N61JAR9aKYSWbnLA88YcFr9K/6ISYvRNybX7QW7Rs8= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= -k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI= -k8s.io/kube-aggregator v0.24.0 h1:ax2B6v5y+sLISgal5COnlDRKOSr97uXpwif6nnK3a/M= -k8s.io/kube-aggregator v0.24.0/go.mod h1:ftfs6Fi46z3cKzeF2kvNBPLbMlSKuqZbesJGNp/cQnw= -k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-aggregator v0.25.4 h1:QIeTa29I2a0VOFwZtpquz/bkNPk+dnUiDPbI/Vq7MbI= +k8s.io/kube-aggregator v0.25.4/go.mod h1:PH65mLSQoUld53w0VkdYcsIGh7wjJGZ5DyfoARronz0= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo= -k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/kubectl v0.22.2/go.mod h1:BApg2j0edxLArCOfO0ievI27EeTQqBDMNU9VQH734iQ= +k8s.io/metrics v0.22.2/go.mod h1:GUcsBtpsqQD1tKFS/2wCKu4ZBowwRncLOJH1rgWs3uw= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 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/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 h1:dUk62HQ3ZFhD48Qr8MIXCiKA8wInBQCtuE4QGfFW7yA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= -sigs.k8s.io/cli-utils v0.16.0 h1:Wr32m1oxjIqc9G9l+igr13PeIM9LCyq8jQ8KjXKelvg= -sigs.k8s.io/cli-utils v0.16.0/go.mod h1:9Jqm9K2W6ShhCxsEuaz6HSRKKOXigPUx3ZfypGgxBLY= -sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= -sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= -sigs.k8s.io/kustomize/kyaml v0.4.0/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 h1:LYqFq+6Cj2D0gFfrJvL7iElD4ET6ir3VDdhDdTK7rgc= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33/go.mod h1:soWkSNf2tZC7aMibXEqVhCd73GOY5fJikn8qbdzemB0= +sigs.k8s.io/cli-utils v0.27.0 h1:BxI7lPNn0fBZa5g4UwR+ShJyL4CCxELA6tLHbr2YrpU= +sigs.k8s.io/cli-utils v0.27.0/go.mod h1:8ll2fyx+bzjbwmwUnKBQU+2LDbMDsxy44DiDZ+drALg= +sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg= +sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= +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/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= +sigs.k8s.io/kustomize/cmd/config v0.9.13/go.mod h1:7547FLF8W/lTaDf0BDqFTbZxM9zqwEJqCKN9sSR0xSs= +sigs.k8s.io/kustomize/kustomize/v4 v4.2.0/go.mod h1:MOkR6fmhwG7hEDRXBYELTi5GSFcLwfqwzTRHW3kv5go= +sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= +sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From b70a8d05489e64ea8aa148ba1eab1cb48a42141b Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Tue, 6 Dec 2022 09:04:31 -0600 Subject: [PATCH 15/36] Fixing view link for management crds --- pkg/resources/common/formatter.go | 37 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/pkg/resources/common/formatter.go b/pkg/resources/common/formatter.go index 06dd345..2922481 100644 --- a/pkg/resources/common/formatter.go +++ b/pkg/resources/common/formatter.go @@ -30,21 +30,32 @@ func DefaultTemplate(clientGetter proxy.ClientGetter, func selfLink(gvr schema2.GroupVersionResource, meta metav1.Object) (prefix string) { buf := &strings.Builder{} - if gvr.Group == "" { - buf.WriteString("/api/v1/") - } else { - buf.WriteString("/apis/") + if gvr.Group == "management.cattle.io" && gvr.Version == "v3" { + buf.WriteString("/v1/") buf.WriteString(gvr.Group) - buf.WriteString("/") - buf.WriteString(gvr.Version) - buf.WriteString("/") + buf.WriteString(".") + buf.WriteString(gvr.Resource) + if meta.GetNamespace() != "" { + buf.WriteString("/") + buf.WriteString(meta.GetNamespace()) + } + } else { + if gvr.Group == "" { + buf.WriteString("/api/v1/") + } else { + buf.WriteString("/apis/") + buf.WriteString(gvr.Group) + buf.WriteString("/") + buf.WriteString(gvr.Version) + buf.WriteString("/") + } + if meta.GetNamespace() != "" { + buf.WriteString("namespaces/") + buf.WriteString(meta.GetNamespace()) + buf.WriteString("/") + } + buf.WriteString(gvr.Resource) } - if meta.GetNamespace() != "" { - buf.WriteString("namespaces/") - buf.WriteString(meta.GetNamespace()) - buf.WriteString("/") - } - buf.WriteString(gvr.Resource) buf.WriteString("/") buf.WriteString(meta.GetName()) return buf.String() From fe51b086819ccaff8a9ccd419f27de488be33824 Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Tue, 6 Dec 2022 09:04:56 -0600 Subject: [PATCH 16/36] Tests for "Fixing view link for management crds" --- pkg/resources/common/formatter_test.go | 71 ++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/pkg/resources/common/formatter_test.go b/pkg/resources/common/formatter_test.go index 635fec5..e5a3d60 100644 --- a/pkg/resources/common/formatter_test.go +++ b/pkg/resources/common/formatter_test.go @@ -7,6 +7,7 @@ import ( "github.com/rancher/apiserver/pkg/types" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + schema2 "k8s.io/apimachinery/pkg/runtime/schema" ) func Test_includeFields(t *testing.T) { @@ -541,3 +542,73 @@ func Test_excludeValues(t *testing.T) { }) } } + +func Test_selfLink(t *testing.T) { + tests := []struct { + name string + group string + version string + resource string + resourceName string + resourceNamespace string + want string + }{ + { + name: "empty group", + group: "", + version: "v1", + resource: "pods", + resourceName: "rancher", + resourceNamespace: "cattle-system", + want: "/api/v1/namespaces/cattle-system/pods/rancher", + }, + { + name: "third party crd", + group: "fake.group.io", + version: "v4", + resource: "new-crd", + resourceName: "new-resource", + resourceNamespace: "random-ns", + want: "/apis/fake.group.io/v4/namespaces/random-ns/new-crd/new-resource", + }, + { + name: "non-namespaced third party crd", + group: "fake.group.io", + version: "v4", + resource: "new-crd", + resourceName: "new-resource", + want: "/apis/fake.group.io/v4/new-crd/new-resource", + }, + { + name: "rancher crd, non namespaced", + group: "management.cattle.io", + version: "v3", + resource: "cluster", + resourceName: "c-123xyz", + want: "/v1/management.cattle.io.cluster/c-123xyz", + }, + { + name: "rancher crd, namespaced", + group: "management.cattle.io", + version: "v3", + resource: "catalogtemplates", + resourceName: "built-in", + resourceNamespace: "cattle-global-data", + want: "/v1/management.cattle.io.catalogtemplates/cattle-global-data/built-in", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + gvr := schema2.GroupVersionResource{ + Group: test.group, + Version: test.version, + Resource: test.resource, + } + obj := unstructured.Unstructured{} + obj.SetName(test.resourceName) + obj.SetNamespace(test.resourceNamespace) + assert.Equal(t, test.want, selfLink(gvr, &obj), "did not get expected prefix for object") + }) + } +} From 23fdd030e638b267d249206460e4f151bf7ed843 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 12 Dec 2022 17:17:58 -0800 Subject: [PATCH 17/36] Add tests for RBAC partitioner --- pkg/stores/proxy/rbac_store_test.go | 252 ++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 pkg/stores/proxy/rbac_store_test.go diff --git a/pkg/stores/proxy/rbac_store_test.go b/pkg/stores/proxy/rbac_store_test.go new file mode 100644 index 0000000..3feb932 --- /dev/null +++ b/pkg/stores/proxy/rbac_store_test.go @@ -0,0 +1,252 @@ +package proxy + +import ( + "testing" + + "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/steve/pkg/accesscontrol" + "github.com/rancher/steve/pkg/stores/partition" + "github.com/rancher/wrangler/pkg/schemas" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/sets" +) + +func TestAll(t *testing.T) { + tests := []struct { + name string + apiOp *types.APIRequest + id string + schema *types.APISchema + wantPartitions []partition.Partition + }{ + { + name: "all passthrough", + apiOp: &types.APIRequest{}, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "*", + ResourceName: "*", + }, + }, + }, + }, + }, + }, + wantPartitions: passthroughPartitions, + }, + { + name: "global access for global request", + apiOp: &types.APIRequest{}, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "*", + ResourceName: "r1", + }, + accesscontrol.Access{ + Namespace: "*", + ResourceName: "r2", + }, + }, + }, + }, + }, + }, + wantPartitions: []partition.Partition{ + Partition{ + Names: sets.NewString("r1", "r2"), + }, + }, + }, + { + name: "namespace access for global request", + apiOp: &types.APIRequest{}, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "namespaced": true, + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "n1", + ResourceName: "*", + }, + accesscontrol.Access{ + Namespace: "n2", + ResourceName: "*", + }, + }, + }, + }, + }, + }, + wantPartitions: []partition.Partition{ + Partition{ + Namespace: "n1", + All: true, + }, + Partition{ + Namespace: "n2", + All: true, + }, + }, + }, + { + name: "namespace access for namespaced request", + apiOp: &types.APIRequest{ + Namespace: "n1", + }, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "namespaced": true, + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "n1", + ResourceName: "*", + }, + }, + }, + }, + }, + }, + wantPartitions: passthroughPartitions, + }, + { + // we still get a partition even if there is no access to it, it will be rejected by the API server later + name: "namespace access for invalid namespaced request", + apiOp: &types.APIRequest{ + Namespace: "n2", + }, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "namespaced": true, + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "n1", + ResourceName: "*", + }, + }, + }, + }, + }, + }, + wantPartitions: []partition.Partition{ + Partition{ + Namespace: "n2", + }, + }, + }, + { + name: "by names access for global request", + apiOp: &types.APIRequest{}, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "namespaced": true, + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "n1", + ResourceName: "r1", + }, + accesscontrol.Access{ + Namespace: "n1", + ResourceName: "r2", + }, + accesscontrol.Access{ + Namespace: "n2", + ResourceName: "r1", + }, + }, + }, + }, + }, + }, + wantPartitions: []partition.Partition{ + Partition{ + Namespace: "n1", + Names: sets.NewString("r1", "r2"), + }, + Partition{ + Namespace: "n2", + Names: sets.NewString("r1"), + }, + }, + }, + { + name: "by names access for namespaced request", + apiOp: &types.APIRequest{ + Namespace: "n1", + }, + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + Attributes: map[string]interface{}{ + "namespaced": true, + "access": accesscontrol.AccessListByVerb{ + "list": accesscontrol.AccessList{ + accesscontrol.Access{ + Namespace: "n1", + ResourceName: "r1", + }, + accesscontrol.Access{ + Namespace: "n2", + ResourceName: "r1", + }, + }, + }, + }, + }, + }, + wantPartitions: []partition.Partition{ + Partition{ + Namespace: "n1", + Names: sets.NewString("r1"), + }, + }, + }, + { + name: "by id", + apiOp: &types.APIRequest{}, + id: "n1/r1", + schema: &types.APISchema{ + Schema: &schemas.Schema{ + ID: "foo", + }, + }, + wantPartitions: []partition.Partition{ + Partition{ + Namespace: "n1", + Names: sets.NewString("r1"), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + partitioner := rbacPartitioner{} + verb := "list" + gotPartitions, gotErr := partitioner.All(test.apiOp, test.schema, verb, test.id) + assert.Nil(t, gotErr) + assert.Equal(t, test.wantPartitions, gotPartitions) + }) + } +} From c8fb653ca3458e99423356ef15cb57c7d733b90b Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Fri, 11 Nov 2022 16:19:32 -0600 Subject: [PATCH 18/36] Bump dynamic listener to v0.3.5 Bumps dynamic listener to v0.3.5. Adds a small arg change to account for this version bump which addresses an issue caused by the new version --- go.mod | 14 ++--- go.sum | 139 +++++-------------------------------------- pkg/server/server.go | 3 + 3 files changed, 24 insertions(+), 132 deletions(-) diff --git a/go.mod b/go.mod index fe621a3..884a57c 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076 - github.com/rancher/dynamiclistener v0.2.1-0.20200714201033-9c1939da3af9 + github.com/rancher/dynamiclistener v0.3.5 github.com/rancher/kubernetes-provider-detector v0.1.2 github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a @@ -59,7 +59,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.8 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/imdario/mergo v0.3.12 // indirect @@ -89,11 +89,11 @@ require ( go.opentelemetry.io/otel/trace v0.20.0 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.8 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect @@ -103,8 +103,8 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.25.4 // indirect - k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/utils v0.0.0-20221011040102-427025108f67 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 // indirect sigs.k8s.io/cli-utils v0.27.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/go.sum b/go.sum index 94d760a..f321ea8 100644 --- a/go.sum +++ b/go.sum @@ -44,7 +44,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl 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/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -63,7 +62,6 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= @@ -71,20 +69,17 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/adrg/xdg v0.3.1 h1:uIyL9BYfXaFgDyVRKE8wjtm6ETQULweQqTofphEFJYY= github.com/adrg/xdg v0.3.1/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -96,7 +91,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= @@ -120,29 +114,22 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -152,12 +139,8 @@ github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -175,7 +158,6 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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= @@ -193,8 +175,6 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 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= @@ -212,55 +192,19 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -329,8 +273,9 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/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/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -362,24 +307,20 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -417,7 +358,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -440,7 +380,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -450,8 +389,6 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= @@ -459,8 +396,6 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= @@ -498,7 +433,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -514,7 +448,6 @@ github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -573,18 +506,16 @@ github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076 h1:wS95KbXFI1QOV github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= github.com/rancher/client-go v1.25.4-rancher1 h1:9MlBC8QbgngUkhNzMR8rZmmCIj6WNRHFOnYiwC2Kty4= github.com/rancher/client-go v1.25.4-rancher1/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= -github.com/rancher/dynamiclistener v0.2.1-0.20200714201033-9c1939da3af9 h1:Mo5mPXi7k/TgzMcUIuDpbNxiX2bYh68+yEpaur5Nx80= -github.com/rancher/dynamiclistener v0.2.1-0.20200714201033-9c1939da3af9/go.mod h1:qr0QfhwzcVCR+Ao9WyfnE+jmOpfEAdRhXtNOZGJ3nCQ= +github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= +github.com/rancher/dynamiclistener v0.3.5/go.mod h1:dW/YF6/m2+uEyJ5VtEcd9THxda599HP6N9dSXk81+k0= github.com/rancher/kubernetes-provider-detector v0.1.2 h1:iFfmmcZiGya6s3cS4Qxksyqqw5hPbbIDHgKJ2Y44XKM= github.com/rancher/kubernetes-provider-detector v0.1.2/go.mod h1:ypuJS7kP7rUiAn330xG46mj+Nhvym05GM8NqMVekpH0= -github.com/rancher/lasso v0.0.0-20200515155337-a34e1e26ad91/go.mod h1:G6Vv2aj6xB2YjTVagmu4NkhBvbE8nBcGykHRENH6arI= github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c h1:RKGa+6plIHqyfBcC+lnCs3oe7A8wSFkUPBtqWBAi/1E= github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c/go.mod h1:Qewwr/xGzhtG7WCxhubrKZrdAobv5yqAIuHbDoxThZQ= github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 h1:+Oob+DG+SZoX8hnKBWpZfVwxx6K84hMou9nbBOTux7w= github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99/go.mod h1:zpv7z4ySYL5LlEBKEPf/xf3cjx837/J2i/wHpT43viE= github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a h1:Go8MpBEeZCR0yV1ylu2/KjJBvpYomIezU58pejYCtgk= github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a/go.mod h1:vq3LvyOFnLcwMiCE1KdW3foPd6g5kAjZOtOb7JqGHck= -github.com/rancher/wrangler v0.6.2-0.20200714200521-c61fae623942/go.mod h1:8LdIqAQPHysxNlHqmKbUiDIx9ULt9IHUauh9aOnr67k= github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1 h1:Rnrc4yx1Pkqoto7/n6GT68tk5up/SLuan1uSYXACpoM= github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1/go.mod h1:+GbeNyk8mbdOdMSdLIqvz+0JQ/2PhIb4ufMOcmHLIJQ= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -596,7 +527,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -612,23 +542,18 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= @@ -648,18 +573,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -671,9 +591,7 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -681,9 +599,6 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 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= @@ -715,7 +630,6 @@ go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16g go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -727,16 +641,11 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= @@ -785,7 +694,6 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -793,7 +701,6 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r 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-20190320064053-1272bf9dcd53/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= @@ -803,7 +710,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL 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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -838,8 +744,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= +golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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= @@ -879,19 +786,15 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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-20190616124812-15dcb6c0061f/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-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -952,8 +855,9 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -967,9 +871,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 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= @@ -982,7 +886,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190125232054-d66bd3c5d5a6/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= @@ -992,14 +895,11 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn 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-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/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-20190624222133-a101b041ded4/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-20190920225731-5eefd052ad72/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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1151,7 +1051,6 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.23.1/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= @@ -1198,7 +1097,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -1224,7 +1122,6 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1234,11 +1131,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= -k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= @@ -1246,34 +1141,29 @@ k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftc k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= -k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= k8s.io/apiserver v0.25.4 h1:/3TwZcgLqX7wUxq7TtXOUqXeBTwXIblVMQdhR5XZ7yo= k8s.io/apiserver v0.25.4/go.mod h1:rPcm567XxjOnnd7jedDUnGJGmDGAo+cT6H7QHAN+xV0= k8s.io/cli-runtime v0.22.2/go.mod h1:tkm2YeORFpbgQHEK/igqttvPTRIHFRz5kATlw53zlMI= -k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= k8s.io/component-base v0.25.4 h1:n1bjg9Yt+G1C0WnIDJmg2fo6wbEU1UGMRiQSjmj7hNQ= k8s.io/component-base v0.25.4/go.mod h1:nnZJU8OP13PJEm6/p5V2ztgX2oyteIaAGKGMYb2L2cY= k8s.io/component-helpers v0.22.2/go.mod h1:+N61JAR9aKYSWbnLA88YcFr9K/6ISYvRNybX7QW7Rs8= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-aggregator v0.25.4 h1:QIeTa29I2a0VOFwZtpquz/bkNPk+dnUiDPbI/Vq7MbI= k8s.io/kube-aggregator v0.25.4/go.mod h1:PH65mLSQoUld53w0VkdYcsIGh7wjJGZ5DyfoARronz0= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= @@ -1282,16 +1172,15 @@ k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkI k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kubectl v0.22.2/go.mod h1:BApg2j0edxLArCOfO0ievI27EeTQqBDMNU9VQH734iQ= k8s.io/metrics v0.22.2/go.mod h1:GUcsBtpsqQD1tKFS/2wCKu4ZBowwRncLOJH1rgWs3uw= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20221011040102-427025108f67 h1:ZmUY7x0cwj9e7pGyCTIalBi5jpNfigO5sU46/xFoF/w= +k8s.io/utils v0.0.0-20221011040102-427025108f67/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/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 h1:LYqFq+6Cj2D0gFfrJvL7iElD4ET6ir3VDdhDdTK7rgc= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33/go.mod h1:soWkSNf2tZC7aMibXEqVhCd73GOY5fJikn8qbdzemB0= diff --git a/pkg/server/server.go b/pkg/server/server.go index 1a54a9f..788e0fc 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -200,6 +200,9 @@ func (c *Server) ListenAndServe(ctx context.Context, httpsPort, httpPort int, op c.StartAggregation(ctx) + if len(opts.TLSListenerConfig.SANs) == 0 { + opts.TLSListenerConfig.SANs = []string{"127.0.0.1"} + } if err := server.ListenAndServe(ctx, httpsPort, httpPort, c, opts); err != nil { return err } From 1d4a1309742690406369b7c8666cfaf8a593970e Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 28 Oct 2022 15:17:31 -0700 Subject: [PATCH 19/36] Add unit tests for partition listing --- pkg/stores/partition/store_test.go | 354 +++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 pkg/stores/partition/store_test.go diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go new file mode 100644 index 0000000..41fab01 --- /dev/null +++ b/pkg/stores/partition/store_test.go @@ -0,0 +1,354 @@ +package partition + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/wrangler/pkg/schemas" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestList(t *testing.T) { + tests := []struct { + name string + apiOps []*types.APIRequest + partitions []Partition + objects map[string]types.APIObjectList + want []types.APIObjectList + }{ + { + name: "basic", + apiOps: []*types.APIRequest{ + newRequest(""), + }, + partitions: []Partition{ + mockPartition{ + name: "all", + }, + }, + objects: map[string]types.APIObjectList{ + "all": { + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + }, + want: []types.APIObjectList{ + { + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + }, + }, + { + name: "limit and continue", + apiOps: []*types.APIRequest{ + newRequest("limit=1"), + newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith"))))))), + newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin"))))))), + }, + partitions: []Partition{ + mockPartition{ + name: "all", + }, + }, + objects: map[string]types.APIObjectList{ + "all": { + Objects: []types.APIObject{ + newApple("fuji").toObj(), + newApple("granny-smith").toObj(), + newApple("crispin").toObj(), + }, + }, + }, + want: []types.APIObjectList{ + { + Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith"))))), + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin"))))), + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Objects: []types.APIObject{ + newApple("crispin").toObj(), + }, + }, + }, + }, + { + name: "multi-partition", + apiOps: []*types.APIRequest{ + newRequest(""), + }, + partitions: []Partition{ + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, + }, + objects: map[string]types.APIObjectList{ + "pink": { + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + "green": { + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + "yellow": { + Objects: []types.APIObject{ + newApple("crispin").toObj(), + }, + }, + }, + want: []types.APIObjectList{ + { + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + newApple("crispin").toObj(), + }, + }, + }, + }, + { + name: "multi-partition with limit and continue", + apiOps: []*types.APIRequest{ + newRequest("limit=3"), + newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`)))), + newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`)))), + }, + partitions: []Partition{ + mockPartition{ + name: "pink", + }, + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, + mockPartition{ + name: "red", + }, + }, + objects: map[string]types.APIObjectList{ + "pink": { + Objects: []types.APIObject{ + newApple("fuji").toObj(), + newApple("honeycrisp").toObj(), + }, + }, + "green": { + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + newApple("bramley").toObj(), + }, + }, + "yellow": { + Objects: []types.APIObject{ + newApple("crispin").toObj(), + newApple("golden-delicious").toObj(), + }, + }, + "red": { + Objects: []types.APIObject{ + newApple("red-delicious").toObj(), + }, + }, + }, + want: []types.APIObjectList{ + { + Continue: base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`)), + Objects: []types.APIObject{ + newApple("fuji").toObj(), + newApple("honeycrisp").toObj(), + newApple("granny-smith").toObj(), + }, + }, + { + Continue: base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`)), + Objects: []types.APIObject{ + newApple("bramley").toObj(), + newApple("crispin").toObj(), + newApple("golden-delicious").toObj(), + }, + }, + { + Objects: []types.APIObject{ + newApple("red-delicious").toObj(), + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + schema := &types.APISchema{Schema: &schemas.Schema{ID: "apple"}} + stores := map[string]*mockStore{} + for _, p := range test.partitions { + stores[p.Name()] = &mockStore{ + contents: test.objects[p.Name()], + } + } + store := Store{ + Partitioner: mockPartitioner{ + stores: stores, + partitions: test.partitions, + }, + } + for i, req := range test.apiOps { + got, gotErr := store.List(req, schema) + assert.Nil(t, gotErr) + assert.Equal(t, test.want[i], got) + } + }) + } +} + +type mockPartitioner struct { + stores map[string]*mockStore + partitions []Partition +} + +func (m mockPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (Partition, error) { + panic("not implemented") +} + +func (m mockPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]Partition, error) { + return m.partitions, nil +} + +func (m mockPartitioner) Store(apiOp *types.APIRequest, partition Partition) (types.Store, error) { + return m.stores[partition.Name()], nil +} + +type mockPartition struct { + name string +} + +func (m mockPartition) Name() string { + return m.name +} + +type mockStore struct { + contents types.APIObjectList + partition mockPartition +} + +func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { + query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery) + l := query.Get("limit") + if l == "" { + return m.contents, nil + } + i := 0 + if c := query.Get("continue"); c != "" { + start, _ := base64.StdEncoding.DecodeString(c) + for j, obj := range m.contents.Objects { + if string(start) == obj.Name() { + i = j + break + } + } + } + lInt, _ := strconv.Atoi(l) + contents := m.contents + if len(contents.Objects) > i+lInt { + contents.Continue = base64.StdEncoding.EncodeToString([]byte(contents.Objects[i+lInt].Name())) + } + if i > len(contents.Objects) { + return contents, nil + } + if i+lInt > len(contents.Objects) { + contents.Objects = contents.Objects[i:] + return contents, nil + } + contents.Objects = contents.Objects[i : i+lInt] + return contents, nil +} + +func (m *mockStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { + panic("not implemented") +} + +func (m *mockStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { + panic("not implemented") +} + +func (m *mockStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) { + panic("not implemented") +} + +func (m *mockStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { + panic("not implemented") +} + +func (m *mockStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) { + panic("not implemented") +} + +var colorMap = map[string]string{ + "fuji": "pink", + "honeycrisp": "pink", + "granny-smith": "green", + "bramley": "green", + "crispin": "yellow", + "golden-delicious": "yellow", + "red-delicious": "red", +} + +func newRequest(query string) *types.APIRequest { + return &types.APIRequest{ + Request: &http.Request{ + URL: &url.URL{ + Scheme: "https", + Host: "rancher", + Path: "/apples", + RawQuery: query, + }, + }, + } +} + +type apple struct { + unstructured.Unstructured +} + +func newApple(name string) apple { + return apple{unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": name, + }, + "data": map[string]interface{}{ + "color": colorMap[name], + }, + }, + }} +} + +func (a apple) toObj() types.APIObject { + return types.APIObject{ + Type: "apple", + ID: a.Object["metadata"].(map[string]interface{})["name"].(string), + Object: &a.Unstructured, + } +} From 60d234d282428698f7e07349c6a42148e855cc98 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 21 Oct 2022 16:41:08 -0700 Subject: [PATCH 20/36] Refactor proxy store Filtering and sorting needs to operate on unstructured data. It also needs to operate after the parallel partitioner, higher in the store stack. This means that the proxy Store needs to return raw, unstructured data up to the partitioner. This change moves all conversions from unstructured Kubernetes types to apiserver objects up from the proxy store into the partitioner. --- pkg/stores/partition/parallel.go | 32 ++--- pkg/stores/partition/store.go | 137 +++++++++++++++++-- pkg/stores/partition/store_test.go | 91 ++++++------- pkg/stores/proxy/proxy_store.go | 202 ++++++++++------------------- pkg/stores/proxy/rbac_store.go | 10 +- 5 files changed, 259 insertions(+), 213 deletions(-) diff --git a/pkg/stores/partition/parallel.go b/pkg/stores/partition/parallel.go index 4e9f3e5..49f380c 100644 --- a/pkg/stores/partition/parallel.go +++ b/pkg/stores/partition/parallel.go @@ -5,9 +5,9 @@ import ( "encoding/base64" "encoding/json" - "github.com/rancher/apiserver/pkg/types" "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) // Partition represents a named grouping of kubernetes resources, @@ -33,7 +33,7 @@ type ParallelPartitionLister struct { } // PartitionLister lists objects for one partition. -type PartitionLister func(ctx context.Context, partition Partition, cont string, revision string, limit int) (types.APIObjectList, error) +type PartitionLister func(ctx context.Context, partition Partition, cont string, revision string, limit int) (*unstructured.UnstructuredList, error) // Err returns the latest error encountered. func (p *ParallelPartitionLister) Err() error { @@ -72,7 +72,7 @@ func indexOrZero(partitions []Partition, name string) int { // List returns a stream of objects up to the requested limit. // If the continue token is not empty, it decodes it and returns the stream // starting at the indicated marker. -func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume string) (<-chan []types.APIObject, error) { +func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume string) (<-chan []unstructured.Unstructured, error) { var state listState if resume != "" { bytes, err := base64.StdEncoding.DecodeString(resume) @@ -88,7 +88,7 @@ func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume st } } - result := make(chan []types.APIObject) + result := make(chan []unstructured.Unstructured) go p.feeder(ctx, state, limit, result) return result, nil } @@ -120,7 +120,7 @@ type listState struct { // 100000, the result is truncated and a continue token is generated that // indicates the partition and offset for the client to start on in the next // request. -func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, limit int, result chan []types.APIObject) { +func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, limit int, result chan []unstructured.Unstructured) { var ( sem = semaphore.NewWeighted(p.Concurrency) capacity = limit @@ -183,25 +183,25 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l } if state.Revision == "" { - state.Revision = list.Revision + state.Revision = list.GetResourceVersion() } if p.revision == "" { - p.revision = list.Revision + p.revision = list.GetResourceVersion() } // We have already seen the first objects in the list, truncate up to the offset. - if state.PartitionName == partition.Name() && state.Offset > 0 && state.Offset < len(list.Objects) { - list.Objects = list.Objects[state.Offset:] + if state.PartitionName == partition.Name() && state.Offset > 0 && state.Offset < len(list.Items) { + list.Items = list.Items[state.Offset:] } // Case 1: the capacity has been reached across all goroutines but the list is still only partial, // so save the state so that the next page can be requested later. - if len(list.Objects) > capacity { - result <- list.Objects[:capacity] + if len(list.Items) > capacity { + result <- list.Items[:capacity] // save state to redo this list at this offset p.state = &listState{ - Revision: list.Revision, + Revision: list.GetResourceVersion(), PartitionName: partition.Name(), Continue: cont, Offset: capacity, @@ -210,16 +210,16 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l capacity = 0 return nil } - result <- list.Objects - capacity -= len(list.Objects) + result <- list.Items + capacity -= len(list.Items) // Case 2: all objects have been returned, we are done. - if list.Continue == "" { + if list.GetContinue() == "" { return nil } // Case 3: we started at an offset and truncated the list to skip the objects up to the offset. // We're not yet up to capacity and have not retrieved every object, // so loop again and get more data. - state.Continue = list.Continue + state.Continue = list.GetContinue() state.PartitionName = partition.Name() state.Offset = 0 } diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index 4bb5340..ccacd08 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -1,12 +1,21 @@ +// Package partition implements a store with parallel partitioning of data +// so that segmented data can be concurrently collected and returned as a single data set. package partition import ( "context" + "fmt" "net/http" + "reflect" "strconv" "github.com/rancher/apiserver/pkg/types" "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" ) const defaultLimit = 100000 @@ -15,7 +24,7 @@ const defaultLimit = 100000 type Partitioner interface { Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (Partition, error) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]Partition, error) - Store(apiOp *types.APIRequest, partition Partition) (types.Store, error) + Store(apiOp *types.APIRequest, partition Partition) (UnstructuredStore, error) } // Store implements types.Store for partitions. @@ -23,7 +32,17 @@ type Store struct { Partitioner Partitioner } -func (s *Store) getStore(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (types.Store, error) { +// UnstructuredStore is like types.Store but deals in k8s unstructured objects instead of apiserver types. +type UnstructuredStore interface { + ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) + List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) + Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, error) + Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, error) + Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) + Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) +} + +func (s *Store) getStore(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (UnstructuredStore, error) { p, err := s.Partitioner.Lookup(apiOp, schema, verb, id) if err != nil { return nil, err @@ -39,7 +58,11 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri return types.APIObject{}, err } - return target.Delete(apiOp, schema, id) + obj, err := target.Delete(apiOp, schema, id) + if err != nil { + return types.APIObject{}, err + } + return toAPI(schema, obj), nil } // ByID looks up a single object by its ID. @@ -49,14 +72,18 @@ func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string return types.APIObject{}, err } - return target.ByID(apiOp, schema, id) + obj, err := target.ByID(apiOp, schema, id) + if err != nil { + return types.APIObject{}, err + } + return toAPI(schema, obj), nil } func (s *Store) listPartition(ctx context.Context, apiOp *types.APIRequest, schema *types.APISchema, partition Partition, - cont string, revision string, limit int) (types.APIObjectList, error) { + cont string, revision string, limit int) (*unstructured.UnstructuredList, error) { store, err := s.Partitioner.Store(apiOp, partition) if err != nil { - return types.APIObjectList{}, err + return nil, err } req := apiOp.Clone() @@ -88,7 +115,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP } lister := ParallelPartitionLister{ - Lister: func(ctx context.Context, partition Partition, cont string, revision string, limit int) (types.APIObjectList, error) { + Lister: func(ctx context.Context, partition Partition, cont string, revision string, limit int) (*unstructured.UnstructuredList, error) { return s.listPartition(ctx, apiOp, schema, partition, cont, revision, limit) }, Concurrency: 3, @@ -104,7 +131,10 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP } for items := range list { - result.Objects = append(result.Objects, items...) + for _, item := range items { + item := item + result.Objects = append(result.Objects, toAPI(schema, &item)) + } } result.Revision = lister.Revision() @@ -119,7 +149,11 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data ty return types.APIObject{}, err } - return target.Create(apiOp, schema, data) + obj, err := target.Create(apiOp, schema, data) + if err != nil { + return types.APIObject{}, err + } + return toAPI(schema, obj), nil } // Update updates a single object in the store. @@ -129,7 +163,11 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data ty return types.APIObject{}, err } - return target.Update(apiOp, schema, data, id) + obj, err := target.Update(apiOp, schema, data, id) + if err != nil { + return types.APIObject{}, err + } + return toAPI(schema, obj), nil } // Watch returns a channel of events for a list or resource. @@ -159,7 +197,7 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types return err } for i := range c { - response <- i + response <- toAPIEvent(apiOp, schema, i) } return nil }) @@ -189,3 +227,80 @@ func getLimit(req *http.Request) int { } return limit } + +func toAPI(schema *types.APISchema, obj runtime.Object) types.APIObject { + if obj == nil || reflect.ValueOf(obj).IsNil() { + return types.APIObject{} + } + + if unstr, ok := obj.(*unstructured.Unstructured); ok { + obj = moveToUnderscore(unstr) + } + + apiObject := types.APIObject{ + Type: schema.ID, + Object: obj, + } + + m, err := meta.Accessor(obj) + if err != nil { + return apiObject + } + + id := m.GetName() + ns := m.GetNamespace() + if ns != "" { + id = fmt.Sprintf("%s/%s", ns, id) + } + + apiObject.ID = id + return apiObject +} + +func moveToUnderscore(obj *unstructured.Unstructured) *unstructured.Unstructured { + if obj == nil { + return nil + } + + for k := range types.ReservedFields { + v, ok := obj.Object[k] + if ok { + delete(obj.Object, k) + obj.Object["_"+k] = v + } + } + + return obj +} + +func toAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, event watch.Event) types.APIEvent { + name := types.ChangeAPIEvent + switch event.Type { + case watch.Deleted: + name = types.RemoveAPIEvent + case watch.Added: + name = types.CreateAPIEvent + case watch.Error: + name = "resource.error" + } + + apiEvent := types.APIEvent{ + Name: name, + } + + if event.Type == watch.Error { + status, _ := event.Object.(*metav1.Status) + apiEvent.Error = fmt.Errorf(status.Message) + return apiEvent + } + + apiEvent.Object = toAPI(schema, event.Object) + + m, err := meta.Accessor(event.Object) + if err != nil { + return apiEvent + } + + apiEvent.Revision = m.GetResourceVersion() + return apiEvent +} diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index 41fab01..c08775f 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -12,6 +12,7 @@ import ( "github.com/rancher/wrangler/pkg/schemas" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/watch" ) func TestList(t *testing.T) { @@ -19,7 +20,7 @@ func TestList(t *testing.T) { name string apiOps []*types.APIRequest partitions []Partition - objects map[string]types.APIObjectList + objects map[string]*unstructured.UnstructuredList want []types.APIObjectList }{ { @@ -32,10 +33,10 @@ func TestList(t *testing.T) { name: "all", }, }, - objects: map[string]types.APIObjectList{ + objects: map[string]*unstructured.UnstructuredList{ "all": { - Objects: []types.APIObject{ - newApple("fuji").toObj(), + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, }, }, }, @@ -59,12 +60,12 @@ func TestList(t *testing.T) { name: "all", }, }, - objects: map[string]types.APIObjectList{ + objects: map[string]*unstructured.UnstructuredList{ "all": { - Objects: []types.APIObject{ - newApple("fuji").toObj(), - newApple("granny-smith").toObj(), - newApple("crispin").toObj(), + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + newApple("crispin").Unstructured, }, }, }, @@ -101,20 +102,20 @@ func TestList(t *testing.T) { name: "yellow", }, }, - objects: map[string]types.APIObjectList{ + objects: map[string]*unstructured.UnstructuredList{ "pink": { - Objects: []types.APIObject{ - newApple("fuji").toObj(), + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, }, }, "green": { - Objects: []types.APIObject{ - newApple("granny-smith").toObj(), + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, }, }, "yellow": { - Objects: []types.APIObject{ - newApple("crispin").toObj(), + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, }, }, }, @@ -148,28 +149,28 @@ func TestList(t *testing.T) { name: "red", }, }, - objects: map[string]types.APIObjectList{ + objects: map[string]*unstructured.UnstructuredList{ "pink": { - Objects: []types.APIObject{ - newApple("fuji").toObj(), - newApple("honeycrisp").toObj(), + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("honeycrisp").Unstructured, }, }, "green": { - Objects: []types.APIObject{ - newApple("granny-smith").toObj(), - newApple("bramley").toObj(), + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, }, }, "yellow": { - Objects: []types.APIObject{ - newApple("crispin").toObj(), - newApple("golden-delicious").toObj(), + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("golden-delicious").Unstructured, }, }, "red": { - Objects: []types.APIObject{ - newApple("red-delicious").toObj(), + Items: []unstructured.Unstructured{ + newApple("red-delicious").Unstructured, }, }, }, @@ -235,7 +236,7 @@ func (m mockPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, v return m.partitions, nil } -func (m mockPartitioner) Store(apiOp *types.APIRequest, partition Partition) (types.Store, error) { +func (m mockPartitioner) Store(apiOp *types.APIRequest, partition Partition) (UnstructuredStore, error) { return m.stores[partition.Name()], nil } @@ -248,11 +249,11 @@ func (m mockPartition) Name() string { } type mockStore struct { - contents types.APIObjectList + contents *unstructured.UnstructuredList partition mockPartition } -func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { +func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery) l := query.Get("limit") if l == "" { @@ -261,46 +262,46 @@ func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (type i := 0 if c := query.Get("continue"); c != "" { start, _ := base64.StdEncoding.DecodeString(c) - for j, obj := range m.contents.Objects { - if string(start) == obj.Name() { + for j, obj := range m.contents.Items { + if string(start) == obj.GetName() { i = j break } } } lInt, _ := strconv.Atoi(l) - contents := m.contents - if len(contents.Objects) > i+lInt { - contents.Continue = base64.StdEncoding.EncodeToString([]byte(contents.Objects[i+lInt].Name())) + contents := m.contents.DeepCopy() + if len(contents.Items) > i+lInt { + contents.SetContinue(base64.StdEncoding.EncodeToString([]byte(contents.Items[i+lInt].GetName()))) } - if i > len(contents.Objects) { + if i > len(contents.Items) { return contents, nil } - if i+lInt > len(contents.Objects) { - contents.Objects = contents.Objects[i:] + if i+lInt > len(contents.Items) { + contents.Items = contents.Items[i:] return contents, nil } - contents.Objects = contents.Objects[i : i+lInt] + contents.Items = contents.Items[i : i+lInt] return contents, nil } -func (m *mockStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { +func (m *mockStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { panic("not implemented") } -func (m *mockStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { +func (m *mockStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, error) { panic("not implemented") } -func (m *mockStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) { +func (m *mockStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, error) { panic("not implemented") } -func (m *mockStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { +func (m *mockStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { panic("not implemented") } -func (m *mockStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) { +func (m *mockStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) { panic("not implemented") } diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index 8299740..c3c40c8 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -1,3 +1,4 @@ +// Package proxy implements the proxy store, which is responsible for interfacing directly with Kubernetes. package proxy import ( @@ -8,7 +9,6 @@ import ( "io/ioutil" "net/http" "os" - "reflect" "regexp" "strconv" @@ -65,7 +65,7 @@ type RelationshipNotifier interface { OnInboundRelationshipChange(ctx context.Context, schema *types.APISchema, namespace string) <-chan *summary.Relationship } -// Store implements types.Store directly on top of kubernetes. +// Store implements partition.UnstructuredStore directly on top of kubernetes. type Store struct { clientGetter ClientGetter notifier RelationshipNotifier @@ -89,44 +89,14 @@ func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, loo } // ByID looks up a single object by its ID. -func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { - result, err := s.byID(apiOp, schema, apiOp.Namespace, id) - return toAPI(schema, result), err +func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { + return s.byID(apiOp, schema, apiOp.Namespace, id) } func decodeParams(apiOp *types.APIRequest, target runtime.Object) error { return paramCodec.DecodeParameters(apiOp.Request.URL.Query(), metav1.SchemeGroupVersion, target) } -func toAPI(schema *types.APISchema, obj runtime.Object) types.APIObject { - if obj == nil || reflect.ValueOf(obj).IsNil() { - return types.APIObject{} - } - - if unstr, ok := obj.(*unstructured.Unstructured); ok { - obj = moveToUnderscore(unstr) - } - - apiObject := types.APIObject{ - Type: schema.ID, - Object: obj, - } - - m, err := meta.Accessor(obj) - if err != nil { - return apiObject - } - - id := m.GetName() - ns := m.GetNamespace() - if ns != "" { - id = fmt.Sprintf("%s/%s", ns, id) - } - - apiObject.ID = id - return apiObject -} - func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, namespace, id string) (*unstructured.Unstructured, error) { k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, namespace)) if err != nil { @@ -158,22 +128,6 @@ func moveFromUnderscore(obj map[string]interface{}) map[string]interface{} { return obj } -func moveToUnderscore(obj *unstructured.Unstructured) *unstructured.Unstructured { - if obj == nil { - return nil - } - - for k := range types.ReservedFields { - v, ok := obj.Object[k] - if ok { - delete(obj.Object, k) - obj.Object["_"+k] = v - } - } - - return obj -} - func rowToObject(obj *unstructured.Unstructured) { if obj == nil { return @@ -230,77 +184,70 @@ func tableToObjects(obj map[string]interface{}) []unstructured.Unstructured { // to list *all* resources. // With this filter, the request can be performed successfully, and only the allowed resources will // be returned in the list. -func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names sets.String) (types.APIObjectList, error) { +func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names sets.String) (*unstructured.UnstructuredList, error) { if apiOp.Namespace == "*" { // This happens when you grant namespaced objects with "get" by name in a clusterrolebinding. We will treat // this as an invalid situation instead of listing all objects in the cluster and filtering by name. - return types.APIObjectList{}, nil + return nil, nil } adminClient, err := s.clientGetter.TableAdminClient(apiOp, schema, apiOp.Namespace) if err != nil { - return types.APIObjectList{}, err + return nil, err } objs, err := s.list(apiOp, schema, adminClient) if err != nil { - return types.APIObjectList{}, err + return nil, err } - var filtered []types.APIObject - for _, obj := range objs.Objects { - if names.Has(obj.Name()) { + var filtered []unstructured.Unstructured + for _, obj := range objs.Items { + if names.Has(obj.GetName()) { filtered = append(filtered, obj) } } - objs.Objects = filtered + objs.Items = filtered return objs, nil } -// List returns a list of resources. -func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { +// List returns an unstructured list of resources. +func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { client, err := s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace) if err != nil { - return types.APIObjectList{}, err + return nil, err } return s.list(apiOp, schema, client) } -func (s *Store) list(apiOp *types.APIRequest, schema *types.APISchema, client dynamic.ResourceInterface) (types.APIObjectList, error) { +func (s *Store) list(apiOp *types.APIRequest, schema *types.APISchema, client dynamic.ResourceInterface) (*unstructured.UnstructuredList, error) { opts := metav1.ListOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return types.APIObjectList{}, nil + return nil, nil } k8sClient, _ := metricsStore.Wrap(client, nil) resultList, err := k8sClient.List(apiOp, opts) if err != nil { - return types.APIObjectList{}, err + return nil, err } tableToList(resultList) - result := types.APIObjectList{ - Revision: resultList.GetResourceVersion(), - Continue: resultList.GetContinue(), - } - - for i := range resultList.Items { - result.Objects = append(result.Objects, toAPI(schema, &resultList.Items[i])) - } - - return result, nil + return resultList, nil } -func returnErr(err error, c chan types.APIEvent) { - c <- types.APIEvent{ - Name: "resource.error", - Error: err, +func returnErr(err error, c chan watch.Event) { + c <- watch.Event{ + Type: "resource.error", + Object: &metav1.Status{ + Message: err.Error(), + }, } } -func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInterface, schema *types.APISchema, w types.WatchRequest, result chan types.APIEvent) { +func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInterface, schema *types.APISchema, w types.WatchRequest, result chan watch.Event) { rev := w.Revision if rev == "-1" || rev == "0" { rev = "" @@ -342,7 +289,8 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt for rel := range s.notifier.OnInboundRelationshipChange(ctx, schema, apiOp.Namespace) { obj, err := s.byID(apiOp, schema, rel.Namespace, rel.Name) if err == nil { - result <- s.toAPIEvent(apiOp, schema, watch.Modified, obj) + rowToObject(obj) + result <- watch.Event{Type: watch.Modified, Object: obj} } else { logrus.Debugf("notifier watch error: %v", err) returnErr(errors.Wrapf(err, "notifier watch error: %v", err), result) @@ -363,7 +311,10 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt } continue } - result <- s.toAPIEvent(apiOp, schema, event.Type, event.Object) + if unstr, ok := event.Object.(*unstructured.Unstructured); ok { + rowToObject(unstr) + } + result <- event } return fmt.Errorf("closed") }) @@ -378,7 +329,7 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt // to list *all* resources. // With this filter, the request can be performed successfully, and only the allowed resources will // be returned in watch. -func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, names sets.String) (chan types.APIEvent, error) { +func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, names sets.String) (chan watch.Event, error) { adminClient, err := s.clientGetter.TableAdminClientForWatch(apiOp, schema, apiOp.Namespace) if err != nil { return nil, err @@ -388,11 +339,16 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t return nil, err } - result := make(chan types.APIEvent) + result := make(chan watch.Event) go func() { defer close(result) for item := range c { - if item.Error == nil && names.Has(item.Object.Name()) { + + m, err := meta.Accessor(item.Object) + if err != nil { + return + } + if item.Type != watch.Error && names.Has(m.GetName()) { result <- item } } @@ -402,7 +358,7 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t } // Watch returns a channel of events for a list or resource. -func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) { +func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) { client, err := s.clientGetter.TableClientForWatch(apiOp, schema, apiOp.Namespace) if err != nil { return nil, err @@ -410,8 +366,8 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types. return s.watch(apiOp, schema, w, client) } -func (s *Store) watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, client dynamic.ResourceInterface) (chan types.APIEvent, error) { - result := make(chan types.APIEvent) +func (s *Store) watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, client dynamic.ResourceInterface) (chan watch.Event, error) { + result := make(chan watch.Event) go func() { s.listAndWatch(apiOp, client, schema, w, result) logrus.Debugf("closing watcher for %s", schema.ID) @@ -420,35 +376,8 @@ func (s *Store) watch(apiOp *types.APIRequest, schema *types.APISchema, w types. return result, nil } -func (s *Store) toAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, et watch.EventType, obj runtime.Object) types.APIEvent { - name := types.ChangeAPIEvent - switch et { - case watch.Deleted: - name = types.RemoveAPIEvent - case watch.Added: - name = types.CreateAPIEvent - } - - if unstr, ok := obj.(*unstructured.Unstructured); ok { - rowToObject(unstr) - } - - event := types.APIEvent{ - Name: name, - Object: toAPI(schema, obj), - } - - m, err := meta.Accessor(obj) - if err != nil { - return event - } - - event.Revision = m.GetResourceVersion() - return event -} - // Create creates a single object in the store. -func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject) (types.APIObject, error) { +func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject) (*unstructured.Unstructured, error) { var ( resp *unstructured.Unstructured ) @@ -474,22 +403,21 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns)) if err != nil { - return types.APIObject{}, err + return nil, err } opts := metav1.CreateOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return types.APIObject{}, err + return nil, err } resp, err = k8sClient.Create(apiOp, &unstructured.Unstructured{Object: input}, opts) rowToObject(resp) - apiObject := toAPI(schema, resp) - return apiObject, err + return resp, err } // Update updates a single object in the store. -func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject, id string) (types.APIObject, error) { +func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject, id string) (*unstructured.Unstructured, error) { var ( err error input = params.Data() @@ -498,13 +426,13 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params ns := types.Namespace(input) k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns)) if err != nil { - return types.APIObject{}, err + return nil, err } if apiOp.Method == http.MethodPatch { bytes, err := ioutil.ReadAll(io.LimitReader(apiOp.Request.Body, 2<<20)) if err != nil { - return types.APIObject{}, err + return nil, err } pType := apitypes.StrategicMergePatchType @@ -514,70 +442,70 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params opts := metav1.PatchOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return types.APIObject{}, err + return nil, err } if pType == apitypes.StrategicMergePatchType { data := map[string]interface{}{} if err := json.Unmarshal(bytes, &data); err != nil { - return types.APIObject{}, err + return nil, err } data = moveFromUnderscore(data) bytes, err = json.Marshal(data) if err != nil { - return types.APIObject{}, err + return nil, err } } resp, err := k8sClient.Patch(apiOp, id, pType, bytes, opts) if err != nil { - return types.APIObject{}, err + return nil, err } - return toAPI(schema, resp), nil + return resp, nil } resourceVersion := input.String("metadata", "resourceVersion") if resourceVersion == "" { - return types.APIObject{}, fmt.Errorf("metadata.resourceVersion is required for update") + return nil, fmt.Errorf("metadata.resourceVersion is required for update") } opts := metav1.UpdateOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return types.APIObject{}, err + return nil, err } resp, err := k8sClient.Update(apiOp, &unstructured.Unstructured{Object: moveFromUnderscore(input)}, metav1.UpdateOptions{}) if err != nil { - return types.APIObject{}, err + return nil, err } rowToObject(resp) - return toAPI(schema, resp), nil + return resp, nil } // Delete deletes an object from a store. -func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) { +func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { opts := metav1.DeleteOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return types.APIObject{}, nil + return nil, nil } k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace)) if err != nil { - return types.APIObject{}, err + return nil, err } if err := k8sClient.Delete(apiOp, id, opts); err != nil { - return types.APIObject{}, err + return nil, err } obj, err := s.byID(apiOp, schema, apiOp.Namespace, id) if err != nil { // ignore lookup error - return types.APIObject{}, validation.ErrorCode{ + return nil, validation.ErrorCode{ Status: http.StatusNoContent, } } - return toAPI(schema, obj), nil + return obj, nil } diff --git a/pkg/stores/proxy/rbac_store.go b/pkg/stores/proxy/rbac_store.go index 1562560..f67089d 100644 --- a/pkg/stores/proxy/rbac_store.go +++ b/pkg/stores/proxy/rbac_store.go @@ -9,7 +9,9 @@ import ( "github.com/rancher/steve/pkg/attributes" "github.com/rancher/steve/pkg/stores/partition" "github.com/rancher/wrangler/pkg/kv" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" ) var ( @@ -85,8 +87,8 @@ func (p *rbacPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, } } -// Store returns a proxy Store suited to listing and watching resources by partition. -func (p *rbacPartitioner) Store(apiOp *types.APIRequest, partition partition.Partition) (types.Store, error) { +// Store returns an UnstructuredStore suited to listing and watching resources by partition. +func (p *rbacPartitioner) Store(apiOp *types.APIRequest, partition partition.Partition) (partition.UnstructuredStore, error) { return &byNameOrNamespaceStore{ Store: p.proxyStore, partition: partition.(Partition), @@ -99,7 +101,7 @@ type byNameOrNamespaceStore struct { } // List returns a list of resources by partition. -func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) { +func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { if b.partition.Passthrough { return b.Store.List(apiOp, schema) } @@ -112,7 +114,7 @@ func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.API } // Watch returns a channel of resources by partition. -func (b *byNameOrNamespaceStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) { +func (b *byNameOrNamespaceStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan watch.Event, error) { if b.partition.Passthrough { return b.Store.Watch(apiOp, schema, wr) } From f8eaa11d8369256f51632d8bd22fd5a4bf484227 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 24 Oct 2022 14:17:14 -0700 Subject: [PATCH 21/36] Add filtering to partition store Extend the partition store to parse query params as list filters. Filter keys use dot notation to denote the subfield of an object to filter on. Example: GET /v1/secrets?filter=metadata.name=foo Filters are ANDed together, so an object must match all filters to be included in the list. Example: GET /v1/secrets?filter=metadata.name=foo&filter=metadata.namespace=bar Arrays are searched for matching items. If any item in the array matches, the item is included in the list. Example: GET /v1/pods?filter=spec.containers.image=alpine --- .../partition/listprocessor/processor.go | 150 ++++ .../partition/listprocessor/processor_test.go | 798 ++++++++++++++++++ pkg/stores/partition/store.go | 34 +- pkg/stores/partition/store_test.go | 87 ++ 4 files changed, 1043 insertions(+), 26 deletions(-) create mode 100644 pkg/stores/partition/listprocessor/processor.go create mode 100644 pkg/stores/partition/listprocessor/processor_test.go diff --git a/pkg/stores/partition/listprocessor/processor.go b/pkg/stores/partition/listprocessor/processor.go new file mode 100644 index 0000000..5ecbe92 --- /dev/null +++ b/pkg/stores/partition/listprocessor/processor.go @@ -0,0 +1,150 @@ +// Package listprocessor contains methods for filtering, sorting, and paginating lists of objects. +package listprocessor + +import ( + "strconv" + "strings" + + "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/wrangler/pkg/data" + "github.com/rancher/wrangler/pkg/data/convert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +const ( + defaultLimit = 100000 + continueParam = "continue" + limitParam = "limit" + filterParam = "filter" +) + +// ListOptions represents the query parameters that may be included in a list request. +type ListOptions struct { + ChunkSize int + Resume string + Filters []Filter +} + +// Filter represents a field to filter by. +// A subfield in an object is represented in a request query using . notation, e.g. 'metadata.name'. +// The subfield is internally represented as a slice, e.g. [metadata, name]. +type Filter struct { + field []string + match string +} + +// ParseQuery parses the query params of a request and returns a ListOptions. +func ParseQuery(apiOp *types.APIRequest) *ListOptions { + chunkSize := getLimit(apiOp) + q := apiOp.Request.URL.Query() + cont := q.Get(continueParam) + filterParams := q[filterParam] + filterOpts := []Filter{} + for _, filters := range filterParams { + filter := strings.Split(filters, "=") + if len(filter) != 2 { + continue + } + filterOpts = append(filterOpts, Filter{field: strings.Split(filter[0], "."), match: filter[1]}) + } + return &ListOptions{ + ChunkSize: chunkSize, + Resume: cont, + Filters: filterOpts, + } +} + +// getLimit extracts the limit parameter from the request or sets a default of 100000. +// Since a default is always set, this implies that clients must always be +// aware that the list may be incomplete. +func getLimit(apiOp *types.APIRequest) int { + limitString := apiOp.Request.URL.Query().Get(limitParam) + limit, err := strconv.Atoi(limitString) + if err != nil { + limit = 0 + } + if limit <= 0 { + limit = defaultLimit + } + return limit +} + +// FilterList accepts a channel of unstructured objects and a slice of filters and returns the filtered list. +// Filters are ANDed together. +func FilterList(list <-chan []unstructured.Unstructured, filters []Filter) []unstructured.Unstructured { + result := []unstructured.Unstructured{} + for items := range list { + for _, item := range items { + if len(filters) == 0 { + result = append(result, item) + continue + } + if matchesAll(item.Object, filters) { + result = append(result, item) + } + } + } + return result +} + +func matchesOne(obj map[string]interface{}, filter Filter) bool { + var objValue interface{} + var ok bool + subField := []string{} + for !ok && len(filter.field) > 0 { + objValue, ok = data.GetValue(obj, filter.field...) + if !ok { + subField = append(subField, filter.field[len(filter.field)-1]) + filter.field = filter.field[:len(filter.field)-1] + } + } + if !ok { + return false + } + switch typedVal := objValue.(type) { + case string, int, bool: + if len(subField) > 0 { + return false + } + stringVal := convert.ToString(typedVal) + if strings.Contains(stringVal, filter.match) { + return true + } + case []interface{}: + filter = Filter{field: subField, match: filter.match} + if matchesAny(typedVal, filter) { + return true + } + } + return false +} + +func matchesAny(obj []interface{}, filter Filter) bool { + for _, v := range obj { + switch typedItem := v.(type) { + case string, int, bool: + stringVal := convert.ToString(typedItem) + if strings.Contains(stringVal, filter.match) { + return true + } + case map[string]interface{}: + if matchesOne(typedItem, filter) { + return true + } + case []interface{}: + if matchesAny(typedItem, filter) { + return true + } + } + } + return false +} + +func matchesAll(obj map[string]interface{}, filters []Filter) bool { + for _, f := range filters { + if !matchesOne(obj, f) { + return false + } + } + return true +} diff --git a/pkg/stores/partition/listprocessor/processor_test.go b/pkg/stores/partition/listprocessor/processor_test.go new file mode 100644 index 0000000..5757b7a --- /dev/null +++ b/pkg/stores/partition/listprocessor/processor_test.go @@ -0,0 +1,798 @@ +package listprocessor + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestFilterList(t *testing.T) { + tests := []struct { + name string + objects [][]unstructured.Unstructured + filters []Filter + want []unstructured.Unstructured + }{ + { + name: "single filter", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "color"}, + match: "pink", + }, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "multi filter", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "color"}, + match: "pink", + }, + { + field: []string{"metadata", "name"}, + match: "honey", + }, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "no matches", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "color"}, + match: "purple", + }, + }, + want: []unstructured.Unstructured{}, + }, + { + name: "no filters", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{}, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + { + name: "filter field does not match", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"spec", "volumes"}, + match: "hostPath", + }, + }, + want: []unstructured.Unstructured{}, + }, + { + name: "filter subfield does not match", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "productType"}, + match: "tablet", + }, + }, + want: []unstructured.Unstructured{}, + }, + { + name: "almost valid filter key", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "color", "shade"}, + match: "green", + }, + }, + want: []unstructured.Unstructured{}, + }, + { + name: "match string array", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "colors": []interface{}{ + "pink", + "red", + "green", + "yellow", + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "berry", + }, + "data": map[string]interface{}{ + "colors": []interface{}{ + "blue", + "red", + "black", + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "banana", + }, + "data": map[string]interface{}{ + "colors": []interface{}{ + "yellow", + }, + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "colors"}, + match: "yellow", + }, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "colors": []interface{}{ + "pink", + "red", + "green", + "yellow", + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "banana", + }, + "data": map[string]interface{}{ + "colors": []interface{}{ + "yellow", + }, + }, + }, + }, + }, + }, + { + name: "match object array", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "varieties": []interface{}{ + map[string]interface{}{ + "name": "fuji", + "color": "pink", + }, + map[string]interface{}{ + "name": "granny-smith", + "color": "green", + }, + map[string]interface{}{ + "name": "red-delicious", + "color": "red", + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "berry", + }, + "data": map[string]interface{}{ + "varieties": []interface{}{ + map[string]interface{}{ + "name": "blueberry", + "color": "blue", + }, + map[string]interface{}{ + "name": "raspberry", + "color": "red", + }, + map[string]interface{}{ + "name": "blackberry", + "color": "black", + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "banana", + }, + "data": map[string]interface{}{ + "varieties": []interface{}{ + map[string]interface{}{ + "name": "cavendish", + "color": "yellow", + }, + map[string]interface{}{ + "name": "plantain", + "color": "green", + }, + }, + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "varieties", "color"}, + match: "red", + }, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "varieties": []interface{}{ + map[string]interface{}{ + "name": "fuji", + "color": "pink", + }, + map[string]interface{}{ + "name": "granny-smith", + "color": "green", + }, + map[string]interface{}{ + "name": "red-delicious", + "color": "red", + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "berry", + }, + "data": map[string]interface{}{ + "varieties": []interface{}{ + map[string]interface{}{ + "name": "blueberry", + "color": "blue", + }, + map[string]interface{}{ + "name": "raspberry", + "color": "red", + }, + map[string]interface{}{ + "name": "blackberry", + "color": "black", + }, + }, + }, + }, + }, + }, + }, + { + name: "match nested array", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + "pink", + "green", + "red", + "purple", + }, + []interface{}{ + "fuji", + "granny-smith", + "red-delicious", + "black-diamond", + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "berry", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + "blue", + "red", + "black", + }, + []interface{}{ + "blueberry", + "raspberry", + "blackberry", + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "banana", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + "yellow", + "green", + }, + []interface{}{ + "cavendish", + "plantain", + }, + }, + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "attributes"}, + match: "black", + }, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + "pink", + "green", + "red", + "purple", + }, + []interface{}{ + "fuji", + "granny-smith", + "red-delicious", + "black-diamond", + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "berry", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + "blue", + "red", + "black", + }, + []interface{}{ + "blueberry", + "raspberry", + "blackberry", + }, + }, + }, + }, + }, + }, + }, + { + name: "match nested object array", + objects: [][]unstructured.Unstructured{ + { + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "apple", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + map[string]interface{}{ + "pink": "fuji", + }, + map[string]interface{}{ + "green": "granny-smith", + }, + map[string]interface{}{ + "pink": "honeycrisp", + }, + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "berry", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + map[string]interface{}{ + "blue": "blueberry", + }, + map[string]interface{}{ + "red": "raspberry", + }, + map[string]interface{}{ + "black": "blackberry", + }, + }, + }, + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "banana", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + map[string]interface{}{ + "yellow": "cavendish", + }, + map[string]interface{}{ + "green": "plantain", + }, + }, + }, + }, + }, + }, + }, + }, + filters: []Filter{ + { + field: []string{"data", "attributes", "green"}, + match: "plantain", + }, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "fruit", + "metadata": map[string]interface{}{ + "name": "banana", + }, + "data": map[string]interface{}{ + "attributes": []interface{}{ + []interface{}{ + map[string]interface{}{ + "yellow": "cavendish", + }, + map[string]interface{}{ + "green": "plantain", + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ch := make(chan []unstructured.Unstructured) + go func() { + for _, o := range test.objects { + ch <- o + } + close(ch) + }() + got := FilterList(ch, test.filters) + assert.Equal(t, test.want, got) + }) + } +} diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index ccacd08..fcdecb0 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -5,11 +5,11 @@ package partition import ( "context" "fmt" - "net/http" "reflect" "strconv" "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/steve/pkg/stores/partition/listprocessor" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,8 +18,6 @@ import ( "k8s.io/apimachinery/pkg/watch" ) -const defaultLimit = 100000 - // Partitioner is an interface for interacting with partitions. type Partitioner interface { Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (Partition, error) @@ -122,19 +120,18 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP Partitions: partitions, } - resume := apiOp.Request.URL.Query().Get("continue") - limit := getLimit(apiOp.Request) + opts := listprocessor.ParseQuery(apiOp) - list, err := lister.List(apiOp.Context(), limit, resume) + stream, err := lister.List(apiOp.Context(), opts.ChunkSize, opts.Resume) if err != nil { return result, err } - for items := range list { - for _, item := range items { - item := item - result.Objects = append(result.Objects, toAPI(schema, &item)) - } + list := listprocessor.FilterList(stream, opts.Filters) + + for _, item := range list { + item := item + result.Objects = append(result.Objects, toAPI(schema, &item)) } result.Revision = lister.Revision() @@ -213,21 +210,6 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types return response, nil } -// getLimit extracts the limit parameter from the request or sets a default of 100000. -// Since a default is always set, this implies that clients must always be -// aware that the list may be incomplete. -func getLimit(req *http.Request) int { - limitString := req.URL.Query().Get("limit") - limit, err := strconv.Atoi(limitString) - if err != nil { - limit = 0 - } - if limit <= 0 { - limit = defaultLimit - } - return limit -} - func toAPI(schema *types.APISchema, obj runtime.Object) types.APIObject { if obj == nil || reflect.ValueOf(obj).IsNil() { return types.APIObject{} diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index c08775f..a6577a2 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -198,6 +198,86 @@ func TestList(t *testing.T) { }, }, }, + { + name: "with filters", + apiOps: []*types.APIRequest{ + newRequest("filter=data.color=green"), + newRequest("filter=data.color=green&filter=metadata.name=bramley"), + }, + partitions: []Partition{ + mockPartition{ + name: "all", + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + newApple("crispin").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + newApple("bramley").toObj(), + }, + }, + { + Objects: []types.APIObject{ + newApple("bramley").toObj(), + }, + }, + }, + }, + { + name: "multi-partition with filters", + apiOps: []*types.APIRequest{ + newRequest("filter=data.category=baking"), + }, + partitions: []Partition{ + mockPartition{ + name: "pink", + }, + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "pink": { + Items: []unstructured.Unstructured{ + newApple("fuji").with(map[string]string{"category": "eating"}).Unstructured, + newApple("honeycrisp").with(map[string]string{"category": "eating,baking"}).Unstructured, + }, + }, + "green": { + Items: []unstructured.Unstructured{ + newApple("granny-smith").with(map[string]string{"category": "baking"}).Unstructured, + newApple("bramley").with(map[string]string{"category": "eating"}).Unstructured, + }, + }, + "yellow": { + Items: []unstructured.Unstructured{ + newApple("crispin").with(map[string]string{"category": "baking"}).Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Objects: []types.APIObject{ + newApple("honeycrisp").with(map[string]string{"category": "eating,baking"}).toObj(), + newApple("granny-smith").with(map[string]string{"category": "baking"}).toObj(), + newApple("crispin").with(map[string]string{"category": "baking"}).toObj(), + }, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -353,3 +433,10 @@ func (a apple) toObj() types.APIObject { Object: &a.Unstructured, } } + +func (a apple) with(data map[string]string) apple { + for k, v := range data { + a.Object["data"].(map[string]interface{})[k] = v + } + return a +} From adecbd91226dc37a76c39be5f79f55e711b8e3ea Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Thu, 27 Oct 2022 14:11:17 -0700 Subject: [PATCH 22/36] Add sorting to partition store Extend the partition store to parse the "sort" query parameter as a sorting condition. Dot notation is used to denote the object field. Preceding the key with "-" denotes descending (reverse) order. Example sorting by name: GET /v1/secrets?sort=metadata.name Reverse sorting by name: GET /v1/secrets?sort=-metadata.name All values are converted to strings and sorted lexicographically. --- .../partition/listprocessor/processor.go | 48 +++ .../partition/listprocessor/processor_test.go | 319 ++++++++++++++++++ pkg/stores/partition/store.go | 1 + pkg/stores/partition/store_test.go | 79 +++++ 4 files changed, 447 insertions(+) diff --git a/pkg/stores/partition/listprocessor/processor.go b/pkg/stores/partition/listprocessor/processor.go index 5ecbe92..c311d1d 100644 --- a/pkg/stores/partition/listprocessor/processor.go +++ b/pkg/stores/partition/listprocessor/processor.go @@ -2,6 +2,7 @@ package listprocessor import ( + "sort" "strconv" "strings" @@ -16,6 +17,7 @@ const ( continueParam = "continue" limitParam = "limit" filterParam = "filter" + sortParam = "sort" ) // ListOptions represents the query parameters that may be included in a list request. @@ -23,6 +25,7 @@ type ListOptions struct { ChunkSize int Resume string Filters []Filter + Sort Sort } // Filter represents a field to filter by. @@ -33,6 +36,25 @@ type Filter struct { match string } +// SortOrder represents whether the list should be ascending or descending. +type SortOrder int + +const ( + // ASC stands for ascending order. + ASC SortOrder = iota + // DESC stands for descending (reverse) order. + DESC +) + +// Sort represents the criteria to sort on. +// The subfield to sort by is represented in a request query using . notation, e.g. 'metadata.name'. +// The subfield is internally represented as a slice, e.g. [metadata, name]. +// The order is represented by prefixing the sort key by '-', e.g. sort=-metadata.name. +type Sort struct { + field []string + order SortOrder +} + // ParseQuery parses the query params of a request and returns a ListOptions. func ParseQuery(apiOp *types.APIRequest) *ListOptions { chunkSize := getLimit(apiOp) @@ -47,10 +69,20 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions { } filterOpts = append(filterOpts, Filter{field: strings.Split(filter[0], "."), match: filter[1]}) } + sort := Sort{} + sortKey := q.Get(sortParam) + if sortKey != "" && sortKey[0] == '-' { + sort.order = DESC + sortKey = sortKey[1:] + } + if sortKey != "" { + sort.field = strings.Split(sortKey, ".") + } return &ListOptions{ ChunkSize: chunkSize, Resume: cont, Filters: filterOpts, + Sort: sort, } } @@ -148,3 +180,19 @@ func matchesAll(obj map[string]interface{}, filters []Filter) bool { } return true } + +// SortList sorts the slice by the provided sort criteria. +func SortList(list []unstructured.Unstructured, s Sort) []unstructured.Unstructured { + if len(s.field) == 0 { + return list + } + sort.Slice(list, func(i, j int) bool { + iField := convert.ToString(data.GetValueN(list[i].Object, s.field...)) + jField := convert.ToString(data.GetValueN(list[j].Object, s.field...)) + if s.order == ASC { + return iField < jField + } + return jField < iField + }) + return list +} diff --git a/pkg/stores/partition/listprocessor/processor_test.go b/pkg/stores/partition/listprocessor/processor_test.go index 5757b7a..b418ff6 100644 --- a/pkg/stores/partition/listprocessor/processor_test.go +++ b/pkg/stores/partition/listprocessor/processor_test.go @@ -796,3 +796,322 @@ func TestFilterList(t *testing.T) { }) } } + +func TestSortList(t *testing.T) { + tests := []struct { + name string + objects []unstructured.Unstructured + sort Sort + want []unstructured.Unstructured + }{ + { + name: "sort metadata.name", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + sort: Sort{ + field: []string{"metadata", "name"}, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "reverse sort metadata.name", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + sort: Sort{ + field: []string{"metadata", "name"}, + order: DESC, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "invalid field", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + sort: Sort{ + field: []string{"data", "productType"}, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "unsorted", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + sort: Sort{}, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := SortList(test.objects, test.sort) + assert.Equal(t, test.want, got) + }) + } +} diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index fcdecb0..0b3dcc0 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -128,6 +128,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP } list := listprocessor.FilterList(stream, opts.Filters) + list = listprocessor.SortList(list, opts.Sort) for _, item := range list { item := item diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index a6577a2..fd65187 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -278,6 +278,85 @@ func TestList(t *testing.T) { }, }, }, + { + name: "with sorting", + apiOps: []*types.APIRequest{ + newRequest("sort=metadata.name"), + newRequest("sort=-metadata.name"), + }, + partitions: []Partition{ + mockPartition{ + name: "all", + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + newApple("crispin").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Objects: []types.APIObject{ + newApple("bramley").toObj(), + newApple("crispin").toObj(), + newApple("fuji").toObj(), + newApple("granny-smith").toObj(), + }, + }, + { + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + newApple("fuji").toObj(), + newApple("crispin").toObj(), + newApple("bramley").toObj(), + }, + }, + }, + }, + { + name: "multi-partition sort=metadata.name", + apiOps: []*types.APIRequest{ + newRequest("sort=metadata.name"), + }, + partitions: []Partition{ + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "pink": { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + }, + }, + "green": { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + }, + }, + "yellow": { + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Objects: []types.APIObject{ + newApple("crispin").toObj(), + newApple("granny-smith").toObj(), + }, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { From 9f1a27db06237e51d4c0bdf54b1b67c3b31b0bfb Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 1 Nov 2022 13:00:22 -0700 Subject: [PATCH 23/36] Add pagination to partition store Extend the partition store to parse page and pagesize parameters and return a subset of list results. The total number of pages is included in the list response. Request an initial page: GET /v1/secrets?pagesize=10 or GET /v1/secrets?pagesize=10&page=1 Request subsequent pages, or arbitrary pages: GET /v1/secrets?pagesize=10&page=37 If a page number is out of bounds, an empty list is returned. --- go.mod | 2 +- go.sum | 4 +- .../partition/listprocessor/processor.go | 59 +++++- .../partition/listprocessor/processor_test.go | 199 ++++++++++++++++++ pkg/stores/partition/store.go | 4 + pkg/stores/partition/store_test.go | 14 ++ 6 files changed, 271 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 884a57c..52b7f06 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 - github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076 + github.com/rancher/apiserver v0.0.0-20221220225852-94cba4f28cfd github.com/rancher/dynamiclistener v0.3.5 github.com/rancher/kubernetes-provider-detector v0.1.2 github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 diff --git a/go.sum b/go.sum index f321ea8..6c0994d 100644 --- a/go.sum +++ b/go.sum @@ -502,8 +502,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076 h1:wS95KbXFI1QOVQr3Tz+qyOJ9iia1ITCnjsapxJyI/9U= -github.com/rancher/apiserver v0.0.0-20221205175736-7c507bd5c076/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= +github.com/rancher/apiserver v0.0.0-20221220225852-94cba4f28cfd h1:g0hNrbONfmY4lxvrD2q9KkueYYY4wKUYscm6Ih0QfQ0= +github.com/rancher/apiserver v0.0.0-20221220225852-94cba4f28cfd/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= github.com/rancher/client-go v1.25.4-rancher1 h1:9MlBC8QbgngUkhNzMR8rZmmCIj6WNRHFOnYiwC2Kty4= github.com/rancher/client-go v1.25.4-rancher1/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= diff --git a/pkg/stores/partition/listprocessor/processor.go b/pkg/stores/partition/listprocessor/processor.go index c311d1d..172312c 100644 --- a/pkg/stores/partition/listprocessor/processor.go +++ b/pkg/stores/partition/listprocessor/processor.go @@ -18,14 +18,17 @@ const ( limitParam = "limit" filterParam = "filter" sortParam = "sort" + pageSizeParam = "pagesize" + pageParam = "page" ) // ListOptions represents the query parameters that may be included in a list request. type ListOptions struct { - ChunkSize int - Resume string - Filters []Filter - Sort Sort + ChunkSize int + Resume string + Filters []Filter + Sort Sort + Pagination Pagination } // Filter represents a field to filter by. @@ -55,6 +58,12 @@ type Sort struct { order SortOrder } +// Pagination represents how to return paginated results. +type Pagination struct { + pageSize int + page int +} + // ParseQuery parses the query params of a request and returns a ListOptions. func ParseQuery(apiOp *types.APIRequest) *ListOptions { chunkSize := getLimit(apiOp) @@ -78,11 +87,22 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions { if sortKey != "" { sort.field = strings.Split(sortKey, ".") } + var err error + pagination := Pagination{} + pagination.pageSize, err = strconv.Atoi(q.Get(pageSizeParam)) + if err != nil { + pagination.pageSize = 0 + } + pagination.page, err = strconv.Atoi(q.Get(pageParam)) + if err != nil { + pagination.page = 1 + } return &ListOptions{ - ChunkSize: chunkSize, - Resume: cont, - Filters: filterOpts, - Sort: sort, + ChunkSize: chunkSize, + Resume: cont, + Filters: filterOpts, + Sort: sort, + Pagination: pagination, } } @@ -196,3 +216,26 @@ func SortList(list []unstructured.Unstructured, s Sort) []unstructured.Unstructu }) return list } + +// PaginateList returns a subset of the result based on the pagination criteria as well as the total number of pages the caller can expect. +func PaginateList(list []unstructured.Unstructured, p Pagination) ([]unstructured.Unstructured, int) { + if p.pageSize <= 0 { + return list, 0 + } + page := p.page - 1 + if p.page < 1 { + page = 0 + } + pages := len(list) / p.pageSize + if len(list)%p.pageSize != 0 { + pages++ + } + offset := p.pageSize * page + if offset > len(list) { + return []unstructured.Unstructured{}, pages + } + if offset+p.pageSize > len(list) { + return list[offset:], pages + } + return list[offset : offset+p.pageSize], pages +} diff --git a/pkg/stores/partition/listprocessor/processor_test.go b/pkg/stores/partition/listprocessor/processor_test.go index b418ff6..4e867fc 100644 --- a/pkg/stores/partition/listprocessor/processor_test.go +++ b/pkg/stores/partition/listprocessor/processor_test.go @@ -1115,3 +1115,202 @@ func TestSortList(t *testing.T) { }) } } + +func TestPaginateList(t *testing.T) { + objects := []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "red-delicious", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "crispin", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "bramley", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "golden-delicious", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "macintosh", + }, + }, + }, + } + tests := []struct { + name string + objects []unstructured.Unstructured + pagination Pagination + want []unstructured.Unstructured + wantPages int + }{ + { + name: "pagesize=3, page=unset", + objects: objects, + pagination: Pagination{ + pageSize: 3, + }, + want: objects[:3], + wantPages: 3, + }, + { + name: "pagesize=3, page=1", + objects: objects, + pagination: Pagination{ + pageSize: 3, + page: 1, + }, + want: objects[:3], + wantPages: 3, + }, + { + name: "pagesize=3, page=2", + objects: objects, + pagination: Pagination{ + pageSize: 3, + page: 2, + }, + want: objects[3:6], + wantPages: 3, + }, + { + name: "pagesize=3, page=last", + objects: objects, + pagination: Pagination{ + pageSize: 3, + page: 3, + }, + want: objects[6:], + wantPages: 3, + }, + { + name: "pagesize=3, page>last", + objects: objects, + pagination: Pagination{ + pageSize: 3, + page: 37, + }, + want: []unstructured.Unstructured{}, + wantPages: 3, + }, + { + name: "pagesize=3, page<0", + objects: objects, + pagination: Pagination{ + pageSize: 3, + page: -4, + }, + want: objects[:3], + wantPages: 3, + }, + { + name: "pagesize=0", + objects: objects, + pagination: Pagination{}, + want: objects, + wantPages: 0, + }, + { + name: "pagesize=-1", + objects: objects, + pagination: Pagination{ + pageSize: -7, + }, + want: objects, + wantPages: 0, + }, + { + name: "even page size, even list size", + objects: objects, + pagination: Pagination{ + pageSize: 2, + page: 2, + }, + want: objects[2:4], + wantPages: 4, + }, + { + name: "even page size, odd list size", + objects: objects[1:], + pagination: Pagination{ + pageSize: 2, + page: 2, + }, + want: objects[3:5], + wantPages: 4, + }, + { + name: "odd page size, even list size", + objects: objects, + pagination: Pagination{ + pageSize: 5, + page: 2, + }, + want: objects[5:], + wantPages: 2, + }, + { + name: "odd page size, odd list size", + objects: objects[1:], + pagination: Pagination{ + pageSize: 3, + page: 2, + }, + want: objects[4:7], + wantPages: 3, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, gotPages := PaginateList(test.objects, test.pagination) + assert.Equal(t, test.want, got) + assert.Equal(t, test.wantPages, gotPages) + }) + } +} diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index 0b3dcc0..6d256f8 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -129,6 +129,8 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP list := listprocessor.FilterList(stream, opts.Filters) list = listprocessor.SortList(list, opts.Sort) + result.Count = len(list) + list, pages := listprocessor.PaginateList(list, opts.Pagination) for _, item := range list { item := item @@ -137,6 +139,8 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP result.Revision = lister.Revision() result.Continue = lister.Continue() + result.Pages = pages + return result, lister.Err() } diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index fd65187..4b50c25 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -42,6 +42,7 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 1, Objects: []types.APIObject{ newApple("fuji").toObj(), }, @@ -71,18 +72,21 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 1, Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith"))))), Objects: []types.APIObject{ newApple("fuji").toObj(), }, }, { + Count: 1, Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin"))))), Objects: []types.APIObject{ newApple("granny-smith").toObj(), }, }, { + Count: 1, Objects: []types.APIObject{ newApple("crispin").toObj(), }, @@ -121,6 +125,7 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 2, Objects: []types.APIObject{ newApple("granny-smith").toObj(), newApple("crispin").toObj(), @@ -176,6 +181,7 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 3, Continue: base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`)), Objects: []types.APIObject{ newApple("fuji").toObj(), @@ -184,6 +190,7 @@ func TestList(t *testing.T) { }, }, { + Count: 3, Continue: base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`)), Objects: []types.APIObject{ newApple("bramley").toObj(), @@ -192,6 +199,7 @@ func TestList(t *testing.T) { }, }, { + Count: 1, Objects: []types.APIObject{ newApple("red-delicious").toObj(), }, @@ -221,12 +229,14 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 2, Objects: []types.APIObject{ newApple("granny-smith").toObj(), newApple("bramley").toObj(), }, }, { + Count: 1, Objects: []types.APIObject{ newApple("bramley").toObj(), }, @@ -270,6 +280,7 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 3, Objects: []types.APIObject{ newApple("honeycrisp").with(map[string]string{"category": "eating,baking"}).toObj(), newApple("granny-smith").with(map[string]string{"category": "baking"}).toObj(), @@ -301,6 +312,7 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 4, Objects: []types.APIObject{ newApple("bramley").toObj(), newApple("crispin").toObj(), @@ -309,6 +321,7 @@ func TestList(t *testing.T) { }, }, { + Count: 4, Objects: []types.APIObject{ newApple("granny-smith").toObj(), newApple("fuji").toObj(), @@ -350,6 +363,7 @@ func TestList(t *testing.T) { }, want: []types.APIObjectList{ { + Count: 2, Objects: []types.APIObject{ newApple("crispin").toObj(), newApple("granny-smith").toObj(), From b0eb50f38d741a86841d2115adb7e53ec5f2c954 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Thu, 3 Nov 2022 15:26:49 -0700 Subject: [PATCH 24/36] Add caching to pagination Cache filtered, sorted results for fast subsequent page retrieval. Requests for cached queries need to include the list revision number along with other queries. If no specific revision is requested, a new fetch is done in order to get the latest revision. The revision is included in the list response. Example first request: GET /v1/secrets?pagesize=10 Example subsequent page request: GET /v1/secrets?pagesize=10&page=1&revision=107740 --- .../partition/listprocessor/processor.go | 38 ++++++- pkg/stores/partition/store.go | 106 +++++++++++++++++- pkg/stores/partition/store_test.go | 64 +++++++---- pkg/stores/proxy/proxy_store.go | 7 +- 4 files changed, 179 insertions(+), 36 deletions(-) diff --git a/pkg/stores/partition/listprocessor/processor.go b/pkg/stores/partition/listprocessor/processor.go index 172312c..614a3ee 100644 --- a/pkg/stores/partition/listprocessor/processor.go +++ b/pkg/stores/partition/listprocessor/processor.go @@ -20,6 +20,7 @@ const ( sortParam = "sort" pageSizeParam = "pagesize" pageParam = "page" + revisionParam = "revision" ) // ListOptions represents the query parameters that may be included in a list request. @@ -29,6 +30,7 @@ type ListOptions struct { Filters []Filter Sort Sort Pagination Pagination + Revision string } // Filter represents a field to filter by. @@ -39,6 +41,12 @@ type Filter struct { match string } +// String returns the filter as a query string. +func (f Filter) String() string { + field := strings.Join(f.field, ".") + return field + "=" + f.match +} + // SortOrder represents whether the list should be ascending or descending. type SortOrder int @@ -58,12 +66,26 @@ type Sort struct { order SortOrder } +// String returns the sort parameters as a query string. +func (s Sort) String() string { + field := strings.Join(s.field, ".") + if s.order == DESC { + field = "-" + field + } + return field +} + // Pagination represents how to return paginated results. type Pagination struct { pageSize int page int } +// PageSize returns the integer page size. +func (p Pagination) PageSize() int { + return p.pageSize +} + // ParseQuery parses the query params of a request and returns a ListOptions. func ParseQuery(apiOp *types.APIRequest) *ListOptions { chunkSize := getLimit(apiOp) @@ -78,14 +100,20 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions { } filterOpts = append(filterOpts, Filter{field: strings.Split(filter[0], "."), match: filter[1]}) } - sort := Sort{} + // sort the filter fields so they can be used as a cache key in the store + sort.Slice(filterOpts, func(i, j int) bool { + fieldI := strings.Join(filterOpts[i].field, ".") + fieldJ := strings.Join(filterOpts[j].field, ".") + return fieldI < fieldJ + }) + sortOpts := Sort{} sortKey := q.Get(sortParam) if sortKey != "" && sortKey[0] == '-' { - sort.order = DESC + sortOpts.order = DESC sortKey = sortKey[1:] } if sortKey != "" { - sort.field = strings.Split(sortKey, ".") + sortOpts.field = strings.Split(sortKey, ".") } var err error pagination := Pagination{} @@ -97,12 +125,14 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions { if err != nil { pagination.page = 1 } + revision := q.Get(revisionParam) return &ListOptions{ ChunkSize: chunkSize, Resume: cont, Filters: filterOpts, - Sort: sort, + Sort: sortOpts, Pagination: pagination, + Revision: revision, } } diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index 6d256f8..ca9c9c7 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -5,17 +5,32 @@ package partition import ( "context" "fmt" + "os" "reflect" "strconv" + "time" "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/steve/pkg/accesscontrol" "github.com/rancher/steve/pkg/stores/partition/listprocessor" + "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/cache" "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/endpoints/request" +) + +const ( + // Number of list request entries to save before cache replacement. + // Not related to the total size in memory of the cache, as any item could take any amount of memory. + cacheSizeEnv = "CATTLE_REQUEST_CACHE_SIZE_INT" + defaultCacheSize = 1000 + // Set to non-empty to disable list request caching entirely. + cacheDisableEnv = "CATTLE_REQUEST_CACHE_DISABLED" ) // Partitioner is an interface for interacting with partitions. @@ -28,6 +43,38 @@ type Partitioner interface { // Store implements types.Store for partitions. type Store struct { Partitioner Partitioner + listCache *cache.LRUExpireCache + asl accesscontrol.AccessSetLookup +} + +// NewStore creates a types.Store implementation with a partitioner and an LRU expiring cache for list responses. +func NewStore(partitioner Partitioner, asl accesscontrol.AccessSetLookup) *Store { + cacheSize := defaultCacheSize + if v := os.Getenv(cacheSizeEnv); v != "" { + sizeInt, err := strconv.Atoi(v) + if err == nil { + cacheSize = sizeInt + } + } + s := &Store{ + Partitioner: partitioner, + asl: asl, + } + if v := os.Getenv(cacheDisableEnv); v == "" { + s.listCache = cache.NewLRUExpireCache(cacheSize) + } + return s +} + +type cacheKey struct { + chunkSize int + resume string + filters string + sort string + pageSize int + accessID string + resourcePath string + revision string } // UnstructuredStore is like types.Store but deals in k8s unstructured objects instead of apiserver types. @@ -122,13 +169,40 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP opts := listprocessor.ParseQuery(apiOp) - stream, err := lister.List(apiOp.Context(), opts.ChunkSize, opts.Resume) + key, err := s.getCacheKey(apiOp, opts) if err != nil { return result, err } - list := listprocessor.FilterList(stream, opts.Filters) - list = listprocessor.SortList(list, opts.Sort) + var list []unstructured.Unstructured + if key.revision != "" && s.listCache != nil { + cachedList, ok := s.listCache.Get(key) + if ok { + logrus.Tracef("found cached list for query %s?%s", apiOp.Request.URL.Path, apiOp.Request.URL.RawQuery) + list = cachedList.(*unstructured.UnstructuredList).Items + result.Continue = cachedList.(*unstructured.UnstructuredList).GetContinue() + } + } + if list == nil { // did not look in cache or was not found in cache + stream, err := lister.List(apiOp.Context(), opts.ChunkSize, opts.Resume) + if err != nil { + return result, err + } + list = listprocessor.FilterList(stream, opts.Filters) + list = listprocessor.SortList(list, opts.Sort) + key.revision = lister.Revision() + listToCache := &unstructured.UnstructuredList{ + Items: list, + } + c := lister.Continue() + if c != "" { + listToCache.SetContinue(c) + } + if s.listCache != nil { + s.listCache.Add(key, listToCache, 30*time.Minute) + } + result.Continue = lister.Continue() + } result.Count = len(list) list, pages := listprocessor.PaginateList(list, opts.Pagination) @@ -137,13 +211,33 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP result.Objects = append(result.Objects, toAPI(schema, &item)) } - result.Revision = lister.Revision() - result.Continue = lister.Continue() + result.Revision = key.revision result.Pages = pages - return result, lister.Err() } +// getCacheKey returns a hashable struct identifying a unique user and request. +func (s *Store) getCacheKey(apiOp *types.APIRequest, opts *listprocessor.ListOptions) (cacheKey, error) { + user, ok := request.UserFrom(apiOp.Request.Context()) + if !ok { + return cacheKey{}, fmt.Errorf("could not find user in request") + } + filters := "" + for _, f := range opts.Filters { + filters = filters + f.String() + } + return cacheKey{ + chunkSize: opts.ChunkSize, + resume: opts.Resume, + filters: filters, + sort: opts.Sort.String(), + pageSize: opts.Pagination.PageSize(), + accessID: s.asl.AccessFor(user).ID, + resourcePath: apiOp.Request.URL.Path, + revision: opts.Revision, + }, nil +} + // Create creates a single object in the store. func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) { target, err := s.getStore(apiOp, schema, "create", "") diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index 4b50c25..21325d7 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -1,6 +1,7 @@ package partition import ( + "context" "encoding/base64" "fmt" "net/http" @@ -9,10 +10,13 @@ import ( "testing" "github.com/rancher/apiserver/pkg/types" + "github.com/rancher/steve/pkg/accesscontrol" "github.com/rancher/wrangler/pkg/schemas" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/request" ) func TestList(t *testing.T) { @@ -26,7 +30,7 @@ func TestList(t *testing.T) { { name: "basic", apiOps: []*types.APIRequest{ - newRequest(""), + newRequest("", "user1"), }, partitions: []Partition{ mockPartition{ @@ -52,9 +56,9 @@ func TestList(t *testing.T) { { name: "limit and continue", apiOps: []*types.APIRequest{ - newRequest("limit=1"), - newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith"))))))), - newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin"))))))), + newRequest("limit=1", "user1"), + newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith")))))), "user1"), + newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin")))))), "user1"), }, partitions: []Partition{ mockPartition{ @@ -96,7 +100,7 @@ func TestList(t *testing.T) { { name: "multi-partition", apiOps: []*types.APIRequest{ - newRequest(""), + newRequest("", "user1"), }, partitions: []Partition{ mockPartition{ @@ -136,9 +140,9 @@ func TestList(t *testing.T) { { name: "multi-partition with limit and continue", apiOps: []*types.APIRequest{ - newRequest("limit=3"), - newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`)))), - newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`)))), + newRequest("limit=3", "user1"), + newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`))), "user1"), + newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`))), "user1"), }, partitions: []Partition{ mockPartition{ @@ -209,8 +213,8 @@ func TestList(t *testing.T) { { name: "with filters", apiOps: []*types.APIRequest{ - newRequest("filter=data.color=green"), - newRequest("filter=data.color=green&filter=metadata.name=bramley"), + newRequest("filter=data.color=green", "user1"), + newRequest("filter=data.color=green&filter=metadata.name=bramley", "user1"), }, partitions: []Partition{ mockPartition{ @@ -246,7 +250,7 @@ func TestList(t *testing.T) { { name: "multi-partition with filters", apiOps: []*types.APIRequest{ - newRequest("filter=data.category=baking"), + newRequest("filter=data.category=baking", "user1"), }, partitions: []Partition{ mockPartition{ @@ -292,8 +296,8 @@ func TestList(t *testing.T) { { name: "with sorting", apiOps: []*types.APIRequest{ - newRequest("sort=metadata.name"), - newRequest("sort=-metadata.name"), + newRequest("sort=metadata.name", "user1"), + newRequest("sort=-metadata.name", "user1"), }, partitions: []Partition{ mockPartition{ @@ -334,7 +338,7 @@ func TestList(t *testing.T) { { name: "multi-partition sort=metadata.name", apiOps: []*types.APIRequest{ - newRequest("sort=metadata.name"), + newRequest("sort=metadata.name", "user1"), }, partitions: []Partition{ mockPartition{ @@ -381,12 +385,11 @@ func TestList(t *testing.T) { contents: test.objects[p.Name()], } } - store := Store{ - Partitioner: mockPartitioner{ - stores: stores, - partitions: test.partitions, - }, - } + asl := &mockAccessSetLookup{} + store := NewStore(mockPartitioner{ + stores: stores, + partitions: test.partitions, + }, asl) for i, req := range test.apiOps { got, gotErr := store.List(req, schema) assert.Nil(t, gotErr) @@ -488,16 +491,19 @@ var colorMap = map[string]string{ "red-delicious": "red", } -func newRequest(query string) *types.APIRequest { +func newRequest(query, username string) *types.APIRequest { return &types.APIRequest{ - Request: &http.Request{ + Request: (&http.Request{ URL: &url.URL{ Scheme: "https", Host: "rancher", Path: "/apples", RawQuery: query, }, - }, + }).WithContext(request.WithUser(context.Background(), &user.DefaultInfo{ + Name: username, + Groups: []string{"system:authenticated"}, + })), } } @@ -533,3 +539,15 @@ func (a apple) with(data map[string]string) apple { } return a } + +type mockAccessSetLookup struct{} + +func (m *mockAccessSetLookup) AccessFor(_ user.Info) *accesscontrol.AccessSet { + return &accesscontrol.AccessSet{ + ID: "aabbccdd", + } +} + +func (m *mockAccessSetLookup) PurgeUserData(_ string) { + panic("not implemented") +} diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index c3c40c8..21aeba6 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -75,14 +75,15 @@ type Store struct { func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, lookup accesscontrol.AccessSetLookup) types.Store { return &errorStore{ Store: &WatchRefresh{ - Store: &partition.Store{ - Partitioner: &rbacPartitioner{ + Store: partition.NewStore( + &rbacPartitioner{ proxyStore: &Store{ clientGetter: clientGetter, notifier: notifier, }, }, - }, + lookup, + ), asl: lookup, }, } From 52e189b1ff5d3d025cea043a9a1baa5bfd9c141f Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 4 Nov 2022 15:41:43 -0700 Subject: [PATCH 25/36] Add pagination tests for partition listing --- pkg/stores/partition/store_test.go | 1385 ++++++++++++++++++++++++++-- 1 file changed, 1332 insertions(+), 53 deletions(-) diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index 21325d7..88f9056 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -2,6 +2,7 @@ package partition import ( "context" + "crypto/sha256" "encoding/base64" "fmt" "net/http" @@ -21,20 +22,37 @@ import ( func TestList(t *testing.T) { tests := []struct { - name string - apiOps []*types.APIRequest - partitions []Partition - objects map[string]*unstructured.UnstructuredList - want []types.APIObjectList + name string + apiOps []*types.APIRequest + access []map[string]string + partitions map[string][]Partition + objects map[string]*unstructured.UnstructuredList + want []types.APIObjectList + wantCache []mockCache + disableCache bool + wantListCalls []map[string]int }{ { name: "basic", apiOps: []*types.APIRequest{ newRequest("", "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "all", + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -60,9 +78,22 @@ func TestList(t *testing.T) { newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith")))))), "user1"), newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin")))))), "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "all", + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -102,12 +133,19 @@ func TestList(t *testing.T) { apiOps: []*types.APIRequest{ newRequest("", "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "green", + access: []map[string]string{ + { + "user1": "roleA", }, - mockPartition{ - name: "yellow", + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -144,18 +182,31 @@ func TestList(t *testing.T) { newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`))), "user1"), newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`))), "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "pink", + access: []map[string]string{ + { + "user1": "roleA", }, - mockPartition{ - name: "green", + { + "user1": "roleA", }, - mockPartition{ - name: "yellow", + { + "user1": "roleA", }, - mockPartition{ - name: "red", + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "pink", + }, + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, + mockPartition{ + name: "red", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -216,9 +267,19 @@ func TestList(t *testing.T) { newRequest("filter=data.color=green", "user1"), newRequest("filter=data.color=green&filter=metadata.name=bramley", "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "all", + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -252,15 +313,22 @@ func TestList(t *testing.T) { apiOps: []*types.APIRequest{ newRequest("filter=data.category=baking", "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "pink", + access: []map[string]string{ + { + "user1": "roleA", }, - mockPartition{ - name: "green", - }, - mockPartition{ - name: "yellow", + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "pink", + }, + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -299,9 +367,19 @@ func TestList(t *testing.T) { newRequest("sort=metadata.name", "user1"), newRequest("sort=-metadata.name", "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "all", + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -340,12 +418,19 @@ func TestList(t *testing.T) { apiOps: []*types.APIRequest{ newRequest("sort=metadata.name", "user1"), }, - partitions: []Partition{ - mockPartition{ - name: "green", + access: []map[string]string{ + { + "user1": "roleA", }, - mockPartition{ - name: "yellow", + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, }, }, objects: map[string]*unstructured.UnstructuredList{ @@ -375,17 +460,1178 @@ func TestList(t *testing.T) { }, }, }, + { + name: "pagination", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1&page=2&revision=42", "user1"), + newRequest("pagesize=1&page=3&revision=42", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "42", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"all": 1}, + {"all": 1}, + {"all": 1}, + }, + }, + { + name: "access-change pagination", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1&page=2&revision=42", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleB", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "42", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleB"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"all": 1}, + {"all": 2}, + }, + }, + { + name: "pagination with cache disabled", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1&page=2&revision=42", "user1"), + newRequest("pagesize=1&page=3&revision=42", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "42", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + }, + }, + wantCache: []mockCache{}, + disableCache: true, + wantListCalls: []map[string]int{ + {"all": 1}, + {"all": 2}, + {"all": 3}, + }, + }, + { + name: "multi-partition pagesize=1", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1&page=2&revision=102", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "green", + }, + mockPartition{ + name: "yellow", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "pink": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "101", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + }, + }, + "green": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "102", + }, + }, + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + }, + }, + "yellow": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "103", + }, + }, + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "102", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "102", + Objects: []types.APIObject{ + newApple("crispin").toObj(), + }, + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("crispin").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("crispin").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"green": 1, "yellow": 1}, + {"green": 1, "yellow": 1}, + }, + }, + { + name: "pagesize=1 & limit=2 & continue", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1&limit=2", "user1"), + newRequest("pagesize=1&page=2&limit=2", "user1"), // does not use cache + newRequest("pagesize=1&page=2&revision=42&limit=2", "user1"), // uses cache + newRequest("pagesize=1&page=3&revision=42&limit=2", "user1"), // next page from cache + newRequest(fmt.Sprintf("pagesize=1&revision=42&limit=2&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`)))))), "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "42", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + newApple("crispin").Unstructured, + newApple("red-delicious").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "42", + Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("crispin").toObj(), + }, + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 2, + resume: "", + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 2, + resume: "", + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 2, + resume: "", + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 2, + resume: "", + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 2, + resume: "", + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + { + chunkSize: 2, + resume: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))), + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("red-delicious").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"all": 2}, + {"all": 4}, + {"all": 4}, + {"all": 4}, + {"all": 5}, + }, + }, + { + name: "multi-user pagination", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1", "user2"), + newRequest("pagesize=1&page=2&revision=42", "user1"), + newRequest("pagesize=1&page=2&revision=42", "user2"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user2": "roleB", + }, + { + "user1": "roleA", + }, + { + "user2": "roleB", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + "user2": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "42", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("fuji").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "42", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user2", "roleB"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user2", "roleB"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user2", "roleB"), + resourcePath: "/apples", + revision: "42", + }: { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"all": 1}, + {"all": 2}, + {"all": 2}, + {"all": 2}, + }, + }, + { + name: "multi-partition multi-user pagination", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1", "user2"), + newRequest("pagesize=1&page=2&revision=102", "user1"), + newRequest("pagesize=1&page=2&revision=103", "user2"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user2": "roleB", + }, + { + "user1": "roleA", + }, + { + "user2": "roleB", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "green", + }, + }, + "user2": { + mockPartition{ + name: "yellow", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "pink": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "101", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + }, + }, + "green": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "102", + }, + }, + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + "yellow": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "103", + }, + }, + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("golden-delicious").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "102", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "103", + Objects: []types.APIObject{ + newApple("crispin").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "102", + Objects: []types.APIObject{ + newApple("bramley").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "103", + Objects: []types.APIObject{ + newApple("golden-delicious").toObj(), + }, + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + cacheKey{ + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user2", "roleB"), + resourcePath: "/apples", + revision: "103", + }: { + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("golden-delicious").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user2", "roleB"), + resourcePath: "/apples", + revision: "103", + }: { + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("golden-delicious").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user2", "roleB"), + resourcePath: "/apples", + revision: "103", + }: { + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("golden-delicious").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"green": 1, "yellow": 0}, + {"green": 1, "yellow": 1}, + {"green": 1, "yellow": 1}, + {"green": 1, "yellow": 1}, + }, + }, + { + name: "multi-partition access-change pagination", + apiOps: []*types.APIRequest{ + newRequest("pagesize=1", "user1"), + newRequest("pagesize=1&page=2&revision=102", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleB", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "green", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "pink": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "101", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + }, + }, + "green": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "102", + }, + }, + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + "yellow": { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "103", + }, + }, + Items: []unstructured.Unstructured{ + newApple("crispin").Unstructured, + newApple("golden-delicious").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 2, + Pages: 2, + Revision: "102", + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + }, + }, + { + Count: 2, + Pages: 2, + Revision: "102", + Objects: []types.APIObject{ + newApple("bramley").toObj(), + }, + }, + }, + wantCache: []mockCache{ + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + cacheKey{ + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + }, + }, + { + contents: map[cacheKey]*unstructured.UnstructuredList{ + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleA"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + { + chunkSize: 100000, + pageSize: 1, + accessID: getAccessID("user1", "roleB"), + resourcePath: "/apples", + revision: "102", + }: { + Items: []unstructured.Unstructured{ + newApple("granny-smith").Unstructured, + newApple("bramley").Unstructured, + }, + }, + }, + }, + }, + wantListCalls: []map[string]int{ + {"green": 1}, + {"green": 2}, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { schema := &types.APISchema{Schema: &schemas.Schema{ID: "apple"}} stores := map[string]*mockStore{} - for _, p := range test.partitions { - stores[p.Name()] = &mockStore{ - contents: test.objects[p.Name()], + for _, partitions := range test.partitions { + for _, p := range partitions { + stores[p.Name()] = &mockStore{ + contents: test.objects[p.Name()], + } } } - asl := &mockAccessSetLookup{} + asl := &mockAccessSetLookup{userRoles: test.access} + if test.disableCache { + t.Setenv("CATTLE_REQUEST_CACHE_DISABLED", "Y") + } store := NewStore(mockPartitioner{ stores: stores, partitions: test.partitions, @@ -394,6 +1640,21 @@ func TestList(t *testing.T) { got, gotErr := store.List(req, schema) assert.Nil(t, gotErr) assert.Equal(t, test.want[i], got) + if test.disableCache { + assert.Nil(t, store.listCache) + } + if len(test.wantCache) > 0 { + assert.Equal(t, len(test.wantCache[i].contents), len(store.listCache.Keys())) + for k, v := range test.wantCache[i].contents { + cachedVal, _ := store.listCache.Get(k) + assert.Equal(t, v, cachedVal) + } + } + if len(test.wantListCalls) > 0 { + for name, _ := range store.Partitioner.(mockPartitioner).stores { + assert.Equal(t, test.wantListCalls[i][name], store.Partitioner.(mockPartitioner).stores[name].called) + } + } } }) } @@ -401,7 +1662,7 @@ func TestList(t *testing.T) { type mockPartitioner struct { stores map[string]*mockStore - partitions []Partition + partitions map[string][]Partition } func (m mockPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (Partition, error) { @@ -409,7 +1670,8 @@ func (m mockPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema } func (m mockPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]Partition, error) { - return m.partitions, nil + user, _ := request.UserFrom(apiOp.Request.Context()) + return m.partitions[user.GetName()], nil } func (m mockPartitioner) Store(apiOp *types.APIRequest, partition Partition) (UnstructuredStore, error) { @@ -427,9 +1689,11 @@ func (m mockPartition) Name() string { type mockStore struct { contents *unstructured.UnstructuredList partition mockPartition + called int } func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { + m.called++ query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery) l := query.Get("limit") if l == "" { @@ -481,6 +1745,10 @@ func (m *mockStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, w ty panic("not implemented") } +type mockCache struct { + contents map[cacheKey]*unstructured.UnstructuredList +} + var colorMap = map[string]string{ "fuji": "pink", "honeycrisp": "pink", @@ -540,14 +1808,25 @@ func (a apple) with(data map[string]string) apple { return a } -type mockAccessSetLookup struct{} +type mockAccessSetLookup struct { + accessID string + userRoles []map[string]string +} -func (m *mockAccessSetLookup) AccessFor(_ user.Info) *accesscontrol.AccessSet { +func (m *mockAccessSetLookup) AccessFor(user user.Info) *accesscontrol.AccessSet { + userName := user.GetName() + access := getAccessID(userName, m.userRoles[0][userName]) + m.userRoles = m.userRoles[1:] return &accesscontrol.AccessSet{ - ID: "aabbccdd", + ID: access, } } func (m *mockAccessSetLookup) PurgeUserData(_ string) { panic("not implemented") } + +func getAccessID(user, role string) string { + h := sha256.Sum256([]byte(user + role)) + return string(h[:]) +} From b151f255816c8e6c36c509e15ca149a2da0d7341 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 14 Nov 2022 14:07:23 -0800 Subject: [PATCH 26/36] Add secondary sort parameter Extend the sorting functionality in the partition store to support primary and secondary sorting criteria. Sorting parameters are specified by a single 'sort' query and comma separated. Example: Sort by name and creation time: GET /v1/secrets?sort=metadata.name,metadata.creationTimestamp Reverse sort by name, normal sort by creation time: GET /v1/secrets?sort=-metadata.name,metadata.creationTimestamp Normal sort by name, reverse sort by creation time: GET /v1/secrets?sort=metadata.name,-metadata.creationTimestamp --- .../partition/listprocessor/processor.go | 66 +++- .../partition/listprocessor/processor_test.go | 320 +++++++++++++++++- pkg/stores/partition/store_test.go | 111 ++++++ 3 files changed, 476 insertions(+), 21 deletions(-) diff --git a/pkg/stores/partition/listprocessor/processor.go b/pkg/stores/partition/listprocessor/processor.go index 614a3ee..2cff396 100644 --- a/pkg/stores/partition/listprocessor/processor.go +++ b/pkg/stores/partition/listprocessor/processor.go @@ -62,16 +62,26 @@ const ( // The subfield is internally represented as a slice, e.g. [metadata, name]. // The order is represented by prefixing the sort key by '-', e.g. sort=-metadata.name. type Sort struct { - field []string - order SortOrder + primaryField []string + secondaryField []string + primaryOrder SortOrder + secondaryOrder SortOrder } // String returns the sort parameters as a query string. func (s Sort) String() string { - field := strings.Join(s.field, ".") - if s.order == DESC { + field := "" + if s.primaryOrder == DESC { field = "-" + field } + field += strings.Join(s.primaryField, ".") + if len(s.secondaryField) > 0 { + field += "," + if s.secondaryOrder == DESC { + field += "-" + } + field += strings.Join(s.secondaryField, ".") + } return field } @@ -107,13 +117,27 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions { return fieldI < fieldJ }) sortOpts := Sort{} - sortKey := q.Get(sortParam) - if sortKey != "" && sortKey[0] == '-' { - sortOpts.order = DESC - sortKey = sortKey[1:] - } - if sortKey != "" { - sortOpts.field = strings.Split(sortKey, ".") + sortKeys := q.Get(sortParam) + if sortKeys != "" { + sortParts := strings.SplitN(sortKeys, ",", 2) + primaryField := sortParts[0] + if primaryField != "" && primaryField[0] == '-' { + sortOpts.primaryOrder = DESC + primaryField = primaryField[1:] + } + if primaryField != "" { + sortOpts.primaryField = strings.Split(primaryField, ".") + } + if len(sortParts) > 1 { + secondaryField := sortParts[1] + if secondaryField != "" && secondaryField[0] == '-' { + sortOpts.secondaryOrder = DESC + secondaryField = secondaryField[1:] + } + if secondaryField != "" { + sortOpts.secondaryField = strings.Split(secondaryField, ".") + } + } } var err error pagination := Pagination{} @@ -233,16 +257,24 @@ func matchesAll(obj map[string]interface{}, filters []Filter) bool { // SortList sorts the slice by the provided sort criteria. func SortList(list []unstructured.Unstructured, s Sort) []unstructured.Unstructured { - if len(s.field) == 0 { + if len(s.primaryField) == 0 { return list } sort.Slice(list, func(i, j int) bool { - iField := convert.ToString(data.GetValueN(list[i].Object, s.field...)) - jField := convert.ToString(data.GetValueN(list[j].Object, s.field...)) - if s.order == ASC { - return iField < jField + leftPrime := convert.ToString(data.GetValueN(list[i].Object, s.primaryField...)) + rightPrime := convert.ToString(data.GetValueN(list[j].Object, s.primaryField...)) + if leftPrime == rightPrime && len(s.secondaryField) > 0 { + leftSecond := convert.ToString(data.GetValueN(list[i].Object, s.secondaryField...)) + rightSecond := convert.ToString(data.GetValueN(list[j].Object, s.secondaryField...)) + if s.secondaryOrder == ASC { + return leftSecond < rightSecond + } + return rightSecond < leftSecond } - return jField < iField + if s.primaryOrder == ASC { + return leftPrime < rightPrime + } + return rightPrime < leftPrime }) return list } diff --git a/pkg/stores/partition/listprocessor/processor_test.go b/pkg/stores/partition/listprocessor/processor_test.go index 4e867fc..dfc5f9d 100644 --- a/pkg/stores/partition/listprocessor/processor_test.go +++ b/pkg/stores/partition/listprocessor/processor_test.go @@ -842,7 +842,7 @@ func TestSortList(t *testing.T) { }, }, sort: Sort{ - field: []string{"metadata", "name"}, + primaryField: []string{"metadata", "name"}, }, want: []unstructured.Unstructured{ { @@ -918,8 +918,8 @@ func TestSortList(t *testing.T) { }, }, sort: Sort{ - field: []string{"metadata", "name"}, - order: DESC, + primaryField: []string{"metadata", "name"}, + primaryOrder: DESC, }, want: []unstructured.Unstructured{ { @@ -995,7 +995,7 @@ func TestSortList(t *testing.T) { }, }, sort: Sort{ - field: []string{"data", "productType"}, + primaryField: []string{"data", "productType"}, }, want: []unstructured.Unstructured{ { @@ -1107,6 +1107,318 @@ func TestSortList(t *testing.T) { }, }, }, + { + name: "primary sort ascending, secondary sort ascending", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + sort: Sort{ + primaryField: []string{"data", "color"}, + secondaryField: []string{"metadata", "name"}, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "primary sort ascending, secondary sort descending", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + sort: Sort{ + primaryField: []string{"data", "color"}, + secondaryField: []string{"metadata", "name"}, + secondaryOrder: DESC, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + }, + }, + { + name: "primary sort descending, secondary sort ascending", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + sort: Sort{ + primaryField: []string{"data", "color"}, + primaryOrder: DESC, + secondaryField: []string{"metadata", "name"}, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, + { + name: "primary sort descending, secondary sort descending", + objects: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + sort: Sort{ + primaryField: []string{"data", "color"}, + primaryOrder: DESC, + secondaryField: []string{"metadata", "name"}, + secondaryOrder: DESC, + }, + want: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "honeycrisp", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "fuji", + }, + "data": map[string]interface{}{ + "color": "pink", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "apple", + "metadata": map[string]interface{}{ + "name": "granny-smith", + }, + "data": map[string]interface{}{ + "color": "green", + }, + }, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index 88f9056..f1e295f 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -413,6 +413,117 @@ func TestList(t *testing.T) { }, }, }, + { + name: "sorting with secondary sort", + apiOps: []*types.APIRequest{ + newRequest("sort=data.color,metadata.name,", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("honeycrisp").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 3, + Objects: []types.APIObject{ + newApple("granny-smith").toObj(), + newApple("fuji").toObj(), + newApple("honeycrisp").toObj(), + }, + }, + }, + }, + { + name: "sorting with missing primary sort is unsorted", + apiOps: []*types.APIRequest{ + newRequest("sort=,metadata.name", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("honeycrisp").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 3, + Objects: []types.APIObject{ + newApple("fuji").toObj(), + newApple("honeycrisp").toObj(), + newApple("granny-smith").toObj(), + }, + }, + }, + }, + { + name: "sorting with missing secondary sort is single-column sorted", + apiOps: []*types.APIRequest{ + newRequest("sort=metadata.name,", "user1"), + }, + access: []map[string]string{ + { + "user1": "roleA", + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + objects: map[string]*unstructured.UnstructuredList{ + "all": { + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("honeycrisp").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + want: []types.APIObjectList{ + { + Count: 3, + Objects: []types.APIObject{ + newApple("fuji").toObj(), + newApple("granny-smith").toObj(), + newApple("honeycrisp").toObj(), + }, + }, + }, + }, { name: "multi-partition sort=metadata.name", apiOps: []*types.APIRequest{ From 1a360d705a3573ce29b24beeaf8243f13d06bf92 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 22 Nov 2022 14:22:07 -0800 Subject: [PATCH 27/36] Add README Add an initial README to explain the basics of the API and the new parameters. --- README.md | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d06e331 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +steve +===== + +Steve is a lightweight API proxy for Kubernetes whose aim is to create an +interface layer suitable for dashboards to efficiently interact with +Kubernetes. + +API Usage +--------- + +### Kubernetes proxy + +Requests made to `/api`, `/api/*`, `/apis/*`, `/openapi/*` and `/version` will +be proxied directly to Kubernetes. + +### /v1 API + +Steve registers all Kubernetes resources as schemas in the /v1 API. Any +endpoint can support methods GET, POST, PATCH, PUT, or DELETE, depending on +what the underlying Kubernetes endpoint supports and the user's permissions. + +* `/v1/{type}` - all cluster-scoped resources OR all resources in all + namespaces of type `{type}` that the user has access to +* `/v1/{type}/{name}` - cluster-scoped resource of type `{type}` and unique name `{name}` +* `/v1/{type}/{namespace}` - all resources of type `{type}` under namespace `{namespace}` +* `/v1/{type}/{namespace}/{name}` - resource of type `{type}` under namespace + `{namespace}` with name `{name}` unique within the namespace + +### Query parameters + +Steve supports query parameters to perform actions or process data on top of +what Kubernetes supports. + +#### `link` + +Trigger a link handler, which is registered with the schema. Examples are +calling the shell for a cluster, or following logs during cluster or catalog +operations: + +``` +GET /v1/management.cattle.io.clusters/local?link=log +``` + +#### `action` + +Trigger an action handler, which is registered with the schema. Examples are +generating a kubeconfig for a cluster, or installing an app from a catalog: + +``` +POST /v1/catalog.cattle.io.clusterrepos/rancher-partner-charts?action=install +``` + +#### `filter` + +Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`). + +Filter results by a designated field. Filter keys use dot notation to denote +the subfield of an object to filter on. The filter value is matched as a +substring. + +Example, filtering by object name: + +``` +/v1/{type}?filter=metadata.name=foo +``` + +Filters are ANDed together, so an object must match all filters to be +included in the list. + +``` +/v1/{type}?filter=metadata.name=foo&filter=metadata.namespace=bar +``` + +Arrays are searched for matching items. If any item in the array matches, the +item is included in the list. + +``` +/v1/{type}?filter=spec.containers.image=alpine +``` + +#### `sort` + +Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`). + +Results can be sorted lexicographically by primary and secondary columns. + +Sorting by only a primary column, for example name: + +``` +/v1/{type}?sort=metadata.name +``` + +Reverse sorting by name: + +``` +/v1/{type}?sort=-metadata.name +``` + +The secondary sort criteria is comma separated. + +Example, sorting by name and creation time in ascending order: + +``` +/v1/{type}?sort=metadata.name,metadata.creationTimestamp +``` + +Reverse sort by name, normal sort by creation time: + +``` +/v1/{type}?sort=-metadata.name,metadata.creationTimestamp +``` + +Normal sort by name, reverse sort by creation time: + +``` +/v1/{type}?sort=metadata.name,-metadata.creationTimestamp +``` + +#### `page`, `pagesize`, and `revision` + +Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`). + +Results can be batched by pages for easier display. + +Example initial request returning a page with 10 results: + +``` +/v1/{type}?pagesize=10 +``` + +Pages are one-indexed, so this is equivalent to + +``` +/v1/{type}?pagesize=10&page=1 +``` +To retrieve subsequent pages, the page number and the list revision number must +be included in the request. This ensures the page will be retrieved from the +cache, rather than making a new request to Kubernetes. If the revision number +is omitted, a new fetch is performed in order to get the latest revision. The +revision is included in the list response. + +``` +/v1/{type}?pagezie=10&page=2&revision=107440 +``` + +The total number of pages and individual items are included in the list +response as `pages` and `count` respectively. + +If a page number is out of bounds, an empty list is returned. + +`page` and `pagesize` can be used alongside the `limit` and `continue` +parameters supported by Kubernetes. `limit` and `continue` are typically used +for server-side chunking and do not guarantee results in any order. From 7c0228e575cded2f66297e4bbdeb3abe6f3048be Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Wed, 7 Dec 2022 14:39:05 -0800 Subject: [PATCH 28/36] Use limit=-1 to disable default list limit The default chunk size in the partition was set to 100000. It could be overridden as a larger or smaller number, but not disabled altogether. This change adds the ability for users to explicitly opt out of the limit by specifying a negative number or zero. The default behavior is the same. --- README.md | 33 +++++++++++++++++++ .../partition/listprocessor/processor.go | 7 ++-- pkg/stores/partition/parallel.go | 4 +-- pkg/stores/partition/store_test.go | 12 +++++++ 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d06e331..8343c5a 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,39 @@ generating a kubeconfig for a cluster, or installing an app from a catalog: POST /v1/catalog.cattle.io.clusterrepos/rancher-partner-charts?action=install ``` +#### `limit` + +Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`). + +Set the maximum number of results to retrieve from Kubernetes. The limit is +passed on as a parameter to the Kubernetes request. The purpose of setting this +limit is to prevent a huge response from overwhelming Steve and Rancher. For +more information about setting limits, review the Kubernetes documentation on +[retrieving results in +chunks](https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks). + +The limit controls the size of the set coming from Kubernetes, and then +filtering, sorting, and pagination are applied on that set. Because of this, if +the result set is partial, there is no guarantee that the result returned to +the client is fully sorted across the entire list, only across the returned +chunk. + +The returned response will include a `continue` token, which indicates that the +result is partial and must be used in the subsequent request to retrieve the +next chunk. + +The default limit is 100000. To override the default, set `limit=-1`. + +#### `continue` + +Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`). + +Continue retrieving the next chunk of a partial list. The continue token is +included in the response of a limited list and indicates that the result is +partial. This token can then be used as a query parameter to retrieve the next +chunk. All chunks have been retrieved when the continue field in the response +is empty. + #### `filter` Only applicable to list requests (`/v1/{type}` and `/v1/{type}/{namespace}`). diff --git a/pkg/stores/partition/listprocessor/processor.go b/pkg/stores/partition/listprocessor/processor.go index 2cff396..67cace3 100644 --- a/pkg/stores/partition/listprocessor/processor.go +++ b/pkg/stores/partition/listprocessor/processor.go @@ -161,15 +161,12 @@ func ParseQuery(apiOp *types.APIRequest) *ListOptions { } // getLimit extracts the limit parameter from the request or sets a default of 100000. -// Since a default is always set, this implies that clients must always be -// aware that the list may be incomplete. +// The default limit can be explicitly disabled by setting it to zero or negative. +// If the default is accepted, clients must be aware that the list may be incomplete, and use the "continue" token to get the next chunk of results. func getLimit(apiOp *types.APIRequest) int { limitString := apiOp.Request.URL.Query().Get(limitParam) limit, err := strconv.Atoi(limitString) if err != nil { - limit = 0 - } - if limit <= 0 { limit = defaultLimit } return limit diff --git a/pkg/stores/partition/parallel.go b/pkg/stores/partition/parallel.go index 49f380c..03c8089 100644 --- a/pkg/stores/partition/parallel.go +++ b/pkg/stores/partition/parallel.go @@ -137,7 +137,7 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l }() for i := indexOrZero(p.Partitions, state.PartitionName); i < len(p.Partitions); i++ { - if capacity <= 0 || isDone(ctx) { + if (limit > 0 && capacity <= 0) || isDone(ctx) { break } @@ -197,7 +197,7 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l // Case 1: the capacity has been reached across all goroutines but the list is still only partial, // so save the state so that the next page can be requested later. - if len(list.Items) > capacity { + if limit > 0 && len(list.Items) > capacity { result <- list.Items[:capacity] // save state to redo this list at this offset p.state = &listState{ diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index f1e295f..c0b2b46 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -77,6 +77,7 @@ func TestList(t *testing.T) { newRequest("limit=1", "user1"), newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith")))))), "user1"), newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin")))))), "user1"), + newRequest("limit=-1", "user1"), }, access: []map[string]string{ { @@ -88,6 +89,9 @@ func TestList(t *testing.T) { { "user1": "roleA", }, + { + "user1": "roleA", + }, }, partitions: map[string][]Partition{ "user1": { @@ -126,6 +130,14 @@ func TestList(t *testing.T) { newApple("crispin").toObj(), }, }, + { + Count: 3, + Objects: []types.APIObject{ + newApple("fuji").toObj(), + newApple("granny-smith").toObj(), + newApple("crispin").toObj(), + }, + }, }, }, { From fa7fb372455f93dd2a2f5c55a9026a6adad371cf Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Wed, 7 Dec 2022 17:57:14 -0800 Subject: [PATCH 29/36] Retrieve exact revision regardless of cache state Without this change, the cache is checked if a revision is specified, but the revision is ignored if the value is not found in the cache. This is a problem if by chance sequential paginated requests land on separate pods, because a different version number may be fetched on the sequent request and cause the caches to be inconsistent with one another. This change ensures that `revision` is passed as `resourceVersion` to the Kubernetes request, since `revision` has no meaning to Kubernetes. --- pkg/stores/partition/parallel.go | 4 +- pkg/stores/partition/store.go | 13 +++- pkg/stores/partition/store_test.go | 115 ++++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/pkg/stores/partition/parallel.go b/pkg/stores/partition/parallel.go index 03c8089..35403aa 100644 --- a/pkg/stores/partition/parallel.go +++ b/pkg/stores/partition/parallel.go @@ -72,7 +72,7 @@ func indexOrZero(partitions []Partition, name string) int { // List returns a stream of objects up to the requested limit. // If the continue token is not empty, it decodes it and returns the stream // starting at the indicated marker. -func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume string) (<-chan []unstructured.Unstructured, error) { +func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume, revision string) (<-chan []unstructured.Unstructured, error) { var state listState if resume != "" { bytes, err := base64.StdEncoding.DecodeString(resume) @@ -86,6 +86,8 @@ func (p *ParallelPartitionLister) List(ctx context.Context, limit int, resume st if state.Limit > 0 { limit = state.Limit } + } else { + state.Revision = revision } result := make(chan []unstructured.Unstructured) diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index ca9c9c7..1efbda7 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -136,7 +136,10 @@ func (s *Store) listPartition(ctx context.Context, apiOp *types.APIRequest, sche values := req.Request.URL.Query() values.Set("continue", cont) - values.Set("revision", revision) + if revision != "" && cont == "" { + values.Set("resourceVersion", revision) + values.Set("resourceVersionMatch", "Exact") // supported since k8s 1.19 + } if limit > 0 { values.Set("limit", strconv.Itoa(limit)) } else { @@ -184,11 +187,17 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP } } if list == nil { // did not look in cache or was not found in cache - stream, err := lister.List(apiOp.Context(), opts.ChunkSize, opts.Resume) + stream, err := lister.List(apiOp.Context(), opts.ChunkSize, opts.Resume, opts.Revision) if err != nil { return result, err } list = listprocessor.FilterList(stream, opts.Filters) + // Check for any errors returned during the parallel listing requests. + // We don't want to cache the list or bother with further processing if the list is empty or corrupt. + // FilterList guarantees that the stream has been consumed and the error is populated if there is any. + if lister.Err() != nil { + return result, lister.Err() + } list = listprocessor.SortList(list, opts.Sort) key.revision = lister.Revision() listToCache := &unstructured.UnstructuredList{ diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index c0b2b46..42c8b27 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -1743,7 +1743,7 @@ func TestList(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { schema := &types.APISchema{Schema: &schemas.Schema{ID: "apple"}} - stores := map[string]*mockStore{} + stores := map[string]UnstructuredStore{} for _, partitions := range test.partitions { for _, p := range partitions { stores[p.Name()] = &mockStore{ @@ -1775,7 +1775,7 @@ func TestList(t *testing.T) { } if len(test.wantListCalls) > 0 { for name, _ := range store.Partitioner.(mockPartitioner).stores { - assert.Equal(t, test.wantListCalls[i][name], store.Partitioner.(mockPartitioner).stores[name].called) + assert.Equal(t, test.wantListCalls[i][name], store.Partitioner.(mockPartitioner).stores[name].(*mockStore).called) } } } @@ -1783,8 +1783,74 @@ func TestList(t *testing.T) { } } +func TestListByRevision(t *testing.T) { + + schema := &types.APISchema{Schema: &schemas.Schema{ID: "apple"}} + asl := &mockAccessSetLookup{userRoles: []map[string]string{ + { + "user1": "roleA", + }, + { + "user1": "roleA", + }, + }} + store := NewStore(mockPartitioner{ + stores: map[string]UnstructuredStore{ + "all": &mockVersionedStore{ + versions: []mockStore{ + { + contents: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "1", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + }, + }, + }, + { + contents: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "resourceVersion": "2", + }, + }, + Items: []unstructured.Unstructured{ + newApple("fuji").Unstructured, + newApple("granny-smith").Unstructured, + }, + }, + }, + }, + }, + }, + partitions: map[string][]Partition{ + "user1": { + mockPartition{ + name: "all", + }, + }, + }, + }, asl) + req := newRequest("", "user1") + t.Setenv("CATTLE_REQUEST_CACHE_DISABLED", "Y") + + got, gotErr := store.List(req, schema) + assert.Nil(t, gotErr) + wantVersion := "2" + assert.Equal(t, wantVersion, got.Revision) + + req = newRequest("revision=1", "user1") + got, gotErr = store.List(req, schema) + assert.Nil(t, gotErr) + wantVersion = "1" + assert.Equal(t, wantVersion, got.Revision) +} + type mockPartitioner struct { - stores map[string]*mockStore + stores map[string]UnstructuredStore partitions map[string][]Partition } @@ -1868,6 +1934,49 @@ func (m *mockStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, w ty panic("not implemented") } +type mockVersionedStore struct { + mockStore + versions []mockStore +} + +func (m *mockVersionedStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { + m.called++ + query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery) + rv := len(m.versions) - 1 + if query.Get("resourceVersion") != "" { + rv, _ = strconv.Atoi(query.Get("resourceVersion")) + rv-- + } + l := query.Get("limit") + if l == "" { + return m.versions[rv].contents, nil + } + i := 0 + if c := query.Get("continue"); c != "" { + start, _ := base64.StdEncoding.DecodeString(c) + for j, obj := range m.versions[rv].contents.Items { + if string(start) == obj.GetName() { + i = j + break + } + } + } + lInt, _ := strconv.Atoi(l) + contents := m.versions[rv].contents.DeepCopy() + if len(contents.Items) > i+lInt { + contents.SetContinue(base64.StdEncoding.EncodeToString([]byte(contents.Items[i+lInt].GetName()))) + } + if i > len(contents.Items) { + return contents, nil + } + if i+lInt > len(contents.Items) { + contents.Items = contents.Items[i:] + return contents, nil + } + contents.Items = contents.Items[i : i+lInt] + return contents, nil +} + type mockCache struct { contents map[cacheKey]*unstructured.UnstructuredList } From 956b7351aaebbcaae8811fb11a1c725c407719fc Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Fri, 16 Dec 2022 10:12:57 -0600 Subject: [PATCH 30/36] Initial implmentation of warning headers Attempts to pass through warning headers which k8s returns. Requires an update to rancher/apiserver. --- go.mod | 2 +- go.sum | 4 +- pkg/client/factory.go | 51 +++++------ pkg/resources/cluster/apply.go | 4 +- pkg/stores/partition/parallel.go | 6 +- pkg/stores/partition/store.go | 39 ++++----- pkg/stores/partition/store_test.go | 28 +++---- pkg/stores/proxy/proxy_store.go | 130 +++++++++++++++++------------ pkg/stores/proxy/rbac_store.go | 2 +- 9 files changed, 148 insertions(+), 118 deletions(-) diff --git a/go.mod b/go.mod index 52b7f06..171be8e 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 - github.com/rancher/apiserver v0.0.0-20221220225852-94cba4f28cfd + github.com/rancher/apiserver v0.0.0-20221229135954-26bed53611c4 github.com/rancher/dynamiclistener v0.3.5 github.com/rancher/kubernetes-provider-detector v0.1.2 github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 diff --git a/go.sum b/go.sum index 6c0994d..dd01376 100644 --- a/go.sum +++ b/go.sum @@ -502,8 +502,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rancher/apiserver v0.0.0-20221220225852-94cba4f28cfd h1:g0hNrbONfmY4lxvrD2q9KkueYYY4wKUYscm6Ih0QfQ0= -github.com/rancher/apiserver v0.0.0-20221220225852-94cba4f28cfd/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= +github.com/rancher/apiserver v0.0.0-20221229135954-26bed53611c4 h1:eOq/tiwMCzcwexrbUQ9Agd9PHhwwtH9/G4usE0MoN8s= +github.com/rancher/apiserver v0.0.0-20221229135954-26bed53611c4/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= github.com/rancher/client-go v1.25.4-rancher1 h1:9MlBC8QbgngUkhNzMR8rZmmCIj6WNRHFOnYiwC2Kty4= github.com/rancher/client-go v1.25.4-rancher1/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= diff --git a/pkg/client/factory.go b/pkg/client/factory.go index 064d2b1..09c3f8f 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -111,52 +111,52 @@ func (p *Factory) AdminK8sInterface() (kubernetes.Interface, error) { return kubernetes.NewForConfig(p.clientCfg) } -func (p *Factory) DynamicClient(ctx *types.APIRequest) (dynamic.Interface, error) { - return newDynamicClient(ctx, p.clientCfg, p.impersonate) +func (p *Factory) DynamicClient(ctx *types.APIRequest, warningHandler rest.WarningHandler) (dynamic.Interface, error) { + return newDynamicClient(ctx, p.clientCfg, p.impersonate, warningHandler) } -func (p *Factory) Client(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { - return newClient(ctx, p.clientCfg, s, namespace, p.impersonate) +func (p *Factory) Client(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { + return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) } -func (p *Factory) AdminClient(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { - return newClient(ctx, p.clientCfg, s, namespace, false) +func (p *Factory) AdminClient(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { + return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) } -func (p *Factory) ClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { - return newClient(ctx, p.watchClientCfg, s, namespace, p.impersonate) +func (p *Factory) ClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { + return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) } -func (p *Factory) AdminClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { - return newClient(ctx, p.watchClientCfg, s, namespace, false) +func (p *Factory) AdminClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { + return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) } -func (p *Factory) TableClient(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { +func (p *Factory) TableClient(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.tableClientCfg, s, namespace, p.impersonate) + return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) } - return p.Client(ctx, s, namespace) + return p.Client(ctx, s, namespace, warningHandler) } -func (p *Factory) TableAdminClient(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { +func (p *Factory) TableAdminClient(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.tableClientCfg, s, namespace, false) + return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) } - return p.AdminClient(ctx, s, namespace) + return p.AdminClient(ctx, s, namespace, warningHandler) } -func (p *Factory) TableClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { +func (p *Factory) TableClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.tableWatchClientCfg, s, namespace, p.impersonate) + return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) } - return p.ClientForWatch(ctx, s, namespace) + return p.ClientForWatch(ctx, s, namespace, warningHandler) } -func (p *Factory) TableAdminClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) { +func (p *Factory) TableAdminClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.tableWatchClientCfg, s, namespace, false) + return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) } - return p.AdminClientForWatch(ctx, s, namespace) + return p.AdminClientForWatch(ctx, s, namespace, warningHandler) } func setupConfig(ctx *types.APIRequest, cfg *rest.Config, impersonate bool) (*rest.Config, error) { @@ -173,8 +173,9 @@ func setupConfig(ctx *types.APIRequest, cfg *rest.Config, impersonate bool) (*re return cfg, nil } -func newDynamicClient(ctx *types.APIRequest, cfg *rest.Config, impersonate bool) (dynamic.Interface, error) { +func newDynamicClient(ctx *types.APIRequest, cfg *rest.Config, impersonate bool, warningHandler rest.WarningHandler) (dynamic.Interface, error) { cfg, err := setupConfig(ctx, cfg, impersonate) + cfg.WarningHandler = warningHandler if err != nil { return nil, err } @@ -182,8 +183,8 @@ func newDynamicClient(ctx *types.APIRequest, cfg *rest.Config, impersonate bool) return dynamic.NewForConfig(cfg) } -func newClient(ctx *types.APIRequest, cfg *rest.Config, s *types.APISchema, namespace string, impersonate bool) (dynamic.ResourceInterface, error) { - client, err := newDynamicClient(ctx, cfg, impersonate) +func newClient(ctx *types.APIRequest, cfg *rest.Config, s *types.APISchema, namespace string, impersonate bool, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { + client, err := newDynamicClient(ctx, cfg, impersonate, warningHandler) if err != nil { return nil, err } diff --git a/pkg/resources/cluster/apply.go b/pkg/resources/cluster/apply.go index c36eb98..9e35257 100644 --- a/pkg/resources/cluster/apply.go +++ b/pkg/resources/cluster/apply.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" ) type Apply struct { @@ -110,7 +111,8 @@ func (a *Apply) createApply(apiContext *types.APIRequest) (apply.Apply, error) { } apply := apply.New(client.Discovery(), func(gvr schema.GroupVersionResource) (dynamic.NamespaceableResourceInterface, error) { - dynamicClient, err := a.cg.DynamicClient(apiContext) + // don't record warnings from apply + dynamicClient, err := a.cg.DynamicClient(apiContext, rest.NoWarnings{}) if err != nil { return nil, err } diff --git a/pkg/stores/partition/parallel.go b/pkg/stores/partition/parallel.go index 35403aa..5cf97b5 100644 --- a/pkg/stores/partition/parallel.go +++ b/pkg/stores/partition/parallel.go @@ -5,6 +5,8 @@ import ( "encoding/base64" "encoding/json" + "github.com/rancher/apiserver/pkg/types" + "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -33,7 +35,7 @@ type ParallelPartitionLister struct { } // PartitionLister lists objects for one partition. -type PartitionLister func(ctx context.Context, partition Partition, cont string, revision string, limit int) (*unstructured.UnstructuredList, error) +type PartitionLister func(ctx context.Context, partition Partition, cont string, revision string, limit int) (*unstructured.UnstructuredList, []types.Warning, error) // Err returns the latest error encountered. func (p *ParallelPartitionLister) Err() error { @@ -174,7 +176,7 @@ func (p *ParallelPartitionLister) feeder(ctx context.Context, state listState, l if partition.Name() == state.PartitionName { cont = state.Continue } - list, err := p.Lister(ctx, partition, cont, state.Revision, limit) + list, _, err := p.Lister(ctx, partition, cont, state.Revision, limit) if err != nil { return err } diff --git a/pkg/stores/partition/store.go b/pkg/stores/partition/store.go index 1efbda7..185a6cb 100644 --- a/pkg/stores/partition/store.go +++ b/pkg/stores/partition/store.go @@ -79,11 +79,11 @@ type cacheKey struct { // UnstructuredStore is like types.Store but deals in k8s unstructured objects instead of apiserver types. type UnstructuredStore interface { - ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) - List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) - Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, error) - Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, error) - Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) + ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) + List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) + Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, []types.Warning, error) + Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, []types.Warning, error) + Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) } @@ -103,11 +103,11 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri return types.APIObject{}, err } - obj, err := target.Delete(apiOp, schema, id) + obj, warnings, err := target.Delete(apiOp, schema, id) if err != nil { return types.APIObject{}, err } - return toAPI(schema, obj), nil + return toAPI(schema, obj, warnings), nil } // ByID looks up a single object by its ID. @@ -117,18 +117,18 @@ func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string return types.APIObject{}, err } - obj, err := target.ByID(apiOp, schema, id) + obj, warnings, err := target.ByID(apiOp, schema, id) if err != nil { return types.APIObject{}, err } - return toAPI(schema, obj), nil + return toAPI(schema, obj, warnings), nil } func (s *Store) listPartition(ctx context.Context, apiOp *types.APIRequest, schema *types.APISchema, partition Partition, - cont string, revision string, limit int) (*unstructured.UnstructuredList, error) { + cont string, revision string, limit int) (*unstructured.UnstructuredList, []types.Warning, error) { store, err := s.Partitioner.Store(apiOp, partition) if err != nil { - return nil, err + return nil, nil, err } req := apiOp.Clone() @@ -163,7 +163,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP } lister := ParallelPartitionLister{ - Lister: func(ctx context.Context, partition Partition, cont string, revision string, limit int) (*unstructured.UnstructuredList, error) { + Lister: func(ctx context.Context, partition Partition, cont string, revision string, limit int) (*unstructured.UnstructuredList, []types.Warning, error) { return s.listPartition(ctx, apiOp, schema, partition, cont, revision, limit) }, Concurrency: 3, @@ -217,7 +217,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP for _, item := range list { item := item - result.Objects = append(result.Objects, toAPI(schema, &item)) + result.Objects = append(result.Objects, toAPI(schema, &item, nil)) } result.Revision = key.revision @@ -254,11 +254,11 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data ty return types.APIObject{}, err } - obj, err := target.Create(apiOp, schema, data) + obj, warnings, err := target.Create(apiOp, schema, data) if err != nil { return types.APIObject{}, err } - return toAPI(schema, obj), nil + return toAPI(schema, obj, warnings), nil } // Update updates a single object in the store. @@ -268,11 +268,11 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data ty return types.APIObject{}, err } - obj, err := target.Update(apiOp, schema, data, id) + obj, warnings, err := target.Update(apiOp, schema, data, id) if err != nil { return types.APIObject{}, err } - return toAPI(schema, obj), nil + return toAPI(schema, obj, warnings), nil } // Watch returns a channel of events for a list or resource. @@ -318,7 +318,7 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types return response, nil } -func toAPI(schema *types.APISchema, obj runtime.Object) types.APIObject { +func toAPI(schema *types.APISchema, obj runtime.Object, warnings []types.Warning) types.APIObject { if obj == nil || reflect.ValueOf(obj).IsNil() { return types.APIObject{} } @@ -344,6 +344,7 @@ func toAPI(schema *types.APISchema, obj runtime.Object) types.APIObject { } apiObject.ID = id + apiObject.Warnings = warnings return apiObject } @@ -384,7 +385,7 @@ func toAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, event watch.Ev return apiEvent } - apiEvent.Object = toAPI(schema, event.Object) + apiEvent.Object = toAPI(schema, event.Object, nil) m, err := meta.Accessor(event.Object) if err != nil { diff --git a/pkg/stores/partition/store_test.go b/pkg/stores/partition/store_test.go index 42c8b27..72d668f 100644 --- a/pkg/stores/partition/store_test.go +++ b/pkg/stores/partition/store_test.go @@ -1881,12 +1881,12 @@ type mockStore struct { called int } -func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { +func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) { m.called++ query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery) l := query.Get("limit") if l == "" { - return m.contents, nil + return m.contents, nil, nil } i := 0 if c := query.Get("continue"); c != "" { @@ -1904,29 +1904,29 @@ func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*uns contents.SetContinue(base64.StdEncoding.EncodeToString([]byte(contents.Items[i+lInt].GetName()))) } if i > len(contents.Items) { - return contents, nil + return contents, nil, nil } if i+lInt > len(contents.Items) { contents.Items = contents.Items[i:] - return contents, nil + return contents, nil, nil } contents.Items = contents.Items[i : i+lInt] - return contents, nil + return contents, nil, nil } -func (m *mockStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { +func (m *mockStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) { panic("not implemented") } -func (m *mockStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, error) { +func (m *mockStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, []types.Warning, error) { panic("not implemented") } -func (m *mockStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, error) { +func (m *mockStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, []types.Warning, error) { panic("not implemented") } -func (m *mockStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { +func (m *mockStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) { panic("not implemented") } @@ -1939,7 +1939,7 @@ type mockVersionedStore struct { versions []mockStore } -func (m *mockVersionedStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { +func (m *mockVersionedStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) { m.called++ query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery) rv := len(m.versions) - 1 @@ -1949,7 +1949,7 @@ func (m *mockVersionedStore) List(apiOp *types.APIRequest, schema *types.APISche } l := query.Get("limit") if l == "" { - return m.versions[rv].contents, nil + return m.versions[rv].contents, nil, nil } i := 0 if c := query.Get("continue"); c != "" { @@ -1967,14 +1967,14 @@ func (m *mockVersionedStore) List(apiOp *types.APIRequest, schema *types.APISche contents.SetContinue(base64.StdEncoding.EncodeToString([]byte(contents.Items[i+lInt].GetName()))) } if i > len(contents.Items) { - return contents, nil + return contents, nil, nil } if i+lInt > len(contents.Items) { contents.Items = contents.Items[i:] - return contents, nil + return contents, nil, nil } contents.Items = contents.Items[i : i+lInt] - return contents, nil + return contents, nil, nil } type mockCache struct { diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index 21aeba6..fc5f574 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -32,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" ) const watchTimeoutEnv = "CATTLE_WATCH_TIMEOUT_SECONDS" @@ -51,13 +52,28 @@ type ClientGetter interface { IsImpersonating() bool K8sInterface(ctx *types.APIRequest) (kubernetes.Interface, error) AdminK8sInterface() (kubernetes.Interface, error) - Client(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) - DynamicClient(ctx *types.APIRequest) (dynamic.Interface, error) - AdminClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) - TableClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) - TableAdminClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) - TableClientForWatch(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) - TableAdminClientForWatch(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error) + Client(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) + DynamicClient(ctx *types.APIRequest, warningHandler rest.WarningHandler) (dynamic.Interface, error) + AdminClient(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) + TableClient(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) + TableAdminClient(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) + TableClientForWatch(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) + TableAdminClientForWatch(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) +} + +type warningBuffer struct { + Warnings []types.Warning +} + +func (w *warningBuffer) HandleWarningHeader(code int, agent string, text string) { + if w.Warnings == nil { + w.Warnings = []types.Warning{} + } + w.Warnings = append(w.Warnings, types.Warning{ + Code: code, + Agent: agent, + Text: text, + }) } // RelationshipNotifier is an interface for handling wrangler summary.Relationship events. @@ -90,7 +106,7 @@ func NewProxyStore(clientGetter ClientGetter, notifier RelationshipNotifier, loo } // ByID looks up a single object by its ID. -func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { +func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) { return s.byID(apiOp, schema, apiOp.Namespace, id) } @@ -98,20 +114,21 @@ func decodeParams(apiOp *types.APIRequest, target runtime.Object) error { return paramCodec.DecodeParameters(apiOp.Request.URL.Query(), metav1.SchemeGroupVersion, target) } -func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, namespace, id string) (*unstructured.Unstructured, error) { - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, namespace)) +func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, namespace, id string) (*unstructured.Unstructured, []types.Warning, error) { + buffer := &warningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, namespace, buffer)) if err != nil { - return nil, err + return nil, nil, err } opts := metav1.GetOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return nil, err + return nil, nil, err } obj, err := k8sClient.Get(apiOp, id, opts) rowToObject(obj) - return obj, err + return obj, buffer.Warnings, err } func moveFromUnderscore(obj map[string]interface{}) map[string]interface{} { @@ -185,21 +202,21 @@ func tableToObjects(obj map[string]interface{}) []unstructured.Unstructured { // to list *all* resources. // With this filter, the request can be performed successfully, and only the allowed resources will // be returned in the list. -func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names sets.String) (*unstructured.UnstructuredList, error) { +func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names sets.String) (*unstructured.UnstructuredList, []types.Warning, error) { if apiOp.Namespace == "*" { // This happens when you grant namespaced objects with "get" by name in a clusterrolebinding. We will treat // this as an invalid situation instead of listing all objects in the cluster and filtering by name. - return nil, nil + return nil, nil, nil } - - adminClient, err := s.clientGetter.TableAdminClient(apiOp, schema, apiOp.Namespace) + buffer := &warningBuffer{} + adminClient, err := s.clientGetter.TableAdminClient(apiOp, schema, apiOp.Namespace, buffer) if err != nil { - return nil, err + return nil, nil, err } objs, err := s.list(apiOp, schema, adminClient) if err != nil { - return nil, err + return nil, nil, err } var filtered []unstructured.Unstructured @@ -210,16 +227,18 @@ func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names } objs.Items = filtered - return objs, nil + return objs, buffer.Warnings, nil } // List returns an unstructured list of resources. -func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { - client, err := s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace) +func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) { + buffer := &warningBuffer{} + client, err := s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace, buffer) if err != nil { - return nil, err + return nil, nil, err } - return s.list(apiOp, schema, client) + result, err := s.list(apiOp, schema, client) + return result, buffer.Warnings, err } func (s *Store) list(apiOp *types.APIRequest, schema *types.APISchema, client dynamic.ResourceInterface) (*unstructured.UnstructuredList, error) { @@ -288,7 +307,7 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt if s.notifier != nil { eg.Go(func() error { for rel := range s.notifier.OnInboundRelationshipChange(ctx, schema, apiOp.Namespace) { - obj, err := s.byID(apiOp, schema, rel.Namespace, rel.Name) + obj, _, err := s.byID(apiOp, schema, rel.Namespace, rel.Name) if err == nil { rowToObject(obj) result <- watch.Event{Type: watch.Modified, Object: obj} @@ -331,7 +350,8 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt // With this filter, the request can be performed successfully, and only the allowed resources will // be returned in watch. func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, names sets.String) (chan watch.Event, error) { - adminClient, err := s.clientGetter.TableAdminClientForWatch(apiOp, schema, apiOp.Namespace) + buffer := &warningBuffer{} + adminClient, err := s.clientGetter.TableAdminClientForWatch(apiOp, schema, apiOp.Namespace, buffer) if err != nil { return nil, err } @@ -360,7 +380,8 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t // Watch returns a channel of events for a list or resource. func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) { - client, err := s.clientGetter.TableClientForWatch(apiOp, schema, apiOp.Namespace) + buffer := &warningBuffer{} + client, err := s.clientGetter.TableClientForWatch(apiOp, schema, apiOp.Namespace, buffer) if err != nil { return nil, err } @@ -378,7 +399,7 @@ func (s *Store) watch(apiOp *types.APIRequest, schema *types.APISchema, w types. } // Create creates a single object in the store. -func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject) (*unstructured.Unstructured, error) { +func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject) (*unstructured.Unstructured, []types.Warning, error) { var ( resp *unstructured.Unstructured ) @@ -402,38 +423,40 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params gvk := attributes.GVK(schema) input["apiVersion"], input["kind"] = gvk.ToAPIVersionAndKind() - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns)) + buffer := &warningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns, buffer)) if err != nil { - return nil, err + return nil, nil, err } opts := metav1.CreateOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return nil, err + return nil, nil, err } resp, err = k8sClient.Create(apiOp, &unstructured.Unstructured{Object: input}, opts) rowToObject(resp) - return resp, err + return resp, buffer.Warnings, err } // Update updates a single object in the store. -func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject, id string) (*unstructured.Unstructured, error) { +func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject, id string) (*unstructured.Unstructured, []types.Warning, error) { var ( err error input = params.Data() ) ns := types.Namespace(input) - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns)) + buffer := &warningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns, buffer)) if err != nil { - return nil, err + return nil, nil, err } if apiOp.Method == http.MethodPatch { bytes, err := ioutil.ReadAll(io.LimitReader(apiOp.Request.Body, 2<<20)) if err != nil { - return nil, err + return nil, nil, err } pType := apitypes.StrategicMergePatchType @@ -443,70 +466,71 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params opts := metav1.PatchOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return nil, err + return nil, nil, err } if pType == apitypes.StrategicMergePatchType { data := map[string]interface{}{} if err := json.Unmarshal(bytes, &data); err != nil { - return nil, err + return nil, nil, err } data = moveFromUnderscore(data) bytes, err = json.Marshal(data) if err != nil { - return nil, err + return nil, nil, err } } resp, err := k8sClient.Patch(apiOp, id, pType, bytes, opts) if err != nil { - return nil, err + return nil, nil, err } - return resp, nil + return resp, buffer.Warnings, nil } resourceVersion := input.String("metadata", "resourceVersion") if resourceVersion == "" { - return nil, fmt.Errorf("metadata.resourceVersion is required for update") + return nil, nil, fmt.Errorf("metadata.resourceVersion is required for update") } opts := metav1.UpdateOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return nil, err + return nil, nil, err } resp, err := k8sClient.Update(apiOp, &unstructured.Unstructured{Object: moveFromUnderscore(input)}, metav1.UpdateOptions{}) if err != nil { - return nil, err + return nil, nil, err } rowToObject(resp) - return resp, nil + return resp, buffer.Warnings, nil } // Delete deletes an object from a store. -func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) { +func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) { opts := metav1.DeleteOptions{} if err := decodeParams(apiOp, &opts); err != nil { - return nil, nil + return nil, nil, nil } - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace)) + buffer := &warningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace, buffer)) if err != nil { - return nil, err + return nil, nil, err } if err := k8sClient.Delete(apiOp, id, opts); err != nil { - return nil, err + return nil, nil, err } - obj, err := s.byID(apiOp, schema, apiOp.Namespace, id) + obj, _, err := s.byID(apiOp, schema, apiOp.Namespace, id) if err != nil { // ignore lookup error - return nil, validation.ErrorCode{ + return nil, nil, validation.ErrorCode{ Status: http.StatusNoContent, } } - return obj, nil + return obj, buffer.Warnings, nil } diff --git a/pkg/stores/proxy/rbac_store.go b/pkg/stores/proxy/rbac_store.go index f67089d..8835f69 100644 --- a/pkg/stores/proxy/rbac_store.go +++ b/pkg/stores/proxy/rbac_store.go @@ -101,7 +101,7 @@ type byNameOrNamespaceStore struct { } // List returns a list of resources by partition. -func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, error) { +func (b *byNameOrNamespaceStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) { if b.partition.Passthrough { return b.Store.List(apiOp, schema) } From 65ca114ce0a86db1c9cefe45c1c41bf3b5f3594a Mon Sep 17 00:00:00 2001 From: Chad Roberts Date: Mon, 2 Jan 2023 07:59:24 -0500 Subject: [PATCH 31/36] Expose WarningBuffer type for external use --- pkg/stores/proxy/proxy_store.go | 55 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index fc5f574..1f91c4a 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -61,15 +61,12 @@ type ClientGetter interface { TableAdminClientForWatch(ctx *types.APIRequest, schema *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) } -type warningBuffer struct { - Warnings []types.Warning -} +// WarningBuffer holds warnings that may be returned from the kubernetes api +type WarningBuffer []types.Warning -func (w *warningBuffer) HandleWarningHeader(code int, agent string, text string) { - if w.Warnings == nil { - w.Warnings = []types.Warning{} - } - w.Warnings = append(w.Warnings, types.Warning{ +// HandleWarningHeader takes the components of a kubernetes warning header and stores them +func (w *WarningBuffer) HandleWarningHeader(code int, agent string, text string) { + *w = append(*w, types.Warning{ Code: code, Agent: agent, Text: text, @@ -115,8 +112,8 @@ func decodeParams(apiOp *types.APIRequest, target runtime.Object) error { } func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, namespace, id string) (*unstructured.Unstructured, []types.Warning, error) { - buffer := &warningBuffer{} - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, namespace, buffer)) + buffer := WarningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, namespace, &buffer)) if err != nil { return nil, nil, err } @@ -128,7 +125,7 @@ func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, namespace obj, err := k8sClient.Get(apiOp, id, opts) rowToObject(obj) - return obj, buffer.Warnings, err + return obj, buffer, err } func moveFromUnderscore(obj map[string]interface{}) map[string]interface{} { @@ -208,8 +205,8 @@ func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names // this as an invalid situation instead of listing all objects in the cluster and filtering by name. return nil, nil, nil } - buffer := &warningBuffer{} - adminClient, err := s.clientGetter.TableAdminClient(apiOp, schema, apiOp.Namespace, buffer) + buffer := WarningBuffer{} + adminClient, err := s.clientGetter.TableAdminClient(apiOp, schema, apiOp.Namespace, &buffer) if err != nil { return nil, nil, err } @@ -227,18 +224,18 @@ func (s *Store) ByNames(apiOp *types.APIRequest, schema *types.APISchema, names } objs.Items = filtered - return objs, buffer.Warnings, nil + return objs, buffer, nil } // List returns an unstructured list of resources. func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) { - buffer := &warningBuffer{} - client, err := s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace, buffer) + buffer := WarningBuffer{} + client, err := s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace, &buffer) if err != nil { return nil, nil, err } result, err := s.list(apiOp, schema, client) - return result, buffer.Warnings, err + return result, buffer, err } func (s *Store) list(apiOp *types.APIRequest, schema *types.APISchema, client dynamic.ResourceInterface) (*unstructured.UnstructuredList, error) { @@ -350,7 +347,7 @@ func (s *Store) listAndWatch(apiOp *types.APIRequest, client dynamic.ResourceInt // With this filter, the request can be performed successfully, and only the allowed resources will // be returned in watch. func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest, names sets.String) (chan watch.Event, error) { - buffer := &warningBuffer{} + buffer := &WarningBuffer{} adminClient, err := s.clientGetter.TableAdminClientForWatch(apiOp, schema, apiOp.Namespace, buffer) if err != nil { return nil, err @@ -380,7 +377,7 @@ func (s *Store) WatchNames(apiOp *types.APIRequest, schema *types.APISchema, w t // Watch returns a channel of events for a list or resource. func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) { - buffer := &warningBuffer{} + buffer := &WarningBuffer{} client, err := s.clientGetter.TableClientForWatch(apiOp, schema, apiOp.Namespace, buffer) if err != nil { return nil, err @@ -423,8 +420,8 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params gvk := attributes.GVK(schema) input["apiVersion"], input["kind"] = gvk.ToAPIVersionAndKind() - buffer := &warningBuffer{} - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns, buffer)) + buffer := WarningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns, &buffer)) if err != nil { return nil, nil, err } @@ -436,7 +433,7 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params resp, err = k8sClient.Create(apiOp, &unstructured.Unstructured{Object: input}, opts) rowToObject(resp) - return resp, buffer.Warnings, err + return resp, buffer, err } // Update updates a single object in the store. @@ -447,8 +444,8 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params ) ns := types.Namespace(input) - buffer := &warningBuffer{} - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns, buffer)) + buffer := WarningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, ns, &buffer)) if err != nil { return nil, nil, err } @@ -486,7 +483,7 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params return nil, nil, err } - return resp, buffer.Warnings, nil + return resp, buffer, nil } resourceVersion := input.String("metadata", "resourceVersion") @@ -505,7 +502,7 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params } rowToObject(resp) - return resp, buffer.Warnings, nil + return resp, buffer, nil } // Delete deletes an object from a store. @@ -515,8 +512,8 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri return nil, nil, nil } - buffer := &warningBuffer{} - k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace, buffer)) + buffer := WarningBuffer{} + k8sClient, err := metricsStore.Wrap(s.clientGetter.TableClient(apiOp, schema, apiOp.Namespace, &buffer)) if err != nil { return nil, nil, err } @@ -532,5 +529,5 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri Status: http.StatusNoContent, } } - return obj, buffer.Warnings, nil + return obj, buffer, nil } From 59a2c55e1484f1266f0b87bfa3d069b184d6204f Mon Sep 17 00:00:00 2001 From: Michael Bolot Date: Wed, 11 Jan 2023 15:43:42 -0600 Subject: [PATCH 32/36] Fixes a bug with resource.error watch events Watch events need to have a type defined by k8s.io/apimachinery/pkg/watch to be properly interpreted. Fixes a bug where this was not the case --- pkg/stores/proxy/proxy_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/stores/proxy/proxy_store.go b/pkg/stores/proxy/proxy_store.go index 1f91c4a..b32dfc6 100644 --- a/pkg/stores/proxy/proxy_store.go +++ b/pkg/stores/proxy/proxy_store.go @@ -257,7 +257,7 @@ func (s *Store) list(apiOp *types.APIRequest, schema *types.APISchema, client dy func returnErr(err error, c chan watch.Event) { c <- watch.Event{ - Type: "resource.error", + Type: watch.Error, Object: &metav1.Status{ Message: err.Error(), }, From 73d389298c9fa3faf21a87973991c8820781aa58 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 20 Jan 2023 13:52:37 -0800 Subject: [PATCH 33/36] Bump apiserver --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 171be8e..eeda3cc 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.1 - github.com/rancher/apiserver v0.0.0-20221229135954-26bed53611c4 + github.com/rancher/apiserver v0.0.0-20230120214941-e88c32739dc7 github.com/rancher/dynamiclistener v0.3.5 github.com/rancher/kubernetes-provider-detector v0.1.2 github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 diff --git a/go.sum b/go.sum index dd01376..d7d381c 100644 --- a/go.sum +++ b/go.sum @@ -502,8 +502,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rancher/apiserver v0.0.0-20221229135954-26bed53611c4 h1:eOq/tiwMCzcwexrbUQ9Agd9PHhwwtH9/G4usE0MoN8s= -github.com/rancher/apiserver v0.0.0-20221229135954-26bed53611c4/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= +github.com/rancher/apiserver v0.0.0-20230120214941-e88c32739dc7 h1:Ob72oeF0iM8gWEMh+qKT5e1pzTwQU70I5kx4gMaqCmI= +github.com/rancher/apiserver v0.0.0-20230120214941-e88c32739dc7/go.mod h1:xwQhXv3XFxWfA6tLa4ZeaERu8ldNbyKv2sF+mT+c5WA= github.com/rancher/client-go v1.25.4-rancher1 h1:9MlBC8QbgngUkhNzMR8rZmmCIj6WNRHFOnYiwC2Kty4= github.com/rancher/client-go v1.25.4-rancher1/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= From 9a386b205c44a17fbd76a4b9f729aa24849d7453 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 20 Jan 2023 14:19:05 -0800 Subject: [PATCH 34/36] Fix resource counts unit tests Increase the timeout slightly to avoid the occasional premature timeout. Fix error message typo. --- pkg/resources/counts/buffer_test.go | 4 ++-- pkg/resources/counts/counts_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/resources/counts/buffer_test.go b/pkg/resources/counts/buffer_test.go index 3219af4..381b70a 100644 --- a/pkg/resources/counts/buffer_test.go +++ b/pkg/resources/counts/buffer_test.go @@ -64,7 +64,7 @@ func Test_countsBuffer(t *testing.T) { } // due to complexities of cycle calculation, give a slight delay for the event to actually stream - output, err := receiveWithTimeout(outputChannel, debounceDuration+time.Millisecond*1) + output, err := receiveWithTimeout(outputChannel, debounceDuration+time.Millisecond*10) assert.NoError(t, err, "did not expect an error when receiving value from channel") outputCount := output.Object.Object.(Count) assert.Len(t, outputCount.Counts, test.numInputEvents) @@ -93,7 +93,7 @@ func receiveWithTimeout(input chan types.APIEvent, duration time.Duration) (*typ case value := <-input: return &value, nil case <-time.After(duration): - return nil, fmt.Errorf("timeout error, no value recieved after %f seconds", duration.Seconds()) + return nil, fmt.Errorf("timeout error, no value received after %f seconds", duration.Seconds()) } } diff --git a/pkg/resources/counts/counts_test.go b/pkg/resources/counts/counts_test.go index bf7567e..755706e 100644 --- a/pkg/resources/counts/counts_test.go +++ b/pkg/resources/counts/counts_test.go @@ -177,7 +177,7 @@ func receiveWithTimeout(input chan types.APIEvent, duration time.Duration) (*typ case value := <-input: return &value, nil case <-time.After(duration): - return nil, fmt.Errorf("timeout error, no value recieved after %f seconds", duration.Seconds()) + return nil, fmt.Errorf("timeout error, no value received after %f seconds", duration.Seconds()) } } From 23ae2d3abc3be289cd68fc1ec18b7b6fd7ec5c3a Mon Sep 17 00:00:00 2001 From: Ricardo Weir Date: Mon, 13 Feb 2023 16:22:11 -0700 Subject: [PATCH 35/36] Revert client change Prior, table client configs were mistakenly swapped out for regular client configs. This caused requests using these methods to no longer return table column data under metadata.fields. Consequently, table data was blank in the dashboard UI. Now, the client config swap has been reverted and table fields should be populated in the dashboard UI. --- pkg/client/factory.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/client/factory.go b/pkg/client/factory.go index 09c3f8f..bec38ef 100644 --- a/pkg/client/factory.go +++ b/pkg/client/factory.go @@ -124,37 +124,37 @@ func (p *Factory) AdminClient(ctx *types.APIRequest, s *types.APISchema, namespa } func (p *Factory) ClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { - return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) + return newClient(ctx, p.watchClientCfg, s, namespace, p.impersonate, warningHandler) } func (p *Factory) AdminClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { - return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) + return newClient(ctx, p.watchClientCfg, s, namespace, false, warningHandler) } func (p *Factory) TableClient(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) + return newClient(ctx, p.tableClientCfg, s, namespace, p.impersonate, warningHandler) } return p.Client(ctx, s, namespace, warningHandler) } func (p *Factory) TableAdminClient(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) + return newClient(ctx, p.tableClientCfg, s, namespace, false, warningHandler) } return p.AdminClient(ctx, s, namespace, warningHandler) } func (p *Factory) TableClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.clientCfg, s, namespace, p.impersonate, warningHandler) + return newClient(ctx, p.tableWatchClientCfg, s, namespace, p.impersonate, warningHandler) } return p.ClientForWatch(ctx, s, namespace, warningHandler) } func (p *Factory) TableAdminClientForWatch(ctx *types.APIRequest, s *types.APISchema, namespace string, warningHandler rest.WarningHandler) (dynamic.ResourceInterface, error) { if attributes.Table(s) { - return newClient(ctx, p.clientCfg, s, namespace, false, warningHandler) + return newClient(ctx, p.tableWatchClientCfg, s, namespace, false, warningHandler) } return p.AdminClientForWatch(ctx, s, namespace, warningHandler) } From 9260c5daca6dab2ff8238bf58f8558e1bc75241f Mon Sep 17 00:00:00 2001 From: Ricardo Weir Date: Tue, 21 Feb 2023 15:45:11 -0700 Subject: [PATCH 36/36] Update wrangler to v1.1.0 --- go.mod | 6 +++--- go.sum | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index eeda3cc..582117c 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,9 @@ require ( github.com/rancher/kubernetes-provider-detector v0.1.2 github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a - github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1 + github.com/rancher/wrangler v1.1.0 github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/urfave/cli v1.22.2 github.com/urfave/cli/v2 v2.1.1 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 @@ -75,7 +75,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c // indirect + github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.opentelemetry.io/contrib v0.20.0 // indirect diff --git a/go.sum b/go.sum index d7d381c..7868a60 100644 --- a/go.sum +++ b/go.sum @@ -510,14 +510,14 @@ github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S github.com/rancher/dynamiclistener v0.3.5/go.mod h1:dW/YF6/m2+uEyJ5VtEcd9THxda599HP6N9dSXk81+k0= github.com/rancher/kubernetes-provider-detector v0.1.2 h1:iFfmmcZiGya6s3cS4Qxksyqqw5hPbbIDHgKJ2Y44XKM= github.com/rancher/kubernetes-provider-detector v0.1.2/go.mod h1:ypuJS7kP7rUiAn330xG46mj+Nhvym05GM8NqMVekpH0= -github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c h1:RKGa+6plIHqyfBcC+lnCs3oe7A8wSFkUPBtqWBAi/1E= -github.com/rancher/lasso v0.0.0-20221202205459-e7138f16489c/go.mod h1:Qewwr/xGzhtG7WCxhubrKZrdAobv5yqAIuHbDoxThZQ= +github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc h1:29VHrInLV4qSevvcvhBj5UhQWkPShxrxv4AahYg2Scw= +github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc/go.mod h1:dEfC9eFQigj95lv/JQ8K5e7+qQCacWs1aIA6nLxKzT8= github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99 h1:+Oob+DG+SZoX8hnKBWpZfVwxx6K84hMou9nbBOTux7w= github.com/rancher/norman v0.0.0-20221205184727-32ef2e185b99/go.mod h1:zpv7z4ySYL5LlEBKEPf/xf3cjx837/J2i/wHpT43viE= github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a h1:Go8MpBEeZCR0yV1ylu2/KjJBvpYomIezU58pejYCtgk= github.com/rancher/remotedialer v0.2.6-0.20220104192242-f3837f8d649a/go.mod h1:vq3LvyOFnLcwMiCE1KdW3foPd6g5kAjZOtOb7JqGHck= -github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1 h1:Rnrc4yx1Pkqoto7/n6GT68tk5up/SLuan1uSYXACpoM= -github.com/rancher/wrangler v1.0.1-0.20221202234327-1cafffeaa9a1/go.mod h1:+GbeNyk8mbdOdMSdLIqvz+0JQ/2PhIb4ufMOcmHLIJQ= +github.com/rancher/wrangler v1.1.0 h1:1VWistON261oKmCPF5fOPMWb/YwjgEciO9pCw5Z0mzQ= +github.com/rancher/wrangler v1.1.0/go.mod h1:lQorqAAIMkNWteece1GiuwZTmMqkaVTXL5qjiiPVDxQ= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -563,6 +563,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -570,8 +571,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/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/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=