diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bd31c20df2..79d1ddae87 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: '' **What happened**: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c10fdbea5d..b4aef6f89c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,14 +3,14 @@ name: continuous-integration on: push: branches: - - master + - main - release* tags: - v1.* - v2.* pull_request: branches: - - master + - main - release* permissions: @@ -21,7 +21,7 @@ env: E2E_SETUP_KUBECTL: yes SUDO: sudo GO_VERSION: "^1.19" - GOLANGCI_LINT_VERSION: "v1.48.0" + GOLANGCI_LINT_VERSION: "v1.50.1" jobs: ci-go-lint: @@ -29,13 +29,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -52,13 +52,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -74,13 +74,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -96,13 +96,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -118,13 +118,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -140,7 +140,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup promtool run: | @@ -155,13 +155,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -177,13 +177,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | @@ -199,13 +199,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup environment run: | diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml new file mode 100644 index 0000000000..2326a220e9 --- /dev/null +++ b/.github/workflows/govulncheck.yml @@ -0,0 +1,26 @@ +name: govulncheck + +on: + schedule: + # Run every Monday + - cron: '0 0 * * 1' + +env: + GO_VERSION: "^1.19" + +jobs: + ci-security-checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + name: Checkout code + - name: Set up Go 1.x + uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + - name: Install govulncheck binary + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + - name: Run security checks + run: | + govulncheck -v ./... diff --git a/.golangci.yml b/.golangci.yml index a0b8723e19..67ee2fb49a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,6 +8,7 @@ linters: - gocyclo - gofmt - goimports + - gosec - gosimple - govet - ineffassign @@ -28,3 +29,10 @@ issues: - path: _test\.go linters: - promlinter + # TODO(mrueg) Improve error handling + - text: "G104:" + linters: + - gosec + - text: "package-comments:" + linters: + - revive diff --git a/CHANGELOG.md b/CHANGELOG.md index e773ba66c2..c9ea168675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +## v2.7.0 / 2022-11-25 + +Note: Experimental VerticalPodAutoscaler metrics are considered deprecated in this release and will be removed in v2.9.0. +Please use CustomResourceStateMetrics to gather metrics from VPA resources. See: #1718 + +Note: Experimental CustomResourceState changed their naming convention for better usability. +The name of the CRD used to be interpolated into the name of the metric which made it impossible to aggregate a CRD across different versions. +This was changed to have the GVK information represented as labels: +kube_myteam_io_v1_Foo_uptime -> kube_crd_uptime{group="myteam.io", kind="Foo", version="v1"} +See: #1847 + +* [CHANGE] Deprecate VerticalPodAutoscaler metrics #1835 @rexagod +* [CHANGE] Recommend kube-scheduler metrics for container resource limits and requests #1849 @rexagod +* [FEATURE] Add experimental kube_pod_container_status_last_terminated_exitcode metric #1752 @ssabo +* [FEATURE] Introduce custom-resources-only flag #1813 @bavarianbidi +* [FEATURE] Allow allowlist filtering by wildcard key #1823 @rexagod +* [FEATURE] Add ContainerResourceSourceType to hpa spec and target metrics #1831 @whitebear009 +* [FEATURE] Represent group, version and kind of a resource as labels #1850 @rexagod +* [FEATURE] Sharding metrics per node via fieldSelector #1864 @CatherineF-dev +* [FEATURE] Add experimental StatefulSet retention policy metrics #1876 @mattcary +* [FEATURE] Allow labelFromKey field for all applicable metric types #1880 @rexagod +* [FEATURE] Introduce Viper, allow hot-reload on config change #1827 @rexagod +* [FEATURE] Introduce Cobra, allow configuration via environment variables #1834 @rexagod +* [FEATURE] Add experimental kube_node_deletion_timestamp metric #1890 @rexagod +* [FEATURE] Support autoscaling/v2 resources for HorizontalPodAutoscaler #1906 @JoaoBraveCoding +* [FEATURE] Add IngressClass metrics #1905 @kaitoii11 +* [ENHANCEMENT] Import Kubernetes metrics stability framework #1844 @CatherineF-dev +* [ENHANCEMENT] Promote kube_pod_container_status_waiting_reason and kube_deployment_status_replicas_ready to stable #1821 @CatherineF-dev +* [ENHANCEMENT] Build with Kubernetes 1.25 and go 1.19 #1819 @mrueg +* [BUGFIX] Handle singular labels in allowlist #1826 @rexagod +* [BUGFIX] Do not expose ingress path metric when service is nil #1841 @evir35 +* [BUGFIX] Allow lease metrics to be exported across all namespaces #1845 @lantingchiang + ## v2.6.0 / 2022-08-26 * [FEATURE] Add local storage labels to kube_persistentvolume_info #1814 @nabokihms * [FEATURE] Add support for StateSet and Info metrics for Custom-Resource State #1777 @chrischdi diff --git a/Makefile b/Makefile index c33e9a177a..665044e578 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,8 @@ GIT_COMMIT ?= $(shell git rev-parse --short HEAD) OS ?= $(shell uname -s | tr A-Z a-z) ALL_ARCH = amd64 arm arm64 ppc64le s390x PKG = github.com/prometheus/common -PROMETHEUS_VERSION = 2.37.0 -GO_VERSION = 1.19.2 +PROMETHEUS_VERSION = 2.40.6 +GO_VERSION = 1.19.4 IMAGE = $(REGISTRY)/kube-state-metrics MULTI_ARCH_IMG = $(IMAGE)-$(ARCH) USER ?= $(shell id -u -n) @@ -78,7 +78,7 @@ shellcheck: # the two. test-benchmark-compare: @git fetch - ./tests/compare_benchmarks.sh master + ./tests/compare_benchmarks.sh main ./tests/compare_benchmarks.sh ${LATEST_RELEASE_BRANCH} all: all-container diff --git a/OWNERS b/OWNERS index 067494f09c..11dcc5a67c 100644 --- a/OWNERS +++ b/OWNERS @@ -1,7 +1,10 @@ reviewers: + - CatherineF-dev - dgrisonnet - fpetkovski + - logicalhan - mrueg + - rexagod approvers: - dgrisonnet - fpetkovski diff --git a/README.md b/README.md index 096c11840c..c4bce76cca 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Overview [![Build Status](https://github.com/kubernetes/kube-state-metrics/workflows/continuous-integration/badge.svg)](https://github.com/kubernetes/kube-state-metrics/actions) -[![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes/kube-state-metrics)](https://goreportcard.com/report/github.com/kubernetes/kube-state-metrics) [![GoDoc](https://godoc.org/github.com/kubernetes/kube-state-metrics?status.svg)](https://godoc.org/github.com/kubernetes/kube-state-metrics) +[![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes/kube-state-metrics)](https://goreportcard.com/report/github.com/kubernetes/kube-state-metrics) +[![Go Reference](https://pkg.go.dev/badge/github.com/kubernetes/kube-state-metrics.svg)](https://pkg.go.dev/github.com/kubernetes/kube-state-metrics) +[![govulncheck](https://github.com/kubernetes/kube-state-metrics/actions/workflows/govulncheck.yml/badge.svg)](https://github.com/kubernetes/kube-state-metrics/actions/workflows/govulncheck.yml) kube-state-metrics (KSM) is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects. (See examples in @@ -45,6 +47,7 @@ are deleted they are no longer visible on the `/metrics` endpoint. - [Resource recommendation](#resource-recommendation) - [Horizontal sharding](#horizontal-sharding) - [Automated sharding](#automated-sharding) + - [Daemonset sharding for pod metrics](#daemonset-sharding-pod-metrics) - [Setup](#setup) - [Building the Docker container](#building-the-docker-container) - [Usage](#usage) @@ -71,11 +74,11 @@ Generally, it is recommended to use the latest release of kube-state-metrics. If | kube-state-metrics | Kubernetes client-go Version | |--------------------|:----------------------------:| -| **v2.3.0** | v1.23 | | **v2.4.2** | v1.23 | | **v2.5.0** | v1.24 | | **v2.6.0** | v1.24 | -| **master** | v1.25 | +| **v2.7.0** | v1.25 | +| **main** | v1.26 | #### Resource group version compatibility @@ -87,7 +90,7 @@ release. #### Container Image The latest container image can be found at: -* `registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.6.0` (arch: `amd64`, `arm`, `arm64`, `ppc64le` and `s390x`) +* `registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.7.0` (arch: `amd64`, `arm`, `arm64`, `ppc64le` and `s390x`) ### Metrics Documentation @@ -153,7 +156,7 @@ http_request_duration_seconds_count{handler="metrics",method="get"} 30 kube-state-metrics also exposes build and configuration metrics: ``` -kube_state_metrics_build_info{branch="master",goversion="go1.15.3",revision="6c9d775d",version="v2.0.0-beta"} 1 +kube_state_metrics_build_info{branch="main",goversion="go1.15.3",revision="6c9d775d",version="v2.0.0-beta"} 1 kube_state_metrics_shard_ordinal{shard_ordinal="0"} 0 kube_state_metrics_total_shards 1 ``` @@ -163,6 +166,17 @@ please check the blog post [here](https://www.robustperception.io/exposing-the-s Sharding metrics expose `--shard` and `--total-shards` flags and can be used to validate run-time configuration, see [`/examples/prometheus-alerting-rules`](./examples/prometheus-alerting-rules). +kube-state-metrics also exposes metrics about it config file and the Custom Resource State config file: + +``` +kube_state_metrics_config_hash{filename="crs.yml",type="customresourceconfig"} 2.38272279311849e+14 +kube_state_metrics_config_hash{filename="config.yml",type="config"} 2.65285922340846e+14 +kube_state_metrics_last_config_reload_success_timestamp_seconds{filename="crs.yml",type="customresourceconfig"} 1.6704882592037103e+09 +kube_state_metrics_last_config_reload_success_timestamp_seconds{filename="config.yml",type="config"} 1.6704882592035313e+09 +kube_state_metrics_last_config_reload_successful{filename="crs.yml",type="customresourceconfig"} 1 +kube_state_metrics_last_config_reload_successful{filename="config.yml",type="config"} 1 +``` + ### Scaling kube-state-metrics #### Resource recommendation @@ -235,6 +249,36 @@ This way of deploying shards is useful when you want to manage KSM shards throug The downside of using an auto-sharded setup comes from the rollout strategy supported by `StatefulSet`s. When managed by a `StatefulSet`, pods are replaced one at a time with each pod first getting terminated and then recreated. Besides such rollouts being slower, they will also lead to short downtime for each shard. If a Prometheus scrape happens during a rollout, it can miss some of the metrics exported by kube-state-metrics. +### Daemonset sharding for pod metrics + +For pod metrics, they can be sharded per node with the following flag: +* `--node` + +Each kube-state-metrics pod uses FieldSelector (spec.nodeName) to watch/list pod metrics only on the same node. + +A daemonset kube-state-metrics example: +``` +apiVersion: apps/v1 +kind: DaemonSet +spec: + template: + spec: + containers: + - image: registry.k8s.io/kube-state-metrics/kube-state-metrics:IMAGE_TAG + name: kube-state-metrics + args: + - --resource=pods + - --node=$(NODE_NAME) + env: + - name: NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName +``` + +Other metrics can be sharded via [Horizontal sharding](#horizontal-sharding). + ### Setup Install this project to your `$GOPATH` using `go get`: diff --git a/RELEASE.md b/RELEASE.md index e2ab973030..072da51edf 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -6,9 +6,9 @@ We use [Semantic Versioning](http://semver.org/). We maintain a separate branch for each minor release, named `release-.`, e.g. `release-1.1`, `release-2.0`. -The usual flow is to merge new features and changes into the master branch and to merge bug fixes into the latest release branch. Bug fixes are then merged into master from the latest release branch. The master branch should always contain all commits from the latest release branch. +The usual flow is to merge new features and changes into the main branch and to merge bug fixes into the latest release branch. Bug fixes are then merged into main from the latest release branch. The main branch should always contain all commits from the latest release branch. -If a bug fix got accidentally merged into master, cherry-pick commits have to be created in the latest release branch, which then have to be merged back into master. Try to avoid that situation. +If a bug fix got accidentally merged into main, cherry-pick commits have to be created in the latest release branch, which then have to be merged back into main. Try to avoid that situation. Maintaining the release branches for older minor releases happens on a best effort basis. @@ -34,7 +34,7 @@ Maintaining the release branches for older minor releases happens on a best effo * Create a new **pre-release** on github * New images are automatically built and pushed to `gcr.io/k8s-staging-kube-state-metrics/kube-state-metrics` * Promote image by sending a PR to [kubernetes/k8s.io](https://github.com/kubernetes/k8s.io) repository. Follow the [example PR](https://github.com/kubernetes/k8s.io/pull/3798). Use [kpromo pr](https://github.com/kubernetes-sigs/promo-tools/blob/main/docs/promotion-pull-requests.md) to update the manifest files in this repository, e.g. `kpromo pr --fork=$YOURNAME -i --project=kube-state-metrics -t=v2.5.0` -* Create a PR to merge the changes of this release back into the master branch. +* Create a PR to merge the changes of this release back into the main branch. * Once the PR to promote the image is merged, mark the pre-release as a regular release. ## Stable release diff --git a/VERSION b/VERSION index e70b4523ae..24ba9a38de 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6.0 +2.7.0 diff --git a/docs/README.md b/docs/README.md index 617c28f39e..a6f6840f30 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,6 +65,8 @@ See each file for specific documentation about the exposed metrics: - [ClusterRole Metrics](clusterrole-metrics.md) - [ClusterRoleBinding Metrics](clusterrolebinding-metrics.md) +- [EndpointSlice Metrics](endpointslice-metrics.md) +- [IngressClass Metrics](ingressclass-metrics.md) - [Role Metrics](role-metrics.md) - [RoleBinding Metrics](rolebinding-metrics.md) - [ServiceAccount Metrics](serviceaccount-metrics.md) diff --git a/docs/cli-arguments.md b/docs/cli-arguments.md index 7f7ee451ab..67190c66b5 100644 --- a/docs/cli-arguments.md +++ b/docs/cli-arguments.md @@ -24,12 +24,25 @@ spec: [embedmd]:# (../help.txt) ```txt $ kube-state-metrics -h -Usage of ./kube-state-metrics: +kube-state-metrics is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects. + +Usage: + kube-state-metrics [flags] + kube-state-metrics [command] + +Available Commands: + completion Generate completion script for kube-state-metrics. + help Help about any command + version Print version information. + +Flags: --add_dir_header If true, adds the file directory to the header of the log messages --alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) --apiserver string The URL of the apiserver to use as a master + --config string Path to the kube-state-metrics options config file --custom-resource-state-config string Inline Custom Resource State Metrics config YAML (experimental) --custom-resource-state-config-file string Path to a Custom Resource State Metrics config file (experimental) + --custom-resource-state-only Only provide Custom Resource State metrics (experimental) --enable-gzip-encoding Gzip responses when requested by clients via 'Accept-Encoding: gzip' header. -h, --help Print Help text --host string Host to expose metrics on. (default "::") @@ -42,10 +55,11 @@ Usage of ./kube-state-metrics: --metric-allowlist string Comma-separated list of metrics to be exposed. This list comprises of exact metric names and/or regex patterns. The allowlist and denylist are mutually exclusive. --metric-annotations-allowlist string Comma-separated list of Kubernetes annotations keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional annotations provide a list of resource names in their plural form and Kubernetes annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. A single '*' can be provided per resource instead to allow any annotations, but that has severe performance implications (Example: '=pods=[*]'). --metric-denylist string Comma-separated list of metrics not to be enabled. This list comprises of exact metric names and/or regex patterns. The allowlist and denylist are mutually exclusive. - --metric-labels-allowlist string Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional labels provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]'). + --metric-labels-allowlist string Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional labels provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]'). Additionally, an asterisk (*) can be provided as a key, which will resolve to all resources, i.e., assuming '--resources=deployments,pods', '=*=[*]' will resolve to '=deployments=[*],pods=[*]'. --metric-opt-in-list string Comma-separated list of metrics which are opt-in and not enabled by default. This is in addition to the metric allow- and denylists --namespaces string Comma-separated list of namespaces to be enabled. Defaults to "" --namespaces-denylist string Comma-separated list of namespaces not to be enabled. If namespaces and namespaces-denylist are both set, only namespaces that are excluded in namespaces-denylist will be used. + --node string Name of the node that contains the kube-state-metrics pod. Most likely it should be passed via the downward API. This is used for daemonset sharding. Only available for resources (pod metrics) that support spec.nodeName fieldSelector. This is experimental. --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) --pod string Name of the pod that contains the kube-state-metrics container. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice. --pod-namespace string Name of the namespace of the pod specified by --pod. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice. @@ -63,4 +77,6 @@ Usage of ./kube-state-metrics: -v, --v Level number for the log level verbosity --version kube-state-metrics build version information --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging + +Use "kube-state-metrics [command] --help" for more information about a command. ``` diff --git a/docs/customresourcestate-metrics.md b/docs/customresourcestate-metrics.md index 3242cefdfd..5296091ebc 100644 --- a/docs/customresourcestate-metrics.md +++ b/docs/customresourcestate-metrics.md @@ -49,6 +49,8 @@ spec: - --resources=certificatesigningrequests,configmaps,cronjobs,daemonsets,deployments,endpoints,foos,horizontalpodautoscalers,ingresses,jobs,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,secrets,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments,verticalpodautoscalers ``` +NOTE: The `group`, `version`, and `kind` common labels are reserved, and will be overwritten by the values from the `groupVersionKind` field. + ### Examples The examples in this section will use the following custom resource: @@ -115,7 +117,7 @@ spec: Produces the metric: ```prometheus -kube_myteam_io_v1_Foo_uptime 43.21 +kube_crd_uptime{group="myteam.io", kind="Foo", version="v1"} 43.21 ``` #### Multiple Metrics/Kitchen Sink @@ -144,12 +146,13 @@ spec: path: [status, sub] # if path targets an object, the object key will be used as label value + # This is not supported for StateSet type as all values will be truthy, which is redundant. labelFromKey: type # label values can be resolved specific to this path labelsFromPath: active: [active] - # The actual field to use as metric value. Should be a number. - value: [ready] + # The actual field to use as metric value. Should be a number, boolean or RFC3339 timestamp string. + valueFrom: [ready] commonLabels: custom_metric: "yes" labelsFromPath: @@ -166,8 +169,8 @@ spec: Produces the following metrics: ```prometheus -kube_myteam_io_v1_Foo_active_count{active="1",custom_metric="yes",foo="bar",name="foo",bar="baz",qux="quxx",type="type-a"} 1 -kube_myteam_io_v1_Foo_active_count{active="3",custom_metric="yes",foo="bar",name="foo",bar="baz",qux="quxx",type="type-b"} 3 +kube_crd_ready_count{group="myteam.io", kind="Foo", version="v1", active="1",custom_metric="yes",foo="bar",name="foo",bar="baz",qux="quxx",type="type-a"} 2 +kube_crd_ready_count{group="myteam.io", kind="Foo", version="v1", active="3",custom_metric="yes",foo="bar",name="foo",bar="baz",qux="quxx",type="type-b"} 4 ``` ### Metric types @@ -202,7 +205,7 @@ spec: Produces the metric: ```prometheus -kube_myteam_io_v1_Foo_uptime 43.21 +kube_crd_uptime{group="myteam.io", kind="Foo", version="v1"} 43.21 ``` #### StateSet @@ -228,15 +231,15 @@ spec: list: [Pending, Bar, Baz] ``` -Metrics of type `SateSet` will generate a metric for each value defined in `list` for each resource. +Metrics of type `StateSet` will generate a metric for each value defined in `list` for each resource. The value will be 1, if the value matches the one in list. Produces the metric: ```prometheus -kube_myteam_io_v1_Foo_status_phase{phase="Pending"} 1 -kube_myteam_io_v1_Foo_status_phase{phase="Bar"} 0 -kube_myteam_io_v1_Foo_status_phase{phase="Baz"} 0 +kube_crd_status_phase{group="myteam.io", kind="Foo", version="v1", phase="Pending"} 1 +kube_crd_status_phase{group="myteam.io", kind="Foo", version="v1", phase="Bar"} 0 +kube_crd_status_phase{group="myteam.io", kind="Foo", version="v1", phase="Baz"} 0 ``` #### Info @@ -266,7 +269,7 @@ spec: Produces the metric: ```prometheus -kube_myteam_io_v1_Foo_version{version="v1.2.3"} 1 +kube_crd_version{group="myteam.io", kind="Foo", version="v1", version="v1.2.3"} 1 ``` ### Naming @@ -288,7 +291,7 @@ spec: Produces: ```prometheus -myteam_foos_uptime 43.21 +myteam_foos_uptime{group="myteam.io", kind="Foo", version="v1"} 43.21 ``` To omit namespace and/or subsystem altogether, set them to the empty string: @@ -304,6 +307,11 @@ spec: ... ``` +Produces: +```prometheus +uptime{group="myteam.io", kind="Foo", version="v1"} 43.21 +``` + ### Logging If a metric path is registered but not found on a custom resource, an error will be logged. For some resources, diff --git a/docs/developer/guide.md b/docs/developer/guide.md index b515564e40..eb5a72e51b 100644 --- a/docs/developer/guide.md +++ b/docs/developer/guide.md @@ -6,17 +6,28 @@ Any contribution to improving this documentation will be appreciated. ## Table of Contents - [Add New Kubernetes Resource Metric Collector](#add-new-kubernetes-resource-metric-collector) +- [Add New Metrics](#add-new-metrics) ### Add New Kubernetes Resource Metric Collector The following steps are needed to introduce a new resource and its respective resource metrics. -- Reference your new resource(s) to the [docs/README.md](https://github.com/kubernetes/kube-state-metrics/blob/master/docs/README.md#exposed-metrics). -- Reference your new resource(s) in the [docs/cli-arguments.md](https://github.com/kubernetes/kube-state-metrics/blob/master/docs/cli-arguments.md#available-options) as part of the `--resources` flag. -- Create a new `.md` in the [docs](https://github.com/kubernetes/kube-state-metrics/tree/master/docs) directory to provide documentation on the resource(s) and metrics you implemented. Follow the formatting of all other resources. -- Add the resource(s) you are representing to the [jsonnet/kube-state-metrics/kube-state-metrics.libsonnet](https://github.com/kubernetes/kube-state-metrics/blob/master/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet) under the appropriate `apiGroup` using the `verbs`: `list` and `watch`. -- Run `make examples/standard`, this should re-generate [examples/standard/cluster-role.yaml](https://github.com/kubernetes/kube-state-metrics/blob/master/examples/standard/cluster-role.yaml) with the resource(s) added to [jsonnet/kube-state-metrics/kube-state-metrics.libsonnet](https://github.com/kubernetes/kube-state-metrics/blob/master/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet). -- Reference and add build functions for the new resource(s) in [internal/store/builder.go](https://github.com/kubernetes/kube-state-metrics/blob/master/internal/store/builder.go). -- Reference the new resource in [pkg/options/resource.go](https://github.com/kubernetes/kube-state-metrics/blob/master/pkg/options/resource.go). -- Add a sample Kubernetes manifest to be used by tests in the [tests/manifests/](https://github.com/kubernetes/kube-state-metrics/tree/master/tests/manifests) directory. -- Lastly, and most importantly, actually implement your new resource(s) and its test binary in [internal/store](https://github.com/kubernetes/kube-state-metrics/tree/master/internal/store). Follow the formatting and structure of other resources. +- Reference your new resource(s) to the [docs/README.md](https://github.com/kubernetes/kube-state-metrics/blob/main/docs/README.md#exposed-metrics). +- Reference your new resource(s) in the [docs/cli-arguments.md](https://github.com/kubernetes/kube-state-metrics/blob/main/docs/cli-arguments.md#available-options) as part of the `--resources` flag. +- Create a new `.md` in the [docs](https://github.com/kubernetes/kube-state-metrics/tree/main/docs) directory to provide documentation on the resource(s) and metrics you implemented. Follow the formatting of all other resources. +- Add the resource(s) you are representing to the [jsonnet/kube-state-metrics/kube-state-metrics.libsonnet](https://github.com/kubernetes/kube-state-metrics/blob/main/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet) under the appropriate `apiGroup` using the `verbs`: `list` and `watch`. +- Run `make examples/standard`, this should re-generate [examples/standard/cluster-role.yaml](https://github.com/kubernetes/kube-state-metrics/blob/main/examples/standard/cluster-role.yaml) with the resource(s) added to [jsonnet/kube-state-metrics/kube-state-metrics.libsonnet](https://github.com/kubernetes/kube-state-metrics/blob/main/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet). +- Reference and add build functions for the new resource(s) in [internal/store/builder.go](https://github.com/kubernetes/kube-state-metrics/blob/main/internal/store/builder.go). +- Reference the new resource in [pkg/options/resource.go](https://github.com/kubernetes/kube-state-metrics/blob/main/pkg/options/resource.go). +- Add a sample Kubernetes manifest to be used by tests in the [tests/manifests/](https://github.com/kubernetes/kube-state-metrics/tree/main/tests/manifests) directory. +- Lastly, and most importantly, actually implement your new resource(s) and its test binary in [internal/store](https://github.com/kubernetes/kube-state-metrics/tree/main/internal/store). Follow the formatting and structure of other resources. + +### Add New Metrics + +- Make metrics experimental first when introducing them, refer [#1910](https://github.com/kubernetes/kube-state-metrics/pull/1910) for more information. + +| Metric stability level | | +|------------------------|--------------------| +| EXPERIMENTAL | basemetrics.ALPHA | +| STABLE | basemetrics.STABLE | + diff --git a/docs/endpointslice-metrics.md b/docs/endpointslice-metrics.md new file mode 100644 index 0000000000..f2327a5265 --- /dev/null +++ b/docs/endpointslice-metrics.md @@ -0,0 +1,10 @@ +# Endpoint Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_endpointslice_annotations | Gauge | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`annotation_ENDPOINTSLICE_ANNOTATION`=<ENDPOINTSLICE_ANNOTATION> | EXPERIMENTAL | +| kube_endpointslice_info | Gauge | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace> | EXPERIMENTAL | +| kube_endpointslice_ports | Gauge | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`port_name`=<endpointslice-port-name>
`port_protocol`=<endpointslice-port-protocol>
`port_number`=<endpointslice-port-number> | EXPERIMENTAL | +| kube_endpointslice_endpoints | Gauge | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`ready`=<endpointslice-ready>
`serving`=<endpointslice-serving>
`terminating`=<endpointslice-terminating>
`hostname`=<endpointslice-hostname>
`targetref_kind`=<endpointslice-targetref-kind>
`targetref_name`=<endpointslice-targetref-name>
`targetref_namespace`=<endpointslice-targetref-namespace>
`nodename`=<endpointslice-nodename>
`endpoint_zone`=<endpointslice-zone> | EXPERIMENTAL | +| kube_endpointslice_labels | Gauge | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace>
`label_ENDPOINTSLICE_LABEL`=<ENDPOINTSLICE_LABEL> | EXPERIMENTAL | +| kube_endpointslice_created | Gauge | `endpointslice`=<endpointslice-name>
`namespace`=<endpointslice-namespace> | EXPERIMENTAL | diff --git a/docs/ingressclass-metrics.md b/docs/ingressclass-metrics.md new file mode 100644 index 0000000000..f9fade6171 --- /dev/null +++ b/docs/ingressclass-metrics.md @@ -0,0 +1,8 @@ +# IngressClass Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_ingressclass_annotations | Gauge | `ingressclass`=<ingressclass-name>
`annotation_INGRESSCLASS_ANNOTATION`=<INGRESSCLASS_ANNOTATION> | EXPERIMENTAL | +| kube_ingressclass_info | Gauge | `ingressclass`=<ingressclass-name>
`controller`=<ingress-controller-name>
| EXPERIMENTAL | +| kube_ingressclass_labels | Gauge | `ingressclass`=<ingressclass-name>
`label_INGRESSCLASS_LABEL`=<INGRESSCLASS_LABEL> | EXPERIMENTAL| +| kube_ingressclass_created | Gauge | `ingressclass`=<ingressclass-name> | EXPERIMENTAL| diff --git a/docs/node-metrics.md b/docs/node-metrics.md index cc817caaa6..43d55c1e35 100644 --- a/docs/node-metrics.md +++ b/docs/node-metrics.md @@ -8,7 +8,8 @@ | kube_node_role | Gauge | The role of a cluster node | | `node`=<node-address>
`role`=<NODE_ROLE> | EXPERIMENTAL | | kube_node_spec_unschedulable | Gauge | Whether a node can schedule new pods | | `node`=<node-address>| STABLE | | kube_node_spec_taint | Gauge | The taint of a cluster node. | |`node`=<node-address>
`key`=<taint-key>
`value=`<taint-value>
`effect=`<taint-effect> | STABLE | -| kube_node_status_capacity | Gauge | The capacity for different resources of a node | `cpu`=<core>
`ephemeral_storage`=<byte>
`pods`=<integer>
`attachable_volumes_*`=<byte>
`hugepages_*`=<byte>
`memory`=<byte> |`node`=<node-address>
`resource`=<resource-name>
`unit`=<resource-unit>| STABLE | -| kube_node_status_allocatable | Gauge | The allocatable for different resources of a node that are available for scheduling | `cpu`=<core>
`ephemeral_storage`=<byte>
`pods`=<integer>
`attachable_volumes_*`=<byte>
`hugepages_*`=<byte>
`memory`=<byte> |`node`=<node-address>
`resource`=<resource-name>
`unit`=<resource-unit>| STABLE | +| kube_node_status_capacity | Gauge | The total amount of resources available for a node | `cpu`=<core>
`ephemeral_storage`=<byte>
`pods`=<integer>
`attachable_volumes_*`=<byte>
`hugepages_*`=<byte>
`memory`=<byte> |`node`=<node-address>
`resource`=<resource-name>
`unit`=<resource-unit>| STABLE | +| kube_node_status_allocatable | Gauge | The amount of resources allocatable for pods (after reserving some for system daemons) | `cpu`=<core>
`ephemeral_storage`=<byte>
`pods`=<integer>
`attachable_volumes_*`=<byte>
`hugepages_*`=<byte>
`memory`=<byte> |`node`=<node-address>
`resource`=<resource-name>
`unit`=<resource-unit>| STABLE | | kube_node_status_condition | Gauge | The condition of a cluster node | |`node`=<node-address>
`condition`=<node-condition>
`status`=<true\|false\|unknown> | STABLE | | kube_node_created | Gauge | Unix creation timestamp | seconds |`node`=<node-address>| STABLE | +| kube_node_deletion_timestamp | Gauge | Unix creation timestamp | seconds |`node`=<node-address>| EXPERIMENTAL | diff --git a/docs/persistentvolumeclaim-metrics.md b/docs/persistentvolumeclaim-metrics.md index 6294d6a3fe..2c92bc8247 100644 --- a/docs/persistentvolumeclaim-metrics.md +++ b/docs/persistentvolumeclaim-metrics.md @@ -13,4 +13,4 @@ Note: -- A special `` string will be used if PVC has no storage class. +- An empty string will be used if PVC has no storage class. diff --git a/docs/pod-metrics.md b/docs/pod-metrics.md index 8aa10d900b..a524d42207 100644 --- a/docs/pod-metrics.md +++ b/docs/pod-metrics.md @@ -1,56 +1,55 @@ # Pod Metrics - -| Metric name| Metric type | Description | Unit (where applicable) | Labels/tags | Status | Opt-in | -| ---------- | ----------- | ----------- | ----------------------- | ----------- | ------ | ------ | -| kube_pod_annotations | Gauge | Kubernetes annotations converted to Prometheus labels | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`annotation_POD_ANNOTATION`=<POD_ANNOTATION>
`uid`=<pod-uid> | EXPERIMENTAL | - -| kube_pod_info | Gauge | Information about pod | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`host_ip`=<host-ip>
`pod_ip`=<pod-ip>
`node`=<node-name>
`created_by_kind`=<created_by_kind>
`created_by_name`=<created_by_name>
`uid`=<pod-uid>
`priority_class`=<priority_class>
`host_network`=<host_network>| STABLE | - | -| kube_pod_ips | Gauge | Pod IP addresses | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`ip`=<pod-ip-address>
`ip_family`=<4 OR 6>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_start_time | Gauge | Start time in unix timestamp for a pod | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_completion_time | Gauge | Completion time in unix timestamp for a pod | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_owner | Gauge | Information about the Pod's owner | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`owner_kind`=<owner kind>
`owner_name`=<owner name>
`owner_is_controller`=<whether owner is controller>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_labels | Gauge | Kubernetes labels converted to Prometheus labels | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`label_POD_LABEL`=<POD_LABEL>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_nodeselectors| Gauge | Describes the Pod nodeSelectors | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`nodeselector_NODE_SELECTOR`=<NODE_SELECTOR>
`uid`=<pod-uid> | EXPERIMENTAL | Opt-in | -| kube_pod_status_phase | Gauge | The pods current phase | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`phase`=<Pending\|Running\|Succeeded\|Failed\|Unknown>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_status_ready | Gauge | Describes whether the pod is ready to serve requests | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`condition`=<true\|false\|unknown>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_status_ready_time | Gauge | Time when pod passed readiness probes. | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | +| Metric name| Metric type | Description | Unit (where applicable) | Labels/tags | Status | Opt-in | +| ---------- | ----------- |-----------------------------------------------------------------------| ----------------------- | ----------- | ------ | ------ | +| kube_pod_annotations | Gauge | Kubernetes annotations converted to Prometheus labels | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`annotation_POD_ANNOTATION`=<POD_ANNOTATION>
`uid`=<pod-uid> | EXPERIMENTAL | - +| kube_pod_info | Gauge | Information about pod | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`host_ip`=<host-ip>
`pod_ip`=<pod-ip>
`node`=<node-name>
`created_by_kind`=<created_by_kind>
`created_by_name`=<created_by_name>
`uid`=<pod-uid>
`priority_class`=<priority_class>
`host_network`=<host_network>| STABLE | - | +| kube_pod_ips | Gauge | Pod IP addresses | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`ip`=<pod-ip-address>
`ip_family`=<4 OR 6>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_start_time | Gauge | Start time in unix timestamp for a pod | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_completion_time | Gauge | Completion time in unix timestamp for a pod | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_owner | Gauge | Information about the Pod's owner | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`owner_kind`=<owner kind>
`owner_name`=<owner name>
`owner_is_controller`=<whether owner is controller>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_labels | Gauge | Kubernetes labels converted to Prometheus labels | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`label_POD_LABEL`=<POD_LABEL>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_nodeselectors| Gauge | Describes the Pod nodeSelectors | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`nodeselector_NODE_SELECTOR`=<NODE_SELECTOR>
`uid`=<pod-uid> | EXPERIMENTAL | Opt-in | +| kube_pod_status_phase | Gauge | The pods current phase | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`phase`=<Pending\|Running\|Succeeded\|Failed\|Unknown>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_status_ready | Gauge | Describes whether the pod is ready to serve requests | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`condition`=<true\|false\|unknown>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_status_ready_time | Gauge | Time when pod passed readiness probes. | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | | kube_pod_status_container_ready_time | Gauge | Time when the container of the pod entered Ready state. | seconds | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | -| kube_pod_status_scheduled | Gauge | Describes the status of the scheduling process for the pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`condition`=<true\|false\|unknown>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_info | Gauge | Information about a container in a pod | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`image`=<image-name>
`image_id`=<image-id>
`image_spec`=<image-spec>
`container_id`=<containerid>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_status_waiting | Gauge | Describes whether the container is currently in waiting state | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_status_waiting_reason | Gauge | Describes the reason the container is currently in waiting state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<container-waiting-reason>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_status_running | Gauge | Describes whether the container is currently in running state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_state_started | Gauge | Start time in unix timestamp for a pod container | seconds |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_status_terminated | Gauge | Describes whether the container is currently in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_status_terminated_reason | Gauge | Describes the reason the container is currently in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<container-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_container_status_last_terminated_reason | Gauge | Describes the last reason the container was in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<last-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_container_status_last_terminated_exitcode | Gauge | Describes the exit code for the last container in terminated state. | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_container_status_ready | Gauge | Describes whether the containers readiness check succeeded | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_status_restarts_total | Counter | The number of container restarts per container | | `container`=<container-name>
`namespace`=<pod-namespace>
`pod`=<pod-name>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_container_resource_requests | Gauge | The number of requested request resource by a container | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_container_resource_limits | Gauge | The number of requested limit resource by a container | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_status_scheduled | Gauge | Describes the status of the scheduling process for the pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`condition`=<true\|false\|unknown>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_info | Gauge | Information about a container in a pod | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`image`=<image-name>
`image_id`=<image-id>
`image_spec`=<image-spec>
`container_id`=<containerid>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_status_waiting | Gauge | Describes whether the container is currently in waiting state | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_status_waiting_reason | Gauge | Describes the reason the container is currently in waiting state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<container-waiting-reason>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_status_running | Gauge | Describes whether the container is currently in running state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_state_started | Gauge | Start time in unix timestamp for a pod container | seconds |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_status_terminated | Gauge | Describes whether the container is currently in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_status_terminated_reason | Gauge | Describes the reason the container is currently in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<container-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_container_status_last_terminated_reason | Gauge | Describes the last reason the container was in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<last-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_container_status_last_terminated_exitcode | Gauge | Describes the exit code for the last container in terminated state. | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_container_status_ready | Gauge | Describes whether the containers readiness check succeeded | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_status_restarts_total | Counter | The number of container restarts per container | | `container`=<container-name>
`namespace`=<pod-namespace>
`pod`=<pod-name>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_container_resource_requests | Gauge | The number of requested request resource by a container. It is recommended to use the `kube_pod_resource_requests` metric exposed by kube-scheduler instead, as it is more precise. | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_container_resource_limits | Gauge | The number of requested limit resource by a container. It is recommended to use the `kube_pod_resource_limits` metric exposed by kube-scheduler instead, as it is more precise. | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | | kube_pod_overhead_cpu_cores | Gauge | The pod overhead in regards to cpu cores associated with running a pod | core |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_overhead_memory_bytes | Gauge | The pod overhead in regards to memory associated with running a pod | bytes |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_runtimeclass_name_info | Gauge | The runtimeclass associated with the pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_created | Gauge | Unix creation timestamp | seconds |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_deletion_timestamp | Gauge | Unix deletion timestamp | seconds |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_restart_policy | Gauge | Describes the restart policy in use by this pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`type`=<Always\|Never\|OnFailure>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_init_container_info | Gauge | Information about an init container in a pod | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`image`=<image-name>
`image_id`=<image-id>
`image_spec`=<image-spec>
`container_id`=<containerid>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_init_container_status_waiting | Gauge | Describes whether the init container is currently in waiting state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_overhead_memory_bytes | Gauge | The pod overhead in regards to memory associated with running a pod | bytes |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_runtimeclass_name_info | Gauge | The runtimeclass associated with the pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_created | Gauge | Unix creation timestamp | seconds |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_deletion_timestamp | Gauge | Unix deletion timestamp | seconds |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_restart_policy | Gauge | Describes the restart policy in use by this pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`type`=<Always\|Never\|OnFailure>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_init_container_info | Gauge | Information about an init container in a pod | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`image`=<image-name>
`image_id`=<image-id>
`image_spec`=<image-spec>
`container_id`=<containerid>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_init_container_status_waiting | Gauge | Describes whether the init container is currently in waiting state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | | kube_pod_init_container_status_waiting_reason | Gauge | Describes the reason the init container is currently in waiting state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<container-waiting-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_init_container_status_running | Gauge | Describes whether the init container is currently in running state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_init_container_status_running | Gauge | Describes whether the init container is currently in running state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | | kube_pod_init_container_status_terminated | Gauge | Describes whether the init container is currently in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | | kube_pod_init_container_status_terminated_reason | Gauge | Describes the reason the init container is currently in terminated state | | `container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<container-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_init_container_status_last_terminated_reason | Gauge | Describes the last reason the init container was in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<last-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_init_container_status_ready | Gauge | Describes whether the init containers readiness check succeeded | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_init_container_status_restarts_total | Counter | The number of restarts for the init container | integer |`container`=<container-name>
`namespace`=<pod-namespace>
`pod`=<pod-name>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_init_container_resource_limits | Gauge | The number of CPU cores requested limit by an init container | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_init_container_resource_requests | Gauge | The number of CPU cores requested by an init container | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_spec_volumes_persistentvolumeclaims_info | Gauge | Information about persistentvolumeclaim volumes in a pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`volume`=<volume-name>
`persistentvolumeclaim`=<persistentvolumeclaim-claimname>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_spec_volumes_persistentvolumeclaims_readonly | Gauge | Describes whether a persistentvolumeclaim is mounted read only | bool |`pod`=<pod-name>
`namespace`=<pod-namespace>
`volume`=<volume-name>
`persistentvolumeclaim`=<persistentvolumeclaim-claimname>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_status_reason | Gauge | The pod status reasons | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<Evicted\|NodeAffinity\|NodeLost\|Shutdown\|UnexpectedAdmissionError>
`uid`=<pod-uid> | EXPERIMENTAL | - | -| kube_pod_status_scheduled_time | Gauge | Unix timestamp when pod moved into scheduled status | seconds |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_status_unschedulable | Gauge | Describes the unschedulable status for the pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | -| kube_pod_tolerations | Gauge | Information about the pod tolerations | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid>
`key`=<toleration-key>
`operator`=<toleration-operator>
`value`=<toleration-value>
`effect`=<toleration-effect> `toleration_seconds`=<toleration-seconds> | EXPERIMENTAL | - | +| kube_pod_init_container_status_last_terminated_reason | Gauge | Describes the last reason the init container was in terminated state | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<last-terminated-reason>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_init_container_status_ready | Gauge | Describes whether the init containers readiness check succeeded | |`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_init_container_status_restarts_total | Counter | The number of restarts for the init container | integer |`container`=<container-name>
`namespace`=<pod-namespace>
`pod`=<pod-name>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_init_container_resource_limits | Gauge | The number of CPU cores requested limit by an init container | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_init_container_resource_requests | Gauge | The number of CPU cores requested by an init container | `cpu`=<core>
`memory`=<bytes> |`resource`=<resource-name>
`unit`=<resource-unit>
`container`=<container-name>
`pod`=<pod-name>
`namespace`=<pod-namespace>
`node`=< node-name>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_spec_volumes_persistentvolumeclaims_info | Gauge | Information about persistentvolumeclaim volumes in a pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`volume`=<volume-name>
`persistentvolumeclaim`=<persistentvolumeclaim-claimname>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_spec_volumes_persistentvolumeclaims_readonly | Gauge | Describes whether a persistentvolumeclaim is mounted read only | bool |`pod`=<pod-name>
`namespace`=<pod-namespace>
`volume`=<volume-name>
`persistentvolumeclaim`=<persistentvolumeclaim-claimname>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_status_reason | Gauge | The pod status reasons | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`reason`=<Evicted\|NodeAffinity\|NodeLost\|Shutdown\|UnexpectedAdmissionError>
`uid`=<pod-uid> | EXPERIMENTAL | - | +| kube_pod_status_scheduled_time | Gauge | Unix timestamp when pod moved into scheduled status | seconds |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_status_unschedulable | Gauge | Describes the unschedulable status for the pod | |`pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid> | STABLE | - | +| kube_pod_tolerations | Gauge | Information about the pod tolerations | | `pod`=<pod-name>
`namespace`=<pod-namespace>
`uid`=<pod-uid>
`key`=<toleration-key>
`operator`=<toleration-operator>
`value`=<toleration-value>
`effect`=<toleration-effect> `toleration_seconds`=<toleration-seconds> | EXPERIMENTAL | - | ## Useful metrics queries diff --git a/docs/statefulset-metrics.md b/docs/statefulset-metrics.md index fae40311c3..e7d0aa567e 100644 --- a/docs/statefulset-metrics.md +++ b/docs/statefulset-metrics.md @@ -11,6 +11,7 @@ | kube_statefulset_status_observed_generation | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | STABLE | | kube_statefulset_replicas | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | STABLE | | kube_statefulset_metadata_generation | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | STABLE | +| kube_statefulset_persistentvolumeclaim_retention_policy | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace>
`when_deleted`=<statefulset-when-deleted-pvc-policy>
`when_scaled`=<statefulset-when-scaled-pvc-policy> | EXPERIMENTAL | | kube_statefulset_created | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | STABLE | | kube_statefulset_labels | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace>
`label_STATEFULSET_LABEL`=<STATEFULSET_LABEL> | STABLE | | kube_statefulset_status_current_revision | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace>
`revision`=<statefulset-current-revision> | STABLE | diff --git a/docs/verticalpodautoscaler-metrics.md b/docs/verticalpodautoscaler-metrics.md index 3077c2395c..49d380de99 100644 --- a/docs/verticalpodautoscaler-metrics.md +++ b/docs/verticalpodautoscaler-metrics.md @@ -1,16 +1,53 @@ # Vertical Pod Autoscaler Metrics -| Metric name | Metric type | Labels/tags | Status | -| -------------------------------- | ----------- | ------------------------------------------------------------- | ------ | -| kube_verticalpodautoscaler_annotations | Gauge | `annotation_app`=<foo>
`namespace`=<namespace>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_minallowed | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_maxallowed | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_labels | Gauge | `label_app`=<foo>
`namespace`=<namespace>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | -| kube_verticalpodautoscaler_spec_updatepolicy_updatemode | Gauge | `namespace`=<namespace>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`update_mode`=<foo>
`verticalpodautoscaler`=<vertical pod autoscaler name> | EXPERIMENTAL | +## DEPRECATION NOTICE + +From v2.9.0 onwards, `vericalpodautoscalers` will be removed from the list of default resources. This means that specifying that in the `--resource` flag will **not** generate metrics for the same. In order to generate `verticalpodautoscalers` metrics, you will have to explicitly specify it in `--custom-resource-state-config*` (either the inline yaml, or the configuration file), like so: +```yaml +# Using --resource=verticalpodautoscalers, we get the following output: +# HELP kube_verticalpodautoscaler_annotations (Deprecated since v2.9.0) Kubernetes annotations converted to Prometheus labels. +# TYPE kube_verticalpodautoscaler_annotations gauge +# kube_verticalpodautoscaler_annotations{namespace="default",verticalpodautoscaler="hamster-vpa",target_api_version="apps/v1",target_kind="Deployment",target_name="hamster"} 1 +# A similar result can be achieved by specifying the following in --custom-resource-state-config: +kind: CustomResourceStateMetrics +spec: + resources: + - groupVersionKind: + group: autoscaling.k8s.io + kind: "VerticalPodAutoscaler" + version: "v1" + labelsFromPath: + verticalpodautoscaler: [metadata, name] + namespace: [metadata, namespace] + target_api_version: [apiVersion] + target_kind: [spec, targetRef, kind] + target_name: [spec, targetRef, name] + metrics: + - name: "annotations" + help: "Kubernetes annotations converted to Prometheus labels." + each: + type: Gauge + gauge: + path: [metadata, annotations] +# This will output the following metric: +# HELP kube_crd_autoscaling_k8s_io_v1_VerticalPodAutoscaler_annotations Kubernetes annotations converted to Prometheus labels. +# TYPE kube_crd_autoscaling_k8s_io_v1_VerticalPodAutoscaler_annotations gauge +# kube_crd_autoscaling_k8s_io_v1_VerticalPodAutoscaler_annotations{namespace="default",target_api_version="autoscaling.k8s.io/v1",target_kind="Deployment",target_name="hamster",verticalpodautoscaler="hamster-vpa"} 123 +``` +PS. The above configuration was tested on [this](https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/examples/hamster.yaml) VPA configuration, with an added annotation (`foo: 123`). +*** + +| Metric name | Metric type | Labels/tags | Status | +| -------------------------------- | ----------- | ------------------------------------------------------------- | ------ | +| kube_verticalpodautoscaler_annotations | Gauge | `annotation_app`=<foo>
`namespace`=<namespace>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_minallowed | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_maxallowed | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound | Gauge | `container`=<container name>
`namespace`=<namespace>
`resource`=<cpu memory>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`unit`=<core byte>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_labels | Gauge | `label_app`=<foo>
`namespace`=<namespace>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | +| kube_verticalpodautoscaler_spec_updatepolicy_updatemode | Gauge | `namespace`=<namespace>
`target_api_version`=<api version>
`target_kind`=<target kind>
`target_name`=<target name>
`update_mode`=<foo>
`verticalpodautoscaler`=<vertical pod autoscaler name> | DEPRECATED | ## Configuration diff --git a/examples/autosharding/cluster-role-binding.yaml b/examples/autosharding/cluster-role-binding.yaml index d9e76e28fd..72764789f8 100644 --- a/examples/autosharding/cluster-role-binding.yaml +++ b/examples/autosharding/cluster-role-binding.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics roleRef: apiGroup: rbac.authorization.k8s.io diff --git a/examples/autosharding/cluster-role.yaml b/examples/autosharding/cluster-role.yaml index 2a54e60129..e20db08235 100644 --- a/examples/autosharding/cluster-role.yaml +++ b/examples/autosharding/cluster-role.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics rules: - apiGroups: @@ -77,6 +77,13 @@ rules: verbs: - list - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch - apiGroups: - storage.k8s.io resources: @@ -97,6 +104,7 @@ rules: - networking.k8s.io resources: - networkpolicies + - ingressclasses - ingresses verbs: - list diff --git a/examples/autosharding/role-binding.yaml b/examples/autosharding/role-binding.yaml index a64631035d..1cb7521df6 100644 --- a/examples/autosharding/role-binding.yaml +++ b/examples/autosharding/role-binding.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system roleRef: diff --git a/examples/autosharding/role.yaml b/examples/autosharding/role.yaml index 940cc16b90..9bca941ad2 100644 --- a/examples/autosharding/role.yaml +++ b/examples/autosharding/role.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system rules: diff --git a/examples/autosharding/service-account.yaml b/examples/autosharding/service-account.yaml index 23501a2eb4..8d6dc8e84d 100644 --- a/examples/autosharding/service-account.yaml +++ b/examples/autosharding/service-account.yaml @@ -5,6 +5,6 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system diff --git a/examples/autosharding/service.yaml b/examples/autosharding/service.yaml index c81f81ea3d..2c41087009 100644 --- a/examples/autosharding/service.yaml +++ b/examples/autosharding/service.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system spec: diff --git a/examples/autosharding/statefulset.yaml b/examples/autosharding/statefulset.yaml index 4190b81477..605d4e541d 100644 --- a/examples/autosharding/statefulset.yaml +++ b/examples/autosharding/statefulset.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system spec: @@ -18,7 +18,7 @@ spec: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 spec: automountServiceAccountToken: true containers: @@ -34,7 +34,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.6.0 + image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.7.0 livenessProbe: httpGet: path: /healthz diff --git a/examples/standard/cluster-role-binding.yaml b/examples/standard/cluster-role-binding.yaml index d9e76e28fd..72764789f8 100644 --- a/examples/standard/cluster-role-binding.yaml +++ b/examples/standard/cluster-role-binding.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics roleRef: apiGroup: rbac.authorization.k8s.io diff --git a/examples/standard/cluster-role.yaml b/examples/standard/cluster-role.yaml index 2a54e60129..e20db08235 100644 --- a/examples/standard/cluster-role.yaml +++ b/examples/standard/cluster-role.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics rules: - apiGroups: @@ -77,6 +77,13 @@ rules: verbs: - list - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch - apiGroups: - storage.k8s.io resources: @@ -97,6 +104,7 @@ rules: - networking.k8s.io resources: - networkpolicies + - ingressclasses - ingresses verbs: - list diff --git a/examples/standard/deployment.yaml b/examples/standard/deployment.yaml index a059056979..bcb18be1df 100644 --- a/examples/standard/deployment.yaml +++ b/examples/standard/deployment.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system spec: @@ -17,11 +17,11 @@ spec: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 spec: automountServiceAccountToken: true containers: - - image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.6.0 + - image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.7.0 livenessProbe: httpGet: path: /healthz diff --git a/examples/standard/service-account.yaml b/examples/standard/service-account.yaml index 23501a2eb4..8d6dc8e84d 100644 --- a/examples/standard/service-account.yaml +++ b/examples/standard/service-account.yaml @@ -5,6 +5,6 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system diff --git a/examples/standard/service.yaml b/examples/standard/service.yaml index c81f81ea3d..2c41087009 100644 --- a/examples/standard/service.yaml +++ b/examples/standard/service.yaml @@ -4,7 +4,7 @@ metadata: labels: app.kubernetes.io/component: exporter app.kubernetes.io/name: kube-state-metrics - app.kubernetes.io/version: 2.6.0 + app.kubernetes.io/version: 2.7.0 name: kube-state-metrics namespace: kube-system spec: diff --git a/go.mod b/go.mod index 8e45889a6b..6caf039c6a 100644 --- a/go.mod +++ b/go.mod @@ -4,47 +4,41 @@ require ( github.com/brancz/gojsontoyaml v0.1.0 github.com/campoy/embedmd v1.0.0 github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 - github.com/gobuffalo/flect v0.2.5 - github.com/google/go-cmp v0.5.8 - github.com/google/go-jsonnet v0.18.0 + github.com/fsnotify/fsnotify v1.6.0 + github.com/gobuffalo/flect v0.3.0 + github.com/google/go-cmp v0.5.9 + github.com/google/go-jsonnet v0.19.1 github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 github.com/oklog/run v1.1.0 - github.com/prometheus/client_golang v1.13.0 - github.com/prometheus/client_model v0.2.0 - github.com/prometheus/common v0.37.0 - github.com/prometheus/exporter-toolkit v0.7.1 + github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_model v0.3.0 + github.com/prometheus/common v0.38.0 + github.com/prometheus/exporter-toolkit v0.8.2 github.com/robfig/cron/v3 v3.0.1 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.0 - golang.org/x/perf v0.0.0-20220722155240-3d85ee92886d + github.com/spf13/cobra v1.6.1 + github.com/spf13/viper v1.14.0 + github.com/stretchr/testify v1.8.1 + golang.org/x/perf v0.0.0-20220920022801-e8d778a60d07 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.25.2 - k8s.io/apimachinery v0.25.2 + k8s.io/api v0.26.0 + k8s.io/apimachinery v0.26.0 k8s.io/autoscaler/vertical-pod-autoscaler v0.12.0 - k8s.io/client-go v0.25.2 - k8s.io/component-base v0.25.2 + k8s.io/client-go v0.26.0 + k8s.io/component-base v0.26.0 k8s.io/klog/v2 v2.80.1 - k8s.io/sample-controller v0.25.2 - k8s.io/utils v0.0.0-20220922133306-665eaaec4324 + k8s.io/sample-controller v0.26.0 + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 ) require ( - cloud.google.com/go v0.97.0 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.27 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/coreos/go-systemd/v22 v22.4.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.13.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect @@ -52,44 +46,57 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/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-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/google/safehtml v0.0.2 // 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/jpillora/backoff 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/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // 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/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + 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/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // 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/protobuf v1.28.1 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // 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 ) -go 1.17 +go 1.18 diff --git a/go.sum b/go.sum index 08186905a8..7f51f9095e 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT 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= @@ -16,17 +17,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY 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.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 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= @@ -44,53 +35,20 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo 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= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= -github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 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/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 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/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/benbjohnson/clock v1.1.0/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= @@ -102,9 +60,6 @@ github.com/brancz/gojsontoyaml v0.1.0/go.mod h1:+ycZY94+V11XZBUaDEsbLr3hPNS/ZPrD github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 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= @@ -114,43 +69,34 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/zWskzfGU= +github.com/coreos/go-systemd/v22 v22.4.0/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/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 h1:0wH6nO9QEa02Qx8sIQGw6ieKdz+BXjpccSOo9vXNl4U= github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0/go.mod h1:4hKCXuwrJoYvHZxJ86+bRVTOMyJ0Ej+RqfSm8mHi6KA= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/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.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +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-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -161,53 +107,35 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn 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-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= -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 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/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/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= 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.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/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-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= -github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk= +github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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= @@ -215,8 +143,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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= @@ -232,10 +158,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= @@ -243,7 +167,6 @@ github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/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= @@ -254,21 +177,18 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= -github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= +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/go-jsonnet v0.19.1 h1:MORxkrG0elylUqh36R4AcSPX0oZQa9hvI3lroN+kDhs= +github.com/google/go-jsonnet v0.19.1/go.mod h1:5JVT33JVCoehdTj5Z2KJq1eIdt3Nb8PCmZ+W5D8U350= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -278,53 +198,41 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf 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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/safehtml v0.0.2 h1:ZOt2VXg4x24bW0m2jtzAOkhoXV0iM8vNKc0paByCZqM= +github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= 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 v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +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.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 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/jsonnet-bundler/jsonnet-bundler v0.5.1 h1:eUd6EA1Qzz73Q4NLNLOrNkMb96+6NTTERbX9lqaxVwk= github.com/jsonnet-bundler/jsonnet-bundler v0.5.1/go.mod h1:Qrdw/7mOFS2SKCOALKFfEH8gdvXJi8XZjw9g5ilpf4I= 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/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 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/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -334,6 +242,8 @@ 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/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= @@ -346,87 +256,51 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/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/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-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -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 h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +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/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -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.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -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_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-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.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.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -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/exporter-toolkit v0.7.1 h1:c6RXaK8xBVercEeUQ4tRNL8UGWzDHfvj9dseo1FcK1Y= -github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= -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/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.38.0 h1:VTQitp6mXTdUoCmDMugDVOJ1opi6ADftKfp/yeqTR/E= +github.com/prometheus/common v0.38.0/go.mod h1:MBXfmBQZrK5XpbCkjofnXs96LD2QQ7fEq4C0xjC/yec= +github.com/prometheus/exporter-toolkit v0.8.2 h1:sbJAfBXQFkG6sUkbwBun8MNdzW9+wd5YfPYofbmj0YM= +github.com/prometheus/exporter-toolkit v0.8.2/go.mod h1:00shzmJL7KxcsabLWcONwpyNEuWhREOnFqZW7vadFS0= 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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -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/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= @@ -436,20 +310,22 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -458,48 +334,30 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 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= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.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.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-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-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/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.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -536,7 +394,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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= @@ -547,13 +404,8 @@ 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/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-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= @@ -561,11 +413,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190827160401-ba9fcec4b297/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= @@ -576,29 +426,17 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ 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-20200520004742-59133d7f0dd7/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-20201110031124-69a78807bb2b/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-20210119194325-5f4716e94777/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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/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-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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= @@ -609,17 +447,10 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ 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-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/perf v0.0.0-20220722155240-3d85ee92886d h1:YurdHpXO0MGox7XtCYq5Xm2kYylGN90LEfw4en36WVM= -golang.org/x/perf v0.0.0-20220722155240-3d85ee92886d/go.mod h1:O34quEX6kI7y6XJXzRa1g18oSkBHYYQN9/OSig0x0QM= +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/perf v0.0.0-20220920022801-e8d778a60d07 h1:LhnzFL4oipoFsDvxTy2IJRHgOlnGzB2dE5l7C4PN7bg= +golang.org/x/perf v0.0.0-20220920022801-e8d778a60d07/go.mod h1:UBKtEnL8aqnd+0JHqZ+2qoMDwtuy6cYhhKNoHLBiTQc= 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= @@ -630,29 +461,20 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-20180909124046-d0be0721c37e/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -666,47 +488,27 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -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= @@ -715,13 +517,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/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-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/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-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= @@ -735,13 +537,11 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn 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-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-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= @@ -761,7 +561,6 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs 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-20200505023115-26f46d2f7ef8/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= @@ -773,17 +572,10 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -815,15 +607,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -855,7 +638,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG 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= @@ -868,27 +650,8 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D 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-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +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/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -903,21 +666,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji 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.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -930,8 +681,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= @@ -943,27 +692,20 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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/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.7/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= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= @@ -971,38 +713,24 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= -k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= -k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= -k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= -k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +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/autoscaler/vertical-pod-autoscaler v0.12.0 h1:cy8LoXSl5GkTNJDTx3ZCS143f9Ai7gqnGkoUxPlGSmI= k8s.io/autoscaler/vertical-pod-autoscaler v0.12.0/go.mod h1:LraL5kR2xX7jb4VMCG6/tUH4I75uRHlnzC0VWQHcyWk= -k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= -k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= -k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= -k8s.io/code-generator v0.25.0/go.mod h1:B6jZgI3DvDFAualltPitbYMQ74NjaCFxum3YeKZZ+3w= -k8s.io/code-generator v0.25.2/go.mod h1:f61OcU2VqVQcjt/6TrU0sta1TA5hHkOO6ZZPwkL9Eys= -k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= -k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= -k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= +k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +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/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= -k8s.io/metrics v0.25.0/go.mod h1:HZZrbhuRX+fsDcRc3u59o2FbrKhqD67IGnoFECNmovc= -k8s.io/sample-controller v0.25.2 h1:nNbg1HWuurEFNnPgQmup4zDza2FJ+TG6vxfPOnSMlEo= -k8s.io/sample-controller v0.25.2/go.mod h1:JYee8Sey/F0WrkhkUdB3vVMQYzzmMQTVRpcgg7Veddg= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +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/sample-controller v0.26.0 h1:cOEpHfipQYZRiQAZDyJrGrBZLIi5q3y2jKBCxU1GU8E= +k8s.io/sample-controller v0.26.0/go.mod h1:t8CYIAljtF67tvO1tzBzfXUmEbnHf1dupDG/orwCsjY= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= +k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= @@ -1012,6 +740,5 @@ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h6 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/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/internal/store/builder.go b/internal/store/builder.go index b8d3c47c4f..726fc63a95 100644 --- a/internal/store/builder.go +++ b/internal/store/builder.go @@ -27,11 +27,12 @@ import ( "github.com/prometheus/client_golang/prometheus" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" - autoscaling "k8s.io/api/autoscaling/v2beta2" + autoscaling "k8s.io/api/autoscaling/v2" batchv1 "k8s.io/api/batch/v1" certv1 "k8s.io/api/certificates/v1" coordinationv1 "k8s.io/api/coordination/v1" v1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" policyv1 "k8s.io/api/policy/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -58,11 +59,12 @@ var _ ksmtypes.BuilderInterface = &Builder{} // Builder helps to build store. It follows the builder pattern // (https://en.wikipedia.org/wiki/Builder_pattern). type Builder struct { - kubeClient clientset.Interface - customResourceClients map[string]interface{} - vpaClient vpaclientset.Interface - namespaces options.NamespaceList - namespaceFilter string + kubeClient clientset.Interface + customResourceClients map[string]interface{} + vpaClient vpaclientset.Interface + namespaces options.NamespaceList + // namespaceFilter is inside fieldSelectorFilter + fieldSelectorFilter string ctx context.Context enabledResources []string familyGeneratorFilter generator.FamilyGeneratorFilter @@ -106,10 +108,19 @@ func (b *Builder) WithEnabledResources(r []string) error { return nil } +// WithFieldSelectorFilter sets the fieldSelector property of a Builder. +func (b *Builder) WithFieldSelectorFilter(fieldSelectorFilter string) { + b.fieldSelectorFilter = fieldSelectorFilter +} + // WithNamespaces sets the namespaces property of a Builder. -func (b *Builder) WithNamespaces(n options.NamespaceList, nsFilter string) { +func (b *Builder) WithNamespaces(n options.NamespaceList) { b.namespaces = n - b.namespaceFilter = nsFilter +} + +// MergeFieldSelectors merges multiple fieldSelectors using AND operator. +func (b *Builder) MergeFieldSelectors(selectors []string) (string, error) { + return options.MergeFieldSelectors(selectors) } // WithSharding sets the shard and totalShards property of a Builder. @@ -200,21 +211,35 @@ func (b *Builder) WithAllowAnnotations(annotations map[string][]string) { } // WithAllowLabels configures which labels can be returned for metrics -func (b *Builder) WithAllowLabels(labels map[string][]string) { +func (b *Builder) WithAllowLabels(labels map[string][]string) error { if len(labels) > 0 { + for label := range labels { + if !resourceExists(label) && label != "*" { + return fmt.Errorf("resource %s does not exist. Available resources: %s", label, strings.Join(availableResources(), ",")) + } + } b.allowLabelsList = labels + // "*" takes precedence over other specifications + if allowedLabels, ok := labels["*"]; ok { + m := make(map[string][]string) + for _, resource := range b.enabledResources { + m[resource] = allowedLabels + } + b.allowLabelsList = m + } } + return nil } // Build initializes and registers all enabled stores. // It returns metrics writers which can be used to write out // metrics from the stores. -func (b *Builder) Build() []metricsstore.MetricsWriter { +func (b *Builder) Build() metricsstore.MetricsWriterList { if b.familyGeneratorFilter == nil { panic("familyGeneratorFilter should not be nil") } - var metricsWriters []metricsstore.MetricsWriter + var metricsWriters metricsstore.MetricsWriterList var activeStoreNames []string for _, c := range b.enabledResources { @@ -222,11 +247,7 @@ func (b *Builder) Build() []metricsstore.MetricsWriter { if ok { stores := cacheStoresToMetricStores(constructor(b)) activeStoreNames = append(activeStoreNames, c) - if len(stores) == 1 { - metricsWriters = append(metricsWriters, stores[0]) - } else { - metricsWriters = append(metricsWriters, metricsstore.NewMultiStoreMetricsWriter(stores)) - } + metricsWriters = append(metricsWriters, metricsstore.NewMetricsWriter(stores...)) } } @@ -269,8 +290,10 @@ var availableStores = map[string]func(f *Builder) []cache.Store{ "daemonsets": func(b *Builder) []cache.Store { return b.buildDaemonSetStores() }, "deployments": func(b *Builder) []cache.Store { return b.buildDeploymentStores() }, "endpoints": func(b *Builder) []cache.Store { return b.buildEndpointsStores() }, + "endpointslices": func(b *Builder) []cache.Store { return b.buildEndpointSlicesStores() }, "horizontalpodautoscalers": func(b *Builder) []cache.Store { return b.buildHPAStores() }, "ingresses": func(b *Builder) []cache.Store { return b.buildIngressStores() }, + "ingressclasses": func(b *Builder) []cache.Store { return b.buildIngressClassStores() }, "jobs": func(b *Builder) []cache.Store { return b.buildJobStores() }, "leases": func(b *Builder) []cache.Store { return b.buildLeasesStores() }, "limitranges": func(b *Builder) []cache.Store { return b.buildLimitRangeStores() }, @@ -330,6 +353,10 @@ func (b *Builder) buildEndpointsStores() []cache.Store { return b.buildStoresFunc(endpointMetricFamilies(b.allowAnnotationsList["endpoints"], b.allowLabelsList["endpoints"]), &v1.Endpoints{}, createEndpointsListWatch, b.useAPIServerCache) } +func (b *Builder) buildEndpointSlicesStores() []cache.Store { + return b.buildStoresFunc(endpointSliceMetricFamilies(b.allowAnnotationsList["endpointslices"], b.allowLabelsList["endpointslices"]), &discoveryv1.EndpointSlice{}, createEndpointSliceListWatch, b.useAPIServerCache) +} + func (b *Builder) buildHPAStores() []cache.Store { return b.buildStoresFunc(hpaMetricFamilies(b.allowAnnotationsList["horizontalpodautoscalers"], b.allowLabelsList["horizontalpodautoscalers"]), &autoscaling.HorizontalPodAutoscaler{}, createHPAListWatch, b.useAPIServerCache) } @@ -446,6 +473,10 @@ func (b *Builder) buildRoleBindingStores() []cache.Store { return b.buildStoresFunc(roleBindingMetricFamilies(b.allowAnnotationsList["rolebindings"], b.allowLabelsList["rolebindings"]), &rbacv1.RoleBinding{}, createRoleBindingListWatch, b.useAPIServerCache) } +func (b *Builder) buildIngressClassStores() []cache.Store { + return b.buildStoresFunc(ingressClassMetricFamilies(b.allowAnnotationsList["ingressclasses"], b.allowLabelsList["ingressclasses"]), &networkingv1.IngressClass{}, createIngressClassListWatch, b.useAPIServerCache) +} + func (b *Builder) buildStores( metricFamilies []generator.FamilyGenerator, expectedType interface{}, @@ -461,7 +492,10 @@ func (b *Builder) buildStores( familyHeaders, composedMetricGenFuncs, ) - listWatcher := listWatchFunc(b.kubeClient, v1.NamespaceAll, b.namespaceFilter) + if b.fieldSelectorFilter != "" { + klog.Infof("FieldSelector is used %s", b.fieldSelectorFilter) + } + listWatcher := listWatchFunc(b.kubeClient, v1.NamespaceAll, b.fieldSelectorFilter) b.startReflector(expectedType, store, listWatcher, useAPIServerCache) return []cache.Store{store} } @@ -472,7 +506,10 @@ func (b *Builder) buildStores( familyHeaders, composedMetricGenFuncs, ) - listWatcher := listWatchFunc(b.kubeClient, ns, b.namespaceFilter) + if b.fieldSelectorFilter != "" { + klog.Infof("FieldSelector is used %s", b.fieldSelectorFilter) + } + listWatcher := listWatchFunc(b.kubeClient, ns, b.fieldSelectorFilter) b.startReflector(expectedType, store, listWatcher, useAPIServerCache) stores = append(stores, store) } @@ -502,7 +539,10 @@ func (b *Builder) buildCustomResourceStores(resourceName string, familyHeaders, composedMetricGenFuncs, ) - listWatcher := listWatchFunc(customResourceClient, v1.NamespaceAll, b.namespaceFilter) + if b.fieldSelectorFilter != "" { + klog.Infof("FieldSelector is used %s", b.fieldSelectorFilter) + } + listWatcher := listWatchFunc(customResourceClient, v1.NamespaceAll, b.fieldSelectorFilter) b.startReflector(expectedType, store, listWatcher, useAPIServerCache) return []cache.Store{store} } @@ -513,7 +553,8 @@ func (b *Builder) buildCustomResourceStores(resourceName string, familyHeaders, composedMetricGenFuncs, ) - listWatcher := listWatchFunc(customResourceClient, ns, b.namespaceFilter) + klog.Infof("FieldSelector is used %s", b.fieldSelectorFilter) + listWatcher := listWatchFunc(customResourceClient, ns, b.fieldSelectorFilter) b.startReflector(expectedType, store, listWatcher, useAPIServerCache) stores = append(stores, store) } diff --git a/internal/store/builder_test.go b/internal/store/builder_test.go new file mode 100644 index 0000000000..758781d6f7 --- /dev/null +++ b/internal/store/builder_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 store + +import ( + "reflect" + "testing" + + "k8s.io/kube-state-metrics/v2/pkg/options" +) + +type LabelsAllowList options.LabelsAllowList + +type expectedError struct { + expectedResourceError bool + expectedLabelError bool + expectedNotEqual bool +} + +func TestWithAllowLabels(t *testing.T) { + tests := []struct { + Desc string + LabelsAllowlist map[string][]string + EnabledResources []string + Wanted LabelsAllowList + err expectedError + }{ + { + Desc: "wildcard key-value as the only element", + LabelsAllowlist: map[string][]string{"*": {"*"}}, + EnabledResources: []string{"cronjobs", "pods", "deployments"}, + Wanted: LabelsAllowList(map[string][]string{ + "deployments": {"*"}, + "pods": {"*"}, + "cronjobs": {"*"}, + }), + }, + { + Desc: "wildcard key-value as not the only element", + LabelsAllowlist: map[string][]string{"*": {"*"}, "pods": {"*"}, "cronjobs": {"*"}}, + EnabledResources: []string{"cronjobs", "pods", "deployments"}, + Wanted: LabelsAllowList(map[string][]string{ + "deployments": {"*"}, + "pods": {"*"}, + "cronjobs": {"*"}, + }), + }, + { + Desc: "wildcard key-value as not the only element, with resource mismatch", + LabelsAllowlist: map[string][]string{"*": {"*"}, "pods": {"*"}, "cronjobs": {"*"}, "configmaps": {"*"}}, + EnabledResources: []string{"cronjobs", "pods", "deployments"}, + Wanted: LabelsAllowList{}, + err: expectedError{ + expectedNotEqual: true, + }, + }, + { + Desc: "wildcard key-value as not the only element, with other mutually-exclusive keys", + LabelsAllowlist: map[string][]string{"*": {"*"}, "foo": {"*"}, "bar": {"*"}, "cronjobs": {"*"}}, + EnabledResources: []string{"cronjobs", "pods", "deployments"}, + Wanted: LabelsAllowList(nil), + err: expectedError{ + expectedLabelError: true, + }, + }, + { + Desc: "wildcard key-value as not the only element, with other resources that do not exist", + LabelsAllowlist: map[string][]string{"*": {"*"}, "cronjobs": {"*"}}, + EnabledResources: []string{"cronjobs", "pods", "deployments", "foo", "bar"}, + Wanted: LabelsAllowList{}, + err: expectedError{ + expectedResourceError: true, + }, + }, + } + + for _, test := range tests { + b := NewBuilder() + + // Set the enabled resources. + err := b.WithEnabledResources(test.EnabledResources) + if err != nil && !test.err.expectedResourceError { + t.Log("Did not expect error while setting resources (--resources).") + t.Errorf("Test error for Desc: %s. Got Error: %v", test.Desc, err) + } + + // Resolve the allow list. + err = b.WithAllowLabels(test.LabelsAllowlist) + if err != nil && !test.err.expectedLabelError { + t.Log("Did not expect error while parsing allow list labels (--metric-labels-allowlist).") + t.Errorf("Test error for Desc: %s. Got Error: %v", test.Desc, err) + } + resolvedAllowLabels := LabelsAllowList(b.allowLabelsList) + + // Evaluate. + if !reflect.DeepEqual(resolvedAllowLabels, test.Wanted) && !test.err.expectedNotEqual { + t.Log("Expected maps to be equal.") + t.Errorf("Test error for Desc: %s\n Want: \n%+v\n Got: \n%#+v", test.Desc, test.Wanted, resolvedAllowLabels) + } + } +} diff --git a/internal/store/endpointslice.go b/internal/store/endpointslice.go new file mode 100644 index 0000000000..0b01284468 --- /dev/null +++ b/internal/store/endpointslice.go @@ -0,0 +1,234 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. +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 store + +import ( + "context" + "strconv" + + basemetrics "k8s.io/component-base/metrics" + + "k8s.io/kube-state-metrics/v2/pkg/metric" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" + + discoveryv1 "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +var ( + descEndpointSliceAnnotationsName = "kube_endpointslice_annotations" + descEndpointSliceAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." + descEndpointSliceLabelsName = "kube_endpointslice_labels" + descEndpointSliceLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descEndpointSliceLabelsDefaultLabels = []string{"endpointslice"} +) + +func endpointSliceMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { + return []generator.FamilyGenerator{ + *generator.NewFamilyGeneratorWithStability( + "kube_endpointslice_info", + "Information about endpointslice.", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family { + + m := metric.Metric{ + LabelKeys: []string{"addresstype"}, + LabelValues: []string{string(s.AddressType)}, + Value: 1, + } + return &metric.Family{Metrics: []*metric.Metric{&m}} + }), + ), + *generator.NewFamilyGeneratorWithStability( + "kube_endpointslice_created", + "Unix creation timestamp", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family { + ms := []*metric.Metric{} + if !s.CreationTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + Value: float64(s.CreationTimestamp.Unix()), + }) + } + return &metric.Family{ + Metrics: ms, + } + }), + ), + *generator.NewFamilyGeneratorWithStability( + "kube_endpointslice_endpoints", + "Endpoints attached to the endpointslice.", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapEndpointSliceFunc(func(e *discoveryv1.EndpointSlice) *metric.Family { + m := []*metric.Metric{} + for _, ep := range e.Endpoints { + var ( + labelKeys, + labelValues []string + ) + + if ep.Conditions.Ready != nil { + labelKeys = append(labelKeys, "ready") + labelValues = append(labelValues, strconv.FormatBool(*ep.Conditions.Ready)) + } + if ep.Conditions.Serving != nil { + labelKeys = append(labelKeys, "serving") + labelValues = append(labelValues, strconv.FormatBool(*ep.Conditions.Serving)) + } + if ep.Conditions.Terminating != nil { + labelKeys = append(labelKeys, "terminating") + labelValues = append(labelValues, strconv.FormatBool(*ep.Conditions.Terminating)) + } + + if ep.Hostname != nil { + labelKeys = append(labelKeys, "hostname") + labelValues = append(labelValues, *ep.Hostname) + } + + if ep.TargetRef != nil { + if ep.TargetRef.Kind != "" { + labelKeys = append(labelKeys, "targetref_kind") + labelValues = append(labelValues, ep.TargetRef.Kind) + } + if ep.TargetRef.Name != "" { + labelKeys = append(labelKeys, "targetref_name") + labelValues = append(labelValues, ep.TargetRef.Name) + } + if ep.TargetRef.Namespace != "" { + labelKeys = append(labelKeys, "targetref_namespace") + labelValues = append(labelValues, ep.TargetRef.Namespace) + } + } + + if ep.NodeName != nil { + labelKeys = append(labelKeys, "endpoint_nodename") + labelValues = append(labelValues, *ep.NodeName) + } + + if ep.Zone != nil { + labelKeys = append(labelKeys, "endpoint_zone") + labelValues = append(labelValues, *ep.Zone) + } + labelKeys = append(labelKeys, "address") + for _, address := range ep.Addresses { + newlabelValues := make([]string, len(labelValues)) + copy(newlabelValues, labelValues) + m = append(m, &metric.Metric{ + LabelKeys: labelKeys, + LabelValues: append(newlabelValues, address), + Value: 1, + }) + } + } + return &metric.Family{ + Metrics: m, + } + }), + ), + + *generator.NewFamilyGeneratorWithStability( + "kube_endpointslice_ports", + "Ports attached to the endpointslice.", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapEndpointSliceFunc(func(e *discoveryv1.EndpointSlice) *metric.Family { + m := []*metric.Metric{} + for _, port := range e.Ports { + m = append(m, &metric.Metric{ + LabelValues: []string{*port.Name, string(*port.Protocol), strconv.FormatInt(int64(*port.Port), 10)}, + LabelKeys: []string{"port_name", "port_protocol", "port_number"}, + Value: 1, + }) + } + return &metric.Family{ + Metrics: m, + } + }), + ), + *generator.NewFamilyGeneratorWithStability( + descEndpointSliceAnnotationsName, + descEndpointSliceAnnotationsHelp, + metric.Gauge, + basemetrics.ALPHA, + "", + wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family { + annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", s.Annotations, allowAnnotationsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: annotationKeys, + LabelValues: annotationValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGeneratorWithStability( + descEndpointSliceLabelsName, + descEndpointSliceLabelsHelp, + metric.Gauge, + basemetrics.ALPHA, + "", + wrapEndpointSliceFunc(func(s *discoveryv1.EndpointSlice) *metric.Family { + labelKeys, labelValues := createPrometheusLabelKeysValues("label", s.Labels, allowLabelsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }, + }, + } + }), + ), + } +} + +func wrapEndpointSliceFunc(f func(*discoveryv1.EndpointSlice) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + endpointSlice := obj.(*discoveryv1.EndpointSlice) + + metricFamily := f(endpointSlice) + + for _, m := range metricFamily.Metrics { + m.LabelKeys, m.LabelValues = mergeKeyValues(descEndpointSliceLabelsDefaultLabels, []string{endpointSlice.Name}, m.LabelKeys, m.LabelValues) + } + + return metricFamily + } +} + +func createEndpointSliceListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher { + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + return kubeClient.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + return kubeClient.DiscoveryV1().EndpointSlices(ns).Watch(context.TODO(), opts) + }, + } +} diff --git a/internal/store/endpointslice_test.go b/internal/store/endpointslice_test.go new file mode 100644 index 0000000000..cca8638cd9 --- /dev/null +++ b/internal/store/endpointslice_test.go @@ -0,0 +1,161 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. +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 store + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +func TestEndpointSliceStore(t *testing.T) { + startTime := 1501569018 + metav1StartTime := metav1.Unix(int64(startTime), 0) + portname := "http" + portnumber := int32(80) + portprotocol := corev1.Protocol("TCP") + nodename := "node" + hostname := "host" + zone := "west" + ready := true + terminating := false + addresses := []string{"10.0.0.1", "192.168.1.10"} + + cases := []generateMetricsTestCase{ + { + Obj: &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_endpointslice-info", + }, + AddressType: "IPv4", + }, + Want: ` + # HELP kube_endpointslice_info Information about endpointslice. + # TYPE kube_endpointslice_info gauge + kube_endpointslice_info{endpointslice="test_endpointslice-info",addresstype="IPv4"} 1 + `, + MetricNames: []string{ + "kube_endpointslice_info", + }, + }, + { + Obj: &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_kube_endpointslice-created", + CreationTimestamp: metav1StartTime, + }, + AddressType: "IPv4", + }, + Want: ` + # HELP kube_endpointslice_created Unix creation timestamp + # TYPE kube_endpointslice_created gauge + kube_endpointslice_created{endpointslice="test_kube_endpointslice-created"} 1.501569018e+09 + `, + MetricNames: []string{ + "kube_endpointslice_created", + }, + }, + { + Obj: &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_endpointslice-ports", + }, + AddressType: "IPv4", + Ports: []discoveryv1.EndpointPort{ + {Name: &portname, + Port: &portnumber, + Protocol: &portprotocol, + }, + }, + }, + Want: ` + # HELP kube_endpointslice_ports Ports attached to the endpointslice. + # TYPE kube_endpointslice_ports gauge + kube_endpointslice_ports{endpointslice="test_endpointslice-ports",port_name="http",port_protocol="TCP",port_number="80"} 1 + `, + MetricNames: []string{ + "kube_endpointslice_ports", + }, + }, + { + Obj: &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_endpointslice-endpoints", + }, + AddressType: "IPv4", + Endpoints: []discoveryv1.Endpoint{ + { + NodeName: &nodename, + Conditions: discoveryv1.EndpointConditions{ + Ready: &ready, + Terminating: &terminating, + }, + Hostname: &hostname, + Zone: &zone, + Addresses: addresses, + }, + }, + }, + Want: ` + # HELP kube_endpointslice_endpoints Endpoints attached to the endpointslice. + # TYPE kube_endpointslice_endpoints gauge + kube_endpointslice_endpoints{address="10.0.0.1",endpoint_nodename="node",endpoint_zone="west",endpointslice="test_endpointslice-endpoints",hostname="host",ready="true",terminating="false"} 1 + kube_endpointslice_endpoints{address="192.168.1.10",endpoint_nodename="node",endpoint_zone="west",endpointslice="test_endpointslice-endpoints",hostname="host",ready="true",terminating="false"} 1 + `, + + MetricNames: []string{ + "kube_endpointslice_endpoints", + }, + }, + { + AllowAnnotationsList: []string{ + "foo", + }, + Obj: &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_endpointslice-labels", + Annotations: map[string]string{ + "foo": "baz", + }, + Labels: map[string]string{ + "foo": "bar", + }, + }, + AddressType: "IPv4", + }, + Want: ` + # HELP kube_endpointslice_annotations Kubernetes annotations converted to Prometheus labels. + # HELP kube_endpointslice_labels Kubernetes labels converted to Prometheus labels. + # TYPE kube_endpointslice_annotations gauge + # TYPE kube_endpointslice_labels gauge + kube_endpointslice_annotations{endpointslice="test_endpointslice-labels",annotation_foo="baz"} 1 + kube_endpointslice_labels{endpointslice="test_endpointslice-labels"} 1 + `, + MetricNames: []string{ + "kube_endpointslice_annotations", "kube_endpointslice_labels", + }, + }, + } + for i, c := range cases { + c.Func = generator.ComposeMetricGenFuncs(endpointSliceMetricFamilies(c.AllowAnnotationsList, nil)) + c.Headers = generator.ExtractMetricFamilyHeaders(endpointSliceMetricFamilies(c.AllowAnnotationsList, nil)) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/internal/store/horizontalpodautoscaler.go b/internal/store/horizontalpodautoscaler.go index 05176811ea..57300f1641 100644 --- a/internal/store/horizontalpodautoscaler.go +++ b/internal/store/horizontalpodautoscaler.go @@ -19,7 +19,7 @@ package store import ( "context" - autoscaling "k8s.io/api/autoscaling/v2beta2" + autoscaling "k8s.io/api/autoscaling/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" @@ -135,48 +135,41 @@ func hpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat ms := make([]*metric.Metric, 0, len(a.Spec.Metrics)) for _, m := range a.Spec.Metrics { var metricName string - + var metricTarget autoscaling.MetricTarget // The variable maps the type of metric to the corresponding value metricMap := make(map[metricTargetType]float64) switch m.Type { case autoscaling.ObjectMetricSourceType: metricName = m.Object.Metric.Name - - if m.Object.Target.Value != nil { - metricMap[value] = float64(m.Object.Target.Value.MilliValue()) / 1000 - } - if m.Object.Target.AverageValue != nil { - metricMap[average] = float64(m.Object.Target.AverageValue.MilliValue()) / 1000 - } + metricTarget = m.Object.Target case autoscaling.PodsMetricSourceType: metricName = m.Pods.Metric.Name - - metricMap[average] = float64(m.Pods.Target.AverageValue.MilliValue()) / 1000 + metricTarget = m.Pods.Target case autoscaling.ResourceMetricSourceType: metricName = string(m.Resource.Name) - - if m.Resource.Target.AverageUtilization != nil { - metricMap[utilization] = float64(*m.Resource.Target.AverageUtilization) - } - - if m.Resource.Target.AverageValue != nil { - metricMap[average] = float64(m.Resource.Target.AverageValue.MilliValue()) / 1000 - } + metricTarget = m.Resource.Target + case autoscaling.ContainerResourceMetricSourceType: + metricName = string(m.ContainerResource.Name) + metricTarget = m.ContainerResource.Target case autoscaling.ExternalMetricSourceType: metricName = m.External.Metric.Name - - if m.External.Target.Value != nil { - metricMap[value] = float64(m.External.Target.Value.MilliValue()) / 1000 - } - if m.External.Target.AverageValue != nil { - metricMap[average] = float64(m.External.Target.AverageValue.MilliValue()) / 1000 - } + metricTarget = m.External.Target default: // Skip unsupported metric type continue } + if metricTarget.Value != nil { + metricMap[value] = float64(metricTarget.Value.MilliValue()) / 1000 + } + if metricTarget.AverageValue != nil { + metricMap[average] = float64(metricTarget.AverageValue.MilliValue()) / 1000 + } + if metricTarget.AverageUtilization != nil { + metricMap[utilization] = float64(*metricTarget.AverageUtilization) + } + for metricTypeIndex, metricValue := range metricMap { ms = append(ms, &metric.Metric{ LabelKeys: targetMetricLabels, @@ -197,48 +190,41 @@ func hpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat ms := make([]*metric.Metric, 0, len(a.Status.CurrentMetrics)) for _, m := range a.Status.CurrentMetrics { var metricName string - + var currentMetric autoscaling.MetricValueStatus // The variable maps the type of metric to the corresponding value metricMap := make(map[metricTargetType]float64) switch m.Type { case autoscaling.ObjectMetricSourceType: metricName = m.Object.Metric.Name - - if m.Object.Current.Value != nil { - metricMap[value] = float64(m.Object.Current.Value.MilliValue()) / 1000 - } - if m.Object.Current.AverageValue != nil { - metricMap[average] = float64(m.Object.Current.AverageValue.MilliValue()) / 1000 - } + currentMetric = m.Object.Current case autoscaling.PodsMetricSourceType: metricName = m.Pods.Metric.Name - - metricMap[average] = float64(m.Pods.Current.AverageValue.MilliValue()) / 1000 + currentMetric = m.Pods.Current case autoscaling.ResourceMetricSourceType: metricName = string(m.Resource.Name) - - if m.Resource.Current.AverageUtilization != nil { - metricMap[utilization] = float64(*m.Resource.Current.AverageUtilization) - } - - if m.Resource.Current.AverageValue != nil { - metricMap[average] = float64(m.Resource.Current.AverageValue.MilliValue()) / 1000 - } + currentMetric = m.Resource.Current + case autoscaling.ContainerResourceMetricSourceType: + metricName = string(m.ContainerResource.Name) + currentMetric = m.ContainerResource.Current case autoscaling.ExternalMetricSourceType: metricName = m.External.Metric.Name - - if m.External.Current.Value != nil { - metricMap[value] = float64(m.External.Current.Value.MilliValue()) / 1000 - } - if m.External.Current.AverageValue != nil { - metricMap[average] = float64(m.External.Current.AverageValue.MilliValue()) / 1000 - } + currentMetric = m.External.Current default: // Skip unsupported metric type continue } + if currentMetric.Value != nil { + metricMap[value] = float64(currentMetric.Value.MilliValue()) / 1000 + } + if currentMetric.AverageValue != nil { + metricMap[average] = float64(currentMetric.AverageValue.MilliValue()) / 1000 + } + if currentMetric.AverageUtilization != nil { + metricMap[utilization] = float64(*currentMetric.AverageUtilization) + } + for metricTypeIndex, metricValue := range metricMap { ms = append(ms, &metric.Metric{ LabelKeys: targetMetricLabels, @@ -365,11 +351,11 @@ func createHPAListWatch(kubeClient clientset.Interface, ns string, fieldSelector return &cache.ListWatch{ ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { opts.FieldSelector = fieldSelector - return kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(context.TODO(), opts) + return kubeClient.AutoscalingV2().HorizontalPodAutoscalers(ns).List(context.TODO(), opts) }, WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { opts.FieldSelector = fieldSelector - return kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Watch(context.TODO(), opts) + return kubeClient.AutoscalingV2().HorizontalPodAutoscalers(ns).Watch(context.TODO(), opts) }, } } diff --git a/internal/store/horizontalpodautoscaler_test.go b/internal/store/horizontalpodautoscaler_test.go index 050b68fc9a..85c7f51b2b 100644 --- a/internal/store/horizontalpodautoscaler_test.go +++ b/internal/store/horizontalpodautoscaler_test.go @@ -19,7 +19,7 @@ package store import ( "testing" - autoscaling "k8s.io/api/autoscaling/v2beta2" + autoscaling "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -128,6 +128,16 @@ func TestHPAStore(t *testing.T) { }, }, }, + { + Type: autoscaling.ContainerResourceMetricSourceType, + ContainerResource: &autoscaling.ContainerResourceMetricSource{ + Name: "cpu", + Container: "container1", + Target: autoscaling.MetricTarget{ + AverageUtilization: int32ptr(80), + }, + }, + }, // No targets, this metric should be ignored { Type: autoscaling.ResourceMetricSourceType, @@ -211,6 +221,7 @@ func TestHPAStore(t *testing.T) { kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="hits",metric_target_type="value",namespace="ns1"} 10 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="connections",metric_target_type="average",namespace="ns1"} 0.7 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="connections",metric_target_type="value",namespace="ns1"} 0.5 + kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 80 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="memory",metric_target_type="average",namespace="ns1"} 819200 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="memory",metric_target_type="utilization",namespace="ns1"} 80 kube_horizontalpodautoscaler_spec_target_metric{horizontalpodautoscaler="hpa1",metric_name="sqs_jobs",metric_target_type="value",namespace="ns1"} 30 @@ -338,6 +349,17 @@ func TestHPAStore(t *testing.T) { }, }, }, + { + Type: "ContainerResource", + ContainerResource: &autoscaling.ContainerResourceMetricStatus{ + Name: "cpu", + Container: "container1", + Current: autoscaling.MetricValueStatus{ + AverageValue: resourcePtr(resource.MustParse("80m")), + AverageUtilization: int32ptr(10), + }, + }, + }, { Type: "External", External: &autoscaling.ExternalMetricStatus{ @@ -379,6 +401,8 @@ func TestHPAStore(t *testing.T) { kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="memory",metric_target_type="utilization",namespace="ns1"} 28 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="average",namespace="ns1"} 0.062 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 6 + kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="average",namespace="ns1"} 0.08 + kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="cpu",metric_target_type="utilization",namespace="ns1"} 10 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="value",namespace="ns1"} 0 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_requests_per_second",metric_target_type="average",namespace="ns1"} 2.9 kube_horizontalpodautoscaler_status_target_metric{horizontalpodautoscaler="hpa2",metric_name="traefik_backend_errors_per_second",metric_target_type="value",namespace="ns1"} 0 diff --git a/internal/store/ingressclass.go b/internal/store/ingressclass.go new file mode 100644 index 0000000000..3e67881ef0 --- /dev/null +++ b/internal/store/ingressclass.go @@ -0,0 +1,140 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. +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 store + +import ( + "context" + + basemetrics "k8s.io/component-base/metrics" + + "k8s.io/kube-state-metrics/v2/pkg/metric" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +var ( + descIngressClassAnnotationsName = "kube_ingressclass_annotations" + descIngressClassAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." + descIngressClassLabelsName = "kube_ingressclass_labels" + descIngressClassLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descIngressClassLabelsDefaultLabels = []string{"ingressclass"} +) + +func ingressClassMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { + return []generator.FamilyGenerator{ + *generator.NewFamilyGeneratorWithStability( + "kube_ingressclass_info", + "Information about ingressclass.", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + + m := metric.Metric{ + LabelKeys: []string{"controller"}, + LabelValues: []string{s.Spec.Controller}, + Value: 1, + } + return &metric.Family{Metrics: []*metric.Metric{&m}} + }), + ), + *generator.NewFamilyGeneratorWithStability( + "kube_ingressclass_created", + "Unix creation timestamp", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + ms := []*metric.Metric{} + if !s.CreationTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + Value: float64(s.CreationTimestamp.Unix()), + }) + } + return &metric.Family{ + Metrics: ms, + } + }), + ), + *generator.NewFamilyGeneratorWithStability( + descIngressClassAnnotationsName, + descIngressClassAnnotationsHelp, + metric.Gauge, + basemetrics.ALPHA, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", s.Annotations, allowAnnotationsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: annotationKeys, + LabelValues: annotationValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGeneratorWithStability( + descIngressClassLabelsName, + descIngressClassLabelsHelp, + metric.Gauge, + basemetrics.ALPHA, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + labelKeys, labelValues := createPrometheusLabelKeysValues("label", s.Labels, allowLabelsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }, + }, + } + }), + ), + } +} + +func wrapIngressClassFunc(f func(*networkingv1.IngressClass) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + ingressClass := obj.(*networkingv1.IngressClass) + + metricFamily := f(ingressClass) + + for _, m := range metricFamily.Metrics { + m.LabelKeys, m.LabelValues = mergeKeyValues(descIngressClassLabelsDefaultLabels, []string{ingressClass.Name}, m.LabelKeys, m.LabelValues) + } + + return metricFamily + } +} + +func createIngressClassListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher { + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + return kubeClient.NetworkingV1().IngressClasses().List(context.TODO(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + return kubeClient.NetworkingV1().IngressClasses().Watch(context.TODO(), opts) + }, + } +} diff --git a/internal/store/ingressclass_test.go b/internal/store/ingressclass_test.go new file mode 100644 index 0000000000..50312d0ca3 --- /dev/null +++ b/internal/store/ingressclass_test.go @@ -0,0 +1,105 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. +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 store + +import ( + "testing" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +func TestIngressClassStore(t *testing.T) { + startTime := 1501569018 + metav1StartTime := metav1.Unix(int64(startTime), 0) + + cases := []generateMetricsTestCase{ + { + Obj: &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_ingressclass-info", + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "controller", + }, + }, + Want: ` + # HELP kube_ingressclass_info Information about ingressclass. + # TYPE kube_ingressclass_info gauge + kube_ingressclass_info{ingressclass="test_ingressclass-info",controller="controller"} 1 + `, + MetricNames: []string{ + "kube_ingressclass_info", + }, + }, + { + Obj: &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_kube_ingressclass-created", + CreationTimestamp: metav1StartTime, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "controller", + }, + }, + Want: ` + # HELP kube_ingressclass_created Unix creation timestamp + # TYPE kube_ingressclass_created gauge + kube_ingressclass_created{ingressclass="test_kube_ingressclass-created"} 1.501569018e+09 + `, + MetricNames: []string{ + "kube_ingressclass_created", + }, + }, + { + AllowAnnotationsList: []string{ + "ingressclass.kubernetes.io/is-default-class", + }, + Obj: &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_ingressclass-labels", + Annotations: map[string]string{ + "ingressclass.kubernetes.io/is-default-class": "true", + }, + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "controller", + }, + }, + Want: ` + # HELP kube_ingressclass_annotations Kubernetes annotations converted to Prometheus labels. + # HELP kube_ingressclass_labels Kubernetes labels converted to Prometheus labels. + # TYPE kube_ingressclass_annotations gauge + # TYPE kube_ingressclass_labels gauge + kube_ingressclass_annotations{ingressclass="test_ingressclass-labels",annotation_ingressclass_kubernetes_io_is_default_class="true"} 1 + kube_ingressclass_labels{ingressclass="test_ingressclass-labels"} 1 + `, + MetricNames: []string{ + "kube_ingressclass_annotations", "kube_ingressclass_labels", + }, + }, + } + for i, c := range cases { + c.Func = generator.ComposeMetricGenFuncs(ingressClassMetricFamilies(c.AllowAnnotationsList, nil)) + c.Headers = generator.ExtractMetricFamilyHeaders(ingressClassMetricFamilies(c.AllowAnnotationsList, nil)) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/internal/store/job.go b/internal/store/job.go index cd878c7e08..fc7bd969c3 100644 --- a/internal/store/job.go +++ b/internal/store/job.go @@ -212,7 +212,8 @@ func jobMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat } } - for _, condition := range j.Status.Conditions { + for _, c := range j.Status.Conditions { + condition := c if condition.Type == v1batch.JobFailed { reasonKnown := false for _, reason := range jobFailureReasons { @@ -363,7 +364,7 @@ func jobMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat Metrics: []*metric.Metric{ { LabelKeys: labelKeys, - LabelValues: []string{"", "", ""}, + LabelValues: []string{"", "", ""}, Value: 1, }, }, diff --git a/internal/store/job_test.go b/internal/store/job_test.go index 3cb2b13174..2394c09091 100644 --- a/internal/store/job_test.go +++ b/internal/store/job_test.go @@ -154,7 +154,7 @@ func TestJobStore(t *testing.T) { }, Want: metadata + ` kube_job_annotations{job_name="SuccessfulJob1",namespace="ns1"} 1 - kube_job_owner{job_name="SuccessfulJob1",namespace="ns1",owner_is_controller="",owner_kind="",owner_name=""} 1 + kube_job_owner{job_name="SuccessfulJob1",namespace="ns1",owner_is_controller="",owner_kind="",owner_name=""} 1 kube_job_complete{condition="false",job_name="SuccessfulJob1",namespace="ns1"} 0 kube_job_complete{condition="true",job_name="SuccessfulJob1",namespace="ns1"} 1 kube_job_complete{condition="unknown",job_name="SuccessfulJob1",namespace="ns1"} 0 @@ -198,7 +198,7 @@ func TestJobStore(t *testing.T) { }, Want: metadata + ` kube_job_annotations{job_name="FailedJob1",namespace="ns1"} 1 - kube_job_owner{job_name="FailedJob1",namespace="ns1",owner_is_controller="",owner_kind="",owner_name=""} 1 + kube_job_owner{job_name="FailedJob1",namespace="ns1",owner_is_controller="",owner_kind="",owner_name=""} 1 kube_job_failed{condition="false",job_name="FailedJob1",namespace="ns1"} 0 kube_job_failed{condition="true",job_name="FailedJob1",namespace="ns1"} 1 kube_job_failed{condition="unknown",job_name="FailedJob1",namespace="ns1"} 0 @@ -243,7 +243,7 @@ func TestJobStore(t *testing.T) { }, }, Want: metadata + ` - kube_job_owner{job_name="SuccessfulJob2NoActiveDeadlineSeconds",namespace="ns1",owner_is_controller="",owner_kind="",owner_name=""} 1 + kube_job_owner{job_name="SuccessfulJob2NoActiveDeadlineSeconds",namespace="ns1",owner_is_controller="",owner_kind="",owner_name=""} 1 kube_job_complete{condition="false",job_name="SuccessfulJob2NoActiveDeadlineSeconds",namespace="ns1"} 0 kube_job_complete{condition="true",job_name="SuccessfulJob2NoActiveDeadlineSeconds",namespace="ns1"} 1 diff --git a/internal/store/lease.go b/internal/store/lease.go index af0cc3db09..5ac49c22ef 100644 --- a/internal/store/lease.go +++ b/internal/store/lease.go @@ -53,7 +53,7 @@ var ( Metrics: []*metric.Metric{ { LabelKeys: labelKeys, - LabelValues: []string{"", "", l.Namespace, holder}, + LabelValues: []string{"", "", l.Namespace, holder}, Value: 1, }, }, diff --git a/internal/store/node.go b/internal/store/node.go index 09ec39140c..51827c8765 100644 --- a/internal/store/node.go +++ b/internal/store/node.go @@ -44,9 +44,10 @@ var ( func nodeMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { return []generator.FamilyGenerator{ + createNodeAnnotationsGenerator(allowAnnotationsList), createNodeCreatedFamilyGenerator(), + createNodeDeletionTimestampFamilyGenerator(), createNodeInfoFamilyGenerator(), - createNodeAnnotationsGenerator(allowAnnotationsList), createNodeLabelsGenerator(allowLabelsList), createNodeRoleFamilyGenerator(), createNodeSpecTaintFamilyGenerator(), @@ -57,6 +58,29 @@ func nodeMetricFamilies(allowAnnotationsList, allowLabelsList []string) []genera } } +func createNodeDeletionTimestampFamilyGenerator() generator.FamilyGenerator { + return *generator.NewFamilyGeneratorWithStability( + "kube_node_deletion_timestamp", + "Unix deletion timestamp", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapNodeFunc(func(n *v1.Node) *metric.Family { + var ms []*metric.Metric + + if n.DeletionTimestamp != nil && !n.DeletionTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + Value: float64(n.DeletionTimestamp.Unix()), + }) + } + + return &metric.Family{ + Metrics: ms, + } + }), + ) +} + func createNodeCreatedFamilyGenerator() generator.FamilyGenerator { return *generator.NewFamilyGeneratorWithStability( "kube_node_created", diff --git a/internal/store/persistentvolumeclaim.go b/internal/store/persistentvolumeclaim.go index b048faa674..9c9179788e 100644 --- a/internal/store/persistentvolumeclaim.go +++ b/internal/store/persistentvolumeclaim.go @@ -270,6 +270,6 @@ func getPersistentVolumeClaimClass(claim *v1.PersistentVolumeClaim) string { return *claim.Spec.StorageClassName } - // Special non-empty string to indicate absence of storage class. - return "" + // An empty string indicates the absence of storage class. + return "" } diff --git a/internal/store/persistentvolumeclaim_test.go b/internal/store/persistentvolumeclaim_test.go index 4902dfe217..39c5c6d824 100644 --- a/internal/store/persistentvolumeclaim_test.go +++ b/internal/store/persistentvolumeclaim_test.go @@ -257,7 +257,7 @@ func TestPersistentVolumeClaimStore(t *testing.T) { # TYPE kube_persistentvolumeclaim_status_phase gauge # TYPE kube_persistentvolumeclaim_status_condition gauge kube_persistentvolumeclaim_created{namespace="",persistentvolumeclaim="mongo-data"} 1.5e+09 - kube_persistentvolumeclaim_info{namespace="",persistentvolumeclaim="mongo-data",storageclass="",volumename=""} 1 + kube_persistentvolumeclaim_info{namespace="",persistentvolumeclaim="mongo-data",storageclass="",volumename=""} 1 kube_persistentvolumeclaim_status_phase{namespace="",persistentvolumeclaim="mongo-data",phase="Bound"} 0 kube_persistentvolumeclaim_status_phase{namespace="",persistentvolumeclaim="mongo-data",phase="Lost"} 1 kube_persistentvolumeclaim_status_phase{namespace="",persistentvolumeclaim="mongo-data",phase="Pending"} 0 diff --git a/internal/store/pod.go b/internal/store/pod.go index 90bb11d3c0..8d6ca2db31 100644 --- a/internal/store/pod.go +++ b/internal/store/pod.go @@ -162,7 +162,7 @@ func createPodContainerInfoFamilyGenerator() generator.FamilyGenerator { func createPodContainerResourceLimitsFamilyGenerator() generator.FamilyGenerator { return *generator.NewFamilyGenerator( "kube_pod_container_resource_limits", - "The number of requested limit resource by a container.", + "The number of requested limit resource by a container. It is recommended to use the kube_pod_resource_limits metric exposed by kube-scheduler instead, as it is more precise.", metric.Gauge, "", wrapPodFunc(func(p *v1.Pod) *metric.Family { @@ -225,7 +225,7 @@ func createPodContainerResourceLimitsFamilyGenerator() generator.FamilyGenerator func createPodContainerResourceRequestsFamilyGenerator() generator.FamilyGenerator { return *generator.NewFamilyGenerator( "kube_pod_container_resource_requests", - "The number of requested request resource by a container.", + "The number of requested request resource by a container. It is recommended to use the kube_pod_resource_requests metric exposed by kube-scheduler instead, as it is more precise.", metric.Gauge, "", wrapPodFunc(func(p *v1.Pod) *metric.Family { @@ -599,8 +599,8 @@ func createPodInfoFamilyGenerator() generator.FamilyGenerator { "", wrapPodFunc(func(p *v1.Pod) *metric.Family { createdBy := metav1.GetControllerOf(p) - createdByKind := "" - createdByName := "" + createdByKind := "" + createdByName := "" if createdBy != nil { if createdBy.Kind != "" { createdByKind = createdBy.Kind @@ -1120,7 +1120,7 @@ func createPodOwnerFamilyGenerator() generator.FamilyGenerator { Metrics: []*metric.Metric{ { LabelKeys: labelKeys, - LabelValues: []string{"", "", ""}, + LabelValues: []string{"", "", ""}, Value: 1, }, }, diff --git a/internal/store/pod_test.go b/internal/store/pod_test.go index dadffc79b7..438d5bcb2d 100644 --- a/internal/store/pod_test.go +++ b/internal/store/pod_test.go @@ -1032,11 +1032,11 @@ func TestPodStore(t *testing.T) { # TYPE kube_pod_owner gauge # TYPE kube_pod_start_time gauge kube_pod_created{namespace="ns1",pod="pod1",uid="abc-123-xxx"} 1.5e+09 - kube_pod_info{created_by_kind="",created_by_name="",host_ip="1.1.1.1",namespace="ns1",node="node1",pod="pod1",pod_ip="1.2.3.4",uid="abc-123-xxx",priority_class="system-node-critical",host_network="true"} 1 + kube_pod_info{created_by_kind="",created_by_name="",host_ip="1.1.1.1",namespace="ns1",node="node1",pod="pod1",pod_ip="1.2.3.4",uid="abc-123-xxx",priority_class="system-node-critical",host_network="true"} 1 kube_pod_ips{namespace="ns1",pod="pod1",uid="abc-123-xxx",ip="1.2.3.4",ip_family="4"} 1 kube_pod_ips{namespace="ns1",pod="pod1",uid="abc-123-xxx",ip="fc00:1234:5678:90ab:cdef:cafe:f00d:d00d",ip_family="6"} 1 kube_pod_start_time{namespace="ns1",pod="pod1",uid="abc-123-xxx"} 1.501569018e+09 - kube_pod_owner{namespace="ns1",owner_is_controller="",owner_kind="",owner_name="",pod="pod1",uid="abc-123-xxx"} 1 + kube_pod_owner{namespace="ns1",owner_is_controller="",owner_kind="",owner_name="",pod="pod1",uid="abc-123-xxx"} 1 `, MetricNames: []string{"kube_pod_created", "kube_pod_info", "kube_pod_ips", "kube_pod_start_time", "kube_pod_completion_time", "kube_pod_owner"}, }, @@ -1625,8 +1625,8 @@ func TestPodStore(t *testing.T) { }, }, Want: ` - # HELP kube_pod_container_resource_limits The number of requested limit resource by a container. - # HELP kube_pod_container_resource_requests The number of requested request resource by a container. + # HELP kube_pod_container_resource_limits The number of requested limit resource by a container. It is recommended to use the kube_pod_resource_limits metric exposed by kube-scheduler instead, as it is more precise. + # HELP kube_pod_container_resource_requests The number of requested request resource by a container. It is recommended to use the kube_pod_resource_requests metric exposed by kube-scheduler instead, as it is more precise. # HELP kube_pod_init_container_resource_limits The number of requested limit resource by an init container. # HELP kube_pod_init_container_resource_requests The number of requested request resource by an init container. # HELP kube_pod_init_container_status_last_terminated_reason Describes the last reason the init container was in terminated state. diff --git a/internal/store/replicaset.go b/internal/store/replicaset.go index 7ea4cff730..21c9a73626 100644 --- a/internal/store/replicaset.go +++ b/internal/store/replicaset.go @@ -178,7 +178,7 @@ func replicaSetMetricFamilies(allowAnnotationsList, allowLabelsList []string) [] Metrics: []*metric.Metric{ { LabelKeys: []string{"owner_kind", "owner_name", "owner_is_controller"}, - LabelValues: []string{"", "", ""}, + LabelValues: []string{"", "", ""}, Value: 1, }, }, diff --git a/internal/store/replicaset_test.go b/internal/store/replicaset_test.go index f25d51e275..80d7fabd7f 100644 --- a/internal/store/replicaset_test.go +++ b/internal/store/replicaset_test.go @@ -129,7 +129,7 @@ func TestReplicaSetStore(t *testing.T) { kube_replicaset_status_fully_labeled_replicas{namespace="ns2",replicaset="rs2"} 5 kube_replicaset_status_ready_replicas{namespace="ns2",replicaset="rs2"} 0 kube_replicaset_spec_replicas{namespace="ns2",replicaset="rs2"} 0 - kube_replicaset_owner{namespace="ns2",owner_is_controller="",owner_kind="",owner_name="",replicaset="rs2"} 1 + kube_replicaset_owner{namespace="ns2",owner_is_controller="",owner_kind="",owner_name="",replicaset="rs2"} 1 `, }, } diff --git a/internal/store/replicationcontroller.go b/internal/store/replicationcontroller.go index f5da7f5bdb..ffc0cc1208 100644 --- a/internal/store/replicationcontroller.go +++ b/internal/store/replicationcontroller.go @@ -185,7 +185,7 @@ var ( if len(owners) == 0 { ms = append(ms, &metric.Metric{ LabelKeys: labelKeys, - LabelValues: []string{"", "", ""}, + LabelValues: []string{"", "", ""}, Value: 1, }) } else { diff --git a/internal/store/replicationcontroller_test.go b/internal/store/replicationcontroller_test.go index f125d294c3..aea8bb1bb5 100644 --- a/internal/store/replicationcontroller_test.go +++ b/internal/store/replicationcontroller_test.go @@ -116,7 +116,7 @@ func TestReplicationControllerStore(t *testing.T) { }, Want: metadata + ` kube_replicationcontroller_metadata_generation{namespace="ns2",replicationcontroller="rc2"} 14 - kube_replicationcontroller_owner{namespace="ns2",owner_is_controller="",owner_kind="",owner_name="",replicationcontroller="rc2"} 1 + kube_replicationcontroller_owner{namespace="ns2",owner_is_controller="",owner_kind="",owner_name="",replicationcontroller="rc2"} 1 kube_replicationcontroller_status_replicas{namespace="ns2",replicationcontroller="rc2"} 0 kube_replicationcontroller_status_observed_generation{namespace="ns2",replicationcontroller="rc2"} 5 kube_replicationcontroller_status_fully_labeled_replicas{namespace="ns2",replicationcontroller="rc2"} 5 diff --git a/internal/store/secret.go b/internal/store/secret.go index fee2a2c8a3..74bca4327f 100644 --- a/internal/store/secret.go +++ b/internal/store/secret.go @@ -33,9 +33,9 @@ import ( var ( descSecretAnnotationsName = "kube_secret_annotations" - descSecretAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." + descSecretAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." //nolint:gosec descSecretLabelsName = "kube_secret_labels" - descSecretLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descSecretLabelsHelp = "Kubernetes labels converted to Prometheus labels." //nolint:gosec descSecretLabelsDefaultLabels = []string{"namespace", "secret"} ) diff --git a/internal/store/statefulset.go b/internal/store/statefulset.go index af6020772e..5be7c1f97e 100644 --- a/internal/store/statefulset.go +++ b/internal/store/statefulset.go @@ -193,6 +193,32 @@ func statefulSetMetricFamilies(allowAnnotationsList, allowLabelsList []string) [ } }), ), + *generator.NewFamilyGeneratorWithStability( + "kube_statefulset_persistentvolumeclaim_retention_policy", + "Count of retention policy for StatefulSet template PVCs", + metric.Gauge, + basemetrics.ALPHA, + "", + wrapStatefulSetFunc(func(s *v1.StatefulSet) *metric.Family { + deletedPolicyLabel := "" + scaledPolicyLabel := "" + if policy := s.Spec.PersistentVolumeClaimRetentionPolicy; policy != nil { + deletedPolicyLabel = string(policy.WhenDeleted) + scaledPolicyLabel = string(policy.WhenScaled) + } + ms := []*metric.Metric{} + if deletedPolicyLabel != "" || scaledPolicyLabel != "" { + ms = append(ms, &metric.Metric{ + LabelKeys: []string{"when_deleted", "when_scaled"}, + LabelValues: []string{deletedPolicyLabel, scaledPolicyLabel}, + Value: 1, + }) + } + return &metric.Family{ + Metrics: ms, + } + }), + ), *generator.NewFamilyGenerator( descStatefulSetAnnotationsName, descStatefulSetAnnotationsHelp, @@ -268,6 +294,7 @@ func statefulSetMetricFamilies(allowAnnotationsList, allowLabelsList []string) [ ), } } + func wrapStatefulSetFunc(f func(*v1.StatefulSet) *metric.Family) func(interface{}) *metric.Family { return func(obj interface{}) *metric.Family { statefulSet := obj.(*v1.StatefulSet) diff --git a/internal/store/statefulset_test.go b/internal/store/statefulset_test.go index a6c63ee519..e48a96d961 100644 --- a/internal/store/statefulset_test.go +++ b/internal/store/statefulset_test.go @@ -63,6 +63,7 @@ func TestStatefulSetStore(t *testing.T) { # HELP kube_statefulset_created [STABLE] Unix creation timestamp # HELP kube_statefulset_labels [STABLE] Kubernetes labels converted to Prometheus labels. # HELP kube_statefulset_metadata_generation [STABLE] Sequence number representing a specific generation of the desired state for the StatefulSet. + # HELP kube_statefulset_persistentvolumeclaim_retention_policy Count of retention policy for StatefulSet template PVCs # HELP kube_statefulset_replicas [STABLE] Number of desired pods for a StatefulSet. # HELP kube_statefulset_status_current_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas). # HELP kube_statefulset_status_observed_generation [STABLE] The generation observed by the StatefulSet controller. @@ -75,6 +76,7 @@ func TestStatefulSetStore(t *testing.T) { # TYPE kube_statefulset_created gauge # TYPE kube_statefulset_labels gauge # TYPE kube_statefulset_metadata_generation gauge + # TYPE kube_statefulset_persistentvolumeclaim_retention_policy gauge # TYPE kube_statefulset_replicas gauge # TYPE kube_statefulset_status_current_revision gauge # TYPE kube_statefulset_status_observed_generation gauge @@ -110,6 +112,7 @@ func TestStatefulSetStore(t *testing.T) { "kube_statefulset_status_replicas_updated", "kube_statefulset_status_update_revision", "kube_statefulset_status_current_revision", + "kube_statefulset_persistentvolumeclaim_retention_policy", }, }, { @@ -140,6 +143,7 @@ func TestStatefulSetStore(t *testing.T) { Want: ` # HELP kube_statefulset_labels [STABLE] Kubernetes labels converted to Prometheus labels. # HELP kube_statefulset_metadata_generation [STABLE] Sequence number representing a specific generation of the desired state for the StatefulSet. + # HELP kube_statefulset_persistentvolumeclaim_retention_policy Count of retention policy for StatefulSet template PVCs # HELP kube_statefulset_replicas [STABLE] Number of desired pods for a StatefulSet. # HELP kube_statefulset_status_current_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas). # HELP kube_statefulset_status_observed_generation [STABLE] The generation observed by the StatefulSet controller. @@ -151,6 +155,7 @@ func TestStatefulSetStore(t *testing.T) { # HELP kube_statefulset_status_update_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas) # TYPE kube_statefulset_labels gauge # TYPE kube_statefulset_metadata_generation gauge + # TYPE kube_statefulset_persistentvolumeclaim_retention_policy gauge # TYPE kube_statefulset_replicas gauge # TYPE kube_statefulset_status_current_revision gauge # TYPE kube_statefulset_status_observed_generation gauge @@ -161,14 +166,14 @@ func TestStatefulSetStore(t *testing.T) { # TYPE kube_statefulset_status_replicas_updated gauge # TYPE kube_statefulset_status_update_revision gauge kube_statefulset_status_update_revision{namespace="ns2",revision="ur2",statefulset="statefulset2"} 1 - kube_statefulset_status_replicas{namespace="ns2",statefulset="statefulset2"} 5 + kube_statefulset_status_replicas{namespace="ns2",statefulset="statefulset2"} 5 kube_statefulset_status_replicas_available{namespace="ns2",statefulset="statefulset2"} 4 kube_statefulset_status_replicas_current{namespace="ns2",statefulset="statefulset2"} 2 kube_statefulset_status_replicas_ready{namespace="ns2",statefulset="statefulset2"} 5 kube_statefulset_status_replicas_updated{namespace="ns2",statefulset="statefulset2"} 3 - kube_statefulset_status_observed_generation{namespace="ns2",statefulset="statefulset2"} 2 - kube_statefulset_replicas{namespace="ns2",statefulset="statefulset2"} 6 - kube_statefulset_metadata_generation{namespace="ns2",statefulset="statefulset2"} 21 + kube_statefulset_status_observed_generation{namespace="ns2",statefulset="statefulset2"} 2 + kube_statefulset_replicas{namespace="ns2",statefulset="statefulset2"} 6 + kube_statefulset_metadata_generation{namespace="ns2",statefulset="statefulset2"} 21 kube_statefulset_labels{namespace="ns2",statefulset="statefulset2"} 1 kube_statefulset_status_current_revision{namespace="ns2",revision="cr2",statefulset="statefulset2"} 1 `, @@ -184,6 +189,7 @@ func TestStatefulSetStore(t *testing.T) { "kube_statefulset_status_replicas_updated", "kube_statefulset_status_update_revision", "kube_statefulset_status_current_revision", + "kube_statefulset_persistentvolumeclaim_retention_policy", }, }, { @@ -210,6 +216,7 @@ func TestStatefulSetStore(t *testing.T) { Want: ` # HELP kube_statefulset_labels [STABLE] Kubernetes labels converted to Prometheus labels. # HELP kube_statefulset_metadata_generation [STABLE] Sequence number representing a specific generation of the desired state for the StatefulSet. + # HELP kube_statefulset_persistentvolumeclaim_retention_policy Count of retention policy for StatefulSet template PVCs # HELP kube_statefulset_replicas [STABLE] Number of desired pods for a StatefulSet. # HELP kube_statefulset_status_current_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas). # HELP kube_statefulset_status_replicas [STABLE] The number of replicas per StatefulSet. @@ -220,6 +227,7 @@ func TestStatefulSetStore(t *testing.T) { # HELP kube_statefulset_status_update_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas) # TYPE kube_statefulset_labels gauge # TYPE kube_statefulset_metadata_generation gauge + # TYPE kube_statefulset_persistentvolumeclaim_retention_policy gauge # TYPE kube_statefulset_replicas gauge # TYPE kube_statefulset_status_current_revision gauge # TYPE kube_statefulset_status_replicas gauge @@ -229,13 +237,13 @@ func TestStatefulSetStore(t *testing.T) { # TYPE kube_statefulset_status_replicas_updated gauge # TYPE kube_statefulset_status_update_revision gauge kube_statefulset_status_update_revision{namespace="ns3",revision="ur3",statefulset="statefulset3"} 1 - kube_statefulset_status_replicas{namespace="ns3",statefulset="statefulset3"} 7 + kube_statefulset_status_replicas{namespace="ns3",statefulset="statefulset3"} 7 kube_statefulset_status_replicas_available{namespace="ns3",statefulset="statefulset3"} 0 kube_statefulset_status_replicas_current{namespace="ns3",statefulset="statefulset3"} 0 kube_statefulset_status_replicas_ready{namespace="ns3",statefulset="statefulset3"} 0 kube_statefulset_status_replicas_updated{namespace="ns3",statefulset="statefulset3"} 0 - kube_statefulset_replicas{namespace="ns3",statefulset="statefulset3"} 9 - kube_statefulset_metadata_generation{namespace="ns3",statefulset="statefulset3"} 36 + kube_statefulset_replicas{namespace="ns3",statefulset="statefulset3"} 9 + kube_statefulset_metadata_generation{namespace="ns3",statefulset="statefulset3"} 36 kube_statefulset_labels{namespace="ns3",statefulset="statefulset3"} 1 kube_statefulset_status_current_revision{namespace="ns3",revision="cr3",statefulset="statefulset3"} 1 `, @@ -250,6 +258,81 @@ func TestStatefulSetStore(t *testing.T) { "kube_statefulset_status_replicas_updated", "kube_statefulset_status_update_revision", "kube_statefulset_status_current_revision", + "kube_statefulset_persistentvolumeclaim_retention_policy", + }, + }, + { + Obj: &v1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "statefulset4", + Namespace: "ns4", + Labels: map[string]string{ + "app": "example4", + }, + Generation: 1, + }, + Spec: v1.StatefulSetSpec{ + Replicas: &statefulSet1Replicas, + ServiceName: "statefulset4service", + PersistentVolumeClaimRetentionPolicy: &v1.StatefulSetPersistentVolumeClaimRetentionPolicy{ + WhenDeleted: v1.RetainPersistentVolumeClaimRetentionPolicyType, + WhenScaled: v1.DeletePersistentVolumeClaimRetentionPolicyType, + }, + }, + Status: v1.StatefulSetStatus{ + ObservedGeneration: 0, + Replicas: 7, + UpdateRevision: "ur3", + CurrentRevision: "cr3", + }, + }, + Want: ` + # HELP kube_statefulset_labels [STABLE] Kubernetes labels converted to Prometheus labels. + # HELP kube_statefulset_metadata_generation [STABLE] Sequence number representing a specific generation of the desired state for the StatefulSet. + # HELP kube_statefulset_persistentvolumeclaim_retention_policy Count of retention policy for StatefulSet template PVCs + # HELP kube_statefulset_replicas [STABLE] Number of desired pods for a StatefulSet. + # HELP kube_statefulset_status_current_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [0,currentReplicas). + # HELP kube_statefulset_status_replicas [STABLE] The number of replicas per StatefulSet. + # HELP kube_statefulset_status_replicas_available The number of available replicas per StatefulSet. + # HELP kube_statefulset_status_replicas_current [STABLE] The number of current replicas per StatefulSet. + # HELP kube_statefulset_status_replicas_ready [STABLE] The number of ready replicas per StatefulSet. + # HELP kube_statefulset_status_replicas_updated [STABLE] The number of updated replicas per StatefulSet. + # HELP kube_statefulset_status_update_revision [STABLE] Indicates the version of the StatefulSet used to generate Pods in the sequence [replicas-updatedReplicas,replicas) + # TYPE kube_statefulset_labels gauge + # TYPE kube_statefulset_metadata_generation gauge + # TYPE kube_statefulset_persistentvolumeclaim_retention_policy gauge + # TYPE kube_statefulset_replicas gauge + # TYPE kube_statefulset_status_current_revision gauge + # TYPE kube_statefulset_status_replicas gauge + # TYPE kube_statefulset_status_replicas_available gauge + # TYPE kube_statefulset_status_replicas_current gauge + # TYPE kube_statefulset_status_replicas_ready gauge + # TYPE kube_statefulset_status_replicas_updated gauge + # TYPE kube_statefulset_status_update_revision gauge + kube_statefulset_status_update_revision{namespace="ns4",revision="ur3",statefulset="statefulset4"} 1 + kube_statefulset_status_replicas{namespace="ns4",statefulset="statefulset4"} 7 + kube_statefulset_status_replicas_available{namespace="ns4",statefulset="statefulset4"} 0 + kube_statefulset_status_replicas_current{namespace="ns4",statefulset="statefulset4"} 0 + kube_statefulset_status_replicas_ready{namespace="ns4",statefulset="statefulset4"} 0 + kube_statefulset_status_replicas_updated{namespace="ns4",statefulset="statefulset4"} 0 + kube_statefulset_replicas{namespace="ns4",statefulset="statefulset4"} 3 + kube_statefulset_metadata_generation{namespace="ns4",statefulset="statefulset4"} 1 + kube_statefulset_persistentvolumeclaim_retention_policy{namespace="ns4",statefulset="statefulset4",when_deleted="Retain",when_scaled="Delete"} 1 + kube_statefulset_labels{namespace="ns4",statefulset="statefulset4"} 1 + kube_statefulset_status_current_revision{namespace="ns4",revision="cr3",statefulset="statefulset4"} 1 + `, + MetricNames: []string{ + "kube_statefulset_labels", + "kube_statefulset_metadata_generation", + "kube_statefulset_replicas", + "kube_statefulset_status_replicas", + "kube_statefulset_status_replicas_available", + "kube_statefulset_status_replicas_current", + "kube_statefulset_status_replicas_ready", + "kube_statefulset_status_replicas_updated", + "kube_statefulset_status_update_revision", + "kube_statefulset_status_current_revision", + "kube_statefulset_persistentvolumeclaim_retention_policy", }, }, } @@ -257,7 +340,7 @@ func TestStatefulSetStore(t *testing.T) { c.Func = generator.ComposeMetricGenFuncs(statefulSetMetricFamilies(nil, nil)) c.Headers = generator.ExtractMetricFamilyHeaders(statefulSetMetricFamilies(nil, nil)) if err := c.run(); err != nil { - t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + t.Errorf("unexpected collecting result for statefulset%d run:\n%s", i+1, err) } } } diff --git a/internal/store/verticalpodautoscaler.go b/internal/store/verticalpodautoscaler.go index b3841faf08..ed0fe7f46e 100644 --- a/internal/store/verticalpodautoscaler.go +++ b/internal/store/verticalpodautoscaler.go @@ -48,7 +48,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat descVerticalPodAutoscalerAnnotationsName, descVerticalPodAutoscalerAnnotationsHelp, metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", a.Annotations, allowAnnotationsList) return &metric.Family{ @@ -66,7 +66,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat descVerticalPodAutoscalerLabelsName, descVerticalPodAutoscalerLabelsHelp, metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { labelKeys, labelValues := createPrometheusLabelKeysValues("label", a.Labels, allowLabelsList) return &metric.Family{ @@ -84,7 +84,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_spec_updatepolicy_updatemode", "Update mode of the VerticalPodAutoscaler.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} @@ -122,7 +122,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_minallowed", "Minimum resources the VerticalPodAutoscaler can set for containers matching the name.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} if a.Spec.ResourcePolicy == nil || a.Spec.ResourcePolicy.ContainerPolicies == nil { @@ -144,7 +144,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_maxallowed", "Maximum resources the VerticalPodAutoscaler can set for containers matching the name.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} if a.Spec.ResourcePolicy == nil || a.Spec.ResourcePolicy.ContainerPolicies == nil { @@ -165,7 +165,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound", "Minimum resources the container can use before the VerticalPodAutoscaler updater evicts it.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} if a.Status.Recommendation == nil || a.Status.Recommendation.ContainerRecommendations == nil { @@ -186,7 +186,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound", "Maximum resources the container can use before the VerticalPodAutoscaler updater evicts it.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} if a.Status.Recommendation == nil || a.Status.Recommendation.ContainerRecommendations == nil { @@ -207,7 +207,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target", "Target resources the VerticalPodAutoscaler recommends for the container.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} if a.Status.Recommendation == nil || a.Status.Recommendation.ContainerRecommendations == nil { @@ -227,7 +227,7 @@ func vpaMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat "kube_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget", "Target resources the VerticalPodAutoscaler recommends for the container ignoring bounds.", metric.Gauge, - "", + "v2.9.0", wrapVPAFunc(func(a *autoscaling.VerticalPodAutoscaler) *metric.Family { ms := []*metric.Metric{} if a.Status.Recommendation == nil || a.Status.Recommendation.ContainerRecommendations == nil { diff --git a/internal/store/verticalpodautoscaler_test.go b/internal/store/verticalpodautoscaler_test.go index 82aa7eb5ae..4c107b3c6c 100644 --- a/internal/store/verticalpodautoscaler_test.go +++ b/internal/store/verticalpodautoscaler_test.go @@ -30,14 +30,14 @@ import ( func TestVPAStore(t *testing.T) { const metadata = ` - # HELP kube_verticalpodautoscaler_labels Kubernetes labels converted to Prometheus labels. - # HELP kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_maxallowed Maximum resources the VerticalPodAutoscaler can set for containers matching the name. - # HELP kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_minallowed Minimum resources the VerticalPodAutoscaler can set for containers matching the name. - # HELP kube_verticalpodautoscaler_spec_updatepolicy_updatemode Update mode of the VerticalPodAutoscaler. - # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound Minimum resources the container can use before the VerticalPodAutoscaler updater evicts it. - # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target Target resources the VerticalPodAutoscaler recommends for the container. - # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget Target resources the VerticalPodAutoscaler recommends for the container ignoring bounds. - # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound Maximum resources the container can use before the VerticalPodAutoscaler updater evicts it. + # HELP kube_verticalpodautoscaler_labels (Deprecated since v2.9.0) Kubernetes labels converted to Prometheus labels. + # HELP kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_maxallowed (Deprecated since v2.9.0) Maximum resources the VerticalPodAutoscaler can set for containers matching the name. + # HELP kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_minallowed (Deprecated since v2.9.0) Minimum resources the VerticalPodAutoscaler can set for containers matching the name. + # HELP kube_verticalpodautoscaler_spec_updatepolicy_updatemode (Deprecated since v2.9.0) Update mode of the VerticalPodAutoscaler. + # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_lowerbound (Deprecated since v2.9.0) Minimum resources the container can use before the VerticalPodAutoscaler updater evicts it. + # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_target (Deprecated since v2.9.0) Target resources the VerticalPodAutoscaler recommends for the container. + # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_uncappedtarget (Deprecated since v2.9.0) Target resources the VerticalPodAutoscaler recommends for the container ignoring bounds. + # HELP kube_verticalpodautoscaler_status_recommendation_containerrecommendations_upperbound (Deprecated since v2.9.0) Maximum resources the container can use before the VerticalPodAutoscaler updater evicts it. # TYPE kube_verticalpodautoscaler_labels gauge # TYPE kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_maxallowed gauge # TYPE kube_verticalpodautoscaler_spec_resourcepolicy_container_policies_minallowed gauge diff --git a/internal/wrapper.go b/internal/wrapper.go new file mode 100644 index 0000000000..65db430363 --- /dev/null +++ b/internal/wrapper.go @@ -0,0 +1,96 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 internal + +import ( + "context" + "errors" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" + "k8s.io/klog/v2" + + "k8s.io/kube-state-metrics/v2/pkg/app" + "k8s.io/kube-state-metrics/v2/pkg/customresource" + "k8s.io/kube-state-metrics/v2/pkg/customresourcestate" + "k8s.io/kube-state-metrics/v2/pkg/options" +) + +// RunKubeStateMetricsWrapper is a wrapper around KSM, delegated to the root command. +func RunKubeStateMetricsWrapper(opts *options.Options) { + var factories []customresource.RegistryFactory + if config, set := resolveCustomResourceConfig(opts); set { + crf, err := customresourcestate.FromConfig(config) + if err != nil { + klog.ErrorS(err, "Parsing from Custom Resource State Metrics file failed") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + factories = append(factories, crf...) + } + + KSMRunOrDie := func(ctx context.Context) { + if err := app.RunKubeStateMetricsWrapper(ctx, opts, factories...); err != nil { + klog.ErrorS(err, "Failed to run kube-state-metrics") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + } + ctx, cancel := context.WithCancel(context.Background()) + if file := options.GetConfigFile(*opts); file != "" { + viper.SetConfigType("yaml") + viper.SetConfigFile(file) + if err := viper.ReadInConfig(); err != nil { + if errors.Is(err, viper.ConfigFileNotFoundError{}) { + klog.ErrorS(err, "Options configuration file not found", "file", file) + } else { + klog.ErrorS(err, "Error reading options configuration file", "file", file) + } + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + viper.OnConfigChange(func(e fsnotify.Event) { + klog.Infof("Changes detected: %s\n", e.Name) + cancel() + // Wait for the ports to be released. + <-time.After(3 * time.Second) + ctx, cancel = context.WithCancel(context.Background()) + go KSMRunOrDie(ctx) + }) + viper.WatchConfig() + } + klog.Infoln("Starting kube-state-metrics") + KSMRunOrDie(ctx) + select {} +} + +func resolveCustomResourceConfig(opts *options.Options) (customresourcestate.ConfigDecoder, bool) { + if s := opts.CustomResourceConfig; s != "" { + return yaml.NewDecoder(strings.NewReader(s)), true + } + if file := opts.CustomResourceConfigFile; file != "" { + f, err := os.Open(filepath.Clean(file)) + if err != nil { + klog.ErrorS(err, "Custom Resource State Metrics file could not be opened") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + return yaml.NewDecoder(f), true + } + return nil, false +} diff --git a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet index 44ff393ebc..d562d02c3b 100644 --- a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet +++ b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet @@ -114,6 +114,13 @@ ], verbs: ['list', 'watch'], }, + { + apiGroups: ['discovery.k8s.io'], + resources: [ + 'endpointslices', + ], + verbs: ['list', 'watch'], + }, { apiGroups: ['storage.k8s.io'], resources: [ @@ -134,6 +141,7 @@ apiGroups: ['networking.k8s.io'], resources: [ 'networkpolicies', + 'ingressclasses', 'ingresses', ], verbs: ['list', 'watch'], diff --git a/main.go b/main.go index a0e6c97a45..04fdad5a68 100644 --- a/main.go +++ b/main.go @@ -17,69 +17,27 @@ limitations under the License. package main import ( - "context" - "fmt" - "os" - "strings" - - "github.com/prometheus/common/version" - "gopkg.in/yaml.v3" + "github.com/spf13/cobra" "k8s.io/klog/v2" - "k8s.io/kube-state-metrics/v2/pkg/customresource" - "k8s.io/kube-state-metrics/v2/pkg/customresourcestate" - - "k8s.io/kube-state-metrics/v2/pkg/app" + "k8s.io/kube-state-metrics/v2/internal" "k8s.io/kube-state-metrics/v2/pkg/options" ) func main() { opts := options.NewOptions() - opts.AddFlags() + cmd := options.InitCommand + cmd.Run = func(cmd *cobra.Command, args []string) { + internal.RunKubeStateMetricsWrapper(opts) + } + opts.AddFlags(cmd) if err := opts.Parse(); err != nil { - klog.ErrorS(err, "Parsing flag definitions error") klog.FlushAndExit(klog.ExitFlushTimeout, 1) } - if opts.Version { - fmt.Printf("%s\n", version.Print("kube-state-metrics")) - os.Exit(0) - } - - if opts.Help { - opts.Usage() - os.Exit(0) - } - - var factories []customresource.RegistryFactory - if config, set := resolveCustomResourceConfig(opts); set { - crf, err := customresourcestate.FromConfig(config) - if err != nil { - klog.ErrorS(err, "Parsing from Custom Resource State Metrics file failed") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - factories = append(factories, crf...) - } - - ctx := context.Background() - if err := app.RunKubeStateMetrics(ctx, opts, factories...); err != nil { - klog.ErrorS(err, "Failed to run kube-state-metrics") + if err := opts.Validate(); err != nil { + klog.ErrorS(err, "Validating options error") klog.FlushAndExit(klog.ExitFlushTimeout, 1) } } - -func resolveCustomResourceConfig(opts *options.Options) (customresourcestate.ConfigDecoder, bool) { - if s := opts.CustomResourceConfig; s != "" { - return yaml.NewDecoder(strings.NewReader(s)), true - } - if file := opts.CustomResourceConfigFile; file != "" { - f, err := os.Open(file) - if err != nil { - klog.ErrorS(err, "Custom Resource State Metrics file could not be opened") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - return yaml.NewDecoder(f), true - } - return nil, false -} diff --git a/pkg/app/server.go b/pkg/app/server.go index a4a20234c2..67c235e0c6 100644 --- a/pkg/app/server.go +++ b/pkg/app/server.go @@ -18,13 +18,19 @@ package app import ( "context" + "crypto/md5" //nolint:gosec + "encoding/binary" "fmt" "net" "net/http" "net/http/pprof" + "os" + "path/filepath" "strconv" "time" + "gopkg.in/yaml.v3" + "github.com/oklog/run" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" @@ -66,6 +72,16 @@ func (pl promLogger) Log(v ...interface{}) error { return nil } +// RunKubeStateMetricsWrapper runs KSM with context cancellation. +func RunKubeStateMetricsWrapper(ctx context.Context, opts *options.Options, factories ...customresource.RegistryFactory) error { + err := RunKubeStateMetrics(ctx, opts, factories...) + if ctx.Err() == context.Canceled { + klog.Infoln("Restarting: kube-state-metrics, metrics will be reset") + return nil + } + return err +} + // RunKubeStateMetrics will build and run the kube-state-metrics. // Any out-of-tree custom resource metrics could be registered by newing a registry factory // which implements customresource.RegistryFactory and pass all factories into this function. @@ -85,17 +101,77 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options, factories . ConstLabels: prometheus.Labels{"handler": "metrics"}, }, []string{"method"}, ) + configHash := promauto.With(ksmMetricsRegistry).NewGaugeVec( + prometheus.GaugeOpts{ + Name: "kube_state_metrics_config_hash", + Help: "Hash of the currently loaded configuration.", + }, []string{"type", "filename"}) + configSuccess := promauto.With(ksmMetricsRegistry).NewGaugeVec( + prometheus.GaugeOpts{ + Name: "kube_state_metrics_last_config_reload_successful", + Help: "Whether the last configuration reload attempt was successful.", + }, []string{"type", "filename"}) + configSuccessTime := promauto.With(ksmMetricsRegistry).NewGaugeVec( + prometheus.GaugeOpts{ + Name: "kube_state_metrics_last_config_reload_success_timestamp_seconds", + Help: "Timestamp of the last successful configuration reload.", + }, []string{"type", "filename"}) + storeBuilder.WithMetrics(ksmMetricsRegistry) + got := options.GetConfigFile(*opts) + if got != "" { + configFile, err := os.ReadFile(filepath.Clean(got)) + if err != nil { + return fmt.Errorf("failed to read opts config file: %v", err) + } + // NOTE: Config value will override default values of intersecting options. + err = yaml.Unmarshal(configFile, opts) + if err != nil { + // DO NOT end the process. + // We want to allow the user to still be able to fix the misconfigured config (redeploy or edit the configmaps) and reload KSM automatically once that's done. + klog.Warningf("failed to unmarshal opts config file: %v", err) + // Wait for the next reload. + klog.Infof("misconfigured config detected, KSM will automatically reload on next write to the config") + klog.Infof("waiting for config to be fixed") + configSuccess.WithLabelValues("config", filepath.Clean(got)).Set(0) + <-ctx.Done() + } else { + configSuccess.WithLabelValues("config", filepath.Clean(got)).Set(1) + configSuccessTime.WithLabelValues("config", filepath.Clean(got)).SetToCurrentTime() + hash := md5HashAsMetricValue(configFile) + configHash.WithLabelValues("config", filepath.Clean(got)).Set(hash) + } + } + + if opts.CustomResourceConfigFile != "" { + crcFile, err := os.ReadFile(filepath.Clean(opts.CustomResourceConfigFile)) + if err != nil { + return fmt.Errorf("failed to read custom resource config file: %v", err) + } + configSuccess.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).Set(1) + configSuccessTime.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).SetToCurrentTime() + hash := md5HashAsMetricValue(crcFile) + configHash.WithLabelValues("customresourceconfig", filepath.Clean(opts.CustomResourceConfigFile)).Set(hash) + + } + var resources []string - if len(opts.Resources) == 0 { + switch { + case len(opts.Resources) == 0 && !opts.CustomResourcesOnly: klog.InfoS("Used default resources") resources = options.DefaultResources.AsSlice() // enable custom resource for _, factory := range factories { resources = append(resources, factory.Name()) } - } else { + case opts.CustomResourcesOnly: + // enable custom resource only + for _, factory := range factories { + resources = append(resources, factory.Name()) + } + klog.InfoS("Used CRD resources only", "resources", resources) + default: klog.InfoS("Used resources", "resources", opts.Resources.String()) resources = opts.Resources.AsSlice() } @@ -106,7 +182,13 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options, factories . namespaces := opts.Namespaces.GetNamespaces() nsFieldSelector := namespaces.GetExcludeNSFieldSelector(opts.NamespacesDenylist) - storeBuilder.WithNamespaces(namespaces, nsFieldSelector) + nodeFieldSelector := opts.Node.GetNodeFieldSelector() + merged, err := storeBuilder.MergeFieldSelectors([]string{nsFieldSelector, nodeFieldSelector}) + if err != nil { + return err + } + storeBuilder.WithNamespaces(namespaces) + storeBuilder.WithFieldSelectorFilter(merged) allowDenyList, err := allowdenylist.New(opts.MetricAllowlist, opts.MetricDenylist) if err != nil { @@ -149,7 +231,9 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options, factories . storeBuilder.WithCustomResourceClients(customResourceClients) storeBuilder.WithSharding(opts.Shard, opts.TotalShards) storeBuilder.WithAllowAnnotations(opts.AnnotationsAllowList) - storeBuilder.WithAllowLabels(opts.LabelsAllowList) + if err := storeBuilder.WithAllowLabels(opts.LabelsAllowList); err != nil { + return fmt.Errorf("failed to set up labels allowlist: %v", err) + } ksmMetricsRegistry.MustRegister( collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), @@ -178,17 +262,32 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options, factories . telemetryMux := buildTelemetryServer(ksmMetricsRegistry) telemetryListenAddress := net.JoinHostPort(opts.TelemetryHost, strconv.Itoa(opts.TelemetryPort)) - telemetryServer := http.Server{Handler: telemetryMux, Addr: telemetryListenAddress} + telemetryServer := http.Server{ + Handler: telemetryMux, + ReadHeaderTimeout: 5 * time.Second} + telemetryFlags := web.FlagConfig{ + WebListenAddresses: &[]string{telemetryListenAddress}, + WebSystemdSocket: new(bool), + WebConfigFile: &tlsConfig, + } metricsMux := buildMetricsServer(m, durationVec) metricsServerListenAddress := net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)) - metricsServer := http.Server{Handler: metricsMux, Addr: metricsServerListenAddress} + metricsServer := http.Server{ + Handler: metricsMux, + ReadHeaderTimeout: 5 * time.Second} + + metricsFlags := web.FlagConfig{ + WebListenAddresses: &[]string{metricsServerListenAddress}, + WebSystemdSocket: new(bool), + WebConfigFile: &tlsConfig, + } // Run Telemetry server { g.Add(func() error { klog.InfoS("Started kube-state-metrics self metrics server", "telemetryAddress", telemetryListenAddress) - return web.ListenAndServe(&telemetryServer, tlsConfig, promLogger) + return web.ListenAndServe(&telemetryServer, &telemetryFlags, promLogger) }, func(error) { ctxShutDown, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() @@ -199,7 +298,7 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options, factories . { g.Add(func() error { klog.InfoS("Started metrics server", "metricsServerAddress", metricsServerListenAddress) - return web.ListenAndServe(&metricsServer, tlsConfig, promLogger) + return web.ListenAndServe(&metricsServer, &metricsFlags, promLogger) }, func(error) { ctxShutDown, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() @@ -309,3 +408,14 @@ func buildMetricsServer(m *metricshandler.MetricsHandler, durationObserver prome }) return mux } + +// md5HashAsMetricValue creates an md5 hash and returns the most significant bytes that fit into a float64 +// Taken from https://github.com/prometheus/alertmanager/blob/6ef6e6868dbeb7984d2d577dd4bf75c65bf1904f/config/coordinator.go#L149 +func md5HashAsMetricValue(data []byte) float64 { + sum := md5.Sum(data) //nolint:gosec + // We only want 48 bits as a float64 only has a 53 bit mantissa. + smallSum := sum[0:6] + bytes := make([]byte, 8) + copy(bytes, smallSum) + return float64(binary.LittleEndian.Uint64(bytes)) +} diff --git a/pkg/app/server_test.go b/pkg/app/server_test.go index 345a9dfc87..263385b8f3 100644 --- a/pkg/app/server_test.go +++ b/pkg/app/server_test.go @@ -73,11 +73,14 @@ func BenchmarkKubeStateMetrics(b *testing.B) { builder := store.NewBuilder() builder.WithMetrics(reg) - builder.WithEnabledResources(options.DefaultResources.AsSlice()) + err := builder.WithEnabledResources(options.DefaultResources.AsSlice()) + if err != nil { + b.Fatal(err) + } builder.WithKubeClient(kubeClient) builder.WithSharding(0, 1) builder.WithContext(ctx) - builder.WithNamespaces(options.DefaultNamespaces, "") + builder.WithNamespaces(options.DefaultNamespaces) builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc()) allowDenyListFilter, err := allowdenylist.New(map[string]struct{}{}, map[string]struct{}{}) @@ -118,7 +121,10 @@ func BenchmarkKubeStateMetrics(b *testing.B) { b.StopTimer() buf := bytes.Buffer{} - buf.ReadFrom(resp.Body) + _, err := buf.ReadFrom(resp.Body) + if err != nil { + b.Fatal(err) + } accumulatedContentLength += buf.Len() b.StartTimer() } @@ -144,9 +150,12 @@ func TestFullScrapeCycle(t *testing.T) { reg := prometheus.NewRegistry() builder := store.NewBuilder() builder.WithMetrics(reg) - builder.WithEnabledResources(options.DefaultResources.AsSlice()) + err = builder.WithEnabledResources(options.DefaultResources.AsSlice()) + if err != nil { + t.Fatal(err) + } builder.WithKubeClient(kubeClient) - builder.WithNamespaces(options.DefaultNamespaces, "") + builder.WithNamespaces(options.DefaultNamespaces) builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc()) l, err := allowdenylist.New(map[string]struct{}{}, map[string]struct{}{}) @@ -193,8 +202,8 @@ func TestFullScrapeCycle(t *testing.T) { expected := `# HELP kube_pod_annotations Kubernetes annotations converted to Prometheus labels. # HELP kube_pod_completion_time [STABLE] Completion time in unix timestamp for a pod. # HELP kube_pod_container_info [STABLE] Information about a container in a pod. -# HELP kube_pod_container_resource_limits The number of requested limit resource by a container. -# HELP kube_pod_container_resource_requests The number of requested request resource by a container. +# HELP kube_pod_container_resource_limits The number of requested limit resource by a container. It is recommended to use the kube_pod_resource_limits metric exposed by kube-scheduler instead, as it is more precise. +# HELP kube_pod_container_resource_requests The number of requested request resource by a container. It is recommended to use the kube_pod_resource_requests metric exposed by kube-scheduler instead, as it is more precise. # HELP kube_pod_container_state_started [STABLE] Start time in unix timestamp for a pod container. # HELP kube_pod_container_status_last_terminated_exitcode Describes the exit code for the last container in terminated state. # HELP kube_pod_container_status_last_terminated_reason Describes the last reason the container was in terminated state. @@ -317,9 +326,9 @@ kube_pod_container_status_waiting_reason{namespace="default",pod="pod0",uid="abc kube_pod_container_status_waiting{namespace="default",pod="pod0",uid="abc-0",container="pod1_con1"} 1 kube_pod_container_status_waiting{namespace="default",pod="pod0",uid="abc-0",container="pod1_con2"} 0 kube_pod_created{namespace="default",pod="pod0",uid="abc-0"} 1.5e+09 -kube_pod_info{namespace="default",pod="pod0",uid="abc-0",host_ip="1.1.1.1",pod_ip="1.2.3.4",node="node1",created_by_kind="",created_by_name="",priority_class="",host_network="false"} 1 +kube_pod_info{namespace="default",pod="pod0",uid="abc-0",host_ip="1.1.1.1",pod_ip="1.2.3.4",node="node1",created_by_kind="",created_by_name="",priority_class="",host_network="false"} 1 kube_pod_labels{namespace="default",pod="pod0",uid="abc-0"} 1 -kube_pod_owner{namespace="default",pod="pod0",uid="abc-0",owner_kind="",owner_name="",owner_is_controller=""} 1 +kube_pod_owner{namespace="default",pod="pod0",uid="abc-0",owner_kind="",owner_name="",owner_is_controller=""} 1 kube_pod_restart_policy{namespace="default",pod="pod0",uid="abc-0",type="Always"} 1 kube_pod_status_phase{namespace="default",pod="pod0",uid="abc-0",phase="Failed"} 0 kube_pod_status_phase{namespace="default",pod="pod0",uid="abc-0",phase="Pending"} 0 @@ -432,9 +441,12 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { reg := prometheus.NewRegistry() unshardedBuilder := store.NewBuilder() unshardedBuilder.WithMetrics(reg) - unshardedBuilder.WithEnabledResources(options.DefaultResources.AsSlice()) + err = unshardedBuilder.WithEnabledResources(options.DefaultResources.AsSlice()) + if err != nil { + t.Fatal(err) + } unshardedBuilder.WithKubeClient(kubeClient) - unshardedBuilder.WithNamespaces(options.DefaultNamespaces, "") + unshardedBuilder.WithNamespaces(options.DefaultNamespaces) unshardedBuilder.WithFamilyGeneratorFilter(l) unshardedBuilder.WithAllowLabels(map[string][]string{}) unshardedBuilder.WithGenerateStoresFunc(unshardedBuilder.DefaultGenerateStoresFunc()) @@ -445,9 +457,12 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { regShard1 := prometheus.NewRegistry() shardedBuilder1 := store.NewBuilder() shardedBuilder1.WithMetrics(regShard1) - shardedBuilder1.WithEnabledResources(options.DefaultResources.AsSlice()) + err = shardedBuilder1.WithEnabledResources(options.DefaultResources.AsSlice()) + if err != nil { + t.Fatal(err) + } shardedBuilder1.WithKubeClient(kubeClient) - shardedBuilder1.WithNamespaces(options.DefaultNamespaces, "") + shardedBuilder1.WithNamespaces(options.DefaultNamespaces) shardedBuilder1.WithFamilyGeneratorFilter(l) shardedBuilder1.WithAllowLabels(map[string][]string{}) shardedBuilder1.WithGenerateStoresFunc(shardedBuilder1.DefaultGenerateStoresFunc()) @@ -458,9 +473,12 @@ func TestShardingEquivalenceScrapeCycle(t *testing.T) { regShard2 := prometheus.NewRegistry() shardedBuilder2 := store.NewBuilder() shardedBuilder2.WithMetrics(regShard2) - shardedBuilder2.WithEnabledResources(options.DefaultResources.AsSlice()) + err = shardedBuilder2.WithEnabledResources(options.DefaultResources.AsSlice()) + if err != nil { + t.Fatal(err) + } shardedBuilder2.WithKubeClient(kubeClient) - shardedBuilder2.WithNamespaces(options.DefaultNamespaces, "") + shardedBuilder2.WithNamespaces(options.DefaultNamespaces) shardedBuilder2.WithFamilyGeneratorFilter(l) shardedBuilder2.WithAllowLabels(map[string][]string{}) shardedBuilder2.WithGenerateStoresFunc(shardedBuilder2.DefaultGenerateStoresFunc()) @@ -595,10 +613,14 @@ func TestCustomResourceExtension(t *testing.T) { builder := store.NewBuilder() builder.WithCustomResourceStoreFactories(factories...) builder.WithMetrics(reg) - builder.WithEnabledResources(resources) + err := builder.WithEnabledResources(resources) + if err != nil { + t.Fatal(err) + } + builder.WithKubeClient(kubeClient) builder.WithCustomResourceClients(customResourceClients) - builder.WithNamespaces(options.DefaultNamespaces, "") + builder.WithNamespaces(options.DefaultNamespaces) builder.WithGenerateStoresFunc(builder.DefaultGenerateStoresFunc()) builder.WithGenerateCustomResourceStoresFunc(builder.DefaultGenerateCustomResourceStoresFunc()) diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go index 1e2763c455..ba1f10bac1 100644 --- a/pkg/builder/builder.go +++ b/pkg/builder/builder.go @@ -19,8 +19,6 @@ package builder import ( "context" - generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" - "github.com/prometheus/client_golang/prometheus" vpaclientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned" clientset "k8s.io/client-go/kubernetes" @@ -29,6 +27,7 @@ import ( internalstore "k8s.io/kube-state-metrics/v2/internal/store" ksmtypes "k8s.io/kube-state-metrics/v2/pkg/builder/types" "k8s.io/kube-state-metrics/v2/pkg/customresource" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" metricsstore "k8s.io/kube-state-metrics/v2/pkg/metrics_store" "k8s.io/kube-state-metrics/v2/pkg/options" ) @@ -58,8 +57,8 @@ func (b *Builder) WithEnabledResources(c []string) error { } // WithNamespaces sets the namespaces property of a Builder. -func (b *Builder) WithNamespaces(n options.NamespaceList, nsFilter string) { - b.internal.WithNamespaces(n, nsFilter) +func (b *Builder) WithNamespaces(n options.NamespaceList) { + b.internal.WithNamespaces(n) } // WithSharding sets the shard and totalShards property of a Builder. @@ -135,7 +134,7 @@ func (b *Builder) WithCustomResourceStoreFactories(fs ...customresource.Registry // Build initializes and registers all enabled stores. // Returns metric writers. -func (b *Builder) Build() []metricsstore.MetricsWriter { +func (b *Builder) Build() metricsstore.MetricsWriterList { return b.internal.Build() } diff --git a/pkg/builder/builder_test.go b/pkg/builder/builder_test.go index 9b420d47d0..7189cb1c66 100644 --- a/pkg/builder/builder_test.go +++ b/pkg/builder/builder_test.go @@ -40,9 +40,12 @@ var ( func TestBuilderWithCustomStore(t *testing.T) { b := builder.NewBuilder() b.WithFamilyGeneratorFilter(generator.NewCompositeFamilyGeneratorFilter()) - b.WithEnabledResources([]string{"pods"}) - b.WithGenerateStoresFunc(customStore) + err := b.WithEnabledResources([]string{"pods"}) + if err != nil { + t.Fatal(err) + } + b.WithGenerateStoresFunc(customStore) var fStores []*fakeStore for _, stores := range b.BuildStores() { for _, store := range stores { diff --git a/pkg/builder/types/interfaces.go b/pkg/builder/types/interfaces.go index 21b657a24f..c8ddf40c05 100644 --- a/pkg/builder/types/interfaces.go +++ b/pkg/builder/types/interfaces.go @@ -35,7 +35,8 @@ import ( type BuilderInterface interface { WithMetrics(r prometheus.Registerer) WithEnabledResources(c []string) error - WithNamespaces(n options.NamespaceList, nsFilter string) + WithNamespaces(n options.NamespaceList) + WithFieldSelectorFilter(fieldSelectors string) WithSharding(shard int32, totalShards int) WithContext(ctx context.Context) WithKubeClient(c clientset.Interface) @@ -44,13 +45,13 @@ type BuilderInterface interface { WithUsingAPIServerCache(u bool) WithFamilyGeneratorFilter(l generator.FamilyGeneratorFilter) WithAllowAnnotations(a map[string][]string) - WithAllowLabels(l map[string][]string) + WithAllowLabels(l map[string][]string) error WithGenerateStoresFunc(f BuildStoresFunc) WithGenerateCustomResourceStoresFunc(f BuildCustomResourceStoresFunc) DefaultGenerateStoresFunc() BuildStoresFunc DefaultGenerateCustomResourceStoresFunc() BuildCustomResourceStoresFunc WithCustomResourceStoreFactories(fs ...customresource.RegistryFactory) - Build() []metricsstore.MetricsWriter + Build() metricsstore.MetricsWriterList BuildStores() [][]cache.Store } diff --git a/pkg/customresourcestate/config.go b/pkg/customresourcestate/config.go index 76e7786763..288c1e90b3 100644 --- a/pkg/customresourcestate/config.go +++ b/pkg/customresourcestate/config.go @@ -41,9 +41,8 @@ type MetricsSpec struct { // Resource configures a custom resource for metric generation. type Resource struct { // MetricNamePrefix defines a prefix for all metrics of the resource. - // Falls back to the GroupVersionKind string prefixed with "kube_", with invalid characters replaced by _ if nil. // If set to "", no prefix will be added. - // Example: If GroupVersionKind is "my-team.io/v1/MyResource", MetricNamePrefix will be "kube_my_team_io_v1_MyResource". + // Example: If set to "foo", MetricNamePrefix will be "foo_". MetricNamePrefix *string `yaml:"metricNamePrefix" json:"metricNamePrefix"` // GroupVersionKind of the custom resource to be monitored. @@ -63,17 +62,11 @@ type Resource struct { // GetMetricNamePrefix returns the prefix to use for metrics. func (r Resource) GetMetricNamePrefix() string { - if r.MetricNamePrefix == nil { - return strings.NewReplacer( - "/", "_", - ".", "_", - "-", "_", - ).Replace(fmt.Sprintf("kube_%s_%s_%s", r.GroupVersionKind.Group, r.GroupVersionKind.Version, r.GroupVersionKind.Kind)) + p := r.MetricNamePrefix + if p == nil { + return "kube_crd" } - if *r.MetricNamePrefix == "" { - return "" - } - return *r.MetricNamePrefix + return *p } // GetResourceName returns the lowercase, plural form of the resource Kind. This is ResourcePlural if it is set. diff --git a/pkg/customresourcestate/config_metrics_types.go b/pkg/customresourcestate/config_metrics_types.go index 110c4f9934..6e8e9167cd 100644 --- a/pkg/customresourcestate/config_metrics_types.go +++ b/pkg/customresourcestate/config_metrics_types.go @@ -51,6 +51,8 @@ type MetricGauge struct { // Ref: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#info type MetricInfo struct { MetricMeta `yaml:",inline" json:",inline"` + // LabelFromKey adds a label with the given name if Path is an object. The label value will be the object key. + LabelFromKey string `yaml:"labelFromKey" json:"labelFromKey"` } // MetricStateSet is a metric which represent a series of related boolean values, also called a bitset. diff --git a/pkg/customresourcestate/custom_resource_metrics_test.go b/pkg/customresourcestate/custom_resource_metrics_test.go new file mode 100644 index 0000000000..9bd1a5c536 --- /dev/null +++ b/pkg/customresourcestate/custom_resource_metrics_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 customresourcestate + +import ( + "testing" +) + +func TestNewCustomResourceMetrics(t *testing.T) { + tests := []struct { + r Resource + wantErr bool + name string + }{ + { + // https://github.com/kubernetes/kube-state-metrics/issues/1886 + name: "dynamic metric type (not just hardcoded to gauge)", + r: Resource{ + GroupVersionKind: GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + Labels: Labels{ + LabelsFromPath: map[string][]string{ + "name": {"metadata", "name"}, + }, + }, + Metrics: []Generator{ + { + Name: "test_metrics", + Help: "metrics for testing", + Each: Metric{ + Type: MetricTypeInfo, + Info: &MetricInfo{ + MetricMeta: MetricMeta{ + Path: []string{ + "metadata", + "annotations", + }, + }, + LabelFromKey: "test", + }, + }, + }, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v, err := NewCustomResourceMetrics(tt.r) + expectedError := v.(*customResourceMetrics).Families[0].Each.Type() != "info" + if (err != nil) != tt.wantErr || expectedError { + t.Errorf("NewCustomResourceMetrics() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/pkg/customresourcestate/registry_factory.go b/pkg/customresourcestate/registry_factory.go index 8cfe716c76..e027cae8e7 100644 --- a/pkg/customresourcestate/registry_factory.go +++ b/pkg/customresourcestate/registry_factory.go @@ -34,6 +34,13 @@ import ( func compile(resource Resource) ([]compiledFamily, error) { var families []compiledFamily + // Explicitly add GVK labels to all CR metrics. + if resource.CommonLabels == nil { + resource.CommonLabels = map[string]string{} + } + resource.CommonLabels["group"] = resource.GroupVersionKind.Group + resource.CommonLabels["version"] = resource.GroupVersionKind.Version + resource.CommonLabels["kind"] = resource.GroupVersionKind.Kind for _, f := range resource.Metrics { family, err := compileFamily(f, resource) if err != nil { @@ -111,6 +118,7 @@ type compiledEach compiledMetric type compiledCommon struct { labelFromPath map[string]valuePath path valuePath + t metric.Type } func (c compiledCommon) Path() valuePath { @@ -119,6 +127,9 @@ func (c compiledCommon) Path() valuePath { func (c compiledCommon) LabelFromPath() map[string]valuePath { return c.labelFromPath } +func (c compiledCommon) Type() metric.Type { + return c.t +} type eachValue struct { Labels map[string]string @@ -129,6 +140,7 @@ type compiledMetric interface { Values(v interface{}) (result []eachValue, err []error) Path() valuePath LabelFromPath() map[string]valuePath + Type() metric.Type } // newCompiledMetric returns a compiledMetric depending given the metric type. @@ -139,6 +151,7 @@ func newCompiledMetric(m Metric) (compiledMetric, error) { return nil, errors.New("expected each.gauge to not be nil") } cc, err := compileCommon(m.Gauge.MetricMeta) + cc.t = metric.Gauge if err != nil { return nil, fmt.Errorf("each.gauge: %w", err) } @@ -150,23 +163,27 @@ func newCompiledMetric(m Metric) (compiledMetric, error) { compiledCommon: *cc, ValueFrom: valueFromPath, NilIsZero: m.Gauge.NilIsZero, + labelFromKey: m.Gauge.LabelFromKey, }, nil case MetricTypeInfo: if m.Info == nil { return nil, errors.New("expected each.info to not be nil") } cc, err := compileCommon(m.Info.MetricMeta) + cc.t = metric.Info if err != nil { return nil, fmt.Errorf("each.info: %w", err) } return &compiledInfo{ compiledCommon: *cc, + labelFromKey: m.Info.LabelFromKey, }, nil case MetricTypeStateSet: if m.StateSet == nil { return nil, errors.New("expected each.stateSet to not be nil") } cc, err := compileCommon(m.StateSet.MetricMeta) + cc.t = metric.StateSet if err != nil { return nil, fmt.Errorf("each.stateSet: %w", err) } @@ -188,23 +205,8 @@ func newCompiledMetric(m Metric) (compiledMetric, error) { type compiledGauge struct { compiledCommon ValueFrom valuePath - LabelFromKey string NilIsZero bool -} - -func newCompiledGauge(m *MetricGauge) (*compiledGauge, error) { - cc, err := compileCommon(m.MetricMeta) - if err != nil { - return nil, fmt.Errorf("compile common: %w", err) - } - valueFromPath, err := compilePath(m.ValueFrom) - if err != nil { - return nil, fmt.Errorf("compile path ValueFrom: %w", err) - } - return &compiledGauge{ - compiledCommon: *cc, - ValueFrom: valueFromPath, - }, nil + labelFromKey string } func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error) { @@ -220,8 +222,12 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error) onError(fmt.Errorf("[%s]: %w", key, err)) continue } - if key != "" && c.LabelFromKey != "" { - ev.Labels[c.LabelFromKey] = key + if _, ok := ev.Labels[c.labelFromKey]; ok { + onError(fmt.Errorf("labelFromKey (%s) generated labels conflict with labelsFromPath, consider renaming it", c.labelFromKey)) + continue + } + if key != "" && c.labelFromKey != "" { + ev.Labels[c.labelFromKey] = key } addPathLabels(it, c.LabelFromPath(), ev.Labels) result = append(result, *ev) @@ -250,22 +256,53 @@ func (c *compiledGauge) Values(v interface{}) (result []eachValue, errs []error) type compiledInfo struct { compiledCommon + labelFromKey string } func (c *compiledInfo) Values(v interface{}) (result []eachValue, errs []error) { - if vs, isArray := v.([]interface{}); isArray { - for _, obj := range vs { + onError := func(err ...error) { + errs = append(errs, fmt.Errorf("%s: %v", c.Path(), err)) + } + + switch iter := v.(type) { + case []interface{}: + for _, obj := range iter { ev, err := c.values(obj) if len(err) > 0 { - errs = append(errs, err...) + onError(err...) continue } result = append(result, ev...) } - return + case map[string]interface{}: + value, err := c.values(v) + if err != nil { + onError(err...) + break + } + for _, ev := range value { + if _, ok := ev.Labels[c.labelFromKey]; ok { + onError(fmt.Errorf("labelFromKey (%s) generated labels conflict with labelsFromPath, consider renaming it", c.labelFromKey)) + continue + } + } + // labelFromKey logic + for key := range iter { + if key != "" && c.labelFromKey != "" { + result = append(result, eachValue{ + Labels: map[string]string{ + c.labelFromKey: key, + }, + Value: 1, + }) + } + } + result = append(result, value...) + default: + result, errs = c.values(v) } - return c.values(v) + return } func (c *compiledInfo) values(v interface{}) (result []eachValue, err []error) { @@ -274,7 +311,9 @@ func (c *compiledInfo) values(v interface{}) (result []eachValue, err []error) { } value := eachValue{Value: 1, Labels: map[string]string{}} addPathLabels(v, c.labelFromPath, value.Labels) - result = append(result, value) + if len(value.Labels) != 0 { + result = append(result, value) + } return } @@ -348,7 +387,7 @@ func less(a, b map[string]string) bool { func (c compiledGauge) value(it interface{}) (*eachValue, error) { labels := make(map[string]string) - value, err := getNum(c.ValueFrom.Get(it), c.NilIsZero) + value, err := toFloat64(c.ValueFrom.Get(it), c.NilIsZero) if err != nil { return nil, fmt.Errorf("%s: %w", c.ValueFrom, err) } @@ -471,7 +510,7 @@ func compilePath(path []string) (out valuePath, _ error) { return nil, fmt.Errorf("invalid list lookup: %s", part) } key, val := eq[0], eq[1] - num, notNum := getNum(val, false) + num, notNum := toFloat64(val, false) boolVal, notBool := strconv.ParseBool(val) out = append(out, pathOp{ part: part, @@ -489,7 +528,7 @@ func compilePath(path []string) (out valuePath, _ error) { } if notNum == nil { - if i, err := getNum(candidate, false); err == nil && num == i { + if i, err := toFloat64(candidate, false); err == nil && num == i { return m } } @@ -515,13 +554,14 @@ func compilePath(path []string) (out valuePath, _ error) { } else if s, ok := m.([]interface{}); ok { i, err := strconv.Atoi(part) if err != nil { - return nil + return fmt.Errorf("invalid list index: %s", part) } if i < 0 { + // negative index i += len(s) } if !(0 <= i && i < len(s)) { - return nil + return fmt.Errorf("list index out of range: %s", part) } return s[i] } @@ -537,7 +577,7 @@ func famGen(f compiledFamily) generator.FamilyGenerator { errLog := klog.V(f.ErrorLogV) return generator.FamilyGenerator{ Name: f.Name, - Type: metric.Gauge, + Type: f.Each.Type(), Help: f.Help, GenerateFunc: func(obj interface{}) *metric.Family { return generate(obj.(*unstructured.Unstructured), f, errLog) @@ -578,8 +618,8 @@ func scrapeValuesFor(e compiledEach, obj map[string]interface{}) ([]eachValue, [ return result, errs } -// getNum converts the value to a float64 which is the value type for any metric. -func getNum(value interface{}, nilIsZero bool) (float64, error) { +// toFloat64 converts the value to a float64 which is the value type for any metric. +func toFloat64(value interface{}, nilIsZero bool) (float64, error) { var v float64 // same as bool==false but for bool pointers if value == nil { diff --git a/pkg/customresourcestate/registry_factory_test.go b/pkg/customresourcestate/registry_factory_test.go index b553fa4be5..e4da7d997c 100644 --- a/pkg/customresourcestate/registry_factory_test.go +++ b/pkg/customresourcestate/registry_factory_test.go @@ -155,7 +155,7 @@ func Test_values(t *testing.T) { compiledCommon: compiledCommon{ path: mustCompilePath(t, "status", "active"), }, - LabelFromKey: "type", + labelFromKey: "type", }, wantResult: []eachValue{ newEachValue(t, 1, "type", "type-a"), newEachValue(t, 3, "type", "type-b"), @@ -167,7 +167,7 @@ func Test_values(t *testing.T) { "active": mustCompilePath(t, "active"), }, }, - LabelFromKey: "type", + labelFromKey: "type", ValueFrom: mustCompilePath(t, "ready"), }, wantResult: []eachValue{ newEachValue(t, 2, "type", "type-a", "active", "1"), @@ -201,7 +201,7 @@ func Test_values(t *testing.T) { newEachValue(t, 0), }}, {name: "info", each: &compiledInfo{ - compiledCommon{ + compiledCommon: compiledCommon{ labelFromPath: map[string]valuePath{ "version": mustCompilePath(t, "spec", "version"), }, @@ -210,10 +210,19 @@ func Test_values(t *testing.T) { newEachValue(t, 1, "version", "v0.0.0"), }}, {name: "info nil path", each: &compiledInfo{ - compiledCommon{ + compiledCommon: compiledCommon{ path: mustCompilePath(t, "does", "not", "exist"), }, }, wantResult: nil}, + {name: "info label from key", each: &compiledInfo{ + compiledCommon: compiledCommon{ + path: mustCompilePath(t, "status", "active"), + }, + labelFromKey: "type", + }, wantResult: []eachValue{ + newEachValue(t, 1, "type", "type-a"), + newEachValue(t, 1, "type", "type-b"), + }}, {name: "stateset", each: &compiledStateSet{ compiledCommon: compiledCommon{ path: mustCompilePath(t, "status", "phase"), @@ -324,7 +333,7 @@ func Test_fullName(t *testing.T) { resource: r(nil), f: count, }, - want: "kube_apps_v1_Deployment_count", + want: "kube_crd_count", }, { name: "no prefix", diff --git a/pkg/metric/metric.go b/pkg/metric/metric.go index 96bf8c8063..007eb9fa87 100644 --- a/pkg/metric/metric.go +++ b/pkg/metric/metric.go @@ -38,13 +38,19 @@ var ( ) // Type represents the type of a metric e.g. a counter. See -// https://prometheus.io/docs/concepts/metric_types/. +// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#metric-types. type Type string -// Gauge defines a Prometheus gauge. +// Gauge defines a OpenMetrics gauge. var Gauge Type = "gauge" -// Counter defines a Prometheus counter. +// Info defines an OpenMetrics info. +var Info Type = "info" + +// StateSet defines an OpenMetrics stateset. +var StateSet Type = "stateset" + +// Counter defines a OpenMetrics counter. var Counter Type = "counter" // Metric represents a single time series. diff --git a/pkg/metrics_store/metrics_store.go b/pkg/metrics_store/metrics_store.go index 885dfaf7f2..80d7149781 100644 --- a/pkg/metrics_store/metrics_store.go +++ b/pkg/metrics_store/metrics_store.go @@ -17,7 +17,6 @@ limitations under the License. package metricsstore import ( - "io" "sync" "k8s.io/apimachinery/pkg/api/meta" @@ -144,18 +143,3 @@ func (s *MetricsStore) Replace(list []interface{}, _ string) error { func (s *MetricsStore) Resync() error { return nil } - -// WriteAll writes all metrics of the store into the given writer, zipped with the -// help text of each metric family. -func (s *MetricsStore) WriteAll(w io.Writer) { - s.mutex.RLock() - defer s.mutex.RUnlock() - - for i, help := range s.headers { - w.Write([]byte(help)) - w.Write([]byte{'\n'}) - for _, metricFamilies := range s.metrics { - w.Write(metricFamilies[i]) - } - } -} diff --git a/pkg/metrics_store/metrics_store_test.go b/pkg/metrics_store/metrics_store_test.go index bbd145c981..b32e1bcb38 100644 --- a/pkg/metrics_store/metrics_store_test.go +++ b/pkg/metrics_store/metrics_store_test.go @@ -81,7 +81,11 @@ func TestObjectsSameNameDifferentNamespaces(t *testing.T) { } w := strings.Builder{} - ms.WriteAll(&w) + mw := NewMetricsWriter(ms) + err := mw.WriteAll(&w) + if err != nil { + t.Fatalf("failed to write metrics: %v", err) + } m := w.String() for _, id := range serviceIDS { diff --git a/pkg/metrics_store/metrics_writer.go b/pkg/metrics_store/metrics_writer.go index 5d0362ae15..7cc92c298b 100644 --- a/pkg/metrics_store/metrics_writer.go +++ b/pkg/metrics_store/metrics_writer.go @@ -16,28 +16,28 @@ limitations under the License. package metricsstore -import "io" +import ( + "fmt" + "io" +) -// MetricsWriter is the interface that wraps the WriteAll method. -// WriteAll writes out bytes to the underlying writer. -type MetricsWriter interface { - WriteAll(w io.Writer) -} +// MetricsWriterList represent a list of MetricsWriter +type MetricsWriterList []*MetricsWriter -// MultiStoreMetricsWriter is a struct that holds multiple MetricsStore(s) and +// MetricsWriter is a struct that holds multiple MetricsStore(s) and // implements the MetricsWriter interface. // It should be used with stores which have the same metric headers. // -// MultiStoreMetricsWriter writes out metrics from the underlying stores so that +// MetricsWriter writes out metrics from the underlying stores so that // metrics with the same name coming from different stores end up grouped together. // It also ensures that the metric headers are only written out once. -type MultiStoreMetricsWriter struct { +type MetricsWriter struct { stores []*MetricsStore } -// NewMultiStoreMetricsWriter creates a new MultiStoreMetricsWriter. -func NewMultiStoreMetricsWriter(stores []*MetricsStore) MetricsWriter { - return &MultiStoreMetricsWriter{ +// NewMetricsWriter creates a new MetricsWriter. +func NewMetricsWriter(stores ...*MetricsStore) *MetricsWriter { + return &MetricsWriter{ stores: stores, } } @@ -46,9 +46,9 @@ func NewMultiStoreMetricsWriter(stores []*MetricsStore) MetricsWriter { // // WriteAll writes metrics so that the ones with the same name // are grouped together when written out. -func (m MultiStoreMetricsWriter) WriteAll(w io.Writer) { +func (m MetricsWriter) WriteAll(w io.Writer) error { if len(m.stores) == 0 { - return + return nil } for _, s := range m.stores { @@ -59,12 +59,19 @@ func (m MultiStoreMetricsWriter) WriteAll(w io.Writer) { } for i, help := range m.stores[0].headers { - w.Write([]byte(help)) - w.Write([]byte{'\n'}) + _, err := w.Write([]byte(help + "\n")) + if err != nil { + return fmt.Errorf("failed to write help text: %v", err) + } + for _, s := range m.stores { for _, metricFamilies := range s.metrics { - w.Write(metricFamilies[i]) + _, err := w.Write(metricFamilies[i]) + if err != nil { + return fmt.Errorf("failed to write metrics family: %v", err) + } } } } + return nil } diff --git a/pkg/metrics_store/metrics_writer_test.go b/pkg/metrics_store/metrics_writer_test.go index ff758a6894..b13c160555 100644 --- a/pkg/metrics_store/metrics_writer_test.go +++ b/pkg/metrics_store/metrics_writer_test.go @@ -76,15 +76,19 @@ func TestWriteAllWithSingleStore(t *testing.T) { }, }, } - for _, svc := range svcs { + for _, s := range svcs { + svc := s if err := store.Add(&svc); err != nil { t.Fatal(err) } } - multiNsWriter := metricsstore.NewMultiStoreMetricsWriter([]*metricsstore.MetricsStore{store}) + multiNsWriter := metricsstore.NewMetricsWriter(store) w := strings.Builder{} - multiNsWriter.WriteAll(&w) + err := multiNsWriter.WriteAll(&w) + if err != nil { + t.Fatalf("failed to write metrics: %v", err) + } result := w.String() resultLines := strings.Split(strings.TrimRight(result, "\n"), "\n") @@ -160,7 +164,8 @@ func TestWriteAllWithMultipleStores(t *testing.T) { }, }, } - for _, svc := range svcs1 { + for _, s := range svcs1 { + svc := s if err := s1.Add(&svc); err != nil { t.Fatal(err) } @@ -183,15 +188,19 @@ func TestWriteAllWithMultipleStores(t *testing.T) { }, } s2 := metricsstore.NewMetricsStore([]string{"Info 1 about services", "Info 2 about services"}, genFunc) - for _, svc := range svcs2 { + for _, s := range svcs2 { + svc := s if err := s2.Add(&svc); err != nil { t.Fatal(err) } } - multiNsWriter := metricsstore.NewMultiStoreMetricsWriter([]*metricsstore.MetricsStore{s1, s2}) + multiNsWriter := metricsstore.NewMetricsWriter(s1, s2) w := strings.Builder{} - multiNsWriter.WriteAll(&w) + err := multiNsWriter.WriteAll(&w) + if err != nil { + t.Fatalf("failed to write metrics: %v", err) + } result := w.String() resultLines := strings.Split(strings.TrimRight(result, "\n"), "\n") diff --git a/pkg/metricshandler/metrics_handler.go b/pkg/metricshandler/metrics_handler.go index d46afa2651..a6a245dfe2 100644 --- a/pkg/metricshandler/metrics_handler.go +++ b/pkg/metricshandler/metrics_handler.go @@ -51,7 +51,7 @@ type MetricsHandler struct { // mtx protects metricsWriters, curShard, and curTotalShards mtx *sync.RWMutex - metricsWriters []metricsstore.MetricsWriter + metricsWriters metricsstore.MetricsWriterList curShard int32 curTotalShards int } @@ -200,12 +200,18 @@ func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } for _, w := range m.metricsWriters { - w.WriteAll(writer) + err := w.WriteAll(writer) + if err != nil { + klog.ErrorS(err, "Failed to write metrics") + } } // In case we gzipped the response, we have to close the writer. if closer, ok := writer.(io.Closer); ok { - closer.Close() + err := closer.Close() + if err != nil { + klog.ErrorS(err, "Failed to close the writer") + } } } @@ -226,7 +232,7 @@ func shardingSettingsFromStatefulSet(ss *appsv1.StatefulSet, podName string) (no func detectNominalFromPod(statefulSetName, podName string) (int32, error) { nominalString := strings.TrimPrefix(podName, statefulSetName+"-") - nominal, err := strconv.Atoi(nominalString) + nominal, err := strconv.ParseInt(nominalString, 10, 32) if err != nil { return 0, fmt.Errorf("failed to detect shard index for Pod %s of StatefulSet %s, parsed %s: %w", podName, statefulSetName, nominalString, err) } diff --git a/pkg/options/autoload.go b/pkg/options/autoload.go new file mode 100644 index 0000000000..5d51a3d3af --- /dev/null +++ b/pkg/options/autoload.go @@ -0,0 +1,143 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 options + +import ( + "os" + + "github.com/spf13/cobra" + "k8s.io/klog/v2" +) + +const autoloadZsh = `Generate the autocompletion script for the zsh shell. + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + + echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions in your current shell session: + + source <(kube-state-metrics completion zsh); compdef _kube-state-metrics kube-state-metrics + +To load completions for every new session, execute once: + +#### Linux: + + kube-state-metrics completion zsh > "${fpath[1]}/_kube-state-metrics" + +#### macOS: + + kube-state-metrics completion zsh > $(brew --prefix)/share/zsh/site-functions/_kube-state-metrics + +You will need to start a new shell for this setup to take effect. + +Usage: + kube-state-metrics completion zsh [flags] + +Flags: + --no-descriptions disable completion descriptions +` +const autoloadBash = `Generate the autocompletion script for the bash shell. + +This script depends on the 'bash-completion' package. +If it is not installed already, you can install it via your OS's package manager. + +To load completions in your current shell session: + + source <(kube-state-metrics completion bash) + +To load completions for every new session, execute once: + +#### Linux: + + kube-state-metrics completion bash > /etc/bash_completion.d/kube-state-metrics + +#### macOS: + + kube-state-metrics completion bash > $(brew --prefix)/etc/bash_completion.d/kube-state-metrics + +You will need to start a new shell for this setup to take effect. + +Usage: + kube-state-metrics completion bash + +Flags: + --no-descriptions disable completion descriptions +` + +const autoloadFish = `Generate the autocompletion script for the fish shell. + +To load completions in your current shell session: + + kube-state-metrics completion fish | source + +To load completions for every new session, execute once: + + kube-state-metrics completion fish > ~/.config/fish/completions/kube-state-metrics.fish + +You will need to start a new shell for this setup to take effect. + +Usage: + kube-state-metrics completion fish [flags] + +Flags: + --no-descriptions disable completion descriptions +` + +// FetchLoadInstructions returns instructions for enabling autocompletion for a particular shell. +func FetchLoadInstructions(shell string) string { + switch shell { + case "zsh": + return autoloadZsh + case "bash": + return autoloadBash + case "fish": + return autoloadFish + default: + return "" + } +} + +var completionCommand = &cobra.Command{ + Use: "completion [bash|zsh|fish]", + Short: "Generate completion script for kube-state-metrics.", + DisableFlagsInUseLine: true, + Aliases: []string{"comp", "c"}, + ValidArgs: []string{"bash", "zsh", "fish"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + _ = cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + _ = cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + _ = cmd.Root().GenFishCompletion(os.Stdout, true) + } + klog.FlushAndExit(klog.ExitFlushTimeout, 0) + }, + Example: "kube-state-metrics completion bash > /tmp/kube-state-metrics.bash && source /tmp/kube-state-metrics.bash # for shells compatible with bash", +} + +// InitCommand defines the root command that others will latch onto. +var InitCommand = &cobra.Command{ + Use: "kube-state-metrics", + Short: "Add-on agent to generate and expose cluster-level metrics.", + Long: "kube-state-metrics is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects.", + Args: cobra.NoArgs, +} diff --git a/pkg/options/options.go b/pkg/options/options.go index f87b459c0c..bb26ef21ff 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -20,44 +20,51 @@ import ( "flag" "fmt" "os" + "strings" + "github.com/prometheus/common/version" + "github.com/spf13/cobra" "k8s.io/klog/v2" - - "github.com/spf13/pflag" ) // Options are the configurable parameters for kube-state-metrics. type Options struct { - Apiserver string - Kubeconfig string - Help bool - Port int - Host string - TelemetryPort int - TelemetryHost string - TLSConfig string - Resources ResourceSet - Namespaces NamespaceList - NamespacesDenylist NamespaceList - Shard int32 - TotalShards int - Pod string - Namespace string - MetricDenylist MetricSet - MetricAllowlist MetricSet - MetricOptInList MetricSet - Version bool - AnnotationsAllowList LabelsAllowList - LabelsAllowList LabelsAllowList - - EnableGZIPEncoding bool - - UseAPIServerCache bool - - CustomResourceConfig string - CustomResourceConfigFile string - - flags *pflag.FlagSet + AnnotationsAllowList LabelsAllowList `yaml:"annotations_allow_list"` + Apiserver string `yaml:"apiserver"` + CustomResourceConfig string `yaml:"custom_resource_config"` + CustomResourceConfigFile string `yaml:"custom_resource_config_file"` + CustomResourcesOnly bool `yaml:"custom_resources_only"` + EnableGZIPEncoding bool `yaml:"enable_gzip_encoding"` + Help bool `yaml:"help"` + Host string `yaml:"host"` + Kubeconfig string `yaml:"kubeconfig"` + LabelsAllowList LabelsAllowList `yaml:"labels_allow_list"` + MetricAllowlist MetricSet `yaml:"metric_allowlist"` + MetricDenylist MetricSet `yaml:"metric_denylist"` + MetricOptInList MetricSet `yaml:"metric_opt_in_list"` + Namespace string `yaml:"namespace"` + Namespaces NamespaceList `yaml:"namespaces"` + NamespacesDenylist NamespaceList `yaml:"namespaces_denylist"` + Node NodeType `yaml:"node"` + Pod string `yaml:"pod"` + Port int `yaml:"port"` + Resources ResourceSet `yaml:"resources"` + Shard int32 `yaml:"shard"` + TLSConfig string `yaml:"tls_config"` + TelemetryHost string `yaml:"telemetry_host"` + TelemetryPort int `yaml:"telemetry_port"` + TotalShards int `yaml:"total_shards"` + UseAPIServerCache bool `yaml:"use_api_server_cache"` + Version bool `yaml:"version"` + + Config string + + cmd *cobra.Command +} + +// GetConfigFile is the getter for --config value. +func GetConfigFile(opt Options) string { + return opt.Config } // NewOptions returns a new instance of `Options`. @@ -73,59 +80,96 @@ func NewOptions() *Options { } // AddFlags populated the Options struct from the command line arguments passed. -func (o *Options) AddFlags() { - o.flags = pflag.NewFlagSet("", pflag.ExitOnError) - // add klog flags - klogFlags := flag.NewFlagSet("klog", flag.ExitOnError) - klog.InitFlags(klogFlags) - o.flags.AddGoFlagSet(klogFlags) - o.flags.Lookup("logtostderr").Value.Set("true") - o.flags.Lookup("logtostderr").DefValue = "true" - o.flags.Lookup("logtostderr").NoOptDefVal = "true" - - o.flags.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - o.flags.PrintDefaults() +func (o *Options) AddFlags(cmd *cobra.Command) { + o.cmd = cmd + + completionCommand.SetHelpFunc(func(cmd *cobra.Command, args []string) { + if shellPath, ok := os.LookupEnv("SHELL"); ok { + shell := shellPath[strings.LastIndex(shellPath, "/")+1:] + fmt.Println(FetchLoadInstructions(shell)) + } else { + fmt.Println("SHELL environment variable not set, falling back to bash") + fmt.Println(FetchLoadInstructions("bash")) + } + klog.FlushAndExit(klog.ExitFlushTimeout, 0) + }) + + versionCommand := &cobra.Command{ + Use: "version", + Short: "Print version information.", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("%s\n", version.Print("kube-state-metrics")) + klog.FlushAndExit(klog.ExitFlushTimeout, 0) + }, } - o.flags.BoolVarP(&o.UseAPIServerCache, "use-apiserver-cache", "", false, "Sets resourceVersion=0 for ListWatch requests, using cached resources from the apiserver instead of an etcd quorum read.") - o.flags.StringVar(&o.Apiserver, "apiserver", "", `The URL of the apiserver to use as a master`) - o.flags.StringVar(&o.Kubeconfig, "kubeconfig", "", "Absolute path to the kubeconfig file") - o.flags.StringVar(&o.TLSConfig, "tls-config", "", "Path to the TLS configuration file") - o.flags.BoolVarP(&o.Help, "help", "h", false, "Print Help text") - o.flags.IntVar(&o.Port, "port", 8080, `Port to expose metrics on.`) - o.flags.StringVar(&o.Host, "host", "::", `Host to expose metrics on.`) - o.flags.IntVar(&o.TelemetryPort, "telemetry-port", 8081, `Port to expose kube-state-metrics self metrics on.`) - o.flags.StringVar(&o.TelemetryHost, "telemetry-host", "::", `Host to expose kube-state-metrics self metrics on.`) - o.flags.Var(&o.Resources, "resources", fmt.Sprintf("Comma-separated list of Resources to be enabled. Defaults to %q", &DefaultResources)) - o.flags.Var(&o.Namespaces, "namespaces", fmt.Sprintf("Comma-separated list of namespaces to be enabled. Defaults to %q", &DefaultNamespaces)) - o.flags.Var(&o.NamespacesDenylist, "namespaces-denylist", "Comma-separated list of namespaces not to be enabled. If namespaces and namespaces-denylist are both set, only namespaces that are excluded in namespaces-denylist will be used.") - o.flags.Var(&o.MetricAllowlist, "metric-allowlist", "Comma-separated list of metrics to be exposed. This list comprises of exact metric names and/or regex patterns. The allowlist and denylist are mutually exclusive.") - o.flags.Var(&o.MetricDenylist, "metric-denylist", "Comma-separated list of metrics not to be enabled. This list comprises of exact metric names and/or regex patterns. The allowlist and denylist are mutually exclusive.") - o.flags.Var(&o.MetricOptInList, "metric-opt-in-list", "Comma-separated list of metrics which are opt-in and not enabled by default. This is in addition to the metric allow- and denylists") - o.flags.Var(&o.AnnotationsAllowList, "metric-annotations-allowlist", "Comma-separated list of Kubernetes annotations keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional annotations provide a list of resource names in their plural form and Kubernetes annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. A single '*' can be provided per resource instead to allow any annotations, but that has severe performance implications (Example: '=pods=[*]').") - o.flags.Var(&o.LabelsAllowList, "metric-labels-allowlist", "Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional labels provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]').") - o.flags.Int32Var(&o.Shard, "shard", int32(0), "The instances shard nominal (zero indexed) within the total number of shards. (default 0)") - o.flags.IntVar(&o.TotalShards, "total-shards", 1, "The total number of shards. Sharding is disabled when total shards is set to 1.") + cmd.AddCommand(completionCommand, versionCommand) - autoshardingNotice := "When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice." + o.cmd.Flags().Usage = func() { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + o.cmd.Flags().PrintDefaults() + } - o.flags.StringVar(&o.Pod, "pod", "", "Name of the pod that contains the kube-state-metrics container. "+autoshardingNotice) - o.flags.StringVar(&o.Namespace, "pod-namespace", "", "Name of the namespace of the pod specified by --pod. "+autoshardingNotice) - o.flags.BoolVarP(&o.Version, "version", "", false, "kube-state-metrics build version information") - o.flags.BoolVar(&o.EnableGZIPEncoding, "enable-gzip-encoding", false, "Gzip responses when requested by clients via 'Accept-Encoding: gzip' header.") + klogFlags := flag.NewFlagSet("klog", flag.ExitOnError) + klog.InitFlags(klogFlags) + o.cmd.Flags().AddGoFlagSet(klogFlags) + _ = o.cmd.Flags().Lookup("logtostderr").Value.Set("true") + o.cmd.Flags().Lookup("logtostderr").DefValue = "true" + o.cmd.Flags().Lookup("logtostderr").NoOptDefVal = "true" + + autoshardingNotice := "When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice." - o.flags.StringVar(&o.CustomResourceConfig, "custom-resource-state-config", "", "Inline Custom Resource State Metrics config YAML (experimental)") - o.flags.StringVar(&o.CustomResourceConfigFile, "custom-resource-state-config-file", "", "Path to a Custom Resource State Metrics config file (experimental)") + o.cmd.Flags().BoolVar(&o.CustomResourcesOnly, "custom-resource-state-only", false, "Only provide Custom Resource State metrics (experimental)") + o.cmd.Flags().BoolVar(&o.EnableGZIPEncoding, "enable-gzip-encoding", false, "Gzip responses when requested by clients via 'Accept-Encoding: gzip' header.") + o.cmd.Flags().BoolVarP(&o.Help, "help", "h", false, "Print Help text") + o.cmd.Flags().BoolVarP(&o.UseAPIServerCache, "use-apiserver-cache", "", false, "Sets resourceVersion=0 for ListWatch requests, using cached resources from the apiserver instead of an etcd quorum read.") + o.cmd.Flags().BoolVarP(&o.Version, "version", "", false, "kube-state-metrics build version information") + o.cmd.Flags().Int32Var(&o.Shard, "shard", int32(0), "The instances shard nominal (zero indexed) within the total number of shards. (default 0)") + o.cmd.Flags().IntVar(&o.Port, "port", 8080, `Port to expose metrics on.`) + o.cmd.Flags().IntVar(&o.TelemetryPort, "telemetry-port", 8081, `Port to expose kube-state-metrics self metrics on.`) + o.cmd.Flags().IntVar(&o.TotalShards, "total-shards", 1, "The total number of shards. Sharding is disabled when total shards is set to 1.") + o.cmd.Flags().StringVar(&o.Apiserver, "apiserver", "", `The URL of the apiserver to use as a master`) + o.cmd.Flags().StringVar(&o.CustomResourceConfig, "custom-resource-state-config", "", "Inline Custom Resource State Metrics config YAML (experimental)") + o.cmd.Flags().StringVar(&o.CustomResourceConfigFile, "custom-resource-state-config-file", "", "Path to a Custom Resource State Metrics config file (experimental)") + o.cmd.Flags().StringVar(&o.Host, "host", "::", `Host to expose metrics on.`) + o.cmd.Flags().StringVar(&o.Kubeconfig, "kubeconfig", "", "Absolute path to the kubeconfig file") + o.cmd.Flags().StringVar(&o.Namespace, "pod-namespace", "", "Name of the namespace of the pod specified by --pod. "+autoshardingNotice) + o.cmd.Flags().StringVar(&o.Pod, "pod", "", "Name of the pod that contains the kube-state-metrics container. "+autoshardingNotice) + o.cmd.Flags().StringVar(&o.TLSConfig, "tls-config", "", "Path to the TLS configuration file") + o.cmd.Flags().StringVar(&o.TelemetryHost, "telemetry-host", "::", `Host to expose kube-state-metrics self metrics on.`) + o.cmd.Flags().StringVar(&o.Config, "config", "", "Path to the kube-state-metrics options config file") + o.cmd.Flags().StringVar((*string)(&o.Node), "node", "", "Name of the node that contains the kube-state-metrics pod. Most likely it should be passed via the downward API. This is used for daemonset sharding. Only available for resources (pod metrics) that support spec.nodeName fieldSelector. This is experimental.") + o.cmd.Flags().Var(&o.AnnotationsAllowList, "metric-annotations-allowlist", "Comma-separated list of Kubernetes annotations keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional annotations provide a list of resource names in their plural form and Kubernetes annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. A single '*' can be provided per resource instead to allow any annotations, but that has severe performance implications (Example: '=pods=[*]').") + o.cmd.Flags().Var(&o.LabelsAllowList, "metric-labels-allowlist", "Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional labels provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]'). Additionally, an asterisk (*) can be provided as a key, which will resolve to all resources, i.e., assuming '--resources=deployments,pods', '=*=[*]' will resolve to '=deployments=[*],pods=[*]'.") + o.cmd.Flags().Var(&o.MetricAllowlist, "metric-allowlist", "Comma-separated list of metrics to be exposed. This list comprises of exact metric names and/or regex patterns. The allowlist and denylist are mutually exclusive.") + o.cmd.Flags().Var(&o.MetricDenylist, "metric-denylist", "Comma-separated list of metrics not to be enabled. This list comprises of exact metric names and/or regex patterns. The allowlist and denylist are mutually exclusive.") + o.cmd.Flags().Var(&o.MetricOptInList, "metric-opt-in-list", "Comma-separated list of metrics which are opt-in and not enabled by default. This is in addition to the metric allow- and denylists") + o.cmd.Flags().Var(&o.Namespaces, "namespaces", fmt.Sprintf("Comma-separated list of namespaces to be enabled. Defaults to %q", &DefaultNamespaces)) + o.cmd.Flags().Var(&o.NamespacesDenylist, "namespaces-denylist", "Comma-separated list of namespaces not to be enabled. If namespaces and namespaces-denylist are both set, only namespaces that are excluded in namespaces-denylist will be used.") + o.cmd.Flags().Var(&o.Resources, "resources", fmt.Sprintf("Comma-separated list of Resources to be enabled. Defaults to %q", &DefaultResources)) } // Parse parses the flag definitions from the argument list. func (o *Options) Parse() error { - err := o.flags.Parse(os.Args) + err := o.cmd.Execute() return err } // Usage is the function called when an error occurs while parsing flags. func (o *Options) Usage() { - o.flags.Usage() + _ = o.cmd.Flags().FlagUsages() +} + +// Validate validates arguments +func (o *Options) Validate() error { + shardableResource := "pods" + if o.Node == "" { + return nil + } + for _, x := range o.Resources.AsSlice() { + if x != shardableResource { + return fmt.Errorf("resource %s can't be sharded by field selector spec.nodeName", x) + } + } + return nil } diff --git a/pkg/options/options_test.go b/pkg/options/options_test.go index 1a6f5c7304..0e17c0c5a8 100644 --- a/pkg/options/options_test.go +++ b/pkg/options/options_test.go @@ -18,58 +18,48 @@ package options import ( "os" - "sync" "testing" - - "github.com/spf13/pflag" ) func TestOptionsParse(t *testing.T) { tests := []struct { - Desc string - Args []string - RecoverInvoked bool + Desc string + Args []string + ExpectsError bool }{ { - Desc: "resources command line argument", - Args: []string{"./kube-state-metrics", "--resources=configmaps,pods"}, - RecoverInvoked: false, + Desc: "resources command line argument", + Args: []string{"./kube-state-metrics", "--resources=configmaps,pods"}, + ExpectsError: false, + }, + { + Desc: "namespaces command line argument", + Args: []string{"./kube-state-metrics", "--namespaces=default,kube-system"}, + ExpectsError: false, }, { - Desc: "namespaces command line argument", - Args: []string{"./kube-state-metrics", "--namespaces=default,kube-system"}, - RecoverInvoked: false, + Desc: "foo command line argument", + Args: []string{"./kube-state-metrics", "--foo=bar,baz"}, + ExpectsError: true, }, } - for _, test := range tests { - var wg sync.WaitGroup - - opts := NewOptions() - opts.AddFlags() - - flags := pflag.NewFlagSet("options_test", pflag.PanicOnError) - flags.AddFlagSet(opts.flags) + opts := NewOptions() + opts.AddFlags(InitCommand) - opts.flags = flags - - os.Args = test.Args + for _, test := range tests { + t.Run(test.Desc, func(t *testing.T) { + os.Args = test.Args - wg.Add(1) - go func() { - defer wg.Done() - defer func() { - if err := recover(); err != nil { - test.RecoverInvoked = true - } - }() + err := opts.Parse() - opts.Parse() - }() + if !test.ExpectsError && err != nil { + t.Errorf("Error for test with description: %s: %v", test.Desc, err.Error()) + } - wg.Wait() - if test.RecoverInvoked { - t.Errorf("Test error for Desc: %s. Test panic", test.Desc) - } + if test.ExpectsError && err == nil { + t.Errorf("Expected error for test with description: %s", test.Desc) + } + }) } } diff --git a/pkg/options/types.go b/pkg/options/types.go index 4043f6d5b5..ac0c11275e 100644 --- a/pkg/options/types.go +++ b/pkg/options/types.go @@ -104,6 +104,54 @@ func (r *ResourceSet) Type() string { return "string" } +// NodeType represents a nodeName to query from. +type NodeType string + +// GetNodeFieldSelector returns a nodename field selector. +func (n *NodeType) GetNodeFieldSelector() string { + if string(*n) != "" { + return fields.OneTermEqualSelector("spec.nodeName", string(*n)).String() + } + return EmptyFieldSelector() +} + +// EmptyFieldSelector returns an empty field selector. +func EmptyFieldSelector() string { + return fields.Nothing().String() +} + +// MergeFieldSelectors returns AND of a list of field selectors. +func MergeFieldSelectors(selectors []string) (string, error) { + var err error + merged := EmptyFieldSelector() + for _, s := range selectors { + merged, err = MergeTwoFieldSelectors(merged, s) + if err != nil { + return "", err + } + } + return merged, nil +} + +// MergeTwoFieldSelectors returns AND of two field selectors. +func MergeTwoFieldSelectors(s1 string, s2 string) (string, error) { + selector1, err := fields.ParseSelector(s1) + if err != nil { + return EmptyFieldSelector(), err + } + selector2, err := fields.ParseSelector(s2) + if err != nil { + return EmptyFieldSelector(), err + } + if selector1.Empty() { + return selector2.String(), nil + } + if selector2.Empty() { + return selector1.String(), nil + } + return fields.AndSelectors(selector1, selector2).String(), nil +} + // NamespaceList represents a list of namespaces to query from. type NamespaceList []string diff --git a/pkg/options/types_test.go b/pkg/options/types_test.go index fb75abd7dd..3ccaf4c0e4 100644 --- a/pkg/options/types_test.go +++ b/pkg/options/types_test.go @@ -155,6 +155,100 @@ func TestNamespaceList_ExcludeNamespacesFieldSelector(t *testing.T) { } } +func TestNodeFieldSelector(t *testing.T) { + tests := []struct { + Desc string + Node NodeType + Wanted string + }{ + { + Desc: "empty node name", + Node: "", + Wanted: "", + }, + { + Desc: "with node name", + Node: "k8s-node-1", + Wanted: "spec.nodeName=k8s-node-1", + }, + } + + for _, test := range tests { + node := test.Node + actual := node.GetNodeFieldSelector() + if !reflect.DeepEqual(actual, test.Wanted) { + t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v.", test.Desc, test.Wanted, actual) + } + } +} + +func TestMergeFieldSelectors(t *testing.T) { + tests := []struct { + Desc string + Namespaces NamespaceList + DeniedNamespaces NamespaceList + Node NodeType + Wanted string + }{ + { + Desc: "empty DeniedNamespaces", + Namespaces: NamespaceList{"default", "kube-system"}, + DeniedNamespaces: NamespaceList{}, + Node: "", + Wanted: "", + }, + { + Desc: "all DeniedNamespaces", + Namespaces: DefaultNamespaces, + DeniedNamespaces: NamespaceList{"some-system"}, + Node: "", + Wanted: "metadata.namespace!=some-system", + }, + { + Desc: "general case", + Namespaces: DefaultNamespaces, + DeniedNamespaces: NamespaceList{"case1-system", "case2-system"}, + Node: "", + Wanted: "metadata.namespace!=case1-system,metadata.namespace!=case2-system", + }, + { + Desc: "empty DeniedNamespaces", + Namespaces: NamespaceList{"default", "kube-system"}, + DeniedNamespaces: NamespaceList{}, + Node: "k8s-node-1", + Wanted: "spec.nodeName=k8s-node-1", + }, + { + Desc: "all DeniedNamespaces", + Namespaces: DefaultNamespaces, + DeniedNamespaces: NamespaceList{"some-system"}, + Node: "k8s-node-1", + Wanted: "metadata.namespace!=some-system,spec.nodeName=k8s-node-1", + }, + { + Desc: "general case", + Namespaces: DefaultNamespaces, + DeniedNamespaces: NamespaceList{"case1-system", "case2-system"}, + Node: "k8s-node-1", + Wanted: "metadata.namespace!=case1-system,metadata.namespace!=case2-system,spec.nodeName=k8s-node-1", + }, + } + + for _, test := range tests { + ns := test.Namespaces + deniedNS := test.DeniedNamespaces + selector1 := ns.GetExcludeNSFieldSelector(deniedNS) + selector2 := test.Node.GetNodeFieldSelector() + actual, err := MergeFieldSelectors([]string{selector1, selector2}) + if err != nil { + t.Errorf("Test error for Desc: %s. Can't merge field selector %v.", test.Desc, err) + } + if !reflect.DeepEqual(actual, test.Wanted) { + t.Errorf("Test error for Desc: %s. Want: %+v. Got: %+v.", test.Desc, test.Wanted, actual) + } + } +} + func TestMetricSetSet(t *testing.T) { tests := []struct { Desc string @@ -279,6 +373,15 @@ func TestLabelsAllowListSet(t *testing.T) { "bar", "*"}}), }, + { + Desc: "with key as wildcard", + Value: "*=[*]", + Wanted: LabelsAllowList(map[string][]string{ + "*": { + "*", + }, + }), + }, } for _, test := range tests { diff --git a/scripts/generate-help-text.sh b/scripts/generate-help-text.sh index 8d58828a1c..00393eac81 100755 --- a/scripts/generate-help-text.sh +++ b/scripts/generate-help-text.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash echo "$ kube-state-metrics -h" > help.txt -./kube-state-metrics -h 2>> help.txt +./kube-state-metrics -h >> help.txt exit 0 diff --git a/tests/e2e.sh b/tests/e2e.sh index e286c6431a..1dea5b283d 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -24,13 +24,13 @@ case $(uname -m) in esac NODE_IMAGE_NAME="docker.io/kindest/node" -KUBERNETES_VERSION=${KUBERNETES_VERSION:-"v1.24.0"} +KUBERNETES_VERSION=${KUBERNETES_VERSION:-"v1.26.0"} KUBE_STATE_METRICS_LOG_DIR=./log KUBE_STATE_METRICS_CURRENT_IMAGE_NAME="registry.k8s.io/kube-state-metrics/kube-state-metrics" KUBE_STATE_METRICS_IMAGE_NAME="registry.k8s.io/kube-state-metrics/kube-state-metrics-${ARCH}" E2E_SETUP_KIND=${E2E_SETUP_KIND:-} E2E_SETUP_KUBECTL=${E2E_SETUP_KUBECTL:-} -KIND_VERSION=v0.14.0 +KIND_VERSION=v0.17.0 SUDO=${SUDO:-} OS=$(uname -s | awk '{print tolower($0)}') @@ -155,7 +155,8 @@ echo "kube-state-metrics is up and running" echo "start e2e test for kube-state-metrics" KSM_HTTP_METRICS_URL='http://localhost:8001/api/v1/namespaces/kube-system/services/kube-state-metrics:http-metrics/proxy' KSM_TELEMETRY_URL='http://localhost:8001/api/v1/namespaces/kube-system/services/kube-state-metrics:telemetry/proxy' -go test -v ./tests/e2e/ --ksm-http-metrics-url=${KSM_HTTP_METRICS_URL} --ksm-telemetry-url=${KSM_TELEMETRY_URL} +go test -v ./tests/e2e/main_test.go --ksm-http-metrics-url=${KSM_HTTP_METRICS_URL} --ksm-telemetry-url=${KSM_TELEMETRY_URL} +go test -v ./tests/e2e/hot-reload_test.go mkdir -p ${KUBE_STATE_METRICS_LOG_DIR} diff --git a/tests/e2e/hot-reload_test.go b/tests/e2e/hot-reload_test.go new file mode 100644 index 0000000000..c5d099b58d --- /dev/null +++ b/tests/e2e/hot-reload_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. + +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 e2e + +import ( + "net" + "os" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/wait" + + "k8s.io/kube-state-metrics/v2/internal" + "k8s.io/kube-state-metrics/v2/pkg/options" +) + +func TestConfigHotReload(t *testing.T) { + + // Initialise options. + opts := options.NewOptions() + cmd := options.InitCommand + opts.AddFlags(cmd) + + // Create testdata. + f, err := os.CreateTemp("", "config") + if err != nil { + t.Fatal(err) + } + + // Delete artefacts. + defer func() { + err := os.Remove(opts.Config) + if err != nil { + t.Fatalf("failed to remove config file: %v", err) + } + }() + + // Populate options. + opts.Config = f.Name() + + // Assume $HOME is always defined. + opts.Kubeconfig = os.Getenv("HOME") + "/.kube/config" + + // Run general validation on options. + if err := opts.Parse(); err != nil { + t.Fatalf("failed to parse options: %v", err) + } + + // Make the process asynchronous. + go internal.RunKubeStateMetricsWrapper(opts) + + // Wait for port 8080 to come up. + err = wait.PollImmediate(1*time.Second, 20*time.Second, func() (bool, error) { + conn, err := net.Dial("tcp", "localhost:8080") + if err != nil { + return false, nil + } + err = conn.Close() + if err != nil { + return false, err + } + return true, nil + }) + if err != nil { + t.Fatalf("failed to wait for port 8080 to come up for the first time: %v", err) + } + + // Modify config to trigger hot reload. + config := `foo: "bar"` + err = os.WriteFile(opts.Config, []byte(config), 0600 /* rw------- */) + if err != nil { + t.Fatalf("failed to write config file: %v", err) + } + + // Wait for port 8080 to come up. + ch := make(chan bool, 1) + err = wait.PollImmediate(1*time.Second, 20*time.Second, func() (bool, error) { + conn, err := net.Dial("tcp", "localhost:8080") + if err != nil { + return false, nil + } + err = conn.Close() + if err != nil { + return false, err + } + return true, nil + }) + if err != nil { + t.Fatalf("failed to wait for port 8080 to come up after restarting the process: %v", err) + } + + // Indicate that the test has passed. + ch <- true + + // Wait for process to exit. + select { + case <-ch: + t.Log("test passed successfully") + case <-time.After(20 * time.Second): + t.Fatal("timed out waiting for test to pass, check the logs for more info") + } +} diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go index 130d34085d..d0880a3261 100644 --- a/tests/e2e/main_test.go +++ b/tests/e2e/main_test.go @@ -24,6 +24,7 @@ import ( "log" "os" "path" + "path/filepath" "regexp" "sort" "strings" @@ -165,7 +166,7 @@ func getLabelsDocumentation() (map[string][]string, error) { } filePath := path.Join(docPath, file.Name()) - f, err := os.Open(filePath) + f, err := os.Open(filepath.Clean(filePath)) if err != nil { return nil, fmt.Errorf("cannot read file %s: %w", filePath, err) } @@ -255,6 +256,8 @@ func TestDefaultCollectorMetricsAvailable(t *testing.T) { nonDefaultResources := map[string]bool{ "clusterrole": true, "clusterrolebinding": true, + "endpointslice": true, + "ingressclass": true, "role": true, "rolebinding": true, "serviceaccount": true, diff --git a/tests/lib/lib_test.go b/tests/lib/lib_test.go index b948a35c46..ba96edf00e 100644 --- a/tests/lib/lib_test.go +++ b/tests/lib/lib_test.go @@ -55,7 +55,11 @@ func TestAsLibrary(t *testing.T) { time.Sleep(time.Second) w := strings.Builder{} - c.WriteAll(&w) + mw := metricsstore.NewMetricsWriter(c) + err = mw.WriteAll(&w) + if err != nil { + t.Fatalf("failed to write metrics: %v", err) + } m := w.String() if !strings.Contains(m, service.ObjectMeta.Name) { diff --git a/tests/manifests/endpointslice.yaml b/tests/manifests/endpointslice.yaml new file mode 100644 index 0000000000..078e67f2bb --- /dev/null +++ b/tests/manifests/endpointslice.yaml @@ -0,0 +1,13 @@ +apiVersion: discovery.k8s.io/v1 +kind: EndpointSlice +metadata: + name: example-endpointslice + namespace: default +addressType: IPv4 +endpoints: +- addresses: + - 10.0.0.2 + conditions: + ready: true + serving: true + terminating: false diff --git a/tests/manifests/ingress.yaml b/tests/manifests/ingress.yaml index 05428277e1..7e35f7e343 100644 --- a/tests/manifests/ingress.yaml +++ b/tests/manifests/ingress.yaml @@ -1,4 +1,4 @@ -apiVersion: networking.k8s.io/v1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress diff --git a/tests/manifests/ingressclass.yaml b/tests/manifests/ingressclass.yaml new file mode 100644 index 0000000000..95a5aa1278 --- /dev/null +++ b/tests/manifests/ingressclass.yaml @@ -0,0 +1,8 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: example-ingressclass + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +spec: + controller: example-ingress/controller