From 72ac9c00b9892c3d61f30324294587399105e078 Mon Sep 17 00:00:00 2001 From: likakuli <1154584512@qq.com> Date: Thu, 19 Jan 2023 11:46:36 +0800 Subject: [PATCH] Feature initframework and support capacity estimation (#1) * feat: support capacity estimation Signed-off-by: kaku <1154584512@qq.com> --- .github/workflows/ci.yml | 64 ++ .gitignore | 11 +- LICENSE | 1 + Makefile | 116 +++ README.md | 112 ++- app/cmds/cc.go | 38 + app/cmds/ce.go | 142 +++ app/cmds/ss.go | 38 + app/options/cc.go | 1 + app/options/ce.go | 113 +++ app/options/ss.go | 1 + app/root.go | 87 ++ .../capacity-management-capacity-icon.jpeg | Bin 0 -> 23131 bytes go.mod | 132 +++ go.sum | 858 ++++++++++++++++++ hack/.import-aliases | 45 + .../preferredimports/preferredimports.go | 265 ++++++ hack/util.sh | 20 + hack/verify-import-aliases.sh | 24 + hack/verify-staticcheck.sh | 23 + main.go | 22 + pkg/framework/cc/report.go | 1 + pkg/framework/cc/simulator.go | 1 + pkg/framework/ce/podgenerator.go | 52 ++ pkg/framework/ce/report.go | 281 ++++++ pkg/framework/ce/simulator.go | 342 +++++++ pkg/framework/interface.go | 22 + .../capacityestimationbinder/plugin.go | 55 ++ pkg/framework/ss/report.go | 1 + pkg/framework/ss/simulator.go | 1 + pkg/utils/utils.go | 168 ++++ pkg/version/base.go | 15 + pkg/version/sharedcommand/sharedcommand.go | 33 + pkg/version/version.go | 36 + 34 files changed, 3118 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile create mode 100644 app/cmds/cc.go create mode 100644 app/cmds/ce.go create mode 100644 app/cmds/ss.go create mode 100644 app/options/cc.go create mode 100644 app/options/ce.go create mode 100644 app/options/ss.go create mode 100644 app/root.go create mode 100644 docs/images/capacity-management-capacity-icon.jpeg create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/.import-aliases create mode 100644 hack/tools/preferredimports/preferredimports.go create mode 100755 hack/util.sh create mode 100755 hack/verify-import-aliases.sh create mode 100755 hack/verify-staticcheck.sh create mode 100644 main.go create mode 100644 pkg/framework/cc/report.go create mode 100644 pkg/framework/cc/simulator.go create mode 100644 pkg/framework/ce/podgenerator.go create mode 100644 pkg/framework/ce/report.go create mode 100644 pkg/framework/ce/simulator.go create mode 100644 pkg/framework/interface.go create mode 100644 pkg/framework/plugins/capacityestimationbinder/plugin.go create mode 100644 pkg/framework/ss/report.go create mode 100644 pkg/framework/ss/simulator.go create mode 100644 pkg/utils/utils.go create mode 100644 pkg/version/base.go create mode 100644 pkg/version/sharedcommand/sharedcommand.go create mode 100644 pkg/version/version.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..18c9068 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: build + +on: + # Run this workflow every time a new commit pushed to upstream/fork repository. + # Run workflow on fork repository will help contributors find and resolve issues before sending a PR. + push: + pull_request: +jobs: + golangci: + name: lint + runs-on: ubuntu-18.04 + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: install Go + uses: actions/setup-go@v2 + with: + go-version: 1.19.x + - name: lint + run: hack/verify-staticcheck.sh + - name: import alias + run: hack/verify-import-aliases.sh + fmt: + name: gofmt + runs-on: ubuntu-18.04 + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: install Go + uses: actions/setup-go@v2 + with: + go-version: 1.19.x + - name: go fmt check + run: make fmt-check + vet: + name: go vet + runs-on: ubuntu-18.04 + steps: + - name: checkout code + uses: actions/checkout@v2 + - name: install Go + uses: actions/setup-go@v2 + with: + go-version: 1.19.x + - name: go vet + run: make vet +# test: +# name: unit test +# needs: +# - fmt +# - vet +# runs-on: ubuntu-18.04 +# steps: +# - name: checkout code +# uses: actions/checkout@v2 +# - name: install Go +# uses: actions/setup-go@v2 +# with: +# go-version: 1.19.x +# - name: Run coverage +# run: ./script/test.sh +# - name: Codecov +# uses: codecov/codecov-action@v3.1.0 + diff --git a/.gitignore b/.gitignore index 66fd13c..00620b4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,13 @@ *.out # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ + +# binary file +kcluster-capacity + +# conf +pod.yaml +schedulerconfig +kubeconfig + diff --git a/LICENSE b/LICENSE index 261eeb9..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..784a703 --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +# Go information +GO ?= go +GOFMT ?= gofmt "-s" +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) +SOURCES := $(shell find . -type f -name '*.go') + +GOFILES := $(shell find . -name "*.go" | grep -v vendor) +TESTFOLDER := $(shell $(GO) list ./... | grep -v examples) +TESTTAGS ?= "" +VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) + +# Git information +GIT_VERSION ?= $(shell git describe --tags --dirty --always) +GIT_COMMIT_HASH ?= $(shell git rev-parse HEAD) +GIT_TREESTATE = "clean" +GIT_DIFF = $(shell git diff --quiet >/dev/null 2>&1; if [ $$? -eq 1 ]; then echo "1"; fi) +ifeq ($(GIT_DIFF), 1) + GIT_TREESTATE = "dirty" +endif +BUILDDATE = $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') + +LDFLAGS := "-X github.com/k-cloud-labs/kluster-capacity/pkg/version.gitVersion=$(GIT_VERSION) \ + -X github.com/k-cloud-labs/kluster-capacity/pkg/version.gitCommit=$(GIT_COMMIT_HASH) \ + -X github.com/k-cloud-labs/kluster-capacity/pkg/version.gitTreeState=$(GIT_TREESTATE) \ + -X github.com/k-cloud-labs/kluster-capacity/pkg/version.buildDate=$(BUILDDATE)" + +# Set your version by env or using latest tags from git +VERSION?="" +ifeq ($(VERSION), "") + LATEST_TAG=$(shell git describe --tags --always) + ifeq ($(LATEST_TAG),) + # Forked repo may not sync tags from upstream, so give it a default tag to make CI happy. + VERSION="unknown" + else + VERSION=$(LATEST_TAG) + endif +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# This is a requirement for 'setup-envtest.sh' in the test target. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: checkall +checkall: fmt-check vet ## Do all check + hack/verify-staticcheck.sh + hack/verify-import-aliases.sh + +.PHONY: kluster-capacity +kluster-capacity: $(SOURCES) ## Build kluster-capacity webhook binary file + @CGO_ENABLED=0 GOOS=$(GOOS) go build \ + -ldflags $(LDFLAGS) \ + -o kluster-capacity \ + main.go + +.PHONY: clean +clean: ## Clean kluster-capacity webhook binary file + @rm -rf kluster-capacity + +.PHONY: fmt +fmt: ## Format project files + @$(GOFMT) -w $(GOFILES) + +.PHONY: fmt-check +fmt-check: ## Check project files format info + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +.PHONY: vet +vet: + @$(GO) vet $(VETPACKAGES) + +.PHONY: test +test: fmt-check vet ## Run project unit test and generate coverage result + echo "mode: count" > coverage.out + for d in $(TESTFOLDER); do \ + $(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + cat tmp.out; \ + if grep -q "^--- FAIL" tmp.out; then \ + rm tmp.out; \ + exit 1; \ + elif grep -q "build failed" tmp.out; then \ + rm tmp.out; \ + exit 1; \ + elif grep -q "setup failed" tmp.out; then \ + rm tmp.out; \ + exit 1; \ + fi; \ + if [ -f profile.out ]; then \ + cat profile.out | grep -v "mode:" >> coverage.out; \ + rm profile.out; \ + fi; \ + done diff --git a/README.md b/README.md index 56811b1..02a3705 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,110 @@ -# kluster-capacity -Cluster capacity tool for capacity estimation、scheduler simulation、cluster compression +# kluster-capacity + +![kluster-capacity-logo](docs/images/capacity-management-capacity-icon.jpeg) + +[![Build Status](https://github.com/k-cloud-labs/kluster-capacity/actions/workflows/ci.yml/badge.svg)](https://github.com/k-cloud-labs/kluster-capacity/actions?query=workflow%3Abuild) +[![Go Report Card](https://goreportcard.com/badge/github.com/k-cloud-labs/kluster-capacity)](https://goreportcard.com/report/github.com/k-cloud-labs/kluster-capacity) +[![Go doc](https://img.shields.io/badge/go.dev-reference-brightgreen?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/k-cloud-labs/kluster-capacity) + + +Cluster capacity tool support capacity estimation、scheduler simulation、cluster compression. +This repository is inspired by https://github.com/kubernetes-sigs/cluster-capacity. +And the code of this repository is based on https://github.com/kubernetes-sigs/cluster-capacity. + + +## Capacity Estimation +### Intro +As new pods get scheduled on nodes in a cluster, more resources get consumed. Monitoring available resources in the cluster is very important as operators can increase the current resources in time before all of them get exhausted. Or, carry different steps that lead to increase of available resources. + +Cluster capacity consists of capacities of individual cluster nodes. Capacity covers CPU, memory, disk space and other resources. + +Overall remaining allocatable capacity is a rough estimation since it does not assume all resources being distributed among nodes. Goal is to analyze remaining allocatable resources and estimate available capacity that is still consumable in terms of a number of instances of a pod with given requirements that can be scheduled in a cluster. + +### Build and Run + +Build the framework: + +```sh +$ cd $GOPATH/src/github.com/k-cloud-labs/ +$ git clone https://github.com/k-cloud-labs/kluster-capacity +$ cd kluster-capacity +$ make kluster-capacity +``` +and run the analysis: + +```sh +$ ./kluster-capacity ce --kubeconfig --pod-template +``` +For more information about available options run: + +```sh +$ ./kluster-capacity ce --help +``` + +### Demonstration + +Assuming a cluster is running with 4 nodes and 1 master with each node with 2 CPUs and 4GB of memory. +With pod resource requirements to be `150m` of CPU and ``100Mi`` of Memory. + +```sh +$ ./kluster-capacity ce --kubeconfig --pod-template --verbose +Pod requirements: + - cpu: 150m + - memory: 100Mi + +The cluster can schedule 52 instance(s) of the pod. +Termination reason: FailedScheduling: pod (small-pod-52) failed to fit in any node +fit failure on node (kube-node-1): Insufficient cpu +fit failure on node (kube-node-4): Insufficient cpu +fit failure on node (kube-node-2): Insufficient cpu +fit failure on node (kube-node-3): Insufficient cpu + + +Pod distribution among nodes: + - kube-node-1: 13 instance(s) + - kube-node-4: 13 instance(s) + - kube-node-2: 13 instance(s) + - kube-node-3: 13 instance(s) +``` + +Once the number of running pods in the cluster grows and the analysis is run again, +the number of schedulable pods decreases as well: + +```sh +$ ./kluster-capacity ce --kubeconfig --pods-template --verbose +Pod requirements: + - cpu: 150m + - memory: 100Mi + +The cluster can schedule 46 instance(s) of the pod. +Termination reason: FailedScheduling: pod (small-pod-46) failed to fit in any node +fit failure on node (kube-node-1): Insufficient cpu +fit failure on node (kube-node-4): Insufficient cpu +fit failure on node (kube-node-2): Insufficient cpu +fit failure on node (kube-node-3): Insufficient cpu + + +Pod distribution among nodes: + - kube-node-1: 11 instance(s) + - kube-node-4: 12 instance(s) + - kube-node-2: 11 instance(s) + - kube-node-3: 12 instance(s) +``` + +### Output format +`ce` command has a flag `--output (-o)` to format its output as json or yaml. + +```sh +$ ./kluster-capacity ce --kubeconfig --pods-template -o json +$ ./kluster-capacity ce --kubeconfig --pods-template -o yaml +``` + +The json or yaml output is not versioned and is not guaranteed to be stable across various releases. + +## Scheduler Simulation + + +## Cluster Compression + +## Feature +- [ ] ... \ No newline at end of file diff --git a/app/cmds/cc.go b/app/cmds/cc.go new file mode 100644 index 0000000..d575a0f --- /dev/null +++ b/app/cmds/cc.go @@ -0,0 +1,38 @@ +/* +Copyright © 2023 k-cloud-labs org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmds + +import ( + "errors" + + "github.com/spf13/cobra" +) + +// ccCmd represents the cc command +var ccCmd = &cobra.Command{ + Use: "cc", + Short: "cluster compression", + // TODO: add detail usage info + Long: `cluster compression`, + SilenceErrors: false, + RunE: func(cmd *cobra.Command, args []string) error { + return errors.New("not implemented") + }, +} + +func NewClusterCompressionCmd() *cobra.Command { + return ccCmd +} diff --git a/app/cmds/ce.go b/app/cmds/ce.go new file mode 100644 index 0000000..2279f02 --- /dev/null +++ b/app/cmds/ce.go @@ -0,0 +1,142 @@ +/* +Copyright © 2023 k-cloud-labs org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmds + +import ( + "errors" + "flag" + "fmt" + "os" + + "github.com/lithammer/dedent" + "github.com/spf13/cobra" + clientset "k8s.io/client-go/kubernetes" + cliflag "k8s.io/component-base/cli/flag" + schedconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config" + + "github.com/k-cloud-labs/kluster-capacity/app/options" + "github.com/k-cloud-labs/kluster-capacity/pkg/framework" + "github.com/k-cloud-labs/kluster-capacity/pkg/framework/ce" + "github.com/k-cloud-labs/kluster-capacity/pkg/utils" +) + +var capacityEstimationLong = dedent.Dedent(` + ce simulates an API server with initial state copied from the Kubernetes environment + with its configuration specified in KUBECONFIG. The simulated API server tries to schedule the number of + pods specified by --max-limits flag. If the --max-limits flag is not specified, pods are scheduled until + the simulated API server runs out of resources. + `) + +func NewCapacityEstimationCmd() *cobra.Command { + opt := options.NewCapacityEstimationOptions() + + var cmd = &cobra.Command{ + Use: "ce --kubeconfig KUBECONFIG --pod-template PODYAML", + Short: "ce is used for simulating scheduling of one or multiple pods", + Long: capacityEstimationLong, + SilenceErrors: false, + RunE: func(cmd *cobra.Command, args []string) error { + err := validate(opt) + if err != nil { + return err + } + + err = run(opt) + if err != nil { + return err + } + + return nil + }, + } + + flags := cmd.Flags() + flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) + flags.AddGoFlagSet(flag.CommandLine) + opt.AddFlags(flags) + + return cmd +} + +func validate(opt *options.CapacityEstimationOptions) error { + if len(opt.PodTemplate) == 0 { + return errors.New("pod template file is missing") + } + + _, present := os.LookupEnv("KC_INCLUSTER") + if !present { + if len(opt.KubeConfig) == 0 { + return errors.New("kubeconfig is missing") + } + } + + return nil +} + +func run(opt *options.CapacityEstimationOptions) error { + conf := options.NewCapacityEstimationConfig(opt) + + cc, err := utils.BuildKubeSchedulerCompletedConfig(opt.SchedulerConfig) + if err != nil { + return err + } + + cfg, err := utils.BuildRestConfig(conf.Options.KubeConfig) + if err != nil { + return err + } + + err = conf.ParseAPISpec() + if err != nil { + return fmt.Errorf("failed to parse pod spec file: %v ", err) + } + + conf.KubeClient, err = clientset.NewForConfig(cfg) + if err != nil { + return err + } + conf.RestConfig = cfg + + report, err := runSimulator(conf, cc) + if err != nil { + return err + } + + if err := report.Print(conf.Options.Verbose, conf.Options.OutputFormat); err != nil { + return fmt.Errorf("error while printing: %v", err) + } + + return nil +} + +func runSimulator(conf *options.CapacityEstimationConfig, kubeSchedulerConfig *schedconfig.CompletedConfig) (framework.Printer, error) { + s, err := ce.NewCESimulator(kubeSchedulerConfig, conf.RestConfig, conf.Pod, conf.Options.MaxLimit, conf.Options.ExcludeNodes) + if err != nil { + return nil, err + } + + err = s.SyncWithClient(conf.KubeClient) + if err != nil { + return nil, err + } + + err = s.Run() + if err != nil { + return nil, err + } + + return s.Report(), nil +} diff --git a/app/cmds/ss.go b/app/cmds/ss.go new file mode 100644 index 0000000..aa720db --- /dev/null +++ b/app/cmds/ss.go @@ -0,0 +1,38 @@ +/* +Copyright © 2023 k-cloud-labs org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmds + +import ( + "errors" + + "github.com/spf13/cobra" +) + +// ssCmd represents the ss command +var ssCmd = &cobra.Command{ + Use: "ss", + Short: "scheduler simulation", + // TODO: add detail usage info + Long: `scheduler simulation`, + SilenceErrors: false, + RunE: func(cmd *cobra.Command, args []string) error { + return errors.New("not implemented") + }, +} + +func NewSchedulerSimulationCmd() *cobra.Command { + return ssCmd +} diff --git a/app/options/cc.go b/app/options/cc.go new file mode 100644 index 0000000..0684d05 --- /dev/null +++ b/app/options/cc.go @@ -0,0 +1 @@ +package options diff --git a/app/options/ce.go b/app/options/ce.go new file mode 100644 index 0000000..159ac7c --- /dev/null +++ b/app/options/ce.go @@ -0,0 +1,113 @@ +package options + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/spf13/pflag" + restclient "k8s.io/client-go/rest" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/yaml" + clientset "k8s.io/client-go/kubernetes" + api "k8s.io/kubernetes/pkg/apis/core" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/core/validation" +) + +type CapacityEstimationOptions struct { + PodTemplate string + SchedulerConfig string + OutputFormat string + KubeConfig string + MaxLimit int + // key=v#key=v#key=v, key is resource name and v is resource value + ResourceList []string + Verbose bool + ExcludeNodes []string +} + +type CapacityEstimationConfig struct { + Pod *v1.Pod + KubeClient clientset.Interface + RestConfig *restclient.Config + Options *CapacityEstimationOptions +} + +func NewCapacityEstimationConfig(opt *CapacityEstimationOptions) *CapacityEstimationConfig { + return &CapacityEstimationConfig{ + Options: opt, + } +} + +func NewCapacityEstimationOptions() *CapacityEstimationOptions { + return &CapacityEstimationOptions{ + ResourceList: make([]string, 0), + } +} + +func (s *CapacityEstimationOptions) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&s.KubeConfig, "kubeconfig", s.KubeConfig, "Path to the kubeconfig file to use for the analysis.") + fs.StringVar(&s.PodTemplate, "pod-template", s.PodTemplate, "Path to JSON or YAML file containing pod definition.") + fs.IntVar(&s.MaxLimit, "max-limit", 0, "Number of instances of pod to be scheduled after which analysis stops. By default unlimited.") + fs.StringSliceVar(&s.ResourceList, "resource-list", s.ResourceList, "Resource list used for pod to schedule to return the result in batches.") + fs.StringVar(&s.SchedulerConfig, "scheduler-config", s.SchedulerConfig, "Path to JSON or YAML file containing scheduler configuration.") + fs.BoolVar(&s.Verbose, "verbose", s.Verbose, "Verbose mode") + fs.StringVarP(&s.OutputFormat, "output", "o", s.OutputFormat, "Output format. One of: json|yaml (Note: output is not versioned or guaranteed to be stable across releases).") + fs.StringSliceVar(&s.ExcludeNodes, "exclude-nodes", s.ExcludeNodes, "Exclude nodes to be scheduled") +} + +func (s *CapacityEstimationConfig) ParseAPISpec() error { + var spec io.Reader + var err error + + if strings.HasPrefix(s.Options.PodTemplate, "http://") || strings.HasPrefix(s.Options.PodTemplate, "https://") { + response, err := http.Get(s.Options.PodTemplate) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return fmt.Errorf("unable to read URL %q, server reported %v, status code=%v", s.Options.PodTemplate, response.Status, response.StatusCode) + } + spec = response.Body + } else { + filename, _ := filepath.Abs(s.Options.PodTemplate) + spec, err = os.Open(filename) + if err != nil { + return fmt.Errorf("failed to open config file: %v", err) + } + } + + decoder := yaml.NewYAMLOrJSONDecoder(spec, 4096) + versionedPod := &v1.Pod{} + err = decoder.Decode(versionedPod) + if err != nil { + return fmt.Errorf("failed to decode config file: %v", err) + } + + if versionedPod.ObjectMeta.Namespace == "" { + versionedPod.ObjectMeta.Namespace = "default" + } + + apiv1.SetObjectDefaults_Pod(versionedPod) + + internalPod := &api.Pod{} + if err := apiv1.Convert_v1_Pod_To_core_Pod(versionedPod, internalPod, nil); err != nil { + return fmt.Errorf("unable to convert to internal version: %#v", err) + } + if errs := validation.ValidatePodCreate(internalPod, validation.PodValidationOptions{}); len(errs) > 0 { + var errStrs []string + for _, err := range errs { + errStrs = append(errStrs, fmt.Sprintf("%v: %v", err.Type, err.Field)) + } + return fmt.Errorf("invalid pod: %#v", strings.Join(errStrs, ", ")) + } + + s.Pod = versionedPod + return nil +} diff --git a/app/options/ss.go b/app/options/ss.go new file mode 100644 index 0000000..0684d05 --- /dev/null +++ b/app/options/ss.go @@ -0,0 +1 @@ +package options diff --git a/app/root.go b/app/root.go new file mode 100644 index 0000000..a89c166 --- /dev/null +++ b/app/root.go @@ -0,0 +1,87 @@ +/* +Copyright © 2023 k-cloud-labs org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package app + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/k-cloud-labs/kluster-capacity/app/cmds" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "kluster-capacity", + Short: "A tool which support capacity estimation, scheduler simulation, cluster compression.", + Long: `A tool which support capacity estimation, scheduler simulation, cluster compression.`, + // Uncomment the following line if your bare application + // has an action associated with it: + //Run: func(cmd *cobra.Command, args []string) {}, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.kluster-capacity.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + rootCmd.AddCommand(cmds.NewCapacityEstimationCmd(), cmds.NewSchedulerSimulationCmd(), cmds.NewClusterCompressionCmd()) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".kluster-capacity" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".kluster-capacity") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/docs/images/capacity-management-capacity-icon.jpeg b/docs/images/capacity-management-capacity-icon.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..6b76895c420cbac3ee1b04417ab43e655449ad6f GIT binary patch literal 23131 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 literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e6e9451 --- /dev/null +++ b/go.mod @@ -0,0 +1,132 @@ +module github.com/k-cloud-labs/kluster-capacity + +go 1.19 + +require ( + github.com/ghodss/yaml v1.0.0 + github.com/lithammer/dedent v1.1.0 + github.com/satori/go.uuid v1.2.0 + github.com/spf13/cobra v1.6.1 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.14.0 + k8s.io/api v0.26.0 + k8s.io/apimachinery v0.26.0 + k8s.io/client-go v0.26.0 + k8s.io/component-base v0.26.0 + k8s.io/kube-scheduler v0.0.0 + k8s.io/kubernetes v1.26.0 +) + +require ( + github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/cel-go v0.12.5 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/sys/mountinfo v0.6.2 // 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/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/selinux v1.10.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + go.etcd.io/etcd/api/v3 v3.5.5 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect + go.etcd.io/etcd/client/v3 v3.5.5 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 // indirect + go.opentelemetry.io/otel v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect + go.opentelemetry.io/otel/metric v0.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.10.0 // indirect + go.opentelemetry.io/otel/trace v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.21.0 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect + google.golang.org/grpc v1.50.1 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiserver v0.26.0 // indirect + k8s.io/cloud-provider v0.0.0 // indirect + k8s.io/component-helpers v0.26.0 // indirect + k8s.io/csi-translation-lib v0.0.0 // indirect + k8s.io/dynamic-resource-allocation v0.0.0 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kms v0.26.0 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/mount-utils v0.0.0 // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 // 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 +) + +replace ( + k8s.io/cloud-provider => k8s.io/cloud-provider v0.26.0 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.26.0 + k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.26.0 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.26.0 + k8s.io/mount-utils => k8s.io/mount-utils v0.26.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ce8f0b1 --- /dev/null +++ b/go.sum @@ -0,0 +1,858 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= +cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +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/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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/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-20210805033703-aa0b78936158/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-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +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.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +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.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +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.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/cel-go v0.12.5 h1:DmzaiSgoaqGCjtpPQWl26/gND+yRpim56H1jCVev6d8= +github.com/google/cel-go v0.12.5/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= +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= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +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/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +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 v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +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.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= +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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +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.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= +github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +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-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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +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/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +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.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +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.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.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/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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= +go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= +go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/v2 v2.305.5 h1:DktRP60//JJpnPC0VBymAN/7V71GHMdjDCBt4ZPXDjI= +go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= +go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= +go.etcd.io/etcd/pkg/v3 v3.5.5 h1:Ablg7T7OkR+AeeeU32kdVhw/AGDsitkKPl7aW73ssjU= +go.etcd.io/etcd/raft/v3 v3.5.5 h1:Ibz6XyZ60OYyRopu73lLM/P+qco3YtlZMOhnXNS051I= +go.etcd.io/etcd/server/v3 v3.5.5 h1:jNjYm/9s+f9A9r6+SC4RvNaz6AqixpOvhrFdT0PvIj0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 h1:Ajldaqhxqw/gNzQA45IKFWLdG7jZuXX/wBW1d5qvbUI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= +go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= +go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= +go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= +go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= +go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= +go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +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= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/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/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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/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-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-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-20200905004654-be1d3432aa8f/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-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-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20210423185535-09eb48e85fd7/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-20210603081109-ebe580a85c40/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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +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.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +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-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-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-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= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +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= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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-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/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= +k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o= +k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= +k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= +k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +k8s.io/cloud-provider v0.26.0 h1:kO2BIgCou71QNRHGkpFi/8lnas9UIr+fJz1l/nuiOMo= +k8s.io/cloud-provider v0.26.0/go.mod h1:JwfUAH67C8f7t6tOC4v4ty+DuvIYVjNF6bGVYSDCqqs= +k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= +k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= +k8s.io/component-helpers v0.26.0 h1:KNgwqs3EUdK0HLfW4GhnbD+q/Zl9U021VfIU7qoVYFk= +k8s.io/component-helpers v0.26.0/go.mod h1:jHN01qS/Jdj95WCbTe9S2VZ9yxpxXNY488WjF+yW4fo= +k8s.io/csi-translation-lib v0.26.0 h1:bCvlfw53Kmyn7cvXeYGe9aqqzR1b0xrGs2XEWHFW+es= +k8s.io/csi-translation-lib v0.26.0/go.mod h1:zRKLRqER6rA8NCKQBhVIdkyDHKgNlu2BK1RKTHjcw+8= +k8s.io/dynamic-resource-allocation v0.26.0 h1:zljrsqa0PxrIwNklTnGBA/az6+33SUQwsNNNKdVTzwg= +k8s.io/dynamic-resource-allocation v0.26.0/go.mod h1:K+hO5A+QsSknRjlhfbUtvZVYUblOldvYyT51eGrZyWI= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kms v0.26.0 h1:5+GOQLvUajSd0z5ODF52RzB2rHo1HJUSYsVC3Ri3VgI= +k8s.io/kms v0.26.0/go.mod h1:ReC1IEGuxgfN+PDCIpR6w8+XMmDE7uJhxcCwMZFdIYc= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-scheduler v0.26.0 h1:PjSF4cF9X7cAMj5MZ9ZSq2RJ2VkcKKCKj6fy/EbxtA0= +k8s.io/kube-scheduler v0.26.0/go.mod h1:FmptJbq36ATKYxeR+UqAvUtFaLeoFWgoDk1cdCpVPYQ= +k8s.io/kubernetes v1.26.0 h1:fL8VMr4xlfTazPORLhz5fsvO5I3bsFpmynVxZTH1ItQ= +k8s.io/kubernetes v1.26.0/go.mod h1:z0aCJwn6DxzB/dDiWLbQaJO5jWOR2qoaCMnmSAx45XM= +k8s.io/mount-utils v0.26.0 h1:MG5oXE2aF1UHMJ3KFbVtBtiRA4J/2u0sijrkfsoaMwU= +k8s.io/mount-utils v0.26.0/go.mod h1:au99w4FWU5ZWelLb3Yx6kJc8RZ387IyWVM9tN65Yhxo= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/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/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/.import-aliases b/hack/.import-aliases new file mode 100644 index 0000000..1de30b9 --- /dev/null +++ b/hack/.import-aliases @@ -0,0 +1,45 @@ +{ + "k8s.io/api/admissionregistration/v1": "admissionregistrationv1", + "k8s.io/api/admissionregistration/v1beta1": "admissionregistrationv1beta1", + "k8s.io/api/admission/v1beta1": "admissionv1beta1", + "k8s.io/api/admission/v1": "admissionv1", + "k8s.io/api/apps/v1": "appsv1", + "k8s.io/api/apps/v1beta1": "appsv1beta1", + "k8s.io/api/apps/v1beta2": "appsv1beta2", + "k8s.io/api/authentication/v1": "authenticationv1", + "k8s.io/api/authentication/v1beta1": "authenticationv1beta1", + "k8s.io/api/authorization/v1": "authorizationv1", + "k8s.io/api/authorization/v1beta1": "authorizationv1beta1", + "k8s.io/api/autoscaling/v1": "autoscalingv1", + "k8s.io/api/batch/v1": "batchv1", + "k8s.io/api/batch/v1beta1": "batchv1beta1", + "k8s.io/api/certificates/v1beta1": "certificatesv1beta1", + "k8s.io/api/coordination/v1": "coordinationv1", + "k8s.io/api/coordination/v1beta1": "coordinationv1beta1", + "k8s.io/api/core/v1": "corev1", + "k8s.io/api/discovery/v1": "discoveryv1", + "k8s.io/api/events/v1": "eventsv1", + "k8s.io/api/events/v1beta1": "eventsv1beta1", + "k8s.io/api/extensions/v1beta1": "extensionsv1beta1", + "k8s.io/api/imagepolicy/v1alpha1": "imagepolicyv1alpha1", + "k8s.io/api/networking/v1": "networkingv1", + "k8s.io/api/networking/v1beta1": "networkingv1beta1", + "k8s.io/api/node/v1alpha1": "nodev1alpha1", + "k8s.io/api/node/v1beta1": "nodev1beta1", + "k8s.io/api/node/v1": "nodev1", + "k8s.io/api/policy/v1": "policyv1", + "k8s.io/api/policy/v1beta1": "policyv1beta1", + "k8s.io/api/rbac/v1": "rbacv1", + "k8s.io/api/rbac/v1alpha1": "rbacv1alpha1", + "k8s.io/api/rbac/v1beta1": "rbacv1beta1", + "k8s.io/api/scheduling/v1": "schedulingv1", + "k8s.io/api/scheduling/v1alpha1": "schedulingv1alpha1", + "k8s.io/api/scheduling/v1beta1": "schedulingv1beta1", + "k8s.io/api/storage/v1": "storagev1", + "k8s.io/api/storage/v1alpha1": "storagev1alpha1", + "k8s.io/api/storage/v1beta1": "storagev1beta1", + "k8s.io/apimachinery/pkg/api/errors": "apierrors", + "k8s.io/apimachinery/pkg/apis/meta/v1": "metav1", + + "github.com/k-cloud-labs/pkg/apis/policy/v1alpha1": "policyv1alpha1" +} \ No newline at end of file diff --git a/hack/tools/preferredimports/preferredimports.go b/hack/tools/preferredimports/preferredimports.go new file mode 100644 index 0000000..1d3d7be --- /dev/null +++ b/hack/tools/preferredimports/preferredimports.go @@ -0,0 +1,265 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This code is directly lifted from the Kubernetes codebase in order to avoid relying on the k8s.io/kubernetes package. +// For reference: https://github.com/kubernetes/kubernetes/blob/release-1.22/cmd/preferredimports/preferredimports.go + +// verify that all the imports have our preferred alias(es). +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/build" + "go/format" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + + "golang.org/x/term" +) + +var ( + importAliases = flag.String("import-aliases", "hack/.import-aliases", "json file with import aliases") + confirm = flag.Bool("confirm", false, "update file with the preferred aliases for imports") + regex = flag.String("include-path", "(test/e2e/|test/e2e_node)", "only files with paths matching this regex is touched") + isTerminal = term.IsTerminal(int(os.Stdout.Fd())) + logPrefix = "" + aliases map[string]string +) + +type analyzer struct { + fset *token.FileSet // positions are relative to fset + ctx build.Context + failed bool + donePaths map[string]interface{} +} + +func newAnalyzer() *analyzer { + ctx := build.Default + ctx.CgoEnabled = true + + a := &analyzer{ + fset: token.NewFileSet(), + ctx: ctx, + donePaths: make(map[string]interface{}), + } + + return a +} + +// collect extracts test metadata from a file. +func (a *analyzer) collect(dir string) { + if _, ok := a.donePaths[dir]; ok { + return + } + a.donePaths[dir] = nil + + // Create the AST by parsing src. + fs, err := parser.ParseDir(a.fset, dir, nil, parser.AllErrors|parser.ParseComments) + + if err != nil { + fmt.Fprintln(os.Stderr, "ERROR(syntax)", logPrefix, err) + a.failed = true + return + } + + for _, p := range fs { + // returns first error, but a.handleError deals with it + files := a.filterFiles(p.Files) + for _, file := range files { + replacements := make(map[string]string) + pathToFile := a.fset.File(file.Pos()).Name() + for _, imp := range file.Imports { + importPath := strings.Replace(imp.Path.Value, "\"", "", -1) + pathSegments := strings.Split(importPath, "/") + importName := pathSegments[len(pathSegments)-1] + if imp.Name != nil { + importName = imp.Name.Name + } + if alias, ok := aliases[importPath]; ok { + if alias != importName { + if !*confirm { + fmt.Fprintf(os.Stderr, "%sERROR wrong alias for import \"%s\" should be %s in file %s\n", logPrefix, importPath, alias, pathToFile) + a.failed = true + } + replacements[importName] = alias + if imp.Name != nil { + imp.Name.Name = alias + } else { + imp.Name = ast.NewIdent(alias) + } + } + } + } + + if len(replacements) > 0 { + if *confirm { + fmt.Printf("%sReplacing imports with aliases in file %s\n", logPrefix, pathToFile) + for key, value := range replacements { + renameImportUsages(file, key, value) + } + ast.SortImports(a.fset, file) + var buffer bytes.Buffer + if err = format.Node(&buffer, a.fset, file); err != nil { + panic(fmt.Sprintf("Error formatting ast node after rewriting import.\n%s\n", err.Error())) + } + + fileInfo, err := os.Stat(pathToFile) + if err != nil { + panic(fmt.Sprintf("Error stat'ing file: %s\n%s\n", pathToFile, err.Error())) + } + + err = os.WriteFile(pathToFile, buffer.Bytes(), fileInfo.Mode()) + if err != nil { + panic(fmt.Sprintf("Error writing file: %s\n%s\n", pathToFile, err.Error())) + } + } + } + } + } +} + +func renameImportUsages(f *ast.File, old, new string) { + // use this to avoid renaming the package declaration, eg: + // given: package foo; import foo "bar"; foo.Baz, rename foo->qux + // yield: package foo; import qux "bar"; qux.Baz + var pkg *ast.Ident + + // Rename top-level old to new, both unresolved names + // (probably defined in another file) and names that resolve + // to a declaration we renamed. + ast.Inspect(f, func(node ast.Node) bool { + if node == nil { + return false + } + switch id := node.(type) { + case *ast.File: + pkg = id.Name + case *ast.Ident: + if pkg != nil && id == pkg { + return false + } + if id.Name == old { + id.Name = new + } + } + return true + }) +} + +func (a *analyzer) filterFiles(fs map[string]*ast.File) []*ast.File { + var files []*ast.File + for _, f := range fs { + files = append(files, f) + } + return files +} + +type collector struct { + dirs []string + regex *regexp.Regexp +} + +// handlePath walks the filesystem recursively, collecting directories, +// ignoring some unneeded directories (hidden/vendored) that are handled +// specially later. +func (c *collector) handlePath(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + // Ignore hidden directories (.git, .cache, etc) + if len(path) > 1 && path[0] == '.' || + // Staging code is symlinked from vendor/k8s.io, and uses import + // paths as if it were inside of vendor/. It fails typechecking + // inside of staging/, but works when typechecked as part of vendor/. + path == "staging" || + // OS-specific vendor code tends to be imported by OS-specific + // packages. We recursively typecheck imported vendored packages for + // each OS, but don't typecheck everything for every OS. + path == "vendor" || + path == "_output" || + // This is a weird one. /testdata/ is *mostly* ignored by Go, + // and this translates to kubernetes/vendor not working. + // edit/record.go doesn't compile without gopkg.in/yaml.v2 + // in $GOSRC/$GOROOT (both typecheck and the shell script). + path == "pkg/kubectl/cmd/testdata/edit" { + return filepath.SkipDir + } + if c.regex.MatchString(path) { + c.dirs = append(c.dirs, path) + } + } + return nil +} + +func main() { + flag.Parse() + args := flag.Args() + + if len(args) == 0 { + args = append(args, ".") + } + + regex, err := regexp.Compile(*regex) + if err != nil { + log.Fatalf("Error compiling regex: %v", err) + } + c := collector{regex: regex} + for _, arg := range args { + err := filepath.Walk(arg, c.handlePath) + if err != nil { + log.Fatalf("Error walking: %v", err) + } + } + sort.Strings(c.dirs) + + if len(*importAliases) > 0 { + bytes, err := os.ReadFile(*importAliases) + if err != nil { + log.Fatalf("Error reading import aliases: %v", err) + } + err = json.Unmarshal(bytes, &aliases) + if err != nil { + log.Fatalf("Error loading aliases: %v", err) + } + } + if isTerminal { + logPrefix = "\r" // clear status bar when printing + } + fmt.Println("checking-imports: ") + + a := newAnalyzer() + for _, dir := range c.dirs { + if isTerminal { + fmt.Printf("\r\033[0m %-80s\n", dir) + } + a.collect(dir) + } + fmt.Println() + if a.failed { + os.Exit(1) + } +} diff --git a/hack/util.sh b/hack/util.sh new file mode 100755 index 0000000..00ce8f5 --- /dev/null +++ b/hack/util.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# This script holds common bash variables and utility functions. + +# This function installs a Go tools by 'go install' command. +# Parameters: +# - $1: package name, such as "sigs.k8s.io/controller-tools/cmd/controller-gen" +# - $2: package version, such as "v0.4.1" +function util::install_tools() { + local package="$1" + local version="$2" + echo "go install ${package}@${version}" + GO111MODULE=on go install "${package}"@"${version}" + GOPATH=$(go env GOPATH | awk -F ':' '{print $1}') + export PATH=$PATH:$GOPATH/bin +} \ No newline at end of file diff --git a/hack/verify-import-aliases.sh b/hack/verify-import-aliases.sh new file mode 100755 index 0000000..3bcff33 --- /dev/null +++ b/hack/verify-import-aliases.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${SCRIPT_ROOT}" +ROOT_PATH=$(pwd) + +IMPORT_ALIASES_PATH="${ROOT_PATH}/hack/.import-aliases" +INCLUDE_PATH="(${ROOT_PATH}/cmd|${ROOT_PATH}/pkg)" + +ret=0 +# We can't directly install preferredimports by `go install` due to the go.mod issue: +# go install k8s.io/kubernetes/cmd/preferredimports@v1.21.3: k8s.io/kubernetes@v1.21.3 +# The go.mod file for the module providing named packages contains one or +# more replace directives. It must not contain directives that would cause +# it to be interpreted differently than if it were the main module. +go run "${ROOT_PATH}/hack/tools/preferredimports/preferredimports.go" -import-aliases "${IMPORT_ALIASES_PATH}" -include-path "${INCLUDE_PATH}" "${ROOT_PATH}" || ret=$? +if [[ $ret -ne 0 ]]; then + echo "!!! Please see hack/.import-aliases for the preferred aliases for imports." >&2 + exit 1 +fi \ No newline at end of file diff --git a/hack/verify-staticcheck.sh b/hack/verify-staticcheck.sh new file mode 100755 index 0000000..4036ee4 --- /dev/null +++ b/hack/verify-staticcheck.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +GOLANGCI_LINT_PKG="github.com/golangci/golangci-lint/cmd/golangci-lint" +GOLANGCI_LINT_VER="v1.50.1" + +cd "${REPO_ROOT}" +source "hack/util.sh" + +util::install_tools ${GOLANGCI_LINT_PKG} ${GOLANGCI_LINT_VER} + +if golangci-lint run --timeout=5m; then + echo 'Congratulations! All Go source files have passed staticcheck.' +else + echo # print one empty line, separate from warning messages. + echo 'Please review the above warnings.' + echo 'If the above warnings do not make sense, feel free to file an issue.' + exit 1 +fi \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..ae065e0 --- /dev/null +++ b/main.go @@ -0,0 +1,22 @@ +/* +Copyright © 2023 k-cloud-labs org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import "github.com/k-cloud-labs/kluster-capacity/app" + +func main() { + app.Execute() +} diff --git a/pkg/framework/cc/report.go b/pkg/framework/cc/report.go new file mode 100644 index 0000000..10702f6 --- /dev/null +++ b/pkg/framework/cc/report.go @@ -0,0 +1 @@ +package cc diff --git a/pkg/framework/cc/simulator.go b/pkg/framework/cc/simulator.go new file mode 100644 index 0000000..10702f6 --- /dev/null +++ b/pkg/framework/cc/simulator.go @@ -0,0 +1 @@ +package cc diff --git a/pkg/framework/ce/podgenerator.go b/pkg/framework/ce/podgenerator.go new file mode 100644 index 0000000..aa384be --- /dev/null +++ b/pkg/framework/ce/podgenerator.go @@ -0,0 +1,52 @@ +package ce + +import ( + "fmt" + + uuid "github.com/satori/go.uuid" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + pkgframework "github.com/k-cloud-labs/kluster-capacity/pkg/framework" +) + +type PodGenerator interface { + Generate() *corev1.Pod +} + +type singlePodGenerator struct { + counter uint + podTemplate *corev1.Pod +} + +func NewSinglePodGenerator(podTemplate *corev1.Pod) PodGenerator { + return &singlePodGenerator{ + counter: 0, + podTemplate: podTemplate, + } +} + +func (g *singlePodGenerator) Generate() *corev1.Pod { + pod := g.podTemplate.DeepCopy() + + // reset pod + pod.Spec.NodeName = "" + pod.Namespace = pkgframework.Namespace + pod.Spec.SchedulerName = pkgframework.SchedulerName + pod.Status = corev1.PodStatus{} + + // use simulated pod name with an index to construct the name + pod.ObjectMeta.Name = fmt.Sprintf("%v-%v", g.podTemplate.Name, g.counter) + pod.ObjectMeta.UID = types.UID(uuid.NewV4().String()) + + // Add pod provisioner annotation + if pod.ObjectMeta.Annotations == nil { + pod.ObjectMeta.Annotations = map[string]string{} + } + pod.ObjectMeta.Annotations[pkgframework.PodProvisioner] = pkgframework.SchedulerName + + // Ensures uniqueness + g.counter++ + + return pod +} diff --git a/pkg/framework/ce/report.go b/pkg/framework/ce/report.go new file mode 100644 index 0000000..c51afbc --- /dev/null +++ b/pkg/framework/ce/report.go @@ -0,0 +1,281 @@ +package ce + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/ghodss/yaml" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + schedutil "k8s.io/kubernetes/pkg/scheduler/util" +) + +type CapacityEstimationReview struct { + metav1.TypeMeta + Spec CapacityEstimationReviewSpec `json:"spec"` + Status CapacityEstimationReviewStatus `json:"status"` +} + +type CapacityEstimationReviewSpec struct { + // the pod desired for scheduling + Templates []corev1.Pod `json:"templates"` + PodRequirements []*Requirements `json:"podRequirements"` +} + +type CapacityEstimationReviewStatus struct { + CreationTimestamp time.Time `json:"creationTimestamp"` + // actual number of replicas that could schedule + Replicas int32 `json:"replicas"` + StopReason *CapacityEstimationReviewScheduleStopReason `json:"stopReason"` + // per node information about the scheduling simulation + Pods []*CapacityEstimationReviewResult `json:"pods"` +} + +type CapacityEstimationReviewResult struct { + PodName string `json:"podName"` + // numbers of replicas on nodes + ReplicasOnNodes []*ReplicasOnNode `json:"replicasOnNodes"` + // reason why no more pods could schedule (if any on this node) + Summary []StopReasonSummary `json:"summary"` +} + +type ReplicasOnNode struct { + NodeName string `json:"nodeName"` + Replicas int `json:"replicas"` +} + +type StopReasonSummary struct { + Reason string `json:"reason"` + Count int `json:"count"` +} + +type Resources struct { + PrimaryResources corev1.ResourceList `json:"primaryResources"` + ScalarResources map[corev1.ResourceName]int64 `json:"scalarResources"` +} + +type Requirements struct { + PodName string `json:"podName"` + Resources *Resources `json:"resources"` + NodeSelectors map[string]string `json:"nodeSelectors"` +} + +type CapacityEstimationReviewScheduleStopReason struct { + StopType string `json:"stopType"` + StopMessage string `json:"stopMessage"` +} + +func (r *CapacityEstimationReview) Print(verbose bool, format string) error { + switch format { + case "json": + return capacityEstimationReviewPrintJson(r) + case "yaml": + return capacityEstimationReviewPrintYaml(r) + case "": + capacityEstimationReviewPrettyPrint(r, verbose) + return nil + default: + return fmt.Errorf("output format %q not recognized", format) + } +} + +func generateReport(pods []*corev1.Pod, status Status) *CapacityEstimationReview { + return &CapacityEstimationReview{ + Spec: getReviewSpec(pods), + Status: getReviewStatus(pods, status), + } +} + +func getMainStopReason(message string) *CapacityEstimationReviewScheduleStopReason { + slicedMessage := strings.Split(message, "\n") + colon := strings.Index(slicedMessage[0], ":") + + reason := &CapacityEstimationReviewScheduleStopReason{ + StopType: slicedMessage[0][:colon], + StopMessage: strings.Trim(slicedMessage[0][colon+1:], " "), + } + return reason +} + +func parsePodsReview(templatePods []*corev1.Pod, status Status) []*CapacityEstimationReviewResult { + templatesCount := len(templatePods) + result := make([]*CapacityEstimationReviewResult, 0) + + for i := 0; i < templatesCount; i++ { + result = append(result, &CapacityEstimationReviewResult{ + ReplicasOnNodes: make([]*ReplicasOnNode, 0), + PodName: templatePods[i].Name, + }) + } + + for i, pod := range status.Pods { + nodeName := pod.Spec.NodeName + first := true + for _, sum := range result[i%templatesCount].ReplicasOnNodes { + if sum.NodeName == nodeName { + sum.Replicas++ + first = false + } + } + if first { + result[i%templatesCount].ReplicasOnNodes = append(result[i%templatesCount].ReplicasOnNodes, &ReplicasOnNode{ + NodeName: nodeName, + Replicas: 1, + }) + } + } + + slicedMessage := strings.Split(status.StopReason, "\n") + if len(slicedMessage) == 1 { + return result + } + + return result +} + +func getReviewSpec(podTemplates []*corev1.Pod) CapacityEstimationReviewSpec { + podCopies := make([]corev1.Pod, len(podTemplates)) + deepCopyPods(podTemplates, podCopies) + return CapacityEstimationReviewSpec{ + Templates: podCopies, + PodRequirements: getPodsRequirements(podTemplates), + } +} + +func getReviewStatus(pods []*corev1.Pod, status Status) CapacityEstimationReviewStatus { + return CapacityEstimationReviewStatus{ + CreationTimestamp: time.Now(), + Replicas: int32(len(status.Pods)), + StopReason: getMainStopReason(status.StopReason), + Pods: parsePodsReview(pods, status), + } +} + +func deepCopyPods(in []*corev1.Pod, out []corev1.Pod) { + for i, pod := range in { + out[i] = *pod.DeepCopy() + } +} + +func getPodsRequirements(pods []*corev1.Pod) []*Requirements { + result := make([]*Requirements, 0) + for _, pod := range pods { + podRequirements := &Requirements{ + PodName: pod.Name, + Resources: getResourceRequest(pod), + NodeSelectors: pod.Spec.NodeSelector, + } + result = append(result, podRequirements) + } + return result +} + +func getResourceRequest(pod *corev1.Pod) *Resources { + result := Resources{ + PrimaryResources: corev1.ResourceList{ + corev1.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI), + corev1.ResourceMemory: *resource.NewQuantity(0, resource.BinarySI), + }, + } + + for _, container := range pod.Spec.Containers { + for rName, rQuantity := range container.Resources.Requests { + switch rName { + case corev1.ResourceMemory: + rQuantity.Add(*(result.PrimaryResources.Memory())) + result.PrimaryResources[corev1.ResourceMemory] = rQuantity + case corev1.ResourceCPU: + rQuantity.Add(*(result.PrimaryResources.Cpu())) + result.PrimaryResources[corev1.ResourceCPU] = rQuantity + default: + if schedutil.IsScalarResourceName(rName) { + // Lazily allocate this map only if required. + if result.ScalarResources == nil { + result.ScalarResources = map[corev1.ResourceName]int64{} + } + result.ScalarResources[rName] += rQuantity.Value() + } + } + } + } + return &result +} + +func instancesSum(replicasOnNodes []*ReplicasOnNode) int { + result := 0 + for _, v := range replicasOnNodes { + result += v.Replicas + } + return result +} + +func capacityEstimationReviewPrettyPrint(r *CapacityEstimationReview, verbose bool) { + if verbose { + for _, req := range r.Spec.PodRequirements { + fmt.Printf("%v pod requirements:\n", req.PodName) + fmt.Printf("\t- CPU: %v\n", req.Resources.PrimaryResources.Cpu().String()) + fmt.Printf("\t- Memory: %v\n", req.Resources.PrimaryResources.Memory().String()) + if req.Resources.ScalarResources != nil { + fmt.Printf("\t- ScalarResources: %v\n", req.Resources.ScalarResources) + } + + if req.NodeSelectors != nil { + fmt.Printf("\t- NodeSelector: %v\n", labels.SelectorFromSet(req.NodeSelectors).String()) + } + fmt.Printf("\n") + } + } + + for _, pod := range r.Status.Pods { + if verbose { + fmt.Printf("The cluster can schedule %v instance(s) of the pod %v.\n", instancesSum(pod.ReplicasOnNodes), pod.PodName) + } else { + fmt.Printf("%v\n", instancesSum(pod.ReplicasOnNodes)) + } + } + + if verbose { + fmt.Printf("\nTermination reason: %v: %v\n", r.Status.StopReason.StopType, r.Status.StopReason.StopMessage) + } + + if verbose && r.Status.Replicas > 0 { + for _, pod := range r.Status.Pods { + if pod.Summary != nil { + fmt.Printf("fit failure summary on nodes: ") + for _, fs := range pod.Summary { + fmt.Printf("%v (%v), ", fs.Reason, fs.Count) + } + fmt.Printf("\n") + } + } + fmt.Printf("\nPod distribution among nodes:\n") + for _, pod := range r.Status.Pods { + fmt.Printf("%v\n", pod.PodName) + for _, ron := range pod.ReplicasOnNodes { + fmt.Printf("\t- %v: %v instance(s)\n", ron.NodeName, ron.Replicas) + } + } + } +} + +func capacityEstimationReviewPrintJson(r *CapacityEstimationReview) error { + jsonBytes, err := json.Marshal(r) + if err != nil { + return fmt.Errorf("failed to create json: %v", err) + } + fmt.Println(string(jsonBytes)) + return nil +} + +func capacityEstimationReviewPrintYaml(r *CapacityEstimationReview) error { + yamlBytes, err := yaml.Marshal(r) + if err != nil { + return fmt.Errorf("failed to create yaml: %v", err) + } + fmt.Print(string(yamlBytes)) + return nil +} diff --git a/pkg/framework/ce/simulator.go b/pkg/framework/ce/simulator.go new file mode 100644 index 0000000..cc16373 --- /dev/null +++ b/pkg/framework/ce/simulator.go @@ -0,0 +1,342 @@ +package ce + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/dynamicinformer" + "k8s.io/client-go/informers" + coreinformers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + externalclientset "k8s.io/client-go/kubernetes" + fakeclientset "k8s.io/client-go/kubernetes/fake" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/events" + schedconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config" + "k8s.io/kubernetes/pkg/scheduler" + "k8s.io/kubernetes/pkg/scheduler/framework" + frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime" + "k8s.io/kubernetes/pkg/scheduler/profile" + + pkgframework "github.com/k-cloud-labs/kluster-capacity/pkg/framework" + "github.com/k-cloud-labs/kluster-capacity/pkg/framework/plugins/capacityestimationbinder" +) + +// only support one scheduler for now and the scheduler name is "default-scheduler" +type simulator struct { + externalKubeClient externalclientset.Interface + informerFactory informers.SharedInformerFactory + dynInformerFactory dynamicinformer.DynamicSharedInformerFactory + + // schedulers + scheduler *scheduler.Scheduler + + // pod to schedule + podGenerator PodGenerator + simulatedPod *corev1.Pod + maxSimulated int + simulated int + status Status + excludeNodes sets.Set[string] + + // for scheduler and informer + informerCh chan struct{} + schedulerCh chan struct{} + + // for simulator + stopCh chan struct{} + stopMux sync.Mutex + stopped bool +} + +// Status capture all scheduled pods with reason why the estimation could not continue +type Status struct { + Pods []*corev1.Pod + StopReason string +} + +// NewCESimulator create a ce simulator which is completely independent of apiserver so no need +// for kubeconfig nor for apiserver url +func NewCESimulator(kubeSchedulerConfig *schedconfig.CompletedConfig, kubeConfig *restclient.Config, simulatedPod *corev1.Pod, maxPods int, excludeNodes []string) (pkgframework.Simulator, error) { + client := fakeclientset.NewSimpleClientset() + sharedInformerFactory := informers.NewSharedInformerFactory(client, 0) + sharedInformerFactory.InformerFor(&corev1.Pod{}, newPodInformer) + + // create internal namespace for simulated pod + _, err := client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: pkgframework.Namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + + kubeSchedulerConfig.Client = client + + s := &simulator{ + externalKubeClient: client, + podGenerator: NewSinglePodGenerator(simulatedPod), + simulatedPod: simulatedPod, + simulated: 0, + maxSimulated: maxPods, + stopCh: make(chan struct{}), + informerFactory: sharedInformerFactory, + informerCh: make(chan struct{}), + schedulerCh: make(chan struct{}), + excludeNodes: sets.New[string](excludeNodes...), + } + + // only for latest k8s version + if kubeConfig != nil { + dynClient := dynamic.NewForConfigOrDie(kubeConfig) + s.dynInformerFactory = dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynClient, 0, corev1.NamespaceAll, nil) + } + + scheduler, err := s.createScheduler(kubeSchedulerConfig) + if err != nil { + return nil, err + } + + s.scheduler = scheduler + + s.informerFactory.Start(s.informerCh) + if s.dynInformerFactory != nil { + s.dynInformerFactory.Start(s.informerCh) + } + + return s, nil +} + +func (s *simulator) Run() error { + ctx, cancel := context.WithCancel(context.Background()) + + // wait for all informer cache synced + s.informerFactory.WaitForCacheSync(s.informerCh) + if s.dynInformerFactory != nil { + s.dynInformerFactory.WaitForCacheSync(s.informerCh) + } + go s.scheduler.Run(ctx) + + // create the first simulated pod + err := s.createNextPod() + if err != nil { + cancel() + s.stop(fmt.Sprintf("CreateFirstPod(Unexpected): %s", err.Error())) + return err + } + <-s.stopCh + cancel() + + return nil +} + +func (s *simulator) SyncWithClient(client clientset.Interface) error { + listOptions := metav1.ListOptions{ResourceVersion: "0"} + createOptions := metav1.CreateOptions{} + + nsItems, err := client.CoreV1().Namespaces().List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("unable to list ns: %v", err) + } + + for _, item := range nsItems.Items { + if _, err := s.externalKubeClient.CoreV1().Namespaces().Create(context.TODO(), &item, createOptions); err != nil { + return fmt.Errorf("unable to copy ns: %v", err) + } + } + + podItems, err := client.CoreV1().Pods(metav1.NamespaceAll).List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("unable to list pods: %v", err) + } + + for _, item := range podItems.Items { + // selector := fmt.Sprintf("status.phase!=%v,status.phase!=%v", v1.PodSucceeded, v1.PodFailed) + // field selector are not supported by fake clientset/informers + if item.Status.Phase != corev1.PodSucceeded && item.Status.Phase != corev1.PodFailed { + if _, err := s.externalKubeClient.CoreV1().Pods(item.Namespace).Create(context.TODO(), &item, createOptions); err != nil { + return fmt.Errorf("unable to copy pod: %v", err) + } + } + } + + nodeItems, err := client.CoreV1().Nodes().List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("unable to list nodes: %v", err) + } + + for _, item := range nodeItems.Items { + if s.excludeNodes.Has(item.Name) { + continue + } + if _, err := s.externalKubeClient.CoreV1().Nodes().Create(context.TODO(), &item, createOptions); err != nil { + return fmt.Errorf("unable to copy node: %v", err) + } + } + + pvcItems, err := client.CoreV1().PersistentVolumeClaims(metav1.NamespaceAll).List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("unable to list pvcs: %v", err) + } + + for _, item := range pvcItems.Items { + if _, err := s.externalKubeClient.CoreV1().PersistentVolumeClaims(item.Namespace).Create(context.TODO(), &item, createOptions); err != nil { + return fmt.Errorf("unable to copy pvc: %v", err) + } + } + + pvItems, err := client.CoreV1().PersistentVolumes().List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("unable to list pvcs: %v", err) + } + + for _, item := range pvItems.Items { + if _, err := s.externalKubeClient.CoreV1().PersistentVolumes().Create(context.TODO(), &item, createOptions); err != nil { + return fmt.Errorf("unable to copy pv: %v", err) + } + } + + storageClassesItems, err := client.StorageV1().StorageClasses().List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("unable to list storage classes: %v", err) + } + + for _, item := range storageClassesItems.Items { + if _, err := s.externalKubeClient.StorageV1().StorageClasses().Create(context.TODO(), &item, createOptions); err != nil { + return fmt.Errorf("unable to copy storage class: %v", err) + } + } + + return nil +} + +func (s *simulator) SyncWithInformerFactory(factory informers.SharedInformerFactory) error { + return errors.New("not implemented yet") +} + +func (s *simulator) Report() pkgframework.Printer { + return generateReport([]*corev1.Pod{s.simulatedPod}, s.status) +} + +func (s *simulator) createScheduler(cc *schedconfig.CompletedConfig) (*scheduler.Scheduler, error) { + // TODO: support custom outOfTreeRegistry + outOfTreeRegistry := frameworkruntime.Registry{ + capacityestimationbinder.Name: func(configuration runtime.Object, f framework.Handle) (framework.Plugin, error) { + return capacityestimationbinder.New(s.externalKubeClient, configuration, f, s.postBindHook) + }, + } + + // stop the simulator if necessary + // stop condition: reach limit and failed schedule + _, err := s.informerFactory.Core().V1().Pods().Informer().AddEventHandler( + cache.FilteringResourceEventHandler{ + FilterFunc: func(obj interface{}) bool { + if pod, ok := obj.(*corev1.Pod); ok && pod.Spec.SchedulerName == pkgframework.SchedulerName && + metav1.HasAnnotation(pod.ObjectMeta, pkgframework.PodProvisioner) { + return true + } + return false + }, + Handler: cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(oldObj, newObj interface{}) { + if pod, ok := newObj.(*corev1.Pod); ok { + for _, podCondition := range pod.Status.Conditions { + // Only for pending pods provisioned by ce + if podCondition.Type == corev1.PodScheduled && podCondition.Status == corev1.ConditionFalse && + podCondition.Reason == corev1.PodReasonUnschedulable { + s.stop(fmt.Sprintf("%v: %v", podCondition.Reason, podCondition.Message)) + } + } + } + }, + }, + }, + ) + if err != nil { + return nil, err + } + + // create the scheduler. + return scheduler.New( + s.externalKubeClient, + s.informerFactory, + s.dynInformerFactory, + getRecorderFactory(cc), + s.schedulerCh, + scheduler.WithComponentConfigVersion(cc.ComponentConfig.TypeMeta.APIVersion), + scheduler.WithKubeConfig(cc.KubeConfig), + scheduler.WithProfiles(cc.ComponentConfig.Profiles...), + scheduler.WithPercentageOfNodesToScore(cc.ComponentConfig.PercentageOfNodesToScore), + scheduler.WithFrameworkOutOfTreeRegistry(outOfTreeRegistry), + scheduler.WithPodMaxBackoffSeconds(cc.ComponentConfig.PodMaxBackoffSeconds), + scheduler.WithPodInitialBackoffSeconds(cc.ComponentConfig.PodInitialBackoffSeconds), + scheduler.WithExtenders(cc.ComponentConfig.Extenders...), + scheduler.WithParallelism(cc.ComponentConfig.Parallelism), + ) +} + +func (s *simulator) postBindHook(bindPod *corev1.Pod) error { + s.status.Pods = append(s.status.Pods, bindPod) + + if s.maxSimulated > 0 && s.simulated >= s.maxSimulated { + s.stop(fmt.Sprintf("LimitReached: Maximum number of pods simulated: %v", s.maxSimulated)) + return nil + } + + if err := s.createNextPod(); err != nil { + return fmt.Errorf("unable to create next pod for simulated scheduling: %v", err) + } + return nil +} + +func (s *simulator) stop(reason string) { + s.stopMux.Lock() + defer s.stopMux.Unlock() + + if s.stopped { + return + } + + s.status.StopReason = reason + s.stopped = true + close(s.informerCh) + close(s.schedulerCh) + close(s.stopCh) +} + +func (s *simulator) createNextPod() error { + pod := s.podGenerator.Generate() + + s.simulated++ + + _, err := s.externalKubeClient.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) + return err +} + +func newPodInformer(cs externalclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + selector := fmt.Sprintf("status.phase!=%v,status.phase!=%v", corev1.PodSucceeded, corev1.PodFailed) + tweakListOptions := func(options *metav1.ListOptions) { + options.FieldSelector = selector + } + return coreinformers.NewFilteredPodInformer(cs, metav1.NamespaceAll, resyncPeriod, nil, tweakListOptions) +} + +func getRecorderFactory(cc *schedconfig.CompletedConfig) profile.RecorderFactory { + return func(name string) events.EventRecorder { + return cc.EventBroadcaster.NewRecorder(name) + } +} diff --git a/pkg/framework/interface.go b/pkg/framework/interface.go new file mode 100644 index 0000000..b42c495 --- /dev/null +++ b/pkg/framework/interface.go @@ -0,0 +1,22 @@ +package framework + +import ( + clientset "k8s.io/client-go/kubernetes" +) + +const ( + Namespace = "kclabs-system" + PodProvisioner = "kc.k-cloud-labs.io/provisioned-by" + SchedulerName = "simulator-scheduler" +) + +type Simulator interface { + Run() error + SyncWithClient(p clientset.Interface) error + //SyncWithInformerFactory(factory informers.SharedInformerFactory) error + Report() Printer +} + +type Printer interface { + Print(verbose bool, format string) error +} diff --git a/pkg/framework/plugins/capacityestimationbinder/plugin.go b/pkg/framework/plugins/capacityestimationbinder/plugin.go new file mode 100644 index 0000000..41fee6b --- /dev/null +++ b/pkg/framework/plugins/capacityestimationbinder/plugin.go @@ -0,0 +1,55 @@ +package capacityestimationbinder + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/pkg/scheduler/framework" + + pkgframework "github.com/k-cloud-labs/kluster-capacity/pkg/framework" +) + +const Name = "CapacityEstimationBinder" + +type CapacityEstimationBinder struct { + client kubernetes.Interface + postBindHook func(*corev1.Pod) error +} + +func New(client kubernetes.Interface, _ runtime.Object, _ framework.Handle, postBindHook func(*corev1.Pod) error) (framework.Plugin, error) { + return &CapacityEstimationBinder{ + client: client, + postBindHook: postBindHook, + }, nil +} + +func (b *CapacityEstimationBinder) Name() string { + return Name +} + +func (b *CapacityEstimationBinder) Bind(ctx context.Context, state *framework.CycleState, p *corev1.Pod, nodeName string) *framework.Status { + pod, err := b.client.CoreV1().Pods(p.Namespace).Get(context.TODO(), p.Name, metav1.GetOptions{}) + if err != nil { + return framework.NewStatus(framework.Error, fmt.Sprintf("Unable to bind: %v", err)) + } + updatedPod := pod.DeepCopy() + updatedPod.Spec.NodeName = nodeName + updatedPod.Status.Phase = corev1.PodRunning + + if _, err = b.client.CoreV1().Pods(pod.Namespace).Update(ctx, updatedPod, metav1.UpdateOptions{}); err != nil { + return framework.NewStatus(framework.Error, fmt.Sprintf("Unable to update binded pod: %v", err)) + } + + // ignore non simulated pod + if metav1.HasAnnotation(pod.ObjectMeta, pkgframework.PodProvisioner) { + if err := b.postBindHook(updatedPod); err != nil { + framework.NewStatus(framework.Error, fmt.Sprintf("Invoking postBindHook gives an error: %v", err)) + } + } + + return nil +} diff --git a/pkg/framework/ss/report.go b/pkg/framework/ss/report.go new file mode 100644 index 0000000..5ebc936 --- /dev/null +++ b/pkg/framework/ss/report.go @@ -0,0 +1 @@ +package ss diff --git a/pkg/framework/ss/simulator.go b/pkg/framework/ss/simulator.go new file mode 100644 index 0000000..5ebc936 --- /dev/null +++ b/pkg/framework/ss/simulator.go @@ -0,0 +1 @@ +package ss diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..b2acedb --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,168 @@ +package utils + +import ( + "fmt" + "os" + + corev1 "k8s.io/api/core/v1" + fakeclientset "k8s.io/client-go/kubernetes/fake" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/events" + configv1alpha1 "k8s.io/component-base/config/v1alpha1" + "k8s.io/component-base/logs" + kubeschedulerconfigv1 "k8s.io/kube-scheduler/config/v1" + schedconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config" + kubescheduleroptions "k8s.io/kubernetes/cmd/kube-scheduler/app/options" + "k8s.io/kubernetes/pkg/api/legacyscheme" + kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" + kubeschedulerscheme "k8s.io/kubernetes/pkg/scheduler/apis/config/scheme" + "k8s.io/kubernetes/pkg/scheduler/apis/config/validation" + "k8s.io/kubernetes/pkg/scheduler/framework/plugins/defaultbinder" + + pkgframework "github.com/k-cloud-labs/kluster-capacity/pkg/framework" + "github.com/k-cloud-labs/kluster-capacity/pkg/framework/plugins/capacityestimationbinder" +) + +func init() { + if err := corev1.AddToScheme(legacyscheme.Scheme); err != nil { + fmt.Printf("err: %v\n", err) + } +} + +func BuildRestConfig(config string) (*restclient.Config, error) { + if len(config) != 0 { + master, err := getMasterFromKubeConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to parse kubeconfig file: %v ", err) + } + + cfg, err := clientcmd.BuildConfigFromFlags(master, config) + if err != nil { + return nil, fmt.Errorf("unable to build config: %v", err) + } + + return cfg, nil + } else { + cfg, err := restclient.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("unable to build in cluster config: %v", err) + } + + return cfg, nil + } +} + +func BuildKubeSchedulerCompletedConfig(config string) (*schedconfig.CompletedConfig, error) { + var kcfg *kubeschedulerconfig.KubeSchedulerConfiguration + if len(config) > 0 { + cfg, err := loadConfigFromFile(config) + if err != nil { + return nil, err + } + if err := validation.ValidateKubeSchedulerConfiguration(cfg); err != nil { + return nil, err + } + kcfg = cfg + } + + cc, err := buildKubeSchedulerCompletedConfig(kcfg) + if err != nil { + return nil, fmt.Errorf("failed to init kube scheduler configuration: %v ", err) + } + + return cc, nil +} + +func buildKubeSchedulerCompletedConfig(kcfg *kubeschedulerconfig.KubeSchedulerConfiguration) (*schedconfig.CompletedConfig, error) { + if kcfg == nil { + kcfg = &kubeschedulerconfig.KubeSchedulerConfiguration{} + versionedCfg := kubeschedulerconfigv1.KubeSchedulerConfiguration{} + versionedCfg.DebuggingConfiguration = *configv1alpha1.NewRecommendedDebuggingConfiguration() + + kubeschedulerscheme.Scheme.Default(&versionedCfg) + if err := kubeschedulerscheme.Scheme.Convert(&versionedCfg, kcfg, nil); err != nil { + return nil, err + } + } + + // inject scheduler config + if len(kcfg.Profiles) == 0 { + kcfg.Profiles = []kubeschedulerconfig.KubeSchedulerProfile{ + {}, + } + } + + kcfg.Profiles[0].SchedulerName = pkgframework.SchedulerName + if kcfg.Profiles[0].Plugins == nil { + kcfg.Profiles[0].Plugins = &kubeschedulerconfig.Plugins{} + } + + kcfg.Profiles[0].Plugins.Bind = kubeschedulerconfig.PluginSet{ + Enabled: []kubeschedulerconfig.Plugin{{Name: capacityestimationbinder.Name}}, + Disabled: []kubeschedulerconfig.Plugin{{Name: defaultbinder.Name}}, + } + opts := &kubescheduleroptions.Options{ + ComponentConfig: kcfg, + Logs: logs.NewOptions(), + } + + c := &schedconfig.Config{} + // clear out all unnecessary options so no port is bound + // to allow running multiple instances in a row + opts.Deprecated = nil + opts.SecureServing = nil + if err := opts.ApplyTo(c); err != nil { + return nil, fmt.Errorf("unable to get scheduler kcfg: %v", err) + } + + // Get the completed config + cc := c.Complete() + + // completely ignore the events + cc.EventBroadcaster = events.NewEventBroadcasterAdapter(fakeclientset.NewSimpleClientset()) + + return &cc, nil +} + +func loadConfigFromFile(file string) (*kubeschedulerconfig.KubeSchedulerConfiguration, error) { + data, err := os.ReadFile(file) + if err != nil { + return nil, err + } + return loadConfig(data) +} + +func loadConfig(data []byte) (*kubeschedulerconfig.KubeSchedulerConfiguration, error) { + // The UniversalDecoder runs defaulting and returns the internal type by default. + obj, gvk, err := kubeschedulerscheme.Codecs.UniversalDecoder().Decode(data, nil, nil) + if err != nil { + return nil, err + } + if cfgObj, ok := obj.(*kubeschedulerconfig.KubeSchedulerConfiguration); ok { + // We don't set this field in pkg/scheduler/apis/config/{version}/conversion.go + // because the field will be cleared later by API machinery during + // conversion. See KubeSchedulerConfiguration internal type definition for + // more details. + cfgObj.TypeMeta.APIVersion = gvk.GroupVersion().String() + return cfgObj, nil + } + return nil, fmt.Errorf("couldn't decode as KubeSchedulerConfiguration, got %s: ", gvk) +} + +func getMasterFromKubeConfig(filename string) (string, error) { + config, err := clientcmd.LoadFromFile(filename) + if err != nil { + return "", fmt.Errorf("can not load kubeconfig file: %v", err) + } + + context, ok := config.Contexts[config.CurrentContext] + if !ok { + return "", fmt.Errorf("failed to get master address from kubeconfig") + } + + if val, ok := config.Clusters[context.Cluster]; ok { + return val.Server, nil + } + return "", fmt.Errorf("failed to get master address from kubeconfig") +} diff --git a/pkg/version/base.go b/pkg/version/base.go new file mode 100644 index 0000000..5c87362 --- /dev/null +++ b/pkg/version/base.go @@ -0,0 +1,15 @@ +package version + +// Base version information. +// +// This is the fallback data used when version information from git is not +// provided via go ldflags. It provides an approximation of the kinitiras +// version for ad-hoc builds (e.g. `go build`) that cannot get the version +// information from git. +var ( + gitVersion = "v0.0.0-master" + gitCommit = "unknown" // sha1 from git, output of $(git rev-parse HEAD) + gitTreeState = "unknown" // state of git tree, either "clean" or "dirty" + + buildDate = "unknown" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') +) diff --git a/pkg/version/sharedcommand/sharedcommand.go b/pkg/version/sharedcommand/sharedcommand.go new file mode 100644 index 0000000..490ba21 --- /dev/null +++ b/pkg/version/sharedcommand/sharedcommand.go @@ -0,0 +1,33 @@ +package sharedcommand + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "github.com/k-cloud-labs/kluster-capacity/pkg/version" +) + +var ( + versionShort = `Print the version information.` + versionLong = `Print the version information.` + versionExample = ` # Print %s command version + %s version` +) + +// NewCmdVersion prints out the release version info for this command binary. +// It is used as a subcommand of a parent command. +func NewCmdVersion(out io.Writer, parentCommand string) *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Short: versionShort, + Long: versionLong, + Example: fmt.Sprintf(versionExample, parentCommand, parentCommand), + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintf(out, "%s version: %s\n", parentCommand, version.Get()) + }, + } + + return cmd +} diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..a77b6dc --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,36 @@ +package version + +import ( + "fmt" + "runtime" +) + +// Info contains versioning information. +type Info struct { + GitVersion string `json:"gitVersion"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` + BuildDate string `json:"buildDate"` + GoVersion string `json:"goVersion"` + Compiler string `json:"compiler"` + Platform string `json:"platform"` +} + +// String returns a Go-syntax representation of the Info. +func (info Info) String() string { + return fmt.Sprintf("%#v", info) +} + +// Get returns the overall codebase version. It's for detecting +// what code a binary was built from. +func Get() Info { + return Info{ + GitVersion: gitVersion, + GitCommit: gitCommit, + GitTreeState: gitTreeState, + BuildDate: buildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +}