Compare commits

..
79 Commits
Author SHA1 Message Date
kubernetes-prow[bot] 5a0830ae0b Merge pull request #1897 from Mukuwul/fix/nodeutilization-notsupported-error-message
fix(nodeutilization): correct misleading notSupportedError message
2026-08-29 13:34:05 +00:00
kubernetes-prow[bot] 9914e71ece Merge pull request #1884 from NPastorale/master
feat(deployment): adds revisionHistoryLimit as a configurable parameter
2026-08-29 11:04:03 +00:00
kubernetes-prow[bot] 8bbd0bd661 Merge pull request #1914 from a7i/refactor/e2e-dry-helpers
refactor(test): extract e2e test scripts into reusable lib modules
2026-08-25 11:23:43 +00:00
kubernetes-prow[bot] b66b54f6c0 Merge pull request #1906 from mattburgess/fix-logging-alpha-options
Fix LoggingAlphaOptions wiring
2026-08-25 11:23:36 +00:00
kubernetes-prow[bot] 5aca81876e Merge pull request #1892 from somaz94/feat/chart-host-users
feat: add opt-in hostUsers to the Helm chart
2026-08-24 18:45:01 +00:00
kubernetes-prow[bot] ef090e93de Merge pull request #1915 from PKNaveen/code-cleanup-EvictionRequests
(Code-cleanup) removed EvictionRequests
2026-08-18 11:53:05 +00:00
somaz bdb6f3e42d feat: add opt-in hostUsers to the Helm chart 2026-08-13 10:34:58 +09:00
kubernetes-prow[bot] 4233637de9 Merge pull request #1890 from somaz94/feat/chart-scheduler-runtime-class
feat: add opt-in schedulerName and runtimeClassName to the Helm chart
2026-08-11 12:07:14 +00:00
PKNaveen 6fe697934e removed EvictionRequests 2026-08-11 11:19:34 +00:00
Amir Alavi fb908401dd refactor(test): extract e2e test scripts into reusable lib modules
Split run-e2e-tests.sh into test/lib helpers for kind setup, dependency
install, shared utilities, and centralized version defaults.
2026-08-10 16:56:26 -04:00
kubernetes-prow[bot] ad19067697 Merge pull request #1913 from a7i/centralize-kubevirt-version
refactor(e2e): centralize kubevirt version from go.mod
2026-08-10 15:46:09 +00:00
Amir Alavi 73bcc99a5e refactor(e2e): centralize kubevirt version from go.mod
Derive KUBEVIRT_VERSION in run-e2e-tests.sh from go.mod and pass it to
e2e tests via --kubevirt-version-tag so container disk images stay in sync.
2026-08-10 10:13:12 -04:00
kubernetes-prow[bot] 82b96a392e Merge pull request #1910 from PKNaveen/myfeature
Updated Kubivert to latest v1.9.0
2026-08-10 14:12:10 +00:00
Amir Alavi 5c3469c3ff Bump kubevirt dependencies to v1.9.0
Bump KubeVirt dependencies to v1.9.0 in go.mod, go.sum, and vendor/.
Disable ImageVolume in e2e KubeVirt setup on k8s 1.34 only.

Signed-off-by: Amir Alavi <amiralavi7@gmail.com>
2026-08-10 09:32:41 -04:00
kubernetes-prow[bot] 9d09c0d65f Merge pull request #1908 from sammedsingalkar09/master
security: bump grpc and cel-go to fix vulnerabilities
2026-08-09 19:40:00 +00:00
sammedsingalkar09 0b2a396ac1 fix vuln 2026-08-09 13:11:37 -04:00
kubernetes-prow[bot] e08a32dfa8 Merge pull request #1911 from a7i/fix-e2e-lownodeutilization-metrics-timeout
fix(e2e): resolve LowNodeUtilization metrics timeout, test isolation, and runner resource exhaustion
2026-08-09 17:08:00 +00:00
Amir Alavi b5a214909c Fix e2e test descheduler deployment cleanup and KubeVirt live migration wait 2026-08-09 12:35:43 -04:00
Amir Alavi ec736d7140 fix(e2e): harden test suite isolation, resource usage, and leader election timing
- Isolate TestLiveMigrationInBackground into dedicated e2e-livemigration namespace with t.Cleanup teardown.
- Switch VMI containerdisk image from fedora (1.2GB, 512MB RAM) to cirros-container-disk-demo (12MB, 128MB RAM), reducing Kind memory pressure by 75%.
- Filter pod list API calls with LabelSelector=kubevirt.io=virt-launcher to reduce API server serialization load.
- Override --descheduling-interval to 3s in startDeschedulerServer (e2e_leaderelection_test.go) so leader descheduler re-evaluates pod lifetime periodically.
- Exclude context cancellation/deadline from isClientRateLimiterError in e2e_test.go so context deadlines are not swallowed.
- Add t.Cleanup and IsAlreadyExists handling to prevent leftover resources from causing cascading failures.

Signed-off-by: Amir Alavi <amiralavi7@gmail.com>
2026-08-09 11:18:09 -04:00
Amir Alavi 51ecda87fb fix(e2e): update CPU threshold, timeout, and metrics-server readiness for LowNodeUtilization
- Wait for deployment/metrics-server to be Available before running e2e tests in run-e2e-tests.sh.
- Replace infinite context cancellation polling with a 60s timeout in e2e_lownodeutilization_test.go to prevent 40m CI hangs.
- Set Thresholds.CPU to 20% and TargetThresholds.CPU to 40% for reliable single-pass overutilization detection.
- Add IsAlreadyExists and t.Cleanup handling for descheduler policy ConfigMap lifecycle.

Signed-off-by: Amir Alavi <amiralavi7@gmail.com>
2026-08-09 11:18:09 -04:00
Matt Burgess 39a2b0c685 gen docs 2026-07-30 18:45:17 +01:00
Matt Burgess 3691abd9ac Fix LoggingAlphaOptions wiring
Using --feature-gates=LoggingAlphaOptions=true resulted in the
descheduler not starting due to the feature gate not being recognised.
2026-07-29 21:39:35 +01:00
Mukul 4471d02801 fix(nodeutilization): correct misleading notSupportedError message
notSupportedError signals that a usage client cannot quantify per-pod
resource usage (for example the Prometheus client). Its Error() method
returned "maximum number of evicted pods per node reached", which is
unrelated to that condition and misleading when it surfaces in logs.

Return a message that actually describes the error.

Signed-off-by: Mukul <nmukul32@gmail.com>
2026-07-28 18:56:28 +05:30
somaz 0614367253 feat: add opt-in schedulerName and runtimeClassName to the Helm chart 2026-07-02 16:06:02 +09:00
kubernetes-prow[bot] 7d2b28bf2b Merge pull request #1885 from somaz94/feat/chart-pod-disruption-budget
feat: add opt-in PodDisruptionBudget to the Helm chart
2026-06-30 02:56:08 +00:00
kubernetes-prow[bot] cf2e95cb60 Merge pull request #1889 from sammedsingalkar09/master
deps: bump golang.org/x/net and x/crypto for security fixes
2026-06-29 09:42:09 +00:00
kubernetes-prow[bot] ea957b271b Merge pull request #1888 from yugstar/fix-1887-rbac-remove-pods-delete
Remove unnecessary pods delete permission from ClusterRole
2026-06-27 11:07:02 +00:00
sammedsingalkar09 bb14e73150 security vuln 2026-06-27 14:01:00 +05:30
Aman Raj 341aba2723 Remove unnecessary pods delete permission from ClusterRole
The descheduler ClusterRole granted both pods:delete and
pods/eviction:create. Eviction goes through the eviction subresource
(PolicyV1().Evictions().Evict, requiring pods/eviction:create), and there
are no direct pod Delete calls anywhere in the codebase, so the
pods:delete verb is unused. Drop it to follow least privilege.

Signed-off-by: Aman Raj <aman.yug@gmail.com>
2026-06-27 02:26:45 +05:30
somaz 607b578980 feat: add opt-in PodDisruptionBudget to the Helm chart 2026-06-23 11:02:19 +09:00
NPastorale 436b0ac076 feat(deployment): adds revisionHistoryLimit as a configurable parameter
Signed-off-by: NPastorale <npastorale@gmail.com>
2026-06-22 16:39:24 +02:00
Kubernetes Prow Robot 86183b15a2 Merge pull request #1880 from tiraboschi/fix_eviction_metric
fix: emit eviction metrics for background evictions that never get deleted
2026-06-08 15:43:47 +05:30
Simone Tiraboschi 81fb28c18c fix: emit eviction metrics for background evictions that never get deleted
Pods evicted in background may stay in Succeeded/Failed phase forever
without being garbage-collected, so the metric was silently dropped.
Similarly, entries whose background eviction timed out in cleanCache
were expired without recording an outcome.

- UpdateFunc: emit "success"/"error" metric when a pod transitions to
  PodSucceeded/PodFailed respectively, matching the label convention
  used elsewhere in the file
- cleanCache: add onAssumedTimeout callback on evictionRequestsCache,
  wired in NewPodEvictor to emit "error" for entries that exceed the
  assumed-eviction timeout
- Add TestEvictionInBackgroundMetrics_PodCompleted and
  TestEvictionInBackgroundMetrics_AssumedTimeout to cover both paths

Signed-off-by: Simone Tiraboschi <stirabos@redhat.com>
2026-06-03 19:08:15 +02:00
Kubernetes Prow Robot 3588dacd57 Merge pull request #1879 from a7i/descheduler-v0.36.0-helm
[descheduler v0.36.0] update helm chart
2026-05-20 17:15:48 +05:30
Kubernetes Prow Robot 9cba672e8f Merge pull request #1876 from a7i/copilot-ignore-vendor-master
chore(copilot): ignore vendor directory
2026-05-20 02:53:46 +05:30
Amir Alavi 5ac9278456 [descheduler v0.36.0] update helm chart 2026-05-19 16:43:16 -04:00
Kubernetes Prow Robot 8005cdf783 Merge pull request #1874 from a7i/descheduler-v0.36.0
[v0.36.0] release prep: bump k8s/go deps, manifests, docs, and CI matrix
2026-05-20 01:49:54 +05:30
Amir Alavi 745146e411 ci(e2e): bump KubeVirt to v1.9.0-alpha.0 for k8s 1.36 compatibility
KubeVirt v1.8.2 publishes VMI status checksum fields (uint32 in Go) as
format: int32 in its generated CRD schema. k8s 1.36 enables strict
numeric format validation in CRDs via
https://github.com/kubernetes/kubernetes/pull/136582, which now rejects
the legacy schema and causes virt-handler to enter an infinite VMI
status update re-enqueue loop. Live migrations never complete and the
descheduler e2e TestLiveMigrationInBackground times out.

The schema fix landed upstream in
https://github.com/kubevirt/kubevirt/pull/17469 (merged to main on
2026-04-18, included in v1.9.0-alpha.0 tagged 2026-05-11) but was not
backported to release-1.8, so no v1.8.x release contains it. Bump the
default KUBEVIRT_VERSION to v1.9.0-alpha.0 so the e2e suite consumes a
release whose generated CRDs are compatible with k8s 1.36's stricter
validator.

Tracked in https://github.com/kubevirt/kubevirt/issues/17858.
2026-05-19 15:06:11 -04:00
Amir Alavi 2119baee3d revert: ensureVMIsLiveMigratable retry helper
Reverts b767b9c0f. The helper was added to work around what looked like
the virt-handler containerdisk-socket race on k8s 1.36, but the actual
root cause is unrelated: k8s 1.36's stricter CRD numeric format
validation (kubernetes/kubernetes#136582) rejects VMI status updates
with the pre-fix uint32 Checksum schema. See
https://github.com/kubevirt/kubevirt/issues/17858 for the upstream
context and kubevirt/kubevirt#17469 for the upstream fix (merged to
main, included in v1.9.0-alpha.0, not in v1.8.x).

The follow-up commit bumps KUBEVIRT_VERSION so the test consumes a
KubeVirt release that contains the fix, which removes the need for any
test-side retry.
2026-05-19 15:05:04 -04:00
Amir Alavi b767b9c0f7 test(e2e): wait for VMIs to be LiveMigratable, recreate on race
Adds ensureVMIsLiveMigratable in TestLiveMigrationInBackground. After
the existing wait for virt-launcher pods to reach Running, poll each
VMI for the LiveMigratable=True condition. If a VMI fails to become
migratable within 120s, delete and recreate it (up to 3 attempts).

This works around an upstream KubeVirt race where virt-handler computes
the containerdisk checksum before the disk socket is ready, fails, and
never retries. The recreated VMI lands on a node that has already
cached the containerdisk image, so the socket comes up before
virt-handler's first attempt.

The race surfaces consistently with the kind v1.36.1 node image,
causing TestLiveMigrationInBackground to fail with "Expected at least
3 finished live migrations, got less: 0".
2026-05-19 09:16:51 -04:00
Amir Alavi 03f7dc5889 Bump KubeVirt dependencies to v1.8.2 and migrate to new client package
- Update kubevirt.io/api from v1.3.0 to v1.8.2
- Update kubevirt.io/client-go from v1.3.0 to v1.8.2
- Update kubevirt.io/containerized-data-importer-api from v1.57.0-alpha1 to v1.64.0
- Migrate e2e test from deprecated generated clientset path
  (kubevirt.io/client-go/generated/kubevirt/clientset/versioned)
  to new kubevirt.io/client-go/kubevirt client package
- Update vendor and dependencies for Kubernetes 1.36 compatibility
2026-05-18 22:39:53 -04:00
Amir Alavi dfc3166787 test(e2e): harden kind install and align lint tooling 2026-05-18 12:15:31 -04:00
Amir Alavi 0883de763b ci(manifests): build kind node image for k8s v1.36.1 2026-05-18 12:15:31 -04:00
Amir Alavi 3f8f9bb74e [v0.36.0] release prep: bump k8s/go deps, manifests, docs, and CI matrix 2026-05-18 12:15:30 -04:00
Jan Chaloupka d6720f509c Merge pull request #1878 from ingvagabund/setupTestSandbox-fix
fix(test/setupTestSandbox): wait until initial objects are propagated to informers
2026-05-18 17:18:47 +02:00
Jan Chaloupka 66918dc553 fix(test/setupTestSandbox): wait until initial objects are propagated to informers 2026-05-18 16:46:24 +02:00
Linus Fankhauser 6b5f6b450c chore(defaultevictor): add matchlabels compatibility to the namespaceselector (#1853) 2026-05-18 16:45:53 +05:30
Kubernetes Prow Robot 3ea8051e1e Merge pull request #1873 from tiraboschi/fix_background_eviction_metric_race
evictions: fix assumePod silently dropping success metric on informer race
2026-05-17 18:53:03 +05:30
Amir Alavi 5fd4c1ea00 chore(copilot): ignore vendor directory 2026-05-16 21:59:11 -04:00
Kubernetes Prow Robot 57105044ad Merge pull request #1875 from a7i/pin-github-actions-sha
ci: pin GitHub Actions to immutable SHAs
2026-05-16 18:34:32 +05:30
Amir Alavi b5a7c076f3 ci: pin GitHub Actions to commit SHAs 2026-05-15 22:24:20 -04:00
Simone Tiraboschi a7ac072c7d evictions: fix assumePod silently dropping success metric on informer race
When KubeVirt sets EvictionInProgressAnnotationKey before returning
TooManyRequests, the informer's UpdateFunc can call addPod
(evictionAssumed=false) before evictPod's assumePod call arrives.
assumePod found the entry already present and returned early, leaving
evictionAssumed=false. DeleteFunc then skipped the "success" metric.

Fix: if the existing entry has evictionAssumed=false (added by addPod),
upgrade it in place without double-counting the pod in the counters.

Adds TestEvictionInBackgroundMetrics_InformerRace to reproduce the race
deterministically.

Signed-off-by: Simone Tiraboschi <stirabos@redhat.com>
2026-05-14 17:23:18 +02:00
Kubernetes Prow Robot c00d88f9ed Merge pull request #1837 from a7i/feat/servicemonitor-apiversion-1824
chart: allow overriding ServiceMonitor apiVersion
2026-05-11 17:45:51 +05:30
Kubernetes Prow Robot 290d7f3dfb Merge pull request #1871 from Paramesh324/bump-gcb-docker-gcloud_issue_38936
cloudbuild: pin gcb-docker-gcloud image by digest
2026-05-11 14:15:48 +05:30
Parameshwaran Krishnasamy fab0f2ab70 Replace the GC'd tag v20211118-2f2d816b90 with digest pinning and a
tag comment (v20260205-38cfa9523f) so Cloud Build keeps working after
staging registry retention.
Related: kubernetes/kubernetes#138936

Signed-off-by: Parameshwaran Krishnasamy <Parameshwaran.K@ibm.com>
2026-05-10 12:25:34 +05:30
Kubernetes Prow Robot ddadd33ab2 Merge pull request #1868 from ingvagabund/pin-gh-actions-to-sha
fix(.github/workflows/manifests.yaml): pin actions to a sha
2026-05-06 20:20:26 +05:30
Jan Chaloupka 7be855f7a5 fix(.github/workflows/manifests.yaml): pin actions to a sha 2026-05-06 16:11:17 +02:00
Kubernetes Prow Robot 67a20369b8 Merge pull request #1862 from tiraboschi/fix_1840
fix(descheduler): reset prometheus usage client at each extension point
2026-05-04 20:08:24 +05:30
Simone Tiraboschi e56144c7a2 fix(descheduler): reset prometheus usage client at each extension point
Profile creation was moved outside the descheduling cycle in b214c147,
but reconcileInClusterSAToken() still runs only in runFnc(), after
newDescheduler() returns. This leaves the prometheus client nil when
LowNodeUtilization's New() runs, causing "prometheus client not
initialized" at startup.

Avoid failing at plugin creation time if the prometheus
client is not yet available. Instead, usageClientForMetrics() is now
called at the start of every extension point via a resetUsageClient()
helper, so each descheduling cycle picks up the latest client regardless
of when the SA token is reconciled or rotated.

Fixes: https://github.com/kubernetes-sigs/descheduler/issues/1840

Signed-off-by: Simone Tiraboschi <stirabos@redhat.com>
2026-04-29 11:36:32 +02:00
Kubernetes Prow Robot fa8ae489ae Merge pull request #1856 from tiraboschi/background_eviction_metrics
evictions: fix missing observability for background evictions
2026-04-20 15:23:52 +05:30
Simone Tiraboschi bc0f0354c6 evictions: fix missing observability for background evictions
Background evictions were completely invisible in metrics: the ignore=true
path caused EvictPod to return before incrementing any counter, leaving
operators with no signal that a background eviction had been triggered or
completed.

Add a "background" result label emitted at eviction request time and a
"success" label emitted from the informer DeleteFunc when the pod is
actually gone. The two labels together give a complete picture:
"background" is recorded at eviction request time and may not have a
matching "success" if the descheduler restarts before the pod is deleted,
while "success" confirms the eviction completed within the same lifecycle.

Signed-off-by: Simone Tiraboschi <stirabos@redhat.com>
2026-04-20 11:20:58 +02:00
Kubernetes Prow Robot 0bc278a816 Merge pull request #1859 from sammedsingalkar09/master
security: upgrade grpc and otel sdk dependencies
2026-04-18 20:15:36 +05:30
sammedsingalkar09 212b706950 security: upgrade grpc and otel sdk dependencies
Bump gRPC and OpenTelemetry SDK/exporter dependencies to patched releases and refresh vendored modules to address reported vulnerabilities while keeping tracing resource schema versions consistent.

Made-with: Cursor
2026-04-18 13:12:03 +05:30
Kubernetes Prow Robot 8f9d5c607d Merge pull request #1854 from kubernetes-sigs/security/update-trivy-action-v0.35.0
security: Update trivy-action to use sha for v0.35.0
2026-03-23 15:32:17 +05:30
Priyanka Saggu 1ca2edbb59 security: Update trivy-action to v0.35.0
Updates aquasecurity/trivy-action from mutable references to SHA-pinned
version to address security vulnerabilities.

- Updates to v0.35.0 (57a97c7e)
- Pins to specific SHA for immutability
- Addresses issue: aquasecurity/trivy#10425

Signed-off-by: Priyanka Saggu <priyankasaggu11929@gmail.com>
2026-03-22 18:42:48 +01:00
Kubernetes Prow Robot 0fafc09fff Merge pull request #1844 from a7i/extend-podlifetime-transitions
Extend PodLifeTime with condition, exit code, owner kind, and transition time filters
2026-03-07 01:04:21 +05:30
Amir Alavi a4391ea73b Extract shared container state matching helpers into podutil
Move container waiting/terminated state checking from PodLifeTime and
RemovePodsHavingTooManyRestarts into podutil as separate exported helpers:
HasMatchingContainerWaitingState and HasMatchingContainerTerminatedState.
Each plugin composes only the helpers it needs.
2026-03-06 12:18:05 -05:00
Amir Alavi a845ed3b36 Extend PodLifeTime with condition, exit code, owner kind, and transition time filters 2026-03-06 12:17:07 -05:00
Kubernetes Prow Robot ac815c26f6 Merge pull request #1848 from sammedsingalkar09/master
update golang semconv dependencies
2026-03-06 20:00:22 +05:30
sammedsingalkar09 e76287fbbf update go dependencies 2026-03-06 11:24:00 +05:30
Kubernetes Prow Robot 751ba2e76e Merge pull request #1847 from a7i/fix/upgrade-codeql-action-v4
fix(ci): upgrade codeql-action to v4 and clean up security workflow
2026-03-05 09:06:18 +05:30
Amir Alavi d82437286b fix(ci): upgrade codeql-action to v4 and clean up security workflow
CodeQL Action v1 and v2 have been deprecated. Update
upload-sarif to v4, remove unnecessary strategy block
(missing required matrix property), and remove invalid
exit-code input from the upload-sarif step.
2026-03-04 22:06:29 -05:00
Kubernetes Prow Robot 905e762603 Merge pull request #1842 from ingvagabund/data-races
fix: resolve detected data races
2026-02-25 21:38:25 +05:30
Jan Chaloupka cbdab93459 fix: resolve detected data races 2026-02-25 16:38:10 +01:00
Kubernetes Prow Robot af6e2adf42 Merge pull request #1838 from a7i/helm-icon
Change icon URL in Chart.yaml
2026-02-24 20:05:36 +05:30
Amir Alavi c5d8ec102b chart: allow overriding ServiceMonitor apiVersion 2026-02-21 00:03:18 -05:00
Kamlesh Joshi 9bfdbe92e9 Add init containers support to Helm chart (#1826)
This commit adds support for init containers in the descheduler Helm chart,
allowing users to run initialization tasks before the main descheduler
container starts.

Changes:
- Add initContainers field to values.yaml with example usage
- Update deployment.yaml template to render init containers
- Update cronjob.yaml template to render init containers
- Bump chart version from 0.34.0 to 0.34.1

Init containers can be used for various purposes such as:
- Pre-loading configuration from external sources
- Waiting for dependencies to be ready
- Setting up required files or permissions
- Running security scans or compliance checks

Example usage in values.yaml:
initContainers:
  - name: init-config
    image: busybox:1.28
    command: ['sh', '-c', 'echo Initializing && sleep 5']

Signed-off-by: kjoshi <kjoshi@egnyte.com>
2026-02-21 08:03:39 +05:30
Cayla Fauver 9e9595357a Update helm RBAC to account for pvc failure on 0.35.0 (#1836)
* Synchronize helm clusterrole RBAC with base yaml

I noticed in v0.35.

```
E0219 23:53:57.761596       1 reflector.go:204] "Failed to watch" err="failed to list *v1.PersistentVolumeClaim: persistentvolumeclaims is forbidden: User \"system:serviceaccount:kube-system:descheduler\" cannot list resource \"persistentvolumeclaims\" in API group \"\" at the cluster scope" logger="UnhandledError" reflector="k8s.io/client-go/informers/factory.go:161" type="*v1.PersistentVolumeClaim"
```

I saw it in rbac.yaml https://github.com/kubernetes-sigs/descheduler/blob/bec9cd38d01eab2d35f1d76b1b9845649e11bffa/kubernetes/base/rbac.yaml#L38-L40

So I figured this just needed a bump

* remove dupe

* undo version change
2026-02-21 06:17:38 +05:30
Amir Alavi 2b356b25bf Change icon URL in Chart.yaml
Updated the icon URL to point to the new logo.
2025-11-25 21:32:49 -05:00
2103 changed files with 117789 additions and 127279 deletions
+1
View File
@@ -0,0 +1 @@
vendor/**
+5 -5
View File
@@ -20,25 +20,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
with:
version: v3.15.1
- uses: actions/setup-python@v5.1.1
- uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
with:
python-version: 3.12
- uses: actions/setup-go@v5
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: 'go.mod'
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.8.0
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
with:
version: v3.11.0
+16 -4
View File
@@ -7,19 +7,31 @@ jobs:
deploy:
strategy:
matrix:
k8s-version: ["v1.35.1"]
descheduler-version: ["v0.35.1"]
k8s-version: ["v1.36.1"]
build-kind-node-image: [true]
kind-node-image: ["localhost/kindest/node:v1.36.1"]
descheduler-version: ["v0.36.0"]
descheduler-api: ["v1alpha2"]
manifest: ["deployment"]
kind-version: ["v0.31.0"] # keep in sync with test/run-e2e-tests.sh
kind-version: ["v0.31.0"] # keep in sync with test/lib/e2e-versions.env
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # git ls-remote https://github.com/actions/checkout.git refs/tags/v6.0.2
- name: Install kind
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # git ls-remote https://github.com/helm/kind-action.git refs/tags/v1.14.0
with:
version: ${{ matrix.kind-version }}
kubectl_version: ${{ matrix.k8s-version }}
install_only: true
- name: Build kind node image
if: ${{ matrix.build-kind-node-image == true }}
run: |
kind build node-image ${{ matrix.k8s-version }} --image ${{ matrix.kind-node-image }}
- name: Create kind cluster
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # git ls-remote https://github.com/helm/kind-action.git refs/tags/v1.14.0
with:
node_image: kindest/node:${{ matrix.k8s-version }}
node_image: ${{ matrix.kind-node-image }}
kubectl_version: ${{ matrix.k8s-version }}
config: test/kind-config.yaml
version: ${{ matrix.kind-version }}
+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
with:
fetch-depth: 0
@@ -23,12 +23,12 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v4.2.0
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0
with:
version: v3.15.1
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CR_RELEASE_NAME_TEMPLATE: "descheduler-helm-chart-{{ .Version }}"
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
@@ -29,7 +29,7 @@ jobs:
IMAGE_TAG=${HELM_IMAGE_TAG:-security-test}
VERSION=security-test make image
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: 'descheduler:security-test'
format: 'sarif'
@@ -38,6 +38,6 @@ jobs:
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@7c1e4cf0b20d7c1872b26569c00ba908797a59bf # v4
with:
sarif_file: 'trivy-results.sarif'
+1 -1
View File
@@ -11,7 +11,7 @@
# 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.
FROM golang:1.25.1
FROM golang:1.26.0
WORKDIR /go/src/sigs.k8s.io/descheduler
COPY . .
+2 -2
View File
@@ -29,7 +29,7 @@ LDFLAGS=-ldflags "-X ${LDFLAG_LOCATION}.version=${VERSION} -X ${LDFLAG_LOCATION}
GOLANGCI_VERSION := v2.8.0
HAS_GOLANGCI := $(shell ls _output/bin/golangci-lint 2> /dev/null)
GOFUMPT_VERSION := v0.7.0
GOFUMPT_VERSION := v0.10.0
HAS_GOFUMPT := $(shell command -v gofumpt 2> /dev/null)
GO_VERSION := $(shell (command -v jq > /dev/null && (go mod edit -json | jq -r .Go)) || (sed -En 's/^go (.*)$$/\1/p' go.mod))
@@ -146,7 +146,7 @@ verify-gen:
lint:
ifndef HAS_GOLANGCI
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./_output/bin ${GOLANGCI_VERSION}
GOBIN=$(CURRENT_DIR)/_output/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_VERSION}
endif
./_output/bin/golangci-lint run -v
./_output/bin/golangci-lint fmt -v
+3 -1
View File
@@ -33,11 +33,12 @@ but relies on the default scheduler for that.
## ⚠️ Documentation Versions by Release
If you are using a published release of Descheduler (such as
`registry.k8s.io/descheduler/descheduler:v0.35.0`), follow the documentation in
`registry.k8s.io/descheduler/descheduler:v0.36.0`), follow the documentation in
that version's release branch, as listed below:
|Descheduler Version|Docs link|
|---|---|
|v0.36.x|[`release-1.36`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.36/README.md)|
|v0.35.x|[`release-1.35`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.35/README.md)|
|v0.34.x|[`release-1.34`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.34/README.md)|
|v0.33.x|[`release-1.33`](https://github.com/kubernetes-sigs/descheduler/blob/release-1.33/README.md)|
@@ -1159,6 +1160,7 @@ packages that it is compiled with.
| Descheduler | Supported Kubernetes Version |
|-------------|------------------------------|
| v0.36 | v1.36 |
| v0.35 | v1.35 |
| v0.34 | v1.34 |
| v0.33 | v1.33 |
+2 -2
View File
@@ -1,7 +1,7 @@
apiVersion: v1
name: descheduler
version: 0.35.1
appVersion: 0.35.1
version: 0.36.0
appVersion: 0.36.0
description: Descheduler for Kubernetes is used to rebalance clusters by evicting pods that can potentially be scheduled on better nodes. In the current implementation, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that.
keywords:
- kubernetes
+9
View File
@@ -62,9 +62,17 @@ The following table lists the configurable parameters of the _descheduler_ chart
| `ttlSecondsAfterFinished` | If set, configure `ttlSecondsAfterFinished` for the _descheduler_ job | `nil` |
| `deschedulingInterval` | If using kind:Deployment, sets time between consecutive descheduler executions. | `5m` |
| `replicas` | The replica count for Deployment | `1` |
| `revisionHistoryLimit | The revision history limit for Deployment | `10` |
| `leaderElection` | The options for high availability when running replicated components | _see values.yaml_ |
| `podDisruptionBudget.enabled` | If `true` and `kind` is `Deployment`, create a PodDisruptionBudget for the descheduler | `false` |
| `podDisruptionBudget.minAvailable` | Minimum descheduler pods that must stay available (mutually exclusive with `maxUnavailable`) | `1` |
| `podDisruptionBudget.maxUnavailable`| Maximum descheduler pods that can be unavailable (mutually exclusive with `minAvailable`) | `""` |
| `podDisruptionBudget.annotations` | Annotations to add to the PodDisruptionBudget | `{}` |
| `cmdOptions` | The options to pass to the _descheduler_ command | _see values.yaml_ |
| `priorityClassName` | The name of the priority class to add to pods | `system-cluster-critical` |
| `schedulerName` | The name of the scheduler used to schedule the descheduler cronjob/deployment pods | `""` |
| `runtimeClassName` | The RuntimeClass applied to the descheduler cronjob/deployment pods | `""` |
| `hostUsers` | If set (`true`/`false`), sets `hostUsers` on the pod spec to control user-namespace sharing | `nil` |
| `rbac.create` | If `true`, create & use RBAC resources | `true` |
| `resources` | Descheduler container CPU and memory requests/limits | _see values.yaml_ |
| `serviceAccount.create` | If `true`, create a service account for the cron job | `true` |
@@ -79,6 +87,7 @@ The following table lists the configurable parameters of the _descheduler_ chart
| `nodeSelector` | Node selectors to run the descheduler cronjob/deployment on specific nodes | `nil` |
| `service.enabled` | If `true`, create a service for deployment | `false` |
| `serviceMonitor.enabled` | If `true`, create a ServiceMonitor for deployment | `false` |
| `serviceMonitor.apiVersion` | API version used by the ServiceMonitor resource | `monitoring.coreos.com/v1` |
| `serviceMonitor.namespace` | The namespace where Prometheus expects to find service monitors | `nil` |
| `serviceMonitor.additionalLabels` | Add custom labels to the ServiceMonitor resource | `{}` |
| `serviceMonitor.interval` | The scrape interval. If not set, the Prometheus default scrape interval is used | `nil` |
@@ -17,7 +17,7 @@ rules:
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list", "delete"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods/eviction"]
verbs: ["create"]
@@ -87,6 +87,15 @@ spec:
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName }}
{{- end }}
{{- if .Values.schedulerName }}
schedulerName: {{ .Values.schedulerName }}
{{- end }}
{{- if .Values.runtimeClassName }}
runtimeClassName: {{ .Values.runtimeClassName }}
{{- end }}
{{- if kindIs "bool" .Values.hostUsers }}
hostUsers: {{ .Values.hostUsers }}
{{- end }}
serviceAccountName: {{ template "descheduler.serviceAccountName" . }}
{{- if kindIs "bool" .Values.automountServiceAccountToken }}
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
@@ -18,6 +18,7 @@ spec:
{{- else }}
replicas: 1
{{- end }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "descheduler.selectorLabels" . | nindent 6 }}
@@ -41,6 +42,15 @@ spec:
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName }}
{{- end }}
{{- if .Values.schedulerName }}
schedulerName: {{ .Values.schedulerName }}
{{- end }}
{{- if .Values.runtimeClassName }}
runtimeClassName: {{ .Values.runtimeClassName }}
{{- end }}
{{- if kindIs "bool" .Values.hostUsers }}
hostUsers: {{ .Values.hostUsers }}
{{- end }}
serviceAccountName: {{ template "descheduler.serviceAccountName" . }}
{{- if kindIs "bool" .Values.automountServiceAccountToken }}
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
@@ -0,0 +1,30 @@
{{- if and (eq .Values.kind "Deployment") (.Values.podDisruptionBudget).enabled }}
{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }}
apiVersion: policy/v1
{{- else }}
apiVersion: policy/v1beta1
{{- end }}
kind: PodDisruptionBudget
metadata:
name: {{ template "descheduler.fullname" . }}
namespace: {{ include "descheduler.namespace" . }}
labels:
{{- include "descheduler.labels" . | nindent 4 }}
{{- with .Values.podDisruptionBudget.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.podDisruptionBudget.minAvailable }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
{{- with .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }}
unhealthyPodEvictionPolicy: {{ . }}
{{- end }}
selector:
matchLabels:
{{- include "descheduler.selectorLabels" . | nindent 6 }}
{{- end }}
@@ -1,6 +1,6 @@
{{- if eq .Values.kind "Deployment" }}
{{- if eq .Values.serviceMonitor.enabled true }}
apiVersion: monitoring.coreos.com/v1
apiVersion: {{ .Values.serviceMonitor.apiVersion | default "monitoring.coreos.com/v1" }}
kind: ServiceMonitor
metadata:
name: {{ template "descheduler.fullname" . }}-servicemonitor
@@ -15,3 +15,53 @@ tests:
asserts:
- isKind:
of: CronJob
- it: sets schedulerName when provided
set:
schedulerName: custom-scheduler
template: templates/cronjob.yaml
asserts:
- equal:
path: spec.jobTemplate.spec.template.spec.schedulerName
value: custom-scheduler
- it: sets runtimeClassName when provided
set:
runtimeClassName: gvisor
template: templates/cronjob.yaml
asserts:
- equal:
path: spec.jobTemplate.spec.template.spec.runtimeClassName
value: gvisor
- it: omits schedulerName and runtimeClassName by default
template: templates/cronjob.yaml
asserts:
- notExists:
path: spec.jobTemplate.spec.template.spec.schedulerName
- notExists:
path: spec.jobTemplate.spec.template.spec.runtimeClassName
- it: sets hostUsers to false when configured
set:
hostUsers: false
template: templates/cronjob.yaml
asserts:
- equal:
path: spec.jobTemplate.spec.template.spec.hostUsers
value: false
- it: sets hostUsers to true when configured
set:
hostUsers: true
template: templates/cronjob.yaml
asserts:
- equal:
path: spec.jobTemplate.spec.template.spec.hostUsers
value: true
- it: omits hostUsers by default
template: templates/cronjob.yaml
asserts:
- notExists:
path: spec.jobTemplate.spec.template.spec.hostUsers
@@ -47,3 +47,53 @@ tests:
- contains:
path: spec.template.spec.containers[0].args
content: --leader-elect-resource-namespace=typo
- it: sets schedulerName when provided
set:
schedulerName: custom-scheduler
template: templates/deployment.yaml
asserts:
- equal:
path: spec.template.spec.schedulerName
value: custom-scheduler
- it: sets runtimeClassName when provided
set:
runtimeClassName: gvisor
template: templates/deployment.yaml
asserts:
- equal:
path: spec.template.spec.runtimeClassName
value: gvisor
- it: omits schedulerName and runtimeClassName by default
template: templates/deployment.yaml
asserts:
- notExists:
path: spec.template.spec.schedulerName
- notExists:
path: spec.template.spec.runtimeClassName
- it: sets hostUsers to false when configured
set:
hostUsers: false
template: templates/deployment.yaml
asserts:
- equal:
path: spec.template.spec.hostUsers
value: false
- it: sets hostUsers to true when configured
set:
hostUsers: true
template: templates/deployment.yaml
asserts:
- equal:
path: spec.template.spec.hostUsers
value: true
- it: omits hostUsers by default
template: templates/deployment.yaml
asserts:
- notExists:
path: spec.template.spec.hostUsers
@@ -0,0 +1,75 @@
suite: Test Descheduler PodDisruptionBudget
templates:
- "*.yaml"
release:
name: descheduler
tests:
- it: is not created for the default CronJob kind even when enabled
template: templates/poddisruptionbudget.yaml
set:
podDisruptionBudget:
enabled: true
asserts:
- hasDocuments:
count: 0
- it: is not created for Deployment kind unless enabled
template: templates/poddisruptionbudget.yaml
set:
kind: Deployment
asserts:
- hasDocuments:
count: 0
- it: creates a PodDisruptionBudget for Deployment kind when enabled
template: templates/poddisruptionbudget.yaml
set:
kind: Deployment
podDisruptionBudget:
enabled: true
asserts:
- isKind:
of: PodDisruptionBudget
- equal:
path: spec.minAvailable
value: 1
- notExists:
path: spec.maxUnavailable
- equal:
path: spec.selector.matchLabels["app.kubernetes.io/name"]
value: descheduler
- it: supports maxUnavailable instead of minAvailable
template: templates/poddisruptionbudget.yaml
set:
kind: Deployment
podDisruptionBudget:
enabled: true
minAvailable: ""
maxUnavailable: 1
asserts:
- equal:
path: spec.maxUnavailable
value: 1
- notExists:
path: spec.minAvailable
- it: supports unhealthyPodEvictionPolicy and annotations
template: templates/poddisruptionbudget.yaml
set:
kind: Deployment
podDisruptionBudget:
enabled: true
unhealthyPodEvictionPolicy: IfHealthyBudget
annotations:
example.com/team: platform
asserts:
- equal:
path: spec.unhealthyPodEvictionPolicy
value: IfHealthyBudget
- equal:
path: metadata.annotations["example.com/team"]
value: platform
@@ -0,0 +1,31 @@
suite: Test ServiceMonitor
templates:
- "*.yaml"
release:
name: descheduler
set:
kind: Deployment
serviceMonitor:
enabled: true
tests:
- it: uses default ServiceMonitor apiVersion
template: templates/servicemonitor.yaml
asserts:
- equal:
path: apiVersion
value: monitoring.coreos.com/v1
- it: supports overriding ServiceMonitor apiVersion
set:
serviceMonitor:
enabled: true
apiVersion: azmonitoring.coreos.com/v1
template: templates/servicemonitor.yaml
asserts:
- equal:
path: apiVersion
value: azmonitoring.coreos.com/v1
+30
View File
@@ -68,6 +68,8 @@ deschedulingInterval: 5m
# only if that node is in the same zone as at least one already-running descheduler
replicas: 1
revisionHistoryLimit: 10
# Specifies whether Leader Election resources should be created
# Required when running as a Deployment
# NOTE: Leader election can't be activated if DryRun enabled
@@ -80,6 +82,20 @@ leaderElection: {}
# resourceName: "descheduler"
# resourceNamespace: "kube-system"
# Pod Disruption Budget for the descheduler Deployment. Optional.
# Only rendered when kind is "Deployment". The descheduler runs a single replica
# by default, so this is disabled by default. Enable it for highly available
# setups (replicas > 1 with leaderElection enabled) to keep a descheduler pod
# available during voluntary node disruptions.
podDisruptionBudget:
enabled: false
# minAvailable and maxUnavailable are mutually exclusive; set only one.
minAvailable: 1
maxUnavailable: ""
# See https://kubernetes.io/docs/tasks/run-application/configure-pdb/
# unhealthyPodEvictionPolicy: IfHealthyBudget
annotations: {}
command:
- "/bin/descheduler"
@@ -150,6 +166,19 @@ deschedulerPolicy:
priorityClassName: system-cluster-critical
# schedulerName is the name of the scheduler used to schedule the descheduler pods.
# Leave empty to use the cluster default scheduler.
schedulerName: ""
# runtimeClassName is the RuntimeClass applied to the descheduler pods.
# Leave empty to use the cluster default runtime.
runtimeClassName: ""
# Set the Pod spec `hostUsers` field to control user-namespace sharing.
# Default is not set (the cluster default applies). Set to `false` to opt into
# user-namespace isolation, or `true` to share the host user namespace.
# hostUsers: false
nodeSelector: {}
# foo: bar
@@ -255,6 +284,7 @@ service:
serviceMonitor:
enabled: false
apiVersion: "monitoring.coreos.com/v1"
# The namespace where Prometheus expects to find service monitors.
# namespace: ""
# Add custom labels to the ServiceMonitor resource
+1 -1
View File
@@ -7,7 +7,7 @@ timeout: 3600s
options:
substitution_option: ALLOW_LOOSE
steps:
- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90'
- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud@sha256:ff388e0dc16351e96f8464e2e185b74a7578a5ccb7a112cf3393468e59e6e2d2' # v20260205-38cfa9523f
entrypoint: make
env:
- DOCKER_CLI_EXPERIMENTAL=enabled
+6 -4
View File
@@ -28,13 +28,13 @@ import (
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
"sigs.k8s.io/descheduler/pkg/descheduler"
"sigs.k8s.io/descheduler/pkg/features"
"sigs.k8s.io/descheduler/pkg/tracing"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/mux"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
_ "k8s.io/component-base/logs/json/register"
@@ -49,7 +49,6 @@ func NewDeschedulerCommand(out io.Writer) *cobra.Command {
klog.ErrorS(err, "unable to initialize server")
}
featureGate := featuregate.NewFeatureGate()
logConfig := logsapi.NewLoggingConfiguration()
cmd := &cobra.Command{
@@ -58,7 +57,10 @@ func NewDeschedulerCommand(out io.Writer) *cobra.Command {
Long: "The descheduler evicts pods which may be bound to less desired nodes",
PreRunE: func(cmd *cobra.Command, args []string) error {
logs.InitLogs()
if logsapi.ValidateAndApply(logConfig, featureGate); err != nil {
if err := features.DefaultMutableFeatureGate.SetFromMap(s.FeatureGates); err != nil {
return err
}
if err := logsapi.ValidateAndApply(logConfig, features.DefaultMutableFeatureGate); err != nil {
return err
}
descheduler.SetupPlugins()
@@ -80,9 +82,9 @@ func NewDeschedulerCommand(out io.Writer) *cobra.Command {
}
cmd.SetOut(out)
flags := cmd.Flags()
runtime.Must(logsapi.AddFeatureGates(features.DefaultMutableFeatureGate))
s.AddFlags(flags)
runtime.Must(logsapi.AddFeatureGates(featureGate))
logsapi.AddFlags(logConfig, flags)
return cmd
+71
View File
@@ -0,0 +1,71 @@
/*
Copyright 2026 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package app
import (
"io"
"strings"
"testing"
)
func TestNewDeschedulerCommand_LoggingAlphaOptions(t *testing.T) {
tests := []struct {
name string
args []string
wantErr string
}{
{
name: "LoggingAlphaOptions=false rejects --log-json-split-stream",
args: []string{
"--logging-format=json",
"--log-json-split-stream",
"--feature-gates=LoggingAlphaOptions=false",
},
wantErr: "LoggingAlphaOptions is disabled",
},
{
name: "LoggingAlphaOptions=true accepts --log-json-split-stream",
args: []string{
"--logging-format=json",
"--log-json-split-stream",
"--feature-gates=LoggingAlphaOptions=true",
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cmd := NewDeschedulerCommand(io.Discard)
if err := cmd.ParseFlags(tc.args); err != nil {
t.Fatalf("ParseFlags(%v): %v", tc.args, err)
}
err := cmd.PreRunE(cmd, cmd.Flags().Args())
if tc.wantErr == "" {
if err != nil {
t.Fatalf("expected PreRunE to succeed, got error: %v", err)
}
return
}
if err == nil {
t.Fatalf("expected PreRunE to fail with %q, got nil", tc.wantErr)
}
if !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("expected PreRunE error to contain %q, got :%v", tc.wantErr, err)
}
})
}
}
+4
View File
@@ -26,7 +26,10 @@ descheduler [flags]
--feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:
AllAlpha=true|false (ALPHA - default=false)
AllBeta=true|false (BETA - default=false)
ContextualLogging=true|false (BETA - default=true)
EvictionsInBackground=true|false (ALPHA - default=false)
LoggingAlphaOptions=true|false (ALPHA - default=false)
LoggingBetaOptions=true|false (BETA - default=true)
-h, --help help for descheduler
--http2-max-streams-per-connection int The limit that the server gives to clients for the maximum number of streams in an HTTP/2 connection. Zero means to use golang's default.
--kubeconfig string File with kube configuration. Deprecated, use client-connection-kubeconfig instead.
@@ -57,6 +60,7 @@ descheduler [flags]
--tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be used.
Preferred values: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256.
Insecure values: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_RC4_128_SHA.
--tls-curve-preferences int32Slice Comma-separated list of numeric Go crypto/tls CurveID values, as the allowed key exchange mechanisms for the server. The supported values depend on the Go version used. See https://pkg.go.dev/crypto/tls#CurveID for values supported for each Go version. The order of the list is ignored, and key exchange mechanisms are chosen by Go from this list using an internal preference order. If omitted, the default Go curves will be used. (default [])
--tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13
--tls-private-key-file string File containing the default x509 private key matching --tls-cert-file.
--tls-sni-cert-key namedCertKey A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. The domain patterns also allow IP addresses, but IPs should only be used if the apiserver has visibility to the IP address requested by a client. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: "example.crt,example.key" or "foo.crt,foo.key:*.foo.com,foo.com". (default [])
+19
View File
@@ -29,6 +29,25 @@ View all CLI options.
```
## Run Tests
### All-in-one e2e (kind cluster created by the test script)
```
KIND_E2E=1 make test-e2e
```
Optional skip flags (set to any non-empty value to skip that step):
| Variable | Skips |
|----------|-------|
| `SKIP_INSTALL` | kind node-image build and cluster create |
| `SKIP_KUBECTL_INSTALL` | kubectl download |
| `SKIP_KIND_INSTALL` | kind binary download |
| `SKIP_KUBEVIRT_INSTALL` | KubeVirt operator install |
| `SKIP_METRICS_SERVER_INSTALL` | metrics-server install |
### Manual kind cluster (iterative development)
```
GOOS=linux make dev-image
make kind-multi-node
+1 -1
View File
@@ -4,7 +4,7 @@ Starting with descheduler release v0.10.0 container images are available in the
Descheduler Version | Container Image | Architectures |
------------------- |-------------------------------------------------|-------------------------|
v0.35.1 | registry.k8s.io/descheduler/descheduler:v0.35.1 | AMD64<br>ARM64<br>ARMv7 |
v0.36.0 | registry.k8s.io/descheduler/descheduler:v0.36.0 | AMD64<br>ARM64<br>ARMv7 |
v0.35.0 | registry.k8s.io/descheduler/descheduler:v0.35.0 | AMD64<br>ARM64<br>ARMv7 |
v0.34.0 | registry.k8s.io/descheduler/descheduler:v0.34.0 | AMD64<br>ARM64<br>ARMv7 |
v0.33.0 | registry.k8s.io/descheduler/descheduler:v0.33.0 | AMD64<br>ARM64<br>ARMv7 |
+71 -68
View File
@@ -1,10 +1,8 @@
module sigs.k8s.io/descheduler
go 1.25.0
go 1.26.0
toolchain go1.25.1
godebug default=go1.25
godebug default=go1.26
require (
github.com/client9/misspell v0.3.4
@@ -13,25 +11,25 @@ require (
github.com/prometheus/common v0.67.5
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
go.opentelemetry.io/otel v1.41.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0
go.opentelemetry.io/otel/sdk v1.41.0
go.opentelemetry.io/otel/trace v1.41.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
google.golang.org/grpc v1.79.1
k8s.io/api v0.35.1
k8s.io/apimachinery v0.35.1
k8s.io/apiserver v0.35.1
k8s.io/client-go v0.35.1
k8s.io/code-generator v0.35.1
k8s.io/component-base v0.35.1
k8s.io/component-helpers v0.35.1
k8s.io/klog/v2 v2.130.1
k8s.io/metrics v0.35.1
k8s.io/utils v0.0.0-20260108192941-914a6e750570
kubevirt.io/api v1.3.0
kubevirt.io/client-go v1.3.0
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
google.golang.org/grpc v1.82.1
k8s.io/api v0.36.1
k8s.io/apimachinery v0.36.1
k8s.io/apiserver v0.36.1
k8s.io/client-go v0.36.1
k8s.io/code-generator v0.36.1
k8s.io/component-base v0.36.1
k8s.io/component-helpers v0.36.1
k8s.io/klog/v2 v2.140.0
k8s.io/metrics v0.36.1
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2
kubevirt.io/api v1.9.0
kubevirt.io/client-go v1.9.0
sigs.k8s.io/mdtoc v1.4.0
sigs.k8s.io/yaml v1.6.0
)
@@ -40,21 +38,20 @@ require (
cel.dev/expr v0.25.1 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
@@ -62,78 +59,84 @@ require (
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/swag v0.25.4 // indirect
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
github.com/go-openapi/swag/loading v0.25.4 // indirect
github.com/go-openapi/swag/mangling v0.25.4 // indirect
github.com/go-openapi/swag/netutils v0.25.4 // indirect
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/google/cel-go v0.30.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // 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/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mmarkdown/mmark v2.0.40+incompatible // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 // indirect
github.com/openshift/custom-resource-status v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
go.etcd.io/etcd/client/v3 v3.6.5 // indirect
go.etcd.io/etcd/api/v3 v3.6.8 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.8 // indirect
go.etcd.io/etcd/client/v3 v3.6.8 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/protobuf v1.36.11 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.48.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.30.0 // indirect
k8s.io/apiextensions-apiserver v0.34.3 // indirect
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect
k8s.io/kms v0.35.1 // indirect
k8s.io/kube-openapi v0.30.0 // indirect
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
k8s.io/kms v0.36.1 // indirect
k8s.io/kube-openapi v0.31.0 // indirect
k8s.io/streaming v0.36.1 // indirect
kubevirt.io/containerized-data-importer-api v1.64.0 // indirect
kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/release-utils v0.8.3 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
)
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102
+204 -303
View File
@@ -1,18 +1,14 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
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=
@@ -22,23 +18,17 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -52,9 +42,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -66,8 +55,6 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
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-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
@@ -76,8 +63,6 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
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/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -92,31 +77,52 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
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/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -129,15 +135,14 @@ 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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc=
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/cel-go v0.30.0 h1:ll54AkzKunWkBn9wSoiUXbFZXYZTkdJGNXTBXUoolGo=
github.com/google/cel-go v0.30.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -145,7 +150,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -153,10 +157,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -164,25 +166,18 @@ github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97Dwqy
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
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=
@@ -204,11 +199,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
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/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mmarkdown/mmark v2.0.40+incompatible h1:vMeUeDzBK3H+/mU0oMVfMuhSXJlIA+DE/DMPQNAj5C4=
github.com/mmarkdown/mmark v2.0.40+incompatible/go.mod h1:Uvmoz7tvsWpr7bMVxIpqZPyN3FbOtzDmnsJDFp7ltJs=
@@ -234,54 +227,16 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
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.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
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/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
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.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 h1:t/CahSnpqY46sQR01SoS+Jt0jtjgmhgE6lFmRnO4q70=
github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183/go.mod h1:4VWG+W22wrB4HfBL88P40DxLEpSOaiBVxUnfalfJo9k=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4=
github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -294,10 +249,9 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -314,8 +268,6 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -345,63 +297,46 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM=
go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q=
go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50=
go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw=
go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY=
go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8=
go.etcd.io/etcd/pkg/v3 v3.6.8 h1:Xe+LIL974spy8b4nEx3H0KMr1ofq3r0kh6FbU3aw4es=
go.etcd.io/etcd/pkg/v3 v3.6.8/go.mod h1:TRibVNe+FqJIe1abOAA1PsuQ4wqO87ZaOoprg09Tn8c=
go.etcd.io/etcd/server/v3 v3.6.8 h1:U2strdSEy1U8qcSzRIdkYpvOPtBy/9i/IfaaCI9flZ4=
go.etcd.io/etcd/server/v3 v3.6.8/go.mod h1:88dCtwUnSirkUoJbflQxxWXqtBSZa6lSG0Kuej+dois=
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0 h1:w/o339tDd6Qtu3+ytwt+/jon2yjAs3Ot8Xq8pelfhSo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.66.0/go.mod h1:pdhNtM9C4H5fRdrnwO7NjxzQWhKSSxCHk/KluVqDVC0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0/go.mod h1:yk5LXEYhsL2htyDNJbEq7fWzNEigeEdV5xBF/Y+kAv0=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
@@ -412,28 +347,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -441,25 +374,25 @@ 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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
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/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
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=
@@ -478,40 +411,30 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/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/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -520,16 +443,19 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -551,92 +477,81 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc
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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4=
golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -649,30 +564,25 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
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/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.34.1-0.20250613162507-3f93fece84c7/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
@@ -682,29 +592,23 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
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=
@@ -716,11 +620,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -747,27 +649,26 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ=
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q=
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g=
k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0=
k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU=
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs=
k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4=
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM=
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA=
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=
k8s.io/code-generator v0.35.1 h1:yLKR2la7Z9cWT5qmk67ayx8xXLM4RRKQMnC8YPvTWRI=
k8s.io/code-generator v0.35.1/go.mod h1:F2Fhm7aA69tC/VkMXLDokdovltXEF026Tb9yfQXQWKg=
k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE=
k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs=
k8s.io/component-helpers v0.35.1 h1:vwQ/cAfnVwaPeSXTu4DdK3d3n11Lugc5vMb6EV809ZY=
k8s.io/component-helpers v0.35.1/go.mod h1:HQqMwUk68Yyxgj92dJ+J1w/qbx9M0QR0eZ680m/o+Rk=
k8s.io/code-generator v0.36.1 h1:5bHQ7NbBcFFLHcoyo/hgU3m2tQV5RLz2nv4QNDlsbXc=
k8s.io/code-generator v0.36.1/go.mod h1:oCv8WmrW2RGdcMyvSk1aYbBfSs51ggtSFQr1YNeuAuo=
k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI=
k8s.io/component-helpers v0.36.1 h1:BTrr5fzNSm8TkQfXrKT3N9ioWwiC4n2FTIwGTUo/ccg=
k8s.io/component-helpers v0.36.1/go.mod h1:s38HnzKQRurbUnhI5IV8GwyL/a3lVuNCYZMTd+rITMM=
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/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ=
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
@@ -775,30 +676,31 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kms v0.35.1 h1:kjv2r9g1mY7uL+l1RhyAZvWVZIA/4qIfBHXyjFGLRhU=
k8s.io/kms v0.35.1/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY=
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/metrics v0.35.1 h1:MUcrUcWlq81XiripkydzCGsY9zQawDXfP9IICNNcVVw=
k8s.io/metrics v0.35.1/go.mod h1:9x7xWOAOiWzHA0vaqLgSE4PXF3vyT5ts5XIbx8OSjiI=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kms v0.36.1 h1:XdvKpywoW4k7YUHDh5uYP4mahJXECswHGfCddBBYLZs=
k8s.io/kms v0.36.1/go.mod h1:g91diTD9h0oJCCHkTb00krlF+Qm5HTnkWLi9Q/TpRoc=
k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102 h1:xs2ux1MvyrOdfKwS3vuFWrGuLgDOHk6id975Twx2Jss=
k8s.io/kube-openapi v0.0.0-20260512234627-ef417d054102/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY=
k8s.io/metrics v0.36.1 h1:MQPb+G4RhrKEpt8NETPssbW8QgGUc4Jbqu1jx+kPqGk=
k8s.io/metrics v0.36.1/go.mod h1:xqS8XcWLjDzo6E7DJm/GfjKpRKdN5/MtJAQFuV6nLUc=
k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4=
k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
kubevirt.io/api v1.3.0 h1:9sGElMmnRU50pGED+MPPD2OwQl4S5lvjCUjm+t0mI90=
kubevirt.io/api v1.3.0/go.mod h1:e6LkElYZZm8NcP2gKlFVHZS9pgNhIARHIjSBSfeiP1s=
kubevirt.io/client-go v1.3.0 h1:/HKn4exzwsctEVTwVtEFaeT9D2v4TgWr2SmxITVEZ/4=
kubevirt.io/client-go v1.3.0/go.mod h1:qmcJZvUjbmggY1pp7irO3zesBJj7wwGIWAdnYEoh3yc=
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 h1:IWo12+ei3jltSN5jQN1xjgakfvRSF3G3Rr4GXVOOy2I=
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1/go.mod h1:Y/8ETgHS1GjO89bl682DPtQOYEU/1ctPFBz6Sjxm4DM=
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc=
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc=
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
kubevirt.io/api v1.9.0 h1:FWqx9yGk/GHUP5o4IthGXZ8FeAlgVNv2It48wD86UUo=
kubevirt.io/api v1.9.0/go.mod h1:tFKAVaJMm7bXCMLxOoUdAqBprKAXoCa0sp0gBmnfBgY=
kubevirt.io/client-go v1.9.0 h1:ksR37OV0Zft6g/9YPFEjevlkRpTvWuyVE44zj9xGuGQ=
kubevirt.io/client-go v1.9.0/go.mod h1:cgGTWTqnghJdyiGXx4y9KXAHholmF2BwodUKXuQa9Ns=
kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9uEEDeToYDfRr0mbUxJc=
kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o=
kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY=
kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/mdtoc v1.4.0 h1:2pDEwJSjoVrGr5BPkG+LoLkYLKvgtGYurrBY8ul3SxQ=
@@ -809,11 +711,10 @@ sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxO
sigs.k8s.io/release-utils v0.8.3 h1:KtOtA4qDmzJyeQ2zkDsFVI25+NViwms/o5eL2NftFdA=
sigs.k8s.io/release-utils v0.8.3/go.mod h1:fp82Fma06OXBhEJ+GUJKqvcplDBomruK1R/1fWJnsrQ=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+2
View File
@@ -1,3 +1,5 @@
# Used by e2e tests (test/run-e2e-tests.sh) and make kind-multi-node.
# Worker nodes carry topology zone labels required by topology spread constraint e2e tests.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
+1 -1
View File
@@ -19,7 +19,7 @@
go::verify_version() {
GO_VERSION=($(go version))
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.23|go1.24|go1.25') ]]; then
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.24|go1.25|go1.26') ]]; then
echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt."
exit 1
fi
+1 -1
View File
@@ -15,7 +15,7 @@ rules:
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list", "delete"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods/eviction"]
verbs: ["create"]
+1 -1
View File
@@ -16,7 +16,7 @@ spec:
priorityClassName: system-cluster-critical
containers:
- name: descheduler
image: registry.k8s.io/descheduler/descheduler:v0.35.1
image: registry.k8s.io/descheduler/descheduler:v0.36.0
volumeMounts:
- mountPath: /policy-dir
name: policy-volume
+1 -1
View File
@@ -19,7 +19,7 @@ spec:
serviceAccountName: descheduler-sa
containers:
- name: descheduler
image: registry.k8s.io/descheduler/descheduler:v0.35.1
image: registry.k8s.io/descheduler/descheduler:v0.36.0
imagePullPolicy: IfNotPresent
command:
- "/bin/descheduler"
+1 -1
View File
@@ -14,7 +14,7 @@ spec:
priorityClassName: system-cluster-critical
containers:
- name: descheduler
image: registry.k8s.io/descheduler/descheduler:v0.35.1
image: registry.k8s.io/descheduler/descheduler:v0.36.0
volumeMounts:
- mountPath: /policy-dir
name: policy-volume
+5 -3
View File
@@ -235,8 +235,10 @@ func initDescheduler(t *testing.T, ctx context.Context, featureGates featuregate
if dryRun {
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (bool, error) {
for _, obj := range objects {
// Only check for nodes - secrets are handled by namespacedSharedInformerFactory
if _, ok := obj.(*v1.Node); !ok {
// Only check for nodes and pods - secrets are handled by namespacedSharedInformerFactory
switch obj.(type) {
case *v1.Node, *v1.Pod:
default:
continue
}
exists, err := descheduler.kubeClientSandbox.hasRuntimeObjectInIndexer(obj)
@@ -256,7 +258,7 @@ func initDescheduler(t *testing.T, ctx context.Context, featureGates featuregate
}
return true, nil
}); err != nil {
t.Fatalf("nodes did not propagate to the indexer: %v", err)
t.Fatalf("objects did not propagate to the indexer: %v", err)
}
}
+61 -12
View File
@@ -57,6 +57,7 @@ var (
type evictionRequestItem struct {
podName, podNamespace, podNodeName string
strategyName, profileName string
evictionAssumed bool
assumedTimestamp metav1.Time
}
@@ -68,6 +69,7 @@ type evictionRequestsCache struct {
requestsPerNamespace map[string]uint
requestsTotal uint
assumedRequestTimeoutSeconds uint
onAssumedTimeout func(item evictionRequestItem)
}
func newEvictionRequestsCache(assumedRequestTimeoutSeconds uint) *evictionRequestsCache {
@@ -94,6 +96,9 @@ func (erc *evictionRequestsCache) cleanCache(ctx context.Context) {
requestAgeSeconds := uint(metav1.Now().Sub(item.assumedTimestamp.Local()).Seconds())
if requestAgeSeconds > erc.assumedRequestTimeoutSeconds {
klog.V(4).InfoS("Assumed eviction request in background timed out, deleting", "timeout", erc.assumedRequestTimeoutSeconds, "podNamespace", item.podNamespace, "podName", item.podName)
if erc.onAssumedTimeout != nil {
erc.onAssumedTimeout(item)
}
erc.deleteItem(uid)
}
}
@@ -149,17 +154,35 @@ func (erc *evictionRequestsCache) addPod(pod *v1.Pod) {
erc.requestsTotal++
}
func (erc *evictionRequestsCache) assumePod(pod *v1.Pod) {
func (erc *evictionRequestsCache) assumePod(pod *v1.Pod, profileName, strategyName string) {
erc.mu.Lock()
defer erc.mu.Unlock()
uid := getPodKey(pod)
if _, exists := erc.requests[uid]; exists {
if item, exists := erc.requests[uid]; exists {
if item.evictionAssumed {
// Pod already assumed by a previous strategy/profile; first one wins.
return
}
// The informer's UpdateFunc called addPod (evictionAssumed=false) before
// the TooManyRequests response arrived. Upgrade the entry in place without
// bumping the counters a second time.
erc.requests[uid] = evictionRequestItem{
podNamespace: item.podNamespace,
podName: item.podName,
podNodeName: item.podNodeName,
strategyName: strategyName,
profileName: profileName,
evictionAssumed: true,
assumedTimestamp: metav1.NewTime(time.Now()),
}
return
}
erc.requests[uid] = evictionRequestItem{
podNamespace: pod.Namespace,
podName: pod.Name,
podNodeName: pod.Spec.NodeName,
strategyName: strategyName,
profileName: profileName,
evictionAssumed: true,
assumedTimestamp: metav1.NewTime(time.Now()),
}
@@ -191,6 +214,14 @@ func (erc *evictionRequestsCache) deletePod(pod *v1.Pod) {
}
}
func (erc *evictionRequestsCache) getPod(pod *v1.Pod) (evictionRequestItem, bool) {
erc.mu.RLock()
defer erc.mu.RUnlock()
uid := getPodKey(pod)
item, exists := erc.requests[uid]
return item, exists
}
func (erc *evictionRequestsCache) hasPod(pod *v1.Pod) bool {
erc.mu.RLock()
defer erc.mu.RUnlock()
@@ -263,6 +294,12 @@ func NewPodEvictor(
if featureGates.Enabled(features.EvictionsInBackground) {
erCache := newEvictionRequestsCache(assumedEvictionRequestTimeoutSeconds)
if podEvictor.metricsEnabled {
erCache.onAssumedTimeout = func(item evictionRequestItem) {
metrics.PodsEvicted.With(map[string]string{"result": "error", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
metrics.PodsEvictedTotal.With(map[string]string{"result": "error", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
}
}
handlerRegistration, err := podInformer.AddEventHandler(
cache.ResourceEventHandlerFuncs{
@@ -303,8 +340,18 @@ func NewPodEvictor(
}
// Remove completed/suceeeded or failed pods from the cache
if newPod.Status.Phase == v1.PodSucceeded || newPod.Status.Phase == v1.PodFailed {
klog.V(3).InfoS("Pod with eviction in background completed. Removing pod from the cache.", "pod", klog.KObj(newPod))
erCache.deletePod(newPod)
if item, exists := erCache.getPod(newPod); exists {
klog.V(3).InfoS("Pod with eviction in background completed. Removing pod from the cache.", "pod", klog.KObj(newPod))
if item.evictionAssumed && podEvictor.metricsEnabled {
result := "success"
if newPod.Status.Phase == v1.PodFailed {
result = "error"
}
metrics.PodsEvicted.With(map[string]string{"result": result, "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
metrics.PodsEvictedTotal.With(map[string]string{"result": result, "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
}
erCache.deletePod(newPod)
}
return
}
// Ignore any pod that does not have eviction in progress
@@ -347,8 +394,12 @@ func NewPodEvictor(
klog.ErrorS(nil, "Cannot convert to *v1.Pod", "obj", t)
return
}
if erCache.hasPod(pod) {
if item, exists := erCache.getPod(pod); exists {
klog.V(3).InfoS("Pod with eviction in background deleted/evicted. Removing pod from the cache.", "pod", klog.KObj(pod))
if item.evictionAssumed && podEvictor.metricsEnabled {
metrics.PodsEvicted.With(map[string]string{"result": "success", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
metrics.PodsEvictedTotal.With(map[string]string{"result": "success", "strategy": item.strategyName, "namespace": item.podNamespace, "node": item.podNodeName, "profile": item.profileName}).Inc()
}
}
erCache.deletePod(pod)
},
@@ -427,12 +478,6 @@ func (pe *PodEvictor) evictionRequestsPerNamespace(ns string) uint {
}
}
func (pe *PodEvictor) EvictionRequests(node *v1.Node) uint {
pe.mu.RLock()
defer pe.mu.RUnlock()
return pe.evictionRequestsTotal()
}
func (pe *PodEvictor) TotalEvictionRequests() uint {
pe.mu.RLock()
defer pe.mu.RUnlock()
@@ -608,7 +653,11 @@ func (pe *PodEvictor) evictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio
return true, nil
}
klog.V(3).InfoS("Eviction in background assumed", "pod", klog.KObj(pod))
pe.erCache.assumePod(pod)
pe.erCache.assumePod(pod, opts.ProfileName, opts.StrategyName)
if pe.metricsEnabled {
metrics.PodsEvicted.With(map[string]string{"result": "background", "strategy": opts.StrategyName, "namespace": pod.Namespace, "node": pod.Spec.NodeName, "profile": opts.ProfileName}).Inc()
metrics.PodsEvictedTotal.With(map[string]string{"result": "background", "strategy": opts.StrategyName, "namespace": pod.Namespace, "node": pod.Spec.NodeName, "profile": opts.ProfileName}).Inc()
}
return true, nil
}
}
+373
View File
@@ -39,6 +39,9 @@ import (
"k8s.io/klog/v2"
utilptr "k8s.io/utils/ptr"
metricstest "k8s.io/component-base/metrics/testutil"
deschedulermetrics "sigs.k8s.io/descheduler/metrics"
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
"sigs.k8s.io/descheduler/pkg/features"
"sigs.k8s.io/descheduler/pkg/utils"
@@ -463,6 +466,376 @@ func TestEvictionRequestsCacheCleanup(t *testing.T) {
}
}
func TestEvictionInBackgroundMetrics(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
defer cancel()
deschedulermetrics.Register()
deschedulermetrics.PodsEvicted.Reset()
deschedulermetrics.PodsEvictedTotal.Reset()
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
ownerRef1 := test.GetReplicaSetOwnerRefList()
updatePod := func(pod *v1.Pod) {
pod.Namespace = "dev"
pod.ObjectMeta.OwnerReferences = ownerRef1
}
updatePodWithEvictionAnnotation := func(pod *v1.Pod) {
updatePod(pod)
pod.Annotations = map[string]string{
EvictionRequestAnnotationKey: "",
}
}
// p1, p2: will be evicted in background
// p3, p4: will be evicted normally
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, updatePodWithEvictionAnnotation)
p2 := test.BuildTestPod("p2", 100, 0, node1.Name, updatePodWithEvictionAnnotation)
p3 := test.BuildTestPod("p3", 100, 0, node1.Name, updatePod)
p4 := test.BuildTestPod("p4", 100, 0, node1.Name, updatePod)
client := fakeclientset.NewSimpleClientset(node1, p1, p2, p3, p4)
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
podEvictor, err := NewPodEvictor(
ctx,
client,
eventRecorder,
sharedInformerFactory.Core().V1().Pods().Informer(),
initFeatureGates(),
NewOptions().WithMetricsEnabled(true),
)
if err != nil {
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
}
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
if action.GetSubresource() == "eviction" {
createAct, matched := action.(core.CreateActionImpl)
if !matched {
return false, nil, fmt.Errorf("unable to convert action to core.CreateActionImpl")
}
if eviction, matched := createAct.Object.(*policy.Eviction); matched {
if eviction.GetName() == "p1" || eviction.GetName() == "p2" {
return true, nil, &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonTooManyRequests,
Message: "Eviction triggered evacuation",
},
}
}
return true, nil, nil
}
}
return false, nil, nil
})
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
// Cycle 1: p1/p2 go to background, p3/p4 are evicted normally.
podEvictor.EvictPod(ctx, p1, evictOpts)
podEvictor.EvictPod(ctx, p2, evictOpts)
podEvictor.EvictPod(ctx, p3, evictOpts)
podEvictor.EvictPod(ctx, p4, evictOpts)
// After cycle 1: 2 "background" (p1, p2), 2 "success" (p3, p4).
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 2)
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 2)
// Cycle 2: p1/p2 are still running (background eviction in progress).
// Re-attempting them must not emit any additional metrics.
podEvictor.EvictPod(ctx, p1, evictOpts)
podEvictor.EvictPod(ctx, p2, evictOpts)
// Counts must be unchanged after cycle 2.
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 2)
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 2)
// The background evictions complete: p1 and p2 are deleted.
// The informer's DeleteFunc fires and emits a "success" metric for each assumed pod.
client.CoreV1().Pods(p1.Namespace).Delete(ctx, p1.Name, metav1.DeleteOptions{})
client.CoreV1().Pods(p2.Namespace).Delete(ctx, p2.Name, metav1.DeleteOptions{})
// Poll until DeleteFunc has fired for both pods (informer is async).
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
return !podEvictor.erCache.hasPod(p1) && !podEvictor.erCache.hasPod(p2), nil
}); err != nil {
t.Fatalf("Timed out waiting for background evictions to complete: %v", err)
}
// After deletion: "success" grows by 2 (p1, p2 completed), "background" stays at 2.
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 4)
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 2)
}
// TestEvictionInBackgroundMetrics_InformerRace reproduces the race where the
// informer's UpdateFunc calls addPod (evictionAssumed=false) before evictPod's
// assumePod call, causing the "success" metric to be silently dropped when the
// pod is later deleted.
//
// The race: evictPod sends the eviction request; the KubeVirt handler sets
// EvictionInProgressAnnotationKey before returning TooManyRequests, so the
// informer's UpdateFunc fires and calls addPod (evictionAssumed=false) while
// evictPod is still waiting for the HTTP response. When the response arrives,
// assumePod finds the entry already present and returns early, leaving
// evictionAssumed=false. DeleteFunc then skips the "success" metric.
func TestEvictionInBackgroundMetrics_InformerRace(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
defer cancel()
deschedulermetrics.Register()
deschedulermetrics.PodsEvicted.Reset()
deschedulermetrics.PodsEvictedTotal.Reset()
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
ownerRef1 := test.GetReplicaSetOwnerRefList()
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, func(pod *v1.Pod) {
pod.Namespace = "dev"
pod.ObjectMeta.OwnerReferences = ownerRef1
pod.Annotations = map[string]string{
EvictionRequestAnnotationKey: "",
}
})
client := fakeclientset.NewSimpleClientset(node1, p1)
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
podEvictor, err := NewPodEvictor(
ctx,
client,
eventRecorder,
sharedInformerFactory.Core().V1().Pods().Informer(),
initFeatureGates(),
NewOptions().WithMetricsEnabled(true),
)
if err != nil {
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
}
// calling erCache.addPod from a goroutine.
triggerAddPod := make(chan struct{})
addPodDone := make(chan struct{})
go func() {
<-triggerAddPod
// Simulate what UpdateFunc does when KubeVirt sets
// EvictionInProgressAnnotationKey before the TooManyRequests response
// arrives: addPod inserts the entry with evictionAssumed=false.
podEvictor.erCache.addPod(p1)
close(addPodDone)
}()
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
if action.GetSubresource() != "eviction" {
return false, nil, nil
}
createAct, ok := action.(core.CreateActionImpl)
if !ok {
return false, nil, fmt.Errorf("unable to convert action to core.CreateActionImpl")
}
eviction, ok := createAct.Object.(*policy.Eviction)
if !ok || eviction.GetName() != "p1" {
return false, nil, nil
}
// Trigger addPod and wait for it to complete before returning
// TooManyRequests, ensuring assumePod will find the entry already present.
close(triggerAddPod)
<-addPodDone
return true, nil, &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonTooManyRequests,
Message: "Eviction triggered evacuation",
},
}
})
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
podEvictor.EvictPod(ctx, p1, evictOpts)
// The "background" metric must have been emitted.
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 1)
// Simulate the background eviction completing: the pod is deleted.
client.CoreV1().Pods(p1.Namespace).Delete(ctx, p1.Name, metav1.DeleteOptions{})
// Wait until DeleteFunc has fired and removed the pod from the cache.
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
return !podEvictor.erCache.hasPod(p1), nil
}); err != nil {
t.Fatalf("Timed out waiting for background eviction to complete: %v", err)
}
// "success" must also be 1: the background eviction completed.
// Without the fix this will be 0 because assumePod returned early (entry
// already present from addPod) leaving evictionAssumed=false.
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "success"}, 1)
}
// TestEvictionInBackgroundMetrics_PodCompleted verifies that when a pod
// transitions to a terminal phase via UpdateFunc (without ever being deleted),
// the correct metric is emitted: "success" for PodSucceeded, "error" for PodFailed.
func TestEvictionInBackgroundMetrics_PodCompleted(t *testing.T) {
testCases := []struct {
name string
phase v1.PodPhase
expectedResult string
}{
{name: "PodSucceeded emits success", phase: v1.PodSucceeded, expectedResult: "success"},
{name: "PodFailed emits error", phase: v1.PodFailed, expectedResult: "error"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
defer cancel()
deschedulermetrics.Register()
deschedulermetrics.PodsEvicted.Reset()
deschedulermetrics.PodsEvictedTotal.Reset()
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
ownerRef1 := test.GetReplicaSetOwnerRefList()
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, func(pod *v1.Pod) {
pod.Namespace = "dev"
pod.ObjectMeta.OwnerReferences = ownerRef1
pod.Annotations = map[string]string{
EvictionRequestAnnotationKey: "",
}
})
client := fakeclientset.NewSimpleClientset(node1, p1)
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
podEvictor, err := NewPodEvictor(
ctx,
client,
eventRecorder,
sharedInformerFactory.Core().V1().Pods().Informer(),
initFeatureGates(),
NewOptions().WithMetricsEnabled(true),
)
if err != nil {
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
}
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
if action.GetSubresource() != "eviction" {
return false, nil, nil
}
return true, nil, &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonTooManyRequests,
Message: "Eviction triggered evacuation",
},
}
})
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
podEvictor.EvictPod(ctx, p1, evictOpts)
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 1)
// Transition the pod to a terminal phase without deleting it.
p1Updated := p1.DeepCopy()
p1Updated.Status.Phase = tc.phase
client.CoreV1().Pods(p1.Namespace).UpdateStatus(ctx, p1Updated, metav1.UpdateOptions{})
// Wait for UpdateFunc to fire and remove the pod from the cache.
if err := wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
return !podEvictor.erCache.hasPod(p1), nil
}); err != nil {
t.Fatalf("Timed out waiting for background eviction to complete: %v", err)
}
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": tc.expectedResult}, 1)
})
}
}
// TestEvictionInBackgroundMetrics_AssumedTimeout verifies that when an assumed
// eviction entry expires in cleanCache, the "error" metric is emitted.
func TestEvictionInBackgroundMetrics_AssumedTimeout(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), wait.ForeverTestTimeout)
defer cancel()
deschedulermetrics.Register()
deschedulermetrics.PodsEvicted.Reset()
deschedulermetrics.PodsEvictedTotal.Reset()
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
ownerRef1 := test.GetReplicaSetOwnerRefList()
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, func(pod *v1.Pod) {
pod.Namespace = "dev"
pod.ObjectMeta.OwnerReferences = ownerRef1
pod.Annotations = map[string]string{
EvictionRequestAnnotationKey: "",
}
})
client := fakeclientset.NewSimpleClientset(node1, p1)
sharedInformerFactory := informers.NewSharedInformerFactory(client, 0)
_, eventRecorder := utils.GetRecorderAndBroadcaster(ctx, client)
podEvictor, err := NewPodEvictor(
ctx,
client,
eventRecorder,
sharedInformerFactory.Core().V1().Pods().Informer(),
initFeatureGates(),
NewOptions().WithMetricsEnabled(true),
)
if err != nil {
t.Fatalf("Unexpected error when creating a pod evictor: %v", err)
}
client.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
if action.GetSubresource() != "eviction" {
return false, nil, nil
}
return true, nil, &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonTooManyRequests,
Message: "Eviction triggered evacuation",
},
}
})
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
evictOpts := EvictOptions{StrategyName: "TestStrategy", ProfileName: "TestProfile"}
podEvictor.EvictPod(ctx, p1, evictOpts)
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "background"}, 1)
// Back-date all assumed entries so they appear expired, then run cleanCache.
podEvictor.erCache.mu.Lock()
for uid, item := range podEvictor.erCache.requests {
item.assumedTimestamp = metav1.NewTime(time.Now().Add(-time.Hour))
podEvictor.erCache.requests[uid] = item
}
podEvictor.erCache.mu.Unlock()
podEvictor.erCache.cleanCache(ctx)
metricstest.AssertVectorCount(t, "descheduler_pods_evicted_total", map[string]string{"result": "error"}, 1)
}
func assertEqualEvents(t *testing.T, expected []string, actual <-chan string) {
t.Logf("Assert for events: %v", expected)
c := time.After(wait.ForeverTestTimeout)
+21
View File
@@ -23,6 +23,7 @@ import (
v1 "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
@@ -63,6 +64,26 @@ func setupTestSandbox(ctx context.Context, t *testing.T, initialObjects ...runti
realFactory.Start(ctx.Done())
realFactory.WaitForCacheSync(ctx.Done())
// Wait for initial objects to propagate from real client to fake client via event handlers.
for _, obj := range initialObjects {
if err := wait.PollUntilContextTimeout(ctx, time.Second, 5*time.Second, true, func(ctx context.Context) (bool, error) {
exists, err := sandbox.hasRuntimeObjectInIndexer(obj)
if err != nil {
t.Logf("Error checking if object propagated to fake indexer: %v", err)
return false, nil
}
if !exists {
metaObj, _ := meta.Accessor(obj)
t.Logf("Initial object %s/%s has not propagated to fake indexer yet, waiting...", metaObj.GetNamespace(), metaObj.GetName())
}
return exists, nil
}); err != nil {
metaObj, _ := meta.Accessor(obj)
t.Fatalf("Initial object %s/%s did not propagate to fake indexer within timeout: %v",
metaObj.GetNamespace(), metaObj.GetName(), err)
}
}
return sandbox, realFactory, realClient
}
+1 -1
View File
@@ -46,4 +46,4 @@ var defaultDeschedulerFeatureGates = map[featuregate.Feature]featuregate.Feature
// Tests that need to modify feature gates for the duration of their test should use:
//
// defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.<FeatureName>, <value>)()
var DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
var DefaultMutableFeatureGate featuregate.MutableVersionedFeatureGate = featuregate.NewFeatureGate()
@@ -90,7 +90,7 @@ func New(ctx context.Context, args runtime.Object, handle frameworktypes.Handle)
return nil, err
}
if ev.args.NamespaceLabelSelector != nil && len(ev.args.NamespaceLabelSelector.MatchLabels) > 0 {
if ev.args.NamespaceLabelSelector != nil && (len(ev.args.NamespaceLabelSelector.MatchLabels) > 0 || len(ev.args.NamespaceLabelSelector.MatchExpressions) > 0) {
selector, nslErr := metav1.LabelSelectorAsSelector(ev.args.NamespaceLabelSelector)
if nslErr != nil {
return nil, fmt.Errorf("unable to convert namespaceLabelSelector to label selector: %w", nslErr)
@@ -447,7 +447,7 @@ func (d *DefaultEvictor) PreEvictionFilter(pod *v1.Pod) bool {
}
}
if d.args.NamespaceLabelSelector == nil || len(d.args.NamespaceLabelSelector.MatchLabels) == 0 {
if d.args.NamespaceLabelSelector == nil || (len(d.args.NamespaceLabelSelector.MatchLabels) == 0 && len(d.args.NamespaceLabelSelector.MatchExpressions) == 0) {
return true
}
indexName := namespaceWithLabelSelector + d.handle.PluginInstanceID()
@@ -1030,13 +1030,13 @@ func initializePlugin(ctx context.Context, test testCase) (frameworktypes.Plugin
objs = append(objs, ns)
}
fakeClient := fake.NewSimpleClientset(objs...)
fakeClient := fake.NewClientset(objs...)
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
podInformer := sharedInformerFactory.Core().V1().Pods().Informer()
_ = sharedInformerFactory.Policy().V1().PodDisruptionBudgets().Lister()
_ = sharedInformerFactory.Core().V1().PersistentVolumeClaims().Lister()
_ = sharedInformerFactory.Core().V1().Namespaces().Lister()
_ = sharedInformerFactory.Core().V1().Namespaces().Informer()
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
if err != nil {
return nil, fmt.Errorf("build get pods assigned to node function error: %v", err)
@@ -1283,9 +1283,7 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
fakeClient := fake.NewClientset(node, nsProd, nsBackend, nsTest, podInProd, podInBackend, podInTest)
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
_ = sharedInformerFactory.Core().V1().Namespaces().Lister()
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
_ = sharedInformerFactory.Core().V1().Namespaces().Informer()
// Create Profile 1: targets namespaces with env=prod
profile1Args := &DefaultEvictorArgs{
@@ -1328,6 +1326,9 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
t.Fatalf("unable to initialize profile2 plugin: %v", err)
}
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
// Test Profile 1: evicts pods in ns-prod, reject others
t.Run("profile1", func(t *testing.T) {
profile1 := profile1Plugin.(*DefaultEvictor)
@@ -1362,3 +1363,145 @@ func TestMultipleProfilesWithDifferentNamespaceLabelSelectors(t *testing.T) {
}
})
}
func TestNamespaceLabelSelectorAllOperations(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
node := test.BuildTestNode("node1", 1000, 2000, 10, nil)
nsProd := test.BuildTestNamespace("ns-prod")
nsProd.Labels = map[string]string{"env": "prod", "tier": "frontend"}
nsStaging := test.BuildTestNamespace("ns-staging")
nsStaging.Labels = map[string]string{"env": "staging", "tier": "backend"}
nsTest := test.BuildTestNamespace("ns-test")
nsTest.Labels = map[string]string{"env": "test"}
nsNoLabels := test.BuildTestNamespace("ns-nolabels")
nsNoLabels.Labels = map[string]string{}
podInProdFrontend := test.BuildTestPod("pod-in-prod", 100, 100, node.Name, func(pod *v1.Pod) {
pod.Namespace = nsProd.Name
test.SetNormalOwnerRef(pod)
})
podInStagingBackend := test.BuildTestPod("pod-in-staging", 100, 100, node.Name, func(pod *v1.Pod) {
pod.Namespace = nsStaging.Name
test.SetNormalOwnerRef(pod)
})
podInTest := test.BuildTestPod("pod-in-test", 100, 100, node.Name, func(pod *v1.Pod) {
pod.Namespace = nsTest.Name
test.SetNormalOwnerRef(pod)
})
podInNoLabels := test.BuildTestPod("pod-in-nolabels", 100, 100, node.Name, func(pod *v1.Pod) {
pod.Namespace = nsNoLabels.Name
test.SetNormalOwnerRef(pod)
})
fakeClient := fake.NewClientset(node, nsProd, nsStaging, nsTest, nsNoLabels, podInProdFrontend, podInStagingBackend, podInTest, podInNoLabels)
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
getPodAssignedToNode, _ := podutil.BuildGetPodsAssignedToNodeFunc(sharedInformerFactory.Core().V1().Pods().Informer())
testCases := []struct {
name string
args *DefaultEvictorArgs
pass []*v1.Pod
fail []*v1.Pod
}{
{
name: "MatchLabels and MatchExpressions In",
args: &DefaultEvictorArgs{
NamespaceLabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"tier": "frontend"},
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: "env", Operator: metav1.LabelSelectorOpIn, Values: []string{"prod", "staging"}},
},
},
},
pass: []*v1.Pod{podInProdFrontend},
fail: []*v1.Pod{podInStagingBackend, podInTest},
},
{
name: "MatchLabels and MatchExpressions Exists",
args: &DefaultEvictorArgs{
NamespaceLabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"tier": "backend"},
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: "env", Operator: metav1.LabelSelectorOpExists},
},
},
},
pass: []*v1.Pod{podInStagingBackend},
fail: []*v1.Pod{podInProdFrontend, podInTest},
},
{
name: "Exists and In",
args: &DefaultEvictorArgs{
NamespaceLabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: "tier", Operator: metav1.LabelSelectorOpExists},
{Key: "env", Operator: metav1.LabelSelectorOpIn, Values: []string{"staging"}},
},
},
},
pass: []*v1.Pod{podInStagingBackend},
fail: []*v1.Pod{podInProdFrontend, podInTest},
},
{
name: "DoesNotExist",
args: &DefaultEvictorArgs{
NamespaceLabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: "tier", Operator: metav1.LabelSelectorOpDoesNotExist},
},
},
},
pass: []*v1.Pod{podInTest, podInNoLabels},
fail: []*v1.Pod{podInProdFrontend, podInStagingBackend},
},
{
name: "NotIn",
args: &DefaultEvictorArgs{
NamespaceLabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: "env", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"prod", "test"}},
},
},
},
pass: []*v1.Pod{podInStagingBackend},
fail: []*v1.Pod{podInProdFrontend, podInTest},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
plugin, err := New(ctx, tc.args, &frameworkfake.HandleImpl{
ClientsetImpl: fakeClient,
GetPodsAssignedToNodeFuncImpl: getPodAssignedToNode,
SharedInformerFactoryImpl: sharedInformerFactory,
PluginInstanceIDImpl: "test-" + tc.name,
})
if err != nil {
t.Fatalf("unable to initialize plugin: %v", err)
}
sharedInformerFactory.Start(ctx.Done())
sharedInformerFactory.WaitForCacheSync(ctx.Done())
evictor := plugin.(*DefaultEvictor)
for _, pod := range tc.pass {
if result := evictor.PreEvictionFilter(pod); !result {
t.Errorf("pod %s expected to pass (true), got false", pod.Name)
}
}
for _, pod := range tc.fail {
if result := evictor.PreEvictionFilter(pod); result {
t.Errorf("pod %s expected to fail (false), got true", pod.Name)
}
}
})
}
}
@@ -50,7 +50,7 @@ type notSupportedError struct {
}
func (e notSupportedError) Error() string {
return "maximum number of evicted pods per node reached"
return "pod usage quantification is not supported by this usage client"
}
func newNotSupportedError(usageClientType UsageClientType) *notSupportedError {
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"go.opentelemetry.io/otel/propagation"
sdkresource "go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
"google.golang.org/grpc/credentials"
+2 -44
View File
@@ -189,52 +189,10 @@ func TestRemoveDuplicates(t *testing.T) {
tc.removeDuplicatesArgs.Namespaces = &api.Namespaces{
Include: []string{testNamespace.Name},
}
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(removeDuplicatesPolicy(tc.removeDuplicatesArgs, tc.evictorArgs))
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
t.Logf("Creating %q policy CM with RemoveDuplicates configured...", deschedulerPolicyConfigMapObj.Name)
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
defer func() {
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
}()
createPolicyConfigMap(t, ctx, clientSet, removeDuplicatesPolicy(tc.removeDuplicatesArgs, tc.evictorArgs))
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
deschedulerPodName := ""
defer func() {
if deschedulerPodName != "" {
printPodLogs(ctx, t, clientSet, deschedulerPodName)
}
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
}()
t.Logf("Waiting for the descheduler pod running")
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
if len(deschedulerPods) != 0 {
deschedulerPodName = deschedulerPods[0].Name
}
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
// Run RemoveDuplicates strategy
var meetsExpectations bool
+241 -194
View File
@@ -23,7 +23,7 @@ import (
utilptr "k8s.io/utils/ptr"
kvcorev1 "kubevirt.io/api/core/v1"
generatedclient "kubevirt.io/client-go/generated/kubevirt/clientset/versioned"
kubevirtclient "kubevirt.io/client-go/kubevirt"
"sigs.k8s.io/descheduler/pkg/api"
apiv1alpha2 "sigs.k8s.io/descheduler/pkg/api/v1alpha2"
@@ -34,12 +34,16 @@ import (
const (
vmiCount = 3
// virtLauncherSelector selects KubeVirt virt-launcher pods only,
// avoiding listing unrelated pods in the namespace.
virtLauncherSelector = "kubevirt.io=virt-launcher"
)
func virtualMachineInstance(idx int) *kvcorev1.VirtualMachineInstance {
func virtualMachineInstance(idx int, namespace string) *kvcorev1.VirtualMachineInstance {
return &kvcorev1.VirtualMachineInstance{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("kubevirtvmi-%v", idx),
Name: fmt.Sprintf("kubevirtvmi-%v", idx),
Namespace: namespace,
Annotations: map[string]string{
"descheduler.alpha.kubernetes.io/request-evict-only": "",
},
@@ -58,20 +62,12 @@ func virtualMachineInstance(idx int) *kvcorev1.VirtualMachineInstance {
},
},
},
{
Name: "cloudinitdisk",
DiskDevice: kvcorev1.DiskDevice{
Disk: &kvcorev1.DiskTarget{
Bus: kvcorev1.DiskBusVirtio,
},
},
},
},
Rng: &kvcorev1.Rng{},
},
Resources: kvcorev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("1024M"),
corev1.ResourceMemory: resource.MustParse("128M"),
},
},
},
@@ -81,27 +77,7 @@ func virtualMachineInstance(idx int) *kvcorev1.VirtualMachineInstance {
Name: "containerdisk",
VolumeSource: kvcorev1.VolumeSource{
ContainerDisk: &kvcorev1.ContainerDiskSource{
Image: "quay.io/kubevirt/fedora-with-test-tooling-container-disk:20240710_1265d1090",
},
},
},
{
Name: "cloudinitdisk",
VolumeSource: kvcorev1.VolumeSource{
CloudInitNoCloud: &kvcorev1.CloudInitNoCloudSource{
UserData: `#cloud-config
password: fedora
chpasswd: { expire: False }
packages:
- nginx
runcmd:
- [ "systemctl", "enable", "--now", "nginx" ]`,
NetworkData: `version: 2
ethernets:
eth0:
addresses: [ fd10:0:2::2/120 ]
dhcp4: true
gateway6: fd10:0:2::1`,
Image: kubevirtCirrosContainerDiskImage(),
},
},
},
@@ -110,31 +86,83 @@ ethernets:
}
}
func waitForKubevirtReady(t *testing.T, ctx context.Context, kvClient generatedclient.Interface) {
obj, err := kvClient.KubevirtV1().KubeVirts("kubevirt").Get(ctx, "kubevirt", metav1.GetOptions{})
if err != nil {
t.Fatalf("Unable to get kubevirt/kubevirt: %v", err)
}
available := false
for _, condition := range obj.Status.Conditions {
if condition.Type == kvcorev1.KubeVirtConditionAvailable {
if condition.Status == corev1.ConditionTrue {
available = true
func formatContainerStatuses(pod *corev1.Pod) string {
var parts []string
formatList := func(prefix string, statuses []corev1.ContainerStatus) {
for _, cs := range statuses {
stateStr := "unknown"
if cs.State.Waiting != nil {
stateStr = fmt.Sprintf("waiting(reason=%q, message=%q)", cs.State.Waiting.Reason, cs.State.Waiting.Message)
} else if cs.State.Terminated != nil {
stateStr = fmt.Sprintf("terminated(exitCode=%d, reason=%q, message=%q)", cs.State.Terminated.ExitCode, cs.State.Terminated.Reason, cs.State.Terminated.Message)
} else if cs.State.Running != nil {
stateStr = "running"
}
parts = append(parts, fmt.Sprintf("%s%s: state=%s, ready=%v, restarts=%d", prefix, cs.Name, stateStr, cs.Ready, cs.RestartCount))
}
}
if !available {
t.Fatalf("Kubevirt is not available")
formatList("init:", pod.Status.InitContainerStatuses)
formatList("", pod.Status.ContainerStatuses)
return strings.Join(parts, "; ")
}
// ensureVMIsLiveMigratable waits until every VMI reports the LiveMigratable condition with status True.
func ensureVMIsLiveMigratable(t *testing.T, ctx context.Context, kvClient kubevirtclient.Interface, namespace string) {
t.Helper()
err := wait.PollUntilContextTimeout(ctx, 3*time.Second, 180*time.Second, true, func(ctx context.Context) (bool, error) {
vmiList, err := kvClient.KubevirtV1().VirtualMachineInstances(namespace).List(ctx, metav1.ListOptions{})
if err != nil || len(vmiList.Items) != vmiCount {
return false, nil
}
for _, vmi := range vmiList.Items {
migratable := false
for _, c := range vmi.Status.Conditions {
if c.Type == kvcorev1.VirtualMachineInstanceIsMigratable && c.Status == corev1.ConditionTrue {
migratable = true
break
}
}
if !migratable {
return false, nil
}
}
return true, nil
})
if err != nil {
t.Fatalf("VMIs never became LiveMigratable: %v", err)
}
klog.Infof("All VMIs are LiveMigratable")
}
func waitForKubevirtReady(t *testing.T, ctx context.Context, kvClient kubevirtclient.Interface) {
t.Helper()
err := wait.PollUntilContextTimeout(ctx, 3*time.Second, 180*time.Second, true, func(ctx context.Context) (bool, error) {
obj, err := kvClient.KubevirtV1().KubeVirts("kubevirt").Get(ctx, "kubevirt", metav1.GetOptions{})
if err != nil {
klog.Infof("Unable to get kubevirt/kubevirt: %v", err)
return false, nil
}
for _, condition := range obj.Status.Conditions {
if condition.Type == kvcorev1.KubeVirtConditionAvailable && condition.Status == corev1.ConditionTrue {
return true, nil
}
}
return false, nil
})
if err != nil {
t.Fatalf("Kubevirt is not available: %v", err)
}
klog.Infof("Kubevirt is available")
}
func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient clientset.Interface, kvClient generatedclient.Interface) (bool, error) {
func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient clientset.Interface, kvClient kubevirtclient.Interface, namespace string) (bool, error) {
klog.Infof("Checking all vmi active pods are running")
uidMap := make(map[types.UID]*corev1.Pod)
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: virtLauncherSelector,
})
if err != nil {
if strings.Contains(err.Error(), "client rate limiter") {
if isClientRateLimiterError(err) {
klog.Infof("Unable to list pods: %v", err)
return false, nil
}
@@ -148,7 +176,7 @@ func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient client
uidMap[item.UID] = &pod
}
vmiList, err := kvClient.KubevirtV1().VirtualMachineInstances("default").List(ctx, metav1.ListOptions{})
vmiList, err := kvClient.KubevirtV1().VirtualMachineInstances(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
klog.Infof("Unable to list VMIs: %v", err)
return false, err
@@ -165,14 +193,19 @@ func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient client
klog.Infof("Active pod %v not found", activePod)
return false, nil
}
klog.Infof("Checking whether active pod %v (uid=%v) is running", uidMap[activePod].Name, activePod)
// ignore completed/failed pods
if uidMap[activePod].Status.Phase == corev1.PodFailed || uidMap[activePod].Status.Phase == corev1.PodSucceeded {
klog.Infof("Ignoring active pod %v, phase=%v", uidMap[activePod].Name, uidMap[activePod].Status.Phase)
pod := uidMap[activePod]
klog.Infof("Checking whether active pod %v (uid=%v) is running", pod.Name, activePod)
if pod.Status.Phase == corev1.PodFailed {
details := fmt.Sprintf("pod %s (phase=Failed, reason=%q, message=%q, containers=[%s])", pod.Name, pod.Status.Reason, pod.Status.Message, formatContainerStatuses(pod))
klog.Infof("Active pod failed: %s", details)
continue
}
if uidMap[activePod].Status.Phase != corev1.PodRunning {
klog.Infof("activePod %v is not running: %v\n", uidMap[activePod].Name, uidMap[activePod].Status.Phase)
if pod.Status.Phase == corev1.PodSucceeded {
klog.Infof("Ignoring active pod %v, phase=%v", pod.Name, pod.Status.Phase)
continue
}
if pod.Status.Phase != corev1.PodRunning {
klog.Infof("activePod %v is not running: %v\n", pod.Name, pod.Status.Phase)
return false, nil
}
atLeastOneVmiIsRunning = true
@@ -186,7 +219,7 @@ func allVMIsHaveRunningPods(t *testing.T, ctx context.Context, kubeClient client
return true, nil
}
func podLifeTimePolicy() *apiv1alpha2.DeschedulerPolicy {
func podLifeTimePolicy(namespace string) *apiv1alpha2.DeschedulerPolicy {
return &apiv1alpha2.DeschedulerPolicy{
Profiles: []apiv1alpha2.DeschedulerProfile{
{
@@ -198,7 +231,7 @@ func podLifeTimePolicy() *apiv1alpha2.DeschedulerPolicy {
Object: &podlifetime.PodLifeTimeArgs{
MaxPodLifeTimeSeconds: utilptr.To[uint](1), // set it to immediate eviction
Namespaces: &api.Namespaces{
Include: []string{"default"},
Include: []string{namespace},
},
},
},
@@ -229,10 +262,12 @@ func podLifeTimePolicy() *apiv1alpha2.DeschedulerPolicy {
}
}
func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientset.Interface) []string {
func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientset.Interface, namespace string) []string {
names := []string{}
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: virtLauncherSelector,
})
if err != nil {
if isClientRateLimiterError(err) {
t.Log(err)
@@ -243,9 +278,6 @@ func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientse
}
for _, item := range podList.Items {
if !strings.HasPrefix(item.Name, "virt-launcher-kubevirtvmi-") {
t.Fatalf("Only pod names with 'virt-launcher-kubevirtvmi-' prefix are expected, got %q instead", item.Name)
}
if item.Status.Phase == corev1.PodRunning {
names = append(names, item.Name)
}
@@ -258,13 +290,13 @@ func kVirtRunningPodNames(t *testing.T, ctx context.Context, kubeClient clientse
return names
}
func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientset.Interface, usedRunningPodNames map[string]struct{}) {
func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientset.Interface, namespace string, usedRunningPodNames map[string]struct{}) {
prevTotal := uint(0)
jumps := 0
// keep running the descheduling cycle until the migration is triggered and completed few times or times out
for i := 0; i < 240; i++ {
// monitor how many pods get evicted
names := kVirtRunningPodNames(t, ctx, kubeClient)
names := kVirtRunningPodNames(t, ctx, kubeClient, namespace)
klog.Infof("vmi pods: %#v\n", names)
// The number of pods need to be kept between vmiCount and vmiCount+1.
// At most two pods are expected to have virt-launcher-kubevirtvmi-X prefix name in common.
@@ -299,16 +331,18 @@ func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientse
if prevTotal != 0 && prevTotal != total {
jumps++
}
// Expect at least 3 finished live migrations (two should be enough as well, though ...)
if jumps >= 6 {
// Expect at least 2 finished live migrations
if jumps >= 4 {
break
}
prevTotal = total
time.Sleep(time.Second)
time.Sleep(4 * time.Second)
}
if jumps < 6 {
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
if jumps < 4 {
podList, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: virtLauncherSelector,
})
if err != nil {
klog.Infof("Unable to list pods: %v", err)
} else {
@@ -317,9 +351,9 @@ func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientse
}
}
t.Fatalf("Expected at least 3 finished live migrations, got less: %v", jumps/2.0)
t.Fatalf("Expected at least 2 finished live migrations, got less: %v", jumps/2.0)
}
klog.Infof("The live migration finished 3 times")
klog.Infof("The live migration finished 2 times")
// len(usedRunningPodNames) is expected to be vmiCount + jumps/2 + 1 (one more live migration could still be initiated)
klog.Infof("len(usedRunningPodNames): %v, upper limit: %v\n", len(usedRunningPodNames), vmiCount+jumps/2+1)
@@ -328,7 +362,7 @@ func observeLiveMigration(t *testing.T, ctx context.Context, kubeClient clientse
}
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
names := kVirtRunningPodNames(t, ctx, kubeClient)
names := kVirtRunningPodNames(t, ctx, kubeClient, namespace)
klog.Infof("vmi pods: %#v\n", names)
lNames := len(names)
if lNames != vmiCount {
@@ -346,7 +380,13 @@ func createAndWaitForDeschedulerRunning(t *testing.T, ctx context.Context, kubeC
klog.Infof("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
_, err := kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
if apierrors.IsAlreadyExists(err) {
_ = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
_, err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
}
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
}
klog.Infof("Waiting for the descheduler pod running")
@@ -368,7 +408,7 @@ func updateDeschedulerPolicy(t *testing.T, ctx context.Context, kubeClient clien
}
}
func createKubevirtClient() (generatedclient.Interface, error) {
func createKubevirtClient() (kubevirtclient.Interface, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
overrides := &clientcmd.ConfigOverrides{}
@@ -382,146 +422,49 @@ func createKubevirtClient() (generatedclient.Interface, error) {
config.APIPath = "/apis"
config.ContentType = runtime.ContentTypeJSON
return generatedclient.NewForConfig(config)
return kubevirtclient.NewForConfig(config)
}
func TestLiveMigrationInBackground(t *testing.T) {
initPluginRegistry()
ctx := context.Background()
kubeClient, err := client.CreateClient(componentbaseconfig.ClientConnectionConfiguration{Kubeconfig: os.Getenv("KUBECONFIG")}, "")
if err != nil {
t.Fatalf("Error during kubernetes client creation with %v", err)
func setupE2ELiveMigrationNamespace(t *testing.T, ctx context.Context, kubeClient clientset.Interface, kvClient kubevirtclient.Interface, vmiNamespace string) {
t.Helper()
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: vmiNamespace}}
if _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) {
t.Fatalf("Unable to create namespace %v: %v", vmiNamespace, err)
}
kvClient, err := createKubevirtClient()
if err != nil {
t.Fatalf("Error during kvClient creation with %v", err)
}
waitForKubevirtReady(t, ctx, kvClient)
// Delete all VMIs
defer func() {
t.Cleanup(func() {
cleanupCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
for i := 1; i <= vmiCount; i++ {
vmi := virtualMachineInstance(i)
err := kvClient.KubevirtV1().VirtualMachineInstances("default").Delete(context.Background(), vmi.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
vmi := virtualMachineInstance(i, vmiNamespace)
if err := kvClient.KubevirtV1().VirtualMachineInstances(vmiNamespace).Delete(cleanupCtx, vmi.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
klog.Infof("Unable to delete vmi %v: %v", vmi.Name, err)
}
}
wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) {
podList, err := kubeClient.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
wait.PollUntilContextTimeout(cleanupCtx, 5*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
podList, err := kubeClient.CoreV1().Pods(vmiNamespace).List(ctx, metav1.ListOptions{
LabelSelector: virtLauncherSelector,
})
if err != nil {
return false, err
}
lPods := len(podList.Items)
if lPods > 0 {
klog.Infof("Waiting until all pods under default namespace are gone, %v remaining", lPods)
klog.Infof("Waiting until all virt-launcher pods under %v namespace are gone, %v remaining", vmiNamespace, lPods)
return false, nil
}
return true, nil
})
}()
// Create N vmis and wait for the corresponding vm pods to be ready and running
for i := 1; i <= vmiCount; i++ {
vmi := virtualMachineInstance(i)
_, err = kvClient.KubevirtV1().VirtualMachineInstances("default").Create(context.Background(), vmi, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unable to create KubeVirt vmi: %v\n", err)
if err := kubeClient.CoreV1().Namespaces().Delete(cleanupCtx, vmiNamespace, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
klog.Infof("Unable to delete namespace %v: %v", vmiNamespace, err)
}
}
// Wait until all VMIs have running pods
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
return allVMIsHaveRunningPods(t, ctx, kubeClient, kvClient)
}); err != nil {
t.Fatalf("Error waiting for all vmi active pods to be running: %v", err)
}
usedRunningPodNames := make(map[string]struct{})
// vmiCount number of names is expected
names := kVirtRunningPodNames(t, ctx, kubeClient)
klog.Infof("vmi pods: %#v\n", names)
if len(names) != vmiCount {
t.Fatalf("Expected %v vmi pods, got %v instead", vmiCount, len(names))
}
for _, name := range names {
usedRunningPodNames[name] = struct{}{}
}
policy := podLifeTimePolicy()
// Allow only a single eviction simultaneously
policy.MaxNoOfPodsToEvictPerNamespace = utilptr.To[uint](1)
// Deploy the descheduler with the configured policy
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(policy)
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
klog.Infof("Creating %q policy CM with RemovePodsHavingTooManyRestarts configured...", deschedulerPolicyConfigMapObj.Name)
_, err = kubeClient.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
defer func() {
klog.Infof("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
err = kubeClient.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
}()
deschedulerDeploymentObj := deschedulerDeployment("kube-system")
// Set the descheduling interval to 10s
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "10s", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
deschedulerPodName := ""
defer func() {
if deschedulerPodName != "" {
printPodLogs(ctx, t, kubeClient, deschedulerPodName)
}
klog.Infof("Deleting %q deployment...", deschedulerDeploymentObj.Name)
err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return
}
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(ctx, t, kubeClient, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
}()
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
observeLiveMigration(t, ctx, kubeClient, usedRunningPodNames)
printPodLogs(ctx, t, kubeClient, deschedulerPodName)
klog.Infof("Deleting the current descheduler pod")
err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Error deleting %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
remainingPods := make(map[string]struct{})
for _, name := range kVirtRunningPodNames(t, ctx, kubeClient) {
remainingPods[name] = struct{}{}
}
klog.Infof("Configuring the descheduler policy %v for PodLifetime with no limits", deschedulerPolicyConfigMapObj.Name)
policy.MaxNoOfPodsToEvictPerNamespace = nil
updateDeschedulerPolicy(t, ctx, kubeClient, policy)
deschedulerDeploymentObj = deschedulerDeployment("kube-system")
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "100m", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
})
}
func waitForVMIEvictionsWithNoLimits(t *testing.T, ctx context.Context, kubeClient clientset.Interface, vmiNamespace string, remainingPods map[string]struct{}) {
t.Helper()
klog.Infof("Waiting until all pods are evicted (no limit set)")
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 120*time.Second, true, func(ctx context.Context) (bool, error) {
names := kVirtRunningPodNames(t, ctx, kubeClient)
names := kVirtRunningPodNames(t, ctx, kubeClient, vmiNamespace)
for _, name := range names {
if _, exists := remainingPods[name]; exists {
klog.Infof("Waiting for %v to disappear", name)
@@ -539,3 +482,107 @@ func TestLiveMigrationInBackground(t *testing.T) {
t.Fatalf("Error waiting for %v new vmi active pods to be running: %v", vmiCount, err)
}
}
func TestLiveMigrationInBackground(t *testing.T) {
if *kubevirtVersionTag == "" {
t.Fatal("--kubevirt-version-tag must be set for KubeVirt e2e tests")
}
initPluginRegistry()
ctx := context.Background()
kubeClient, err := client.CreateClient(componentbaseconfig.ClientConnectionConfiguration{Kubeconfig: os.Getenv("KUBECONFIG")}, "")
if err != nil {
t.Fatalf("Error during kubernetes client creation with %v", err)
}
kvClient, err := createKubevirtClient()
if err != nil {
t.Fatalf("Error during kvClient creation with %v", err)
}
waitForKubevirtReady(t, ctx, kvClient)
vmiNamespace := "e2e-livemigration"
setupE2ELiveMigrationNamespace(t, ctx, kubeClient, kvClient, vmiNamespace)
for i := 1; i <= vmiCount; i++ {
vmi := virtualMachineInstance(i, vmiNamespace)
_, err = kvClient.KubevirtV1().VirtualMachineInstances(vmiNamespace).Create(context.Background(), vmi, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unable to create KubeVirt vmi: %v\n", err)
}
}
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
return allVMIsHaveRunningPods(t, ctx, kubeClient, kvClient, vmiNamespace)
}); err != nil {
t.Fatalf("Error waiting for all vmi active pods to be running: %v", err)
}
ensureVMIsLiveMigratable(t, ctx, kvClient, vmiNamespace)
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 300*time.Second, true, func(ctx context.Context) (bool, error) {
return allVMIsHaveRunningPods(t, ctx, kubeClient, kvClient, vmiNamespace)
}); err != nil {
t.Fatalf("Error waiting for all vmi active pods to be running after recreate: %v", err)
}
usedRunningPodNames := make(map[string]struct{})
names := kVirtRunningPodNames(t, ctx, kubeClient, vmiNamespace)
klog.Infof("vmi pods: %#v\n", names)
if len(names) != vmiCount {
t.Fatalf("Expected %v vmi pods, got %v instead", vmiCount, len(names))
}
for _, name := range names {
usedRunningPodNames[name] = struct{}{}
}
policy := podLifeTimePolicy(vmiNamespace)
policy.MaxNoOfPodsToEvictPerNamespace = utilptr.To[uint](1)
deschedulerPolicyConfigMapObj := createPolicyConfigMap(t, ctx, kubeClient, policy)
deschedulerDeploymentObj := deschedulerDeployment("kube-system")
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "10s", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
deschedulerPodName := ""
t.Cleanup(func() {
if deschedulerPodName != "" {
printPodLogs(context.Background(), t, kubeClient, deschedulerPodName)
}
klog.Infof("Deleting %q deployment...", deschedulerDeploymentObj.Name)
if err := kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(context.Background(), deschedulerDeploymentObj.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
klog.Infof("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(context.Background(), t, kubeClient, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
})
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
observeLiveMigration(t, ctx, kubeClient, vmiNamespace, usedRunningPodNames)
printPodLogs(ctx, t, kubeClient, deschedulerPodName)
klog.Infof("Deleting the current descheduler pod")
err = kubeClient.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Error deleting %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
remainingPods := make(map[string]struct{})
for _, name := range kVirtRunningPodNames(t, ctx, kubeClient, vmiNamespace) {
remainingPods[name] = struct{}{}
}
klog.Infof("Configuring the descheduler policy %v for PodLifetime with no limits", deschedulerPolicyConfigMapObj.Name)
policy.MaxNoOfPodsToEvictPerNamespace = nil
updateDeschedulerPolicy(t, ctx, kubeClient, policy)
deschedulerDeploymentObj = deschedulerDeployment("kube-system")
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{"--policy-config-file", "/policy-dir/policy.yaml", "--descheduling-interval", "100m", "--v", "4", "--feature-gates", "EvictionsInBackground=true"}
deschedulerPodName = createAndWaitForDeschedulerRunning(t, ctx, kubeClient, deschedulerDeploymentObj)
waitForVMIEvictionsWithNoLimits(t, ctx, kubeClient, vmiNamespace, remainingPods)
}
+2 -44
View File
@@ -145,52 +145,10 @@ func TestFailedPods(t *testing.T) {
Include: []string{testNamespace.Name},
}
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(removeFailedPodsPolicy(tc.removeFailedPodsArgs, evictorArgs))
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
t.Logf("Creating %q policy CM with RemoveDuplicates configured...", deschedulerPolicyConfigMapObj.Name)
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
defer func() {
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
}()
createPolicyConfigMap(t, ctx, clientSet, removeFailedPodsPolicy(tc.removeFailedPodsArgs, evictorArgs))
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
deschedulerPodName := ""
defer func() {
if deschedulerPodName != "" {
printPodLogs(ctx, t, clientSet, deschedulerPodName)
}
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
}()
t.Logf("Waiting for the descheduler pod running")
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
if len(deschedulerPods) != 0 {
deschedulerPodName = deschedulerPods[0].Name
}
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
// Run RemoveDuplicates strategy
var meetsExpectations bool
+13 -21
View File
@@ -135,9 +135,9 @@ func TestLeaderElection(t *testing.T) {
t.Logf("Removed kube-system/descheduler lease")
t.Log("Starting deschedulers")
pod1Name, deploy1, cm1 := startDeschedulerServer(t, ctx, clientSet, ns1)
pod1Name, deploy1, _ := startDeschedulerServer(t, ctx, clientSet, ns1)
time.Sleep(1 * time.Second)
pod2Name, deploy2, cm2 := startDeschedulerServer(t, ctx, clientSet, ns2)
pod2Name, deploy2, _ := startDeschedulerServer(t, ctx, clientSet, ns2)
defer func() {
for _, podName := range []string{pod1Name, pod2Name} {
printPodLogs(ctx, t, clientSet, podName)
@@ -153,14 +153,6 @@ func TestLeaderElection(t *testing.T) {
waitForPodsToDisappear(ctx, t, clientSet, deploy.Labels, deploy.Namespace)
}
for _, cm := range []*v1.ConfigMap{cm1, cm2} {
t.Logf("Deleting %q CM...", cm.Name)
err = clientSet.CoreV1().ConfigMaps(cm.Namespace).Delete(ctx, cm.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", cm.Name, err)
}
}
clientSet.CoordinationV1().Leases("kube-system").Delete(ctx, "descheduler", metav1.DeleteOptions{})
}()
@@ -223,21 +215,21 @@ func startDeschedulerServer(t *testing.T, ctx context.Context, clientSet clients
EvictFailedBarePods: false,
}
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(podlifetimePolicy(podLifeTimeArgs, evictorArgs))
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
deschedulerPolicyConfigMapObj.Name = fmt.Sprintf("%s-%s", deschedulerPolicyConfigMapObj.Name, testName)
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
t.Logf("Creating %q policy CM with RemoveDuplicates configured...", deschedulerPolicyConfigMapObj.Name)
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
createConfigMapWithCleanup(t, ctx, clientSet, deschedulerPolicyConfigMapObj)
deschedulerDeploymentObj := deschedulerDeployment(testName)
deschedulerDeploymentObj.Name = fmt.Sprintf("%s-%s", deschedulerDeploymentObj.Name, testName)
args := deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = append(args, "--leader-elect", "--leader-elect-retry-period", "1s")
deschedulerDeploymentObj.Spec.Template.Spec.Containers[0].Args = []string{
"--policy-config-file", "/policy-dir/policy.yaml",
"--descheduling-interval", "3s",
"--v", "4",
"--leader-elect",
"--leader-elect-retry-period", "1s",
}
deschedulerDeploymentObj.Spec.Template.Spec.Volumes = []v1.Volume{
{
Name: "policy-volume",
+21 -57
View File
@@ -105,7 +105,10 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
if _, err := clientSet.CoreV1().Namespaces().Create(ctx, testNamespace, metav1.CreateOptions{}); err != nil {
t.Fatalf("Unable to create ns %v: %v", testNamespace.Name, err)
}
defer clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
t.Cleanup(func() {
clientSet.CoreV1().Namespaces().Delete(ctx, testNamespace.Name, metav1.DeleteOptions{})
})
t.Log("Creating duplicates pods")
testLabel := map[string]string{"app": "test-lownodeutilization-kubernetes-metrics", "name": "test-lownodeutilization-kubernetes-metrics"}
@@ -164,11 +167,11 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
expectedEvictedPodCount: 2,
lowNodeUtilizationArgs: &nodeutilization.LowNodeUtilizationArgs{
Thresholds: api.ResourceThresholds{
v1.ResourceCPU: 30,
v1.ResourceCPU: 8,
v1.ResourcePods: 30,
},
TargetThresholds: api.ResourceThresholds{
v1.ResourceCPU: 50,
v1.ResourceCPU: 15,
v1.ResourcePods: 50,
},
MetricsUtilization: &nodeutilization.MetricsUtilization{
@@ -194,21 +197,22 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
}
return
}
defer func() {
t.Cleanup(func() {
clientSet.AppsV1().Deployments(deploymentObj.Namespace).Delete(ctx, deploymentObj.Name, metav1.DeleteOptions{})
waitForPodsToDisappear(ctx, t, clientSet, deploymentObj.Labels, deploymentObj.Namespace)
}()
})
waitForPodsRunning(ctx, t, clientSet, deploymentObj.Labels, tc.replicasNum, deploymentObj.Namespace)
// wait until workerNodes[0].Name has the right actual cpu utilization and all the testing pods are running
// and producing ~12 cores in total
wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(context.Context) (done bool, err error) {
// and producing ~4 cores in total
if err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) {
item, err := metricsClient.MetricsV1beta1().NodeMetricses().Get(ctx, workerNodes[0].Name, metav1.GetOptions{})
if err != nil {
t.Logf("unable to list nodemetricses: %v", err)
return false, nil
}
t.Logf("Waiting for %q nodemetrics cpu utilization to get over 12, currently %v", workerNodes[0].Name, item.Usage.Cpu().Value())
if item.Usage.Cpu().Value() < 12 {
t.Logf("Waiting for %q nodemetrics cpu utilization to get over 3, currently %v", workerNodes[0].Name, item.Usage.Cpu().Value())
if item.Usage.Cpu().Value() < 3 {
return false, nil
}
totalCpu := resource.NewMilliQuantity(0, resource.DecimalSI)
@@ -225,60 +229,20 @@ func TestLowNodeUtilizationKubernetesMetrics(t *testing.T) {
totalCpu.Add(container.Usage[v1.ResourceCPU])
}
}
// Value() will round up (e.g. 11.1 -> 12), which is still ok
t.Logf("Waiting for totalCpu to get to 12 at least, got %v\n", totalCpu.Value())
return totalCpu.Value() >= 12, nil
})
// Value() will round up (e.g. 3.1 -> 4), which is still ok
t.Logf("Waiting for totalCpu to get to 3 at least, got %v\n", totalCpu.Value())
return totalCpu.Value() >= 3, nil
}); err != nil {
t.Fatalf("Error waiting for node/pod metrics: %v", err)
}
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
// Deploy the descheduler with the configured policy
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(lowNodeUtilizationPolicy(tc.lowNodeUtilizationArgs, tc.evictorArgs, tc.metricsCollectorEnabled))
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
t.Logf("Creating %q policy CM with LowNodeUtilization configured...", deschedulerPolicyConfigMapObj.Name)
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
defer func() {
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
}()
createPolicyConfigMap(t, ctx, clientSet, lowNodeUtilizationPolicy(tc.lowNodeUtilizationArgs, tc.evictorArgs, tc.metricsCollectorEnabled))
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
deschedulerPodName := ""
defer func() {
if deschedulerPodName != "" {
printPodLogs(ctx, t, clientSet, deschedulerPodName)
}
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
}()
t.Logf("Waiting for the descheduler pod running")
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
if len(deschedulerPods) != 0 {
deschedulerPodName = deschedulerPods[0].Name
}
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
// Run LowNodeUtilization plugin
var meetsExpectations bool
+1 -15
View File
@@ -289,21 +289,7 @@ func TestProtectPodsWithPVC(t *testing.T) {
t.Fatalf("Error creating %q CM: %v", policycm.Name, err)
}
t.Logf("creating %q policy CM with PodsWithPVC protection enabled...", policycm.Name)
if _, err = cli.CoreV1().ConfigMaps(policycm.Namespace).Create(
ctx, policycm, metav1.CreateOptions{},
); err != nil {
t.Fatalf("error creating %q CM: %v", policycm.Name, err)
}
defer func() {
t.Logf("deleting %q CM...", policycm.Name)
if err := cli.CoreV1().ConfigMaps(policycm.Namespace).Delete(
ctx, policycm.Name, metav1.DeleteOptions{},
); err != nil {
t.Fatalf("unable to delete %q CM: %v", policycm.Name, err)
}
}()
createConfigMapWithCleanup(t, ctx, cli, policycm)
desdep := deschedulerDeployment(namespace.Name)
t.Logf("creating descheduler deployment %v", desdep.Name)
+87 -4
View File
@@ -31,6 +31,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
schedulingv1 "k8s.io/api/scheduling/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
@@ -67,11 +68,16 @@ import (
)
var (
deschedulerImage = flag.String("descheduler-image", "", "descheduler image to set in the pod spec")
podRunAsUserId = flag.Int64("pod-run-as-user-id", 0, ".spec.securityContext.runAsUser setting, not set if 0")
podRunAsGroupId = flag.Int64("pod-run-as-group-id", 0, ".spec.securityContext.runAsGroup setting, not set if 0")
deschedulerImage = flag.String("descheduler-image", "", "descheduler image to set in the pod spec")
kubevirtVersionTag = flag.String("kubevirt-version-tag", "", "KubeVirt release tag for container disk images in KubeVirt e2e tests (e.g. v1.9.0)")
podRunAsUserId = flag.Int64("pod-run-as-user-id", 0, ".spec.securityContext.runAsUser setting, not set if 0")
podRunAsGroupId = flag.Int64("pod-run-as-group-id", 0, ".spec.securityContext.runAsGroup setting, not set if 0")
)
func kubevirtCirrosContainerDiskImage() string {
return fmt.Sprintf("quay.io/kubevirt/cirros-container-disk-demo:%s", *kubevirtVersionTag)
}
func TestMain(m *testing.M) {
flag.Parse()
@@ -84,7 +90,12 @@ func TestMain(m *testing.M) {
}
func isClientRateLimiterError(err error) bool {
return strings.Contains(err.Error(), "client rate limiter")
if err == nil {
return false
}
return strings.Contains(err.Error(), "client rate limiter") &&
!strings.Contains(err.Error(), "context deadline exceeded") &&
!strings.Contains(err.Error(), "context canceled")
}
func initFeatureGates() featuregate.FeatureGate {
@@ -112,6 +123,78 @@ func deschedulerPolicyConfigMap(policy *deschedulerapiv1alpha2.DeschedulerPolicy
return cm, nil
}
// createPolicyConfigMap generates, creates (or recreates if already existing), and registers cleanup for a descheduler policy ConfigMap.
func createPolicyConfigMap(t *testing.T, ctx context.Context, kubeClient clientset.Interface, policy *deschedulerapiv1alpha2.DeschedulerPolicy) *v1.ConfigMap {
t.Helper()
cm, err := deschedulerPolicyConfigMap(policy)
if err != nil {
t.Fatalf("Error creating policy CM object: %v", err)
}
return createConfigMapWithCleanup(t, ctx, kubeClient, cm)
}
// createConfigMapWithCleanup creates (or recreates if already existing) a ConfigMap and registers a t.Cleanup callback to delete it.
func createConfigMapWithCleanup(t *testing.T, ctx context.Context, kubeClient clientset.Interface, cm *v1.ConfigMap) *v1.ConfigMap {
t.Helper()
t.Logf("Creating %q policy CM...", cm.Name)
_, err := kubeClient.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{})
if err != nil {
if apierrors.IsAlreadyExists(err) {
_ = kubeClient.CoreV1().ConfigMaps(cm.Namespace).Delete(ctx, cm.Name, metav1.DeleteOptions{})
_, err = kubeClient.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{})
}
if err != nil {
t.Fatalf("Error creating %q CM: %v", cm.Name, err)
}
}
t.Cleanup(func() {
t.Logf("Deleting %q CM...", cm.Name)
if err := kubeClient.CoreV1().ConfigMaps(cm.Namespace).Delete(context.Background(), cm.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
t.Logf("Unable to delete %q CM: %v", cm.Name, err)
}
})
return cm
}
// createDeschedulerDeploymentWithCleanup creates (or recreates if already existing) a descheduler deployment and registers a t.Cleanup callback.
func createDeschedulerDeploymentWithCleanup(t *testing.T, ctx context.Context, kubeClient clientset.Interface, deployment *appsv1.Deployment) string {
t.Helper()
t.Logf("Creating descheduler deployment %v", deployment.Name)
_, err := kubeClient.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
if err != nil {
if apierrors.IsAlreadyExists(err) {
_ = kubeClient.AppsV1().Deployments(deployment.Namespace).Delete(ctx, deployment.Name, metav1.DeleteOptions{})
_, err = kubeClient.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
}
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deployment.Name, err)
}
}
deschedulerPodName := ""
t.Cleanup(func() {
if deschedulerPodName != "" {
printPodLogs(context.Background(), t, kubeClient, deschedulerPodName)
}
t.Logf("Deleting %q deployment...", deployment.Name)
if err := kubeClient.AppsV1().Deployments(deployment.Namespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
t.Logf("Unable to delete %q deployment: %v", deployment.Name, err)
}
waitForPodsToDisappear(context.Background(), t, kubeClient, deployment.Labels, deployment.Namespace)
})
t.Logf("Waiting for the descheduler pod running")
deschedulerPods := waitForPodsRunning(ctx, t, kubeClient, deployment.Labels, 1, deployment.Namespace)
if len(deschedulerPods) != 0 {
deschedulerPodName = deschedulerPods[0].Name
}
return deschedulerPodName
}
func deschedulerDeployment(testName string) *appsv1.Deployment {
deploymentObject := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
+2 -43
View File
@@ -153,51 +153,10 @@ func TestTooManyRestarts(t *testing.T) {
rs.DefaultFeatureGates = initFeatureGates()
preRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
// Deploy the descheduler with the configured policy
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(tc.policy)
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
t.Logf("Creating %q policy CM with RemovePodsHavingTooManyRestarts configured...", deschedulerPolicyConfigMapObj.Name)
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
defer func() {
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
}()
createPolicyConfigMap(t, ctx, clientSet, tc.policy)
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
deschedulerPodName := ""
defer func() {
if deschedulerPodName != "" {
printPodLogs(ctx, t, clientSet, deschedulerPodName)
}
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
}()
t.Logf("Waiting for the descheduler pod running")
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
if len(deschedulerPods) != 0 {
deschedulerPodName = deschedulerPods[0].Name
}
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
// Run RemovePodsHavingTooManyRestarts strategy
if err := wait.PollUntilContextTimeout(ctx, 1*time.Second, 50*time.Second, true, func(ctx context.Context) (bool, error) {
currentRunNames := sets.NewString(getCurrentPodNames(ctx, clientSet, testNamespace.Name, t)...)
+2 -43
View File
@@ -219,50 +219,9 @@ func TestTopologySpreadConstraint(t *testing.T) {
Include: []string{testNamespace.Name},
},
}
deschedulerPolicyConfigMapObj, err := deschedulerPolicyConfigMap(topologySpreadConstraintPolicy(constraintArgs, evictorArgs))
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
t.Logf("Creating %q policy CM with RemovePodsHavingTooManyRestarts configured...", deschedulerPolicyConfigMapObj.Name)
_, err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Create(ctx, deschedulerPolicyConfigMapObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
defer func() {
t.Logf("Deleting %q CM...", deschedulerPolicyConfigMapObj.Name)
err = clientSet.CoreV1().ConfigMaps(deschedulerPolicyConfigMapObj.Namespace).Delete(ctx, deschedulerPolicyConfigMapObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q CM: %v", deschedulerPolicyConfigMapObj.Name, err)
}
}()
createPolicyConfigMap(t, ctx, clientSet, topologySpreadConstraintPolicy(constraintArgs, evictorArgs))
deschedulerDeploymentObj := deschedulerDeployment(testNamespace.Name)
t.Logf("Creating descheduler deployment %v", deschedulerDeploymentObj.Name)
_, err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Create(ctx, deschedulerDeploymentObj, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Error creating %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
deschedulerPodName := ""
defer func() {
if deschedulerPodName != "" {
printPodLogs(ctx, t, clientSet, deschedulerPodName)
}
t.Logf("Deleting %q deployment...", deschedulerDeploymentObj.Name)
err = clientSet.AppsV1().Deployments(deschedulerDeploymentObj.Namespace).Delete(ctx, deschedulerDeploymentObj.Name, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("Unable to delete %q deployment: %v", deschedulerDeploymentObj.Name, err)
}
waitForPodsToDisappear(ctx, t, clientSet, deschedulerDeploymentObj.Labels, deschedulerDeploymentObj.Namespace)
}()
t.Logf("Waiting for the descheduler pod running")
deschedulerPods := waitForPodsRunning(ctx, t, clientSet, deschedulerDeploymentObj.Labels, 1, deschedulerDeploymentObj.Namespace)
if len(deschedulerPods) != 0 {
deschedulerPodName = deschedulerPods[0].Name
}
createDeschedulerDeploymentWithCleanup(t, ctx, clientSet, deschedulerDeploymentObj)
// Run RemovePodsHavingTooManyRestarts strategy
var meetsEvictedExpectations bool
+2
View File
@@ -1,3 +1,5 @@
# Used by the manifests GitHub Actions workflow (.github/workflows/manifests.yaml).
# Three-node cluster without topology zone labels; not used by e2e tests.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
e2e_repo_root() {
local script_dir="$1"
cd "${script_dir}/.." && pwd
}
collect_logs() {
local namespace pod
echo "Collecting pods and logs"
for namespace in "$@"; do
kubectl get pods -n "${namespace}"
for pod in $(kubectl get pods -n "${namespace}" -o name); do
echo "Logs for ${pod}"
kubectl logs -n "${namespace}" "${pod}"
done
done
}
kind_load_image() {
local image="$1"
if [ "${CONTAINER_ENGINE}" = "podman" ]; then
local archive
archive="/tmp/$(echo "${image}" | tr '/:' '_').tar"
podman save "${image}" -o "${archive}"
${KIND_SUDO} kind load image-archive "${archive}"
rm "${archive}"
else
${KIND_SUDO} kind load docker-image "${image}"
fi
}
+6
View File
@@ -0,0 +1,6 @@
# E2E tool version defaults. Override via environment variables.
# Keep .github/workflows/manifests.yaml matrix versions in sync with these values.
K8S_VERSION=${KUBERNETES_VERSION:-v1.36.1}
KIND_VERSION=${KIND_VERSION:-v0.31.0}
METRICS_SERVER_VERSION=${METRICS_SERVER_VERSION:-v0.8.1}
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
kubectl create -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml"
kubectl create -f "https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml"
# TODO(1.37): drop this patch when the k8s 1.34 e2e lane is removed. ImageVolume needs k8s >= 1.35.
if [[ "${K8S_VERSION}" == v1.34* ]]; then
kubectl -n kubevirt patch kubevirt kubevirt --type=merge --patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true,"disabledFeatureGates":["ImageVolume"]}}}}'
else
kubectl -n kubevirt patch kubevirt kubevirt --type=merge --patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}'
fi
kubectl wait --timeout=300s --for=condition=Available -n kubevirt kv/kubevirt
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
kubectl apply -f "https://github.com/kubernetes-sigs/metrics-server/releases/download/${METRICS_SERVER_VERSION}/components.yaml"
kubectl patch -n kube-system deployment metrics-server --type=json \
-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl wait --timeout=180s --for=condition=Available -n kube-system deployment/metrics-server
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
KIND_NODE_IMAGE=${KIND_NODE_IMAGE:-localhost/kindest/node:${K8S_VERSION}}
if [ -z "${SKIP_KUBECTL_INSTALL}" ]; then
curl -Lo kubectl "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl" && chmod +x kubectl && mv kubectl /usr/local/bin/
fi
if [ -z "${SKIP_KIND_INSTALL}" ]; then
wget "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
chmod +x kind-linux-amd64
mv kind-linux-amd64 kind
export PATH=$PATH:$PWD
fi
if [ -z "${SKIP_INSTALL}" ]; then
${KIND_SUDO} kind build node-image "${K8S_VERSION}" --image "${KIND_NODE_IMAGE}"
${KIND_SUDO} kind create cluster --image "${KIND_NODE_IMAGE}" --config="${REPO_ROOT}/hack/kind_config.yaml"
fi
${CONTAINER_ENGINE} pull registry.k8s.io/pause
kind_load_image registry.k8s.io/pause
kind_load_image "${DESCHEDULER_IMAGE}"
${KIND_SUDO} kind get kubeconfig > /tmp/admin.conf
export KUBECONFIG="/tmp/admin.conf"
mkdir -p ~/gopath/src/sigs.k8s.io/
+22 -64
View File
@@ -18,92 +18,50 @@ set -x
set -o errexit
set -o nounset
BASEDIR=$(dirname "$0")
# shellcheck source=test/lib/e2e-common.sh
source "${BASEDIR}/lib/e2e-common.sh"
# shellcheck source=test/lib/e2e-versions.env
source "${BASEDIR}/lib/e2e-versions.env"
REPO_ROOT="$(e2e_repo_root "${BASEDIR}")"
# Set to empty if unbound/empty
SKIP_INSTALL=${SKIP_INSTALL:-}
KIND_E2E=${KIND_E2E:-}
CONTAINER_ENGINE=${CONTAINER_ENGINE:-docker}
KIND_SUDO=${KIND_SUDO:-}
KIND_VERSION=${KIND_VERSION:-v0.31.0}
SKIP_KUBECTL_INSTALL=${SKIP_KUBECTL_INSTALL:-}
SKIP_KIND_INSTALL=${SKIP_KIND_INSTALL:-}
SKIP_KUBEVIRT_INSTALL=${SKIP_KUBEVIRT_INSTALL:-}
KUBEVIRT_VERSION=${KUBEVIRT_VERSION:-v1.6.2}
SKIP_METRICS_SERVER_INSTALL=${SKIP_METRICS_SERVER_INSTALL:-}
KUBEVIRT_VERSION=${KUBEVIRT_VERSION:-$(grep 'kubevirt.io/api ' "${REPO_ROOT}/go.mod" | awk '{print $2}')}
# Build a descheduler image
IMAGE_TAG=v$(date +%Y%m%d)-$(git describe --tags)
BASEDIR=$(dirname "$0")
VERSION="${IMAGE_TAG}" make -C ${BASEDIR}/.. image
VERSION="${IMAGE_TAG}" make -C "${REPO_ROOT}" image
export DESCHEDULER_IMAGE="docker.io/library/descheduler:${IMAGE_TAG}"
echo "DESCHEDULER_IMAGE: ${DESCHEDULER_IMAGE}"
# This just runs e2e tests.
if [ -n "$KIND_E2E" ]; then
K8S_VERSION=${KUBERNETES_VERSION:-v1.35.1}
if [ -z "${SKIP_KUBECTL_INSTALL}" ]; then
curl -Lo kubectl https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && mv kubectl /usr/local/bin/
fi
if [ -z "${SKIP_KIND_INSTALL}" ]; then
wget https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64
chmod +x kind-linux-amd64
mv kind-linux-amd64 kind
export PATH=$PATH:$PWD
fi
# If we did not set SKIP_INSTALL
if [ -z "$SKIP_INSTALL" ]; then
${KIND_SUDO} kind create cluster --image kindest/node:${K8S_VERSION} --config=./hack/kind_config.yaml
fi
${CONTAINER_ENGINE} pull registry.k8s.io/pause
if [ "${CONTAINER_ENGINE}" == "podman" ]; then
podman save registry.k8s.io/pause -o /tmp/pause.tar
${KIND_SUDO} kind load image-archive /tmp/pause.tar
rm /tmp/pause.tar
podman save ${DESCHEDULER_IMAGE} -o /tmp/descheduler.tar
${KIND_SUDO} kind load image-archive /tmp/descheduler.tar
rm /tmp/descheduler.tar
else
${KIND_SUDO} kind load docker-image registry.k8s.io/pause
${KIND_SUDO} kind load docker-image ${DESCHEDULER_IMAGE}
fi
${KIND_SUDO} kind get kubeconfig > /tmp/admin.conf
export KUBECONFIG="/tmp/admin.conf"
mkdir -p ~/gopath/src/sigs.k8s.io/
if [ -n "${KIND_E2E}" ]; then
# shellcheck source=test/lib/setup-kind.sh
source "${BASEDIR}/lib/setup-kind.sh"
fi
# Deploy rbac, sa and binding for a descheduler running through a deployment
kubectl apply -f kubernetes/base/rbac.yaml
kubectl apply -f "${REPO_ROOT}/kubernetes/base/rbac.yaml"
collect_logs() {
echo "Collecting pods and logs"
kubectl get pods -n default
kubectl get pods -n kubevirt
for pod in $(kubectl get pods -n default -o name); do
echo "Logs for ${pod}"
kubectl logs -n default ${pod}
done
for pod in $(kubectl get pods -n kubevirt -o name); do
echo "Logs for ${pod}"
kubectl logs -n kubevirt ${pod}
done
}
trap "collect_logs" ERR
trap 'collect_logs default kubevirt' ERR
if [ -z "${SKIP_KUBEVIRT_INSTALL}" ]; then
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
kubectl wait --timeout=180s --for=condition=Available -n kubevirt kv/kubevirt
kubectl -n kubevirt patch kubevirt kubevirt --type=merge --patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}'
# shellcheck source=test/lib/install-kubevirt.sh
source "${BASEDIR}/lib/install-kubevirt.sh"
fi
METRICS_SERVER_VERSION="v0.8.1"
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/${METRICS_SERVER_VERSION}/components.yaml
kubectl patch -n kube-system deployment metrics-server --type=json \
-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
if [ -z "${SKIP_METRICS_SERVER_INSTALL}" ]; then
# shellcheck source=test/lib/install-metrics-server.sh
source "${BASEDIR}/lib/install-metrics-server.sh"
fi
PRJ_PREFIX="sigs.k8s.io/descheduler"
go test ${PRJ_PREFIX}/test/e2e/ -v -timeout 0 --args --descheduler-image ${DESCHEDULER_IMAGE} --pod-run-as-user-id 1000 --pod-run-as-group-id 1000
go test ${PRJ_PREFIX}/test/e2e/ -v -timeout 0 --args --descheduler-image "${DESCHEDULER_IMAGE}" --kubevirt-version-tag "${KUBEVIRT_VERSION}" --pod-run-as-user-id 1000 --pod-run-as-group-id 1000
+4 -4
View File
@@ -17,9 +17,9 @@ ANTLR4 that it is compatible with (I.E. uses the /v4 path).
However, this was found to be problematic, as it meant that with the runtime embedded so far underneath the root
of the repo, the `go get` and related commands could not properly resolve the location of the go runtime source code.
This meant that the reference to the runtime in your `go.mod` file would refer to the correct source code, but would not
list the release tag such as @4.12.0 - this was confusing, to say the least.
list the release tag such as @4.13.1 - this was confusing, to say the least.
As of 4.12.1, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr`
As of 4.13.0, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr`
(the go get command should also be used with this path). See the main documentation for the ANTLR4 project for more information,
which is available at [ANTLR docs]. The documentation for using the Go runtime is available at [Go runtime docs].
@@ -49,7 +49,7 @@ Here is a general/recommended template for an ANTLR based recognizer in Go:
.
parser
mygrammar.g4
antlr-4.12.1-complete.jar
antlr-4.13.1-complete.jar
generate.go
generate.sh
parsing - generated code goes here
@@ -71,7 +71,7 @@ And the generate.sh file will look similar to this:
#!/bin/sh
alias antlr4='java -Xmx500M -cp "./antlr4-4.12.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
alias antlr4='java -Xmx500M -cp "./antlr4-4.13.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
antlr4 -Dlanguage=Go -no-visitor -package parsing *.g4
depending on whether you want visitors or listeners or any other ANTLR options. Not that another option here
+3 -5
View File
@@ -4,8 +4,6 @@
package antlr
import "sync"
// ATNInvalidAltNumber is used to represent an ALT number that has yet to be calculated or
// which is invalid for a particular struct such as [*antlr.BaseRuleContext]
var ATNInvalidAltNumber int
@@ -56,9 +54,9 @@ type ATN struct {
//
states []ATNState
mu sync.Mutex
stateMu sync.RWMutex
edgeMu sync.RWMutex
mu Mutex
stateMu RWMutex
edgeMu RWMutex
}
// NewATN returns a new ATN struct representing the given grammarType and is used
-3
View File
@@ -73,9 +73,6 @@ func NewATNConfig1(c *ATNConfig, state ATNState, context *PredictionContext) *AT
// NewATNConfig creates a new ATNConfig instance given an existing config, a state, a context and a semantic context, other 'constructors'
// are just wrappers around this one.
func NewATNConfig(c *ATNConfig, state ATNState, context *PredictionContext, semanticContext SemanticContext) *ATNConfig {
if semanticContext == nil {
panic("semanticContext cannot be nil") // TODO: Remove this - probably put here for some bug that is now fixed
}
b := &ATNConfig{}
b.InitATNConfig(c, state, c.GetAlt(), context, semanticContext)
b.cType = parserConfig
+1 -1
View File
@@ -148,7 +148,7 @@ func (is *InputStream) GetTextFromInterval(i Interval) string {
}
func (*InputStream) GetSourceName() string {
return ""
return "Obtained from string"
}
// String returns the entire input stream as a string
+2 -3
View File
@@ -8,7 +8,6 @@ import (
"container/list"
"runtime/debug"
"sort"
"sync"
)
// Collectable is an interface that a struct should implement if it is to be
@@ -587,12 +586,12 @@ type VisitRecord struct {
type VisitList struct {
cache *list.List
lock sync.RWMutex
lock RWMutex
}
var visitListPool = VisitList{
cache: list.New(),
lock: sync.RWMutex{},
lock: RWMutex{},
}
// NewVisitRecord returns a new VisitRecord instance from the pool if available.
+1 -1
View File
@@ -207,7 +207,7 @@ func (b *BaseLexer) NextToken() Token {
for {
b.thetype = TokenInvalidType
ttype := b.safeMatch()
ttype := b.safeMatch() // Defaults to LexerSkip
if b.input.LA(1) == TokenEOF {
b.hitEOF = true
+1
View File
@@ -40,6 +40,7 @@ func (la *LL1Analyzer) getDecisionLookahead(s ATNState) []*IntervalSet {
for alt := 0; alt < count; alt++ {
look[alt] = NewIntervalSet()
// TODO: This is one of the reasons that ATNConfigs are allocated and freed all the time - fix this tomorrow jim!
lookBusy := NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ClosureBusyCollection, "LL1Analyzer.getDecisionLookahead for lookBusy")
la.look1(s.GetTransitions()[alt].getTarget(), nil, BasePredictionContextEMPTY, look[alt], lookBusy, NewBitSet(), false, false)
+41
View File
@@ -0,0 +1,41 @@
//go:build !antlr.nomutex
// +build !antlr.nomutex
package antlr
import "sync"
// Mutex is a simple mutex implementation which just delegates to sync.Mutex, it
// is used to provide a mutex implementation for the antlr package, which users
// can turn off with the build tag -tags antlr.nomutex
type Mutex struct {
mu sync.Mutex
}
func (m *Mutex) Lock() {
m.mu.Lock()
}
func (m *Mutex) Unlock() {
m.mu.Unlock()
}
type RWMutex struct {
mu sync.RWMutex
}
func (m *RWMutex) Lock() {
m.mu.Lock()
}
func (m *RWMutex) Unlock() {
m.mu.Unlock()
}
func (m *RWMutex) RLock() {
m.mu.RLock()
}
func (m *RWMutex) RUnlock() {
m.mu.RUnlock()
}
+32
View File
@@ -0,0 +1,32 @@
//go:build antlr.nomutex
// +build antlr.nomutex
package antlr
type Mutex struct{}
func (m *Mutex) Lock() {
// No-op
}
func (m *Mutex) Unlock() {
// No-op
}
type RWMutex struct{}
func (m *RWMutex) Lock() {
// No-op
}
func (m *RWMutex) Unlock() {
// No-op
}
func (m *RWMutex) RLock() {
// No-op
}
func (m *RWMutex) RUnlock() {
// No-op
}
+1 -3
View File
@@ -10,8 +10,6 @@ import (
"strings"
)
var ()
// ClosureBusy is a store of ATNConfigs and is a tiny abstraction layer over
// a standard JStore so that we can use Lazy instantiation of the JStore, mostly
// to avoid polluting the stats module with a ton of JStore instances with nothing in them.
@@ -883,7 +881,7 @@ func (p *ParserATNSimulator) getPredicatePredictions(ambigAlts *BitSet, altToPre
// the ERROR state was reached, outerContext as the initial parser context from the paper
// or the parser stack at the instant before prediction commences.
//
// Teh func returns the value to return from [AdaptivePredict], or
// The func returns the value to return from [AdaptivePredict], or
// [ATNInvalidAltNumber] if a suitable alternative was not
// identified and [AdaptivePredict] should report an error instead.
func (p *ParserATNSimulator) getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(configs *ATNConfigSet, outerContext ParserRuleContext) int {
+30 -30
View File
@@ -6,7 +6,6 @@ package antlr
import (
"fmt"
"golang.org/x/exp/slices"
"strconv"
)
@@ -101,7 +100,7 @@ func NewArrayPredictionContext(parents []*PredictionContext, returnStates []int)
hash = murmurUpdate(hash, returnState)
}
hash = murmurFinish(hash, len(parents)<<1)
nec := &PredictionContext{}
nec.cachedHash = hash
nec.pcType = PredictionContextArray
@@ -115,6 +114,9 @@ func (p *PredictionContext) Hash() int {
}
func (p *PredictionContext) Equals(other Collectable[*PredictionContext]) bool {
if p == other {
return true
}
switch p.pcType {
case PredictionContextEmpty:
otherP := other.(*PredictionContext)
@@ -138,13 +140,11 @@ func (p *PredictionContext) ArrayEquals(o Collectable[*PredictionContext]) bool
if p.cachedHash != other.Hash() {
return false // can't be same if hash is different
}
// Must compare the actual array elements and not just the array address
//
return slices.Equal(p.returnStates, other.returnStates) &&
slices.EqualFunc(p.parents, other.parents, func(x, y *PredictionContext) bool {
return x.Equals(y)
})
return intSlicesEqual(p.returnStates, other.returnStates) &&
pcSliceEqual(p.parents, other.parents)
}
func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext]) bool {
@@ -152,23 +152,23 @@ func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext
return false
}
otherP := other.(*PredictionContext)
if otherP == nil {
if otherP == nil || otherP.pcType != PredictionContextSingleton {
return false
}
if p.cachedHash != otherP.Hash() {
return false // Can't be same if hash is different
}
if p.returnState != otherP.getReturnState(0) {
return false
}
// Both parents must be nil if one is
if p.parentCtx == nil {
return otherP.parentCtx == nil
}
return p.parentCtx.Equals(otherP.parentCtx)
}
@@ -225,27 +225,27 @@ func (p *PredictionContext) String() string {
return "$"
case PredictionContextSingleton:
var up string
if p.parentCtx == nil {
up = ""
} else {
up = p.parentCtx.String()
}
if len(up) == 0 {
if p.returnState == BasePredictionContextEmptyReturnState {
return "$"
}
return strconv.Itoa(p.returnState)
}
return strconv.Itoa(p.returnState) + " " + up
case PredictionContextArray:
if p.isEmpty() {
return "[]"
}
s := "["
for i := 0; i < len(p.returnStates); i++ {
if i > 0 {
@@ -263,7 +263,7 @@ func (p *PredictionContext) String() string {
}
}
return s + "]"
default:
return "unknown"
}
@@ -309,18 +309,18 @@ func predictionContextFromRuleContext(a *ATN, outerContext RuleContext) *Predict
parent := predictionContextFromRuleContext(a, outerContext.GetParent().(RuleContext))
state := a.states[outerContext.GetInvokingState()]
transition := state.GetTransitions()[0]
return SingletonBasePredictionContextCreate(parent, transition.(*RuleTransition).followState.GetStateNumber())
}
func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext {
// Share same graph if both same
//
if a == b || a.Equals(b) {
return a
}
if a.pcType == PredictionContextSingleton && b.pcType == PredictionContextSingleton {
return mergeSingletons(a, b, rootIsWildcard, mergeCache)
}
@@ -334,7 +334,7 @@ func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *Pr
return b
}
}
// Convert either Singleton or Empty to arrays, so that we can merge them
//
ara := convertToArray(a)
@@ -395,7 +395,7 @@ func mergeSingletons(a, b *PredictionContext, rootIsWildcard bool, mergeCache *J
return previous
}
}
rootMerge := mergeRoot(a, b, rootIsWildcard)
if rootMerge != nil {
if mergeCache != nil {
@@ -564,7 +564,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
i := 0 // walks a
j := 0 // walks b
k := 0 // walks target M array
mergedReturnStates := make([]int, len(a.returnStates)+len(b.returnStates))
mergedParents := make([]*PredictionContext, len(a.returnStates)+len(b.returnStates))
// walk and merge to yield mergedParents, mergedReturnStates
@@ -626,9 +626,9 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
mergedParents = mergedParents[0:k]
mergedReturnStates = mergedReturnStates[0:k]
}
M := NewArrayPredictionContext(mergedParents, mergedReturnStates)
// if we created same array as a or b, return that instead
// TODO: JI track whether this is possible above during merge sort for speed and possibly avoid an allocation
if M.Equals(a) {
@@ -650,7 +650,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
return b
}
combineCommonParents(&mergedParents)
if mergeCache != nil {
mergeCache.Put(a, b, M)
}
@@ -666,7 +666,7 @@ func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMa
//goland:noinspection GoUnusedFunction
func combineCommonParents(parents *[]*PredictionContext) {
uniqueParents := NewJStore[*PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionContextCollection, "combineCommonParents for PredictionContext")
for p := 0; p < len(*parents); p++ {
parent := (*parents)[p]
_, _ = uniqueParents.Put(parent)
@@ -685,7 +685,7 @@ func getCachedBasePredictionContext(context *PredictionContext, contextCache *Pr
if present {
return existing
}
existing, present = contextCache.Get(context)
if present {
visited.Put(context, existing)
@@ -722,6 +722,6 @@ func getCachedBasePredictionContext(context *PredictionContext, contextCache *Pr
contextCache.add(updated)
visited.Put(updated, updated)
visited.Put(context, updated)
return updated
}
+1 -1
View File
@@ -56,7 +56,7 @@ var tokenTypeMapCache = make(map[string]int)
var ruleIndexMapCache = make(map[string]int)
func (b *BaseRecognizer) checkVersion(toolVersion string) {
runtimeVersion := "4.12.0"
runtimeVersion := "4.13.1"
if runtimeVersion != toolVersion {
fmt.Println("ANTLR runtime and generated code versions disagree: " + runtimeVersion + "!=" + toolVersion)
}
+1 -2
View File
@@ -9,7 +9,6 @@ import (
"path/filepath"
"sort"
"strconv"
"sync"
)
// This file allows the user to collect statistics about the runtime of the ANTLR runtime. It is not enabled by default
@@ -30,7 +29,7 @@ type goRunStats struct {
// within this package.
//
jStats []*JStatRec
jStatsLock sync.RWMutex
jStatsLock RWMutex
topN int
topNByMax []*JStatRec
topNByUsed []*JStatRec
+41 -41
View File
@@ -104,6 +104,25 @@ func (b *BaseToken) GetSource() *TokenSourceCharStreamPair {
return b.source
}
func (b *BaseToken) GetText() string {
if b.text != "" {
return b.text
}
input := b.GetInputStream()
if input == nil {
return ""
}
n := input.Size()
if b.GetStart() < n && b.GetStop() < n {
return input.GetTextFromInterval(NewInterval(b.GetStart(), b.GetStop()))
}
return "<EOF>"
}
func (b *BaseToken) SetText(text string) {
b.text = text
}
func (b *BaseToken) GetTokenIndex() int {
return b.tokenIndex
}
@@ -120,6 +139,28 @@ func (b *BaseToken) GetInputStream() CharStream {
return b.source.charStream
}
func (b *BaseToken) String() string {
txt := b.GetText()
if txt != "" {
txt = strings.Replace(txt, "\n", "\\n", -1)
txt = strings.Replace(txt, "\r", "\\r", -1)
txt = strings.Replace(txt, "\t", "\\t", -1)
} else {
txt = "<no text>"
}
var ch string
if b.GetChannel() > 0 {
ch = ",channel=" + strconv.Itoa(b.GetChannel())
} else {
ch = ""
}
return "[@" + strconv.Itoa(b.GetTokenIndex()) + "," + strconv.Itoa(b.GetStart()) + ":" + strconv.Itoa(b.GetStop()) + "='" +
txt + "',<" + strconv.Itoa(b.GetTokenType()) + ">" +
ch + "," + strconv.Itoa(b.GetLine()) + ":" + strconv.Itoa(b.GetColumn()) + "]"
}
type CommonToken struct {
BaseToken
}
@@ -170,44 +211,3 @@ func (c *CommonToken) clone() *CommonToken {
t.text = c.GetText()
return t
}
func (c *CommonToken) GetText() string {
if c.text != "" {
return c.text
}
input := c.GetInputStream()
if input == nil {
return ""
}
n := input.Size()
if c.start < n && c.stop < n {
return input.GetTextFromInterval(NewInterval(c.start, c.stop))
}
return "<EOF>"
}
func (c *CommonToken) SetText(text string) {
c.text = text
}
func (c *CommonToken) String() string {
txt := c.GetText()
if txt != "" {
txt = strings.Replace(txt, "\n", "\\n", -1)
txt = strings.Replace(txt, "\r", "\\r", -1)
txt = strings.Replace(txt, "\t", "\\t", -1)
} else {
txt = "<no text>"
}
var ch string
if c.channel > 0 {
ch = ",channel=" + strconv.Itoa(c.channel)
} else {
ch = ""
}
return "[@" + strconv.Itoa(c.tokenIndex) + "," + strconv.Itoa(c.start) + ":" + strconv.Itoa(c.stop) + "='" +
txt + "',<" + strconv.Itoa(c.tokenType) + ">" +
ch + "," + strconv.Itoa(c.line) + ":" + strconv.Itoa(c.column) + "]"
}
+53
View File
@@ -326,3 +326,56 @@ func isDirectory(dir string) (bool, error) {
}
return fileInfo.IsDir(), err
}
// intSlicesEqual returns true if the two slices of ints are equal, and is a little
// faster than slices.Equal.
func intSlicesEqual(s1, s2 []int) bool {
if s1 == nil && s2 == nil {
return true
}
if s1 == nil || s2 == nil {
return false
}
if len(s1) == 0 && len(s2) == 0 {
return true
}
if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) {
return false
}
// If the slices are using the same memory, then they are the same slice
if &s1[0] == &s2[0] {
return true
}
for i, v := range s1 {
if v != s2[i] {
return false
}
}
return true
}
func pcSliceEqual(s1, s2 []*PredictionContext) bool {
if s1 == nil && s2 == nil {
return true
}
if s1 == nil || s2 == nil {
return false
}
if len(s1) == 0 && len(s2) == 0 {
return true
}
if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) {
return false
}
// If the slices are using the same memory, then they are the same slice
if &s1[0] == &s2[0] {
return true
}
for i, v := range s1 {
if !v.Equals(s2[i]) {
return false
}
}
return true
}
@@ -1,4 +1,4 @@
// Copyright 2015 go-swagger maintainers
// Copyright 2025
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,22 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package swag
//go:build !unix
import "mime/multipart"
package daemon
// File represents an uploaded file.
type File struct {
Data multipart.File
Header *multipart.FileHeader
}
// Read bytes from the file
func (f *File) Read(p []byte) (n int, err error) {
return f.Data.Read(p)
}
// Close the file
func (f *File) Close() error {
return f.Data.Close()
// SdNotifyMonotonicUsec returns the empty string on unsupported platforms.
func SdNotifyMonotonicUsec() string {
return ""
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2025
//
// 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.
//go:build unix
package daemon
import (
"strconv"
"golang.org/x/sys/unix"
)
// SdNotifyMonotonicUsec returns a MONOTONIC_USEC=... assignment for the current time
// with a trailing newline included. This is typically used with [SdNotifyReloading].
//
// If the monotonic clock is not available on the system, the empty string is returned.
func SdNotifyMonotonicUsec() string {
var ts unix.Timespec
if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts); err != nil {
// Monotonic clock is not available on this system.
return ""
}
return "MONOTONIC_USEC=" + strconv.FormatInt(ts.Nano()/1000, 10) + "\n"
}
+4 -3
View File
@@ -15,6 +15,7 @@
package daemon
import (
"errors"
"fmt"
"os"
"strconv"
@@ -51,10 +52,10 @@ func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
}
s, err := strconv.Atoi(wusec)
if err != nil {
return 0, fmt.Errorf("error converting WATCHDOG_USEC: %s", err)
return 0, fmt.Errorf("error converting WATCHDOG_USEC: %w", err)
}
if s <= 0 {
return 0, fmt.Errorf("error WATCHDOG_USEC must be a positive number")
return 0, errors.New("error WATCHDOG_USEC must be a positive number")
}
interval := time.Duration(s) * time.Microsecond
@@ -63,7 +64,7 @@ func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) {
}
p, err := strconv.Atoi(wpid)
if err != nil {
return 0, fmt.Errorf("error converting WATCHDOG_PID: %s", err)
return 0, fmt.Errorf("error converting WATCHDOG_PID: %w", err)
}
if os.Getpid() != p {
return 0, nil
+1 -1
View File
@@ -41,6 +41,6 @@ const (
)
// Print prints a message to the local systemd journal using Send().
func Print(priority Priority, format string, a ...interface{}) error {
func Print(priority Priority, format string, a ...any) error {
return Send(fmt.Sprintf(format, a...), priority, nil)
}
+4 -6
View File
@@ -13,7 +13,6 @@
// limitations under the License.
//go:build !windows
// +build !windows
// Package journal provides write bindings to the local systemd journal.
// It is implemented in pure Go and connects to the journal directly over its
@@ -31,7 +30,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strconv"
@@ -108,7 +106,7 @@ func fdIsJournalStream(fd int) (bool, error) {
var expectedStat syscall.Stat_t
_, err := fmt.Sscanf(journalStream, "%d:%d", &expectedStat.Dev, &expectedStat.Ino)
if err != nil {
return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %v", journalStream, err)
return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %w", journalStream, err)
}
var stat syscall.Stat_t
@@ -194,7 +192,7 @@ func appendVariable(w io.Writer, name, value string) {
* - the data, followed by a newline
*/
fmt.Fprintln(w, name)
binary.Write(w, binary.LittleEndian, uint64(len(value)))
_ = binary.Write(w, binary.LittleEndian, uint64(len(value)))
fmt.Fprintln(w, value)
} else {
/* just write the variable and value all on one line */
@@ -214,7 +212,7 @@ func validVarName(name string) error {
}
for _, c := range name {
if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') {
if ('A' > c || c > 'Z') && ('0' > c || c > '9') && c != '_' {
return errors.New("Variable name contains invalid characters")
}
}
@@ -239,7 +237,7 @@ func isSocketSpaceError(err error) bool {
// tempFd creates a temporary, unlinked file under `/dev/shm`.
func tempFd() (*os.File, error) {
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
file, err := os.CreateTemp("/dev/shm/", "journal.XXXXX")
if err != nil {
return nil, err
}
-13
View File
@@ -1,13 +0,0 @@
language: go
go:
- 1.x
before_install:
- go test -v
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
+4
View File
@@ -1,5 +1,9 @@
# Change history of go-restful
## [v3.13.0] - 2025-08-14
- optimize performance of path matching in CurlyRouter ( thanks @wenhuang, Wen Huang)
## [v3.12.2] - 2025-02-21
- allow empty payloads in post,put,patch, issue #580 ( thanks @liggitt, Jordan Liggitt)
+1
View File
@@ -84,6 +84,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo
- Configurable (trace) logging
- Customizable gzip/deflate readers and writers using CompressorProvider registration
- Inject your own http.Handler using the `HttpMiddlewareHandlerToFilter` function
- Added `SetPathTokenCacheEnabled` and `SetCustomVerbCacheEnabled` to disable regexp caching (default=true)
## How to customize
There are several hooks to customize the behavior of the go-restful package.
+47 -3
View File
@@ -9,11 +9,35 @@ import (
"regexp"
"sort"
"strings"
"sync"
)
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
type CurlyRouter struct{}
var (
regexCache sync.Map // Cache for compiled regex patterns
pathTokenCacheEnabled = true // Enable/disable path token regex caching
)
// SetPathTokenCacheEnabled enables or disables path token regex caching for CurlyRouter.
// When disabled, regex patterns will be compiled on every request.
// When enabled (default), compiled regex patterns are cached for better performance.
func SetPathTokenCacheEnabled(enabled bool) {
pathTokenCacheEnabled = enabled
}
// getCachedRegexp retrieves a compiled regex from the cache if found and valid.
// Returns the regex and true if found and valid, nil and false otherwise.
func getCachedRegexp(cache *sync.Map, pattern string) (*regexp.Regexp, bool) {
if cached, found := cache.Load(pattern); found {
if regex, ok := cached.(*regexp.Regexp); ok {
return regex, true
}
}
return nil, false
}
// SelectRoute is part of the Router interface and returns the best match
// for the WebService and its Route for the given Request.
func (c CurlyRouter) SelectRoute(
@@ -113,8 +137,28 @@ func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, reque
}
return true, true
}
matched, err := regexp.MatchString(regPart, requestToken)
return (matched && err == nil), false
// Check cache first (if enabled)
if pathTokenCacheEnabled {
if regex, found := getCachedRegexp(&regexCache, regPart); found {
matched := regex.MatchString(requestToken)
return matched, false
}
}
// Compile the regex
regex, err := regexp.Compile(regPart)
if err != nil {
return false, false
}
// Cache the regex (if enabled)
if pathTokenCacheEnabled {
regexCache.Store(regPart, regex)
}
matched := regex.MatchString(requestToken)
return matched, false
}
var jsr311Router = RouterJSR311{}
@@ -168,7 +212,7 @@ func (c CurlyRouter) computeWebserviceScore(requestTokens []string, routeTokens
if matchesToken {
score++ // extra score for regex match
}
}
}
} else {
// not a parameter
if eachRequestToken != eachRouteToken {
+32 -2
View File
@@ -1,14 +1,28 @@
package restful
// Copyright 2025 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"fmt"
"regexp"
"sync"
)
var (
customVerbReg = regexp.MustCompile(":([A-Za-z]+)$")
customVerbReg = regexp.MustCompile(":([A-Za-z]+)$")
customVerbCache sync.Map // Cache for compiled custom verb regexes
customVerbCacheEnabled = true // Enable/disable custom verb regex caching
)
// SetCustomVerbCacheEnabled enables or disables custom verb regex caching.
// When disabled, custom verb regex patterns will be compiled on every request.
// When enabled (default), compiled custom verb regex patterns are cached for better performance.
func SetCustomVerbCacheEnabled(enabled bool) {
customVerbCacheEnabled = enabled
}
func hasCustomVerb(routeToken string) bool {
return customVerbReg.MatchString(routeToken)
}
@@ -20,7 +34,23 @@ func isMatchCustomVerb(routeToken string, pathToken string) bool {
}
customVerb := rs[1]
specificVerbReg := regexp.MustCompile(fmt.Sprintf(":%s$", customVerb))
regexPattern := fmt.Sprintf(":%s$", customVerb)
// Check cache first (if enabled)
if customVerbCacheEnabled {
if specificVerbReg, found := getCachedRegexp(&customVerbCache, regexPattern); found {
return specificVerbReg.MatchString(pathToken)
}
}
// Compile the regex
specificVerbReg := regexp.MustCompile(regexPattern)
// Cache the regex (if enabled)
if customVerbCacheEnabled {
customVerbCache.Store(regexPattern, specificVerbReg)
}
return specificVerbReg.MatchString(pathToken)
}
+19 -23
View File
@@ -1,7 +1,7 @@
/*
Package restful , a lean package for creating REST-style WebServices without magic.
WebServices and Routes
### WebServices and Routes
A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls.
Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes.
@@ -30,14 +30,14 @@ The (*Request, *Response) arguments provide functions for reading information fr
See the example https://github.com/emicklei/go-restful/blob/v3/examples/user-resource/restful-user-resource.go with a full implementation.
Regular expression matching Routes
### Regular expression matching Routes
A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path.
For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters.
Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax)
This feature requires the use of a CurlyRouter.
Containers
### Containers
A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests.
Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container.
@@ -47,7 +47,7 @@ You can create your own Container and create a new http.Server for that particul
container := restful.NewContainer()
server := &http.Server{Addr: ":8081", Handler: container}
Filters
### Filters
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc.
@@ -60,22 +60,21 @@ Use the following statement to pass the request,response pair to the next filter
chain.ProcessFilter(req, resp)
Container Filters
### Container Filters
These are processed before any registered WebService.
// install a (global) filter for the default container (processed before any webservice)
restful.Filter(globalLogging)
WebService Filters
### WebService Filters
These are processed before any Route of a WebService.
// install a webservice filter (processed before any route)
ws.Filter(webserviceLogging).Filter(measureTime)
Route Filters
### Route Filters
These are processed before calling the function associated with the Route.
@@ -84,7 +83,7 @@ These are processed before calling the function associated with the Route.
See the example https://github.com/emicklei/go-restful/blob/v3/examples/filters/restful-filters.go with full implementations.
Response Encoding
### Response Encoding
Two encodings are supported: gzip and deflate. To enable this for all responses:
@@ -95,20 +94,20 @@ Alternatively, you can create a Filter that performs the encoding and install it
See the example https://github.com/emicklei/go-restful/blob/v3/examples/encoding/restful-encoding-filter.go
OPTIONS support
### OPTIONS support
By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request.
Filter(OPTIONSFilter())
CORS
### CORS
By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests.
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
Filter(cors.Filter)
Error Handling
### Error Handling
Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why.
For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation.
@@ -137,11 +136,11 @@ The request does not have or has an unknown Accept Header set for this operation
The request does not have or has an unknown Content-Type Header set for this operation.
ServiceError
### ServiceError
In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response.
Performance options
### Performance options
This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
@@ -156,30 +155,27 @@ Default value is true
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
Trouble shooting
### Trouble shooting
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
Logging
### Logging
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
preferred package is simple.
Resources
### Resources
(c) 2012-2025, http://ernestmicklei.com. MIT License
[project]: https://github.com/emicklei/go-restful
[examples]: https://github.com/emicklei/go-restful/blob/master/examples
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
(c) 2012-2015, http://ernestmicklei.com. MIT License
*/
package restful
-22
View File
@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Peter Bourgon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-160
View File
@@ -1,160 +0,0 @@
# package log
**Deprecation notice:** The core Go kit log packages (log, log/level, log/term, and
log/syslog) have been moved to their own repository at github.com/go-kit/log.
The corresponding packages in this directory remain for backwards compatibility.
Their types alias the types and their functions call the functions provided by
the new repository. Using either import path should be equivalent. Prefer the
new import path when practical.
______
`package log` provides a minimal interface for structured logging in services.
It may be wrapped to encode conventions, enforce type-safety, provide leveled
logging, and so on. It can be used for both typical application log events,
and log-structured data streams.
## Structured logging
Structured logging is, basically, conceding to the reality that logs are
_data_, and warrant some level of schematic rigor. Using a stricter,
key/value-oriented message format for our logs, containing contextual and
semantic information, makes it much easier to get insight into the
operational activity of the systems we build. Consequently, `package log` is
of the strong belief that "[the benefits of structured logging outweigh the
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Migrating from unstructured to structured logging is probably a lot easier
than you'd expect.
```go
// Unstructured
log.Printf("HTTP server listening on %s", addr)
// Structured
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
```
## Usage
### Typical application logging
```go
w := log.NewSyncWriter(os.Stderr)
logger := log.NewLogfmtLogger(w)
logger.Log("question", "what is the meaning of life?", "answer", 42)
// Output:
// question="what is the meaning of life?" answer=42
```
### Contextual Loggers
```go
func main() {
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "instance_id", 123)
logger.Log("msg", "starting")
NewWorker(log.With(logger, "component", "worker")).Run()
NewSlacker(log.With(logger, "component", "slacker")).Run()
}
// Output:
// instance_id=123 msg=starting
// instance_id=123 component=worker msg=running
// instance_id=123 component=slacker msg=running
```
### Interact with stdlib logger
Redirect stdlib logger to Go kit logger.
```go
import (
"os"
stdlog "log"
kitlog "github.com/go-kit/kit/log"
)
func main() {
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
stdlog.Print("I sure like pie")
}
// Output:
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
```
Or, if, for legacy reasons, you need to pipe all of your logging through the
stdlib log package, you can redirect Go kit logger to the stdlib logger.
```go
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
logger.Log("legacy", true, "msg", "at least it's something")
// Output:
// 2016/01/01 12:34:56 legacy=true msg="at least it's something"
```
### Timestamps and callers
```go
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
logger.Log("msg", "hello")
// Output:
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
```
## Levels
Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level).
## Supported output formats
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
- JSON
## Enhancements
`package log` is centered on the one-method Logger interface.
```go
type Logger interface {
Log(keyvals ...interface{}) error
}
```
This interface, and its supporting code like is the product of much iteration
and evaluation. For more details on the evolution of the Logger interface,
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
a talk by [Chris Hines](https://github.com/ChrisHines).
Also, please see
[#63](https://github.com/go-kit/kit/issues/63),
[#76](https://github.com/go-kit/kit/pull/76),
[#131](https://github.com/go-kit/kit/issues/131),
[#157](https://github.com/go-kit/kit/pull/157),
[#164](https://github.com/go-kit/kit/issues/164), and
[#252](https://github.com/go-kit/kit/pull/252)
to review historical conversations about package log and the Logger interface.
Value-add packages and suggestions,
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level),
are of course welcome. Good proposals should
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and
- Be friendly to packages that accept only an unadorned log.Logger.
## Benchmarks & comparisons
There are a few Go logging benchmarks and comparisons that include Go kit's package log.
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log
- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log
-118
View File
@@ -1,118 +0,0 @@
// Package log provides a structured logger.
//
// Deprecated: Use github.com/go-kit/log instead.
//
// Structured logging produces logs easily consumed later by humans or
// machines. Humans might be interested in debugging errors, or tracing
// specific requests. Machines might be interested in counting interesting
// events, or aggregating information for off-line processing. In both cases,
// it is important that the log messages are structured and actionable.
// Package log is designed to encourage both of these best practices.
//
// Basic Usage
//
// The fundamental interface is Logger. Loggers create log events from
// key/value data. The Logger interface has a single method, Log, which
// accepts a sequence of alternating key/value pairs, which this package names
// keyvals.
//
// type Logger interface {
// Log(keyvals ...interface{}) error
// }
//
// Here is an example of a function using a Logger to create log events.
//
// func RunTask(task Task, logger log.Logger) string {
// logger.Log("taskID", task.ID, "event", "starting task")
// ...
// logger.Log("taskID", task.ID, "event", "task complete")
// }
//
// The keys in the above example are "taskID" and "event". The values are
// task.ID, "starting task", and "task complete". Every key is followed
// immediately by its value.
//
// Keys are usually plain strings. Values may be any type that has a sensible
// encoding in the chosen log format. With structured logging it is a good
// idea to log simple values without formatting them. This practice allows
// the chosen logger to encode values in the most appropriate way.
//
// Contextual Loggers
//
// A contextual logger stores keyvals that it includes in all log events.
// Building appropriate contextual loggers reduces repetition and aids
// consistency in the resulting log output. With, WithPrefix, and WithSuffix
// add context to a logger. We can use With to improve the RunTask example.
//
// func RunTask(task Task, logger log.Logger) string {
// logger = log.With(logger, "taskID", task.ID)
// logger.Log("event", "starting task")
// ...
// taskHelper(task.Cmd, logger)
// ...
// logger.Log("event", "task complete")
// }
//
// The improved version emits the same log events as the original for the
// first and last calls to Log. Passing the contextual logger to taskHelper
// enables each log event created by taskHelper to include the task.ID even
// though taskHelper does not have access to that value. Using contextual
// loggers this way simplifies producing log output that enables tracing the
// life cycle of individual tasks. (See the Contextual example for the full
// code of the above snippet.)
//
// Dynamic Contextual Values
//
// A Valuer function stored in a contextual logger generates a new value each
// time an event is logged. The Valuer example demonstrates how this feature
// works.
//
// Valuers provide the basis for consistently logging timestamps and source
// code location. The log package defines several valuers for that purpose.
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
// DefaultCaller. A common logger initialization sequence that ensures all log
// entries contain a timestamp and source location looks like this:
//
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
//
// Concurrent Safety
//
// Applications with multiple goroutines want each log event written to the
// same logger to remain separate from other log events. Package log provides
// two simple solutions for concurrent safe logging.
//
// NewSyncWriter wraps an io.Writer and serializes each call to its Write
// method. Using a SyncWriter has the benefit that the smallest practical
// portion of the logging logic is performed within a mutex, but it requires
// the formatting Logger to make only one call to Write per log event.
//
// NewSyncLogger wraps any Logger and serializes each call to its Log method.
// Using a SyncLogger has the benefit that it guarantees each log event is
// handled atomically within the wrapped logger, but it typically serializes
// both the formatting and output logic. Use a SyncLogger if the formatting
// logger may perform multiple writes per log event.
//
// Error Handling
//
// This package relies on the practice of wrapping or decorating loggers with
// other loggers to provide composable pieces of functionality. It also means
// that Logger.Log must return an error because some
// implementations—especially those that output log data to an io.Writer—may
// encounter errors that cannot be handled locally. This in turn means that
// Loggers that wrap other loggers should return errors from the wrapped
// logger up the stack.
//
// Fortunately, the decorator pattern also provides a way to avoid the
// necessity to check for errors every time an application calls Logger.Log.
// An application required to panic whenever its Logger encounters
// an error could initialize its logger as follows.
//
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
// if err := fmtlogger.Log(keyvals...); err != nil {
// panic(err)
// }
// return nil
// })
package log
-15
View File
@@ -1,15 +0,0 @@
package log
import (
"io"
"github.com/go-kit/log"
)
// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
// single JSON object. Each log event produces no more than one call to
// w.Write. The passed Writer must be safe for concurrent use by multiple
// goroutines if the returned Logger will be used concurrently.
func NewJSONLogger(w io.Writer) Logger {
return log.NewJSONLogger(w)
}
-51
View File
@@ -1,51 +0,0 @@
package log
import (
"github.com/go-kit/log"
)
// Logger is the fundamental interface for all log operations. Log creates a
// log event from keyvals, a variadic sequence of alternating keys and values.
// Implementations must be safe for concurrent use by multiple goroutines. In
// particular, any implementation of Logger that appends to keyvals or
// modifies or retains any of its elements must make a copy first.
type Logger = log.Logger
// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = log.ErrMissingValue
// With returns a new contextual logger with keyvals prepended to those passed
// to calls to Log. If logger is also a contextual logger created by With,
// WithPrefix, or WithSuffix, keyvals is appended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func With(logger Logger, keyvals ...interface{}) Logger {
return log.With(logger, keyvals...)
}
// WithPrefix returns a new contextual logger with keyvals prepended to those
// passed to calls to Log. If logger is also a contextual logger created by
// With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
return log.WithPrefix(logger, keyvals...)
}
// WithSuffix returns a new contextual logger with keyvals appended to those
// passed to calls to Log. If logger is also a contextual logger created by
// With, WithPrefix, or WithSuffix, keyvals is appended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithSuffix(logger Logger, keyvals ...interface{}) Logger {
return log.WithSuffix(logger, keyvals...)
}
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
// object that calls f.
type LoggerFunc = log.LoggerFunc
-15
View File
@@ -1,15 +0,0 @@
package log
import (
"io"
"github.com/go-kit/log"
)
// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
// logfmt format. Each log event produces no more than one call to w.Write.
// The passed Writer must be safe for concurrent use by multiple goroutines if
// the returned Logger will be used concurrently.
func NewLogfmtLogger(w io.Writer) Logger {
return log.NewLogfmtLogger(w)
}
-8
View File
@@ -1,8 +0,0 @@
package log
import "github.com/go-kit/log"
// NewNopLogger returns a logger that doesn't do anything.
func NewNopLogger() Logger {
return log.NewNopLogger()
}
-54
View File
@@ -1,54 +0,0 @@
package log
import (
"io"
"github.com/go-kit/log"
)
// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's
// designed to be passed to a Go kit logger as the writer, for cases where
// it's necessary to redirect all Go kit log output to the stdlib logger.
//
// If you have any choice in the matter, you shouldn't use this. Prefer to
// redirect the stdlib log to the Go kit logger via NewStdlibAdapter.
type StdlibWriter = log.StdlibWriter
// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
// logger's SetOutput. It will extract date/timestamps, filenames, and
// messages, and place them under relevant keys.
type StdlibAdapter = log.StdlibAdapter
// StdlibAdapterOption sets a parameter for the StdlibAdapter.
type StdlibAdapterOption = log.StdlibAdapterOption
// TimestampKey sets the key for the timestamp field. By default, it's "ts".
func TimestampKey(key string) StdlibAdapterOption {
return log.TimestampKey(key)
}
// FileKey sets the key for the file and line field. By default, it's "caller".
func FileKey(key string) StdlibAdapterOption {
return log.FileKey(key)
}
// MessageKey sets the key for the actual log message. By default, it's "msg".
func MessageKey(key string) StdlibAdapterOption {
return log.MessageKey(key)
}
// Prefix configures the adapter to parse a prefix from stdlib log events. If
// you provide a non-empty prefix to the stdlib logger, then your should provide
// that same prefix to the adapter via this option.
//
// By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to
// true if you want to include the parsed prefix in the msg.
func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption {
return log.Prefix(prefix, joinPrefixToMsg)
}
// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
// logger. It's designed to be passed to log.SetOutput.
func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
return log.NewStdlibAdapter(logger, options...)
}
-37
View File
@@ -1,37 +0,0 @@
package log
import (
"io"
"github.com/go-kit/log"
)
// SwapLogger wraps another logger that may be safely replaced while other
// goroutines use the SwapLogger concurrently. The zero value for a SwapLogger
// will discard all log events without error.
//
// SwapLogger serves well as a package global logger that can be changed by
// importers.
type SwapLogger = log.SwapLogger
// NewSyncWriter returns a new writer that is safe for concurrent use by
// multiple goroutines. Writes to the returned writer are passed on to w. If
// another write is already in progress, the calling goroutine blocks until
// the writer is available.
//
// If w implements the following interface, so does the returned writer.
//
// interface {
// Fd() uintptr
// }
func NewSyncWriter(w io.Writer) io.Writer {
return log.NewSyncWriter(w)
}
// NewSyncLogger returns a logger that synchronizes concurrent use of the
// wrapped logger. When multiple goroutines use the SyncLogger concurrently
// only one goroutine will be allowed to log to the wrapped logger at a time.
// The other goroutines will block until the logger is available.
func NewSyncLogger(logger Logger) Logger {
return log.NewSyncLogger(logger)
}
-52
View File
@@ -1,52 +0,0 @@
package log
import (
"time"
"github.com/go-kit/log"
)
// A Valuer generates a log value. When passed to With, WithPrefix, or
// WithSuffix in a value element (odd indexes), it represents a dynamic
// value which is re-evaluated with each log event.
type Valuer = log.Valuer
// Timestamp returns a timestamp Valuer. It invokes the t function to get the
// time; unless you are doing something tricky, pass time.Now.
//
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
// are TimestampFormats that use the RFC3339Nano format.
func Timestamp(t func() time.Time) Valuer {
return log.Timestamp(t)
}
// TimestampFormat returns a timestamp Valuer with a custom time format. It
// invokes the t function to get the time to format; unless you are doing
// something tricky, pass time.Now. The layout string is passed to
// Time.Format.
//
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
// are TimestampFormats that use the RFC3339Nano format.
func TimestampFormat(t func() time.Time, layout string) Valuer {
return log.TimestampFormat(t, layout)
}
// Caller returns a Valuer that returns a file and line from a specified depth
// in the callstack. Users will probably want to use DefaultCaller.
func Caller(depth int) Valuer {
return log.Caller(depth)
}
var (
// DefaultTimestamp is a Valuer that returns the current wallclock time,
// respecting time zones, when bound.
DefaultTimestamp = log.DefaultTimestamp
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
// when bound.
DefaultTimestampUTC = log.DefaultTimestampUTC
// DefaultCaller is a Valuer that returns the file and line where the Log
// method was invoked. It can only be used with log.With.
DefaultCaller = log.DefaultCaller
)
+4
View File
@@ -0,0 +1,4 @@
ignore:
- jsonutils/fixtures_test
- jsonutils/adapters/ifaces/mocks
- jsonutils/adapters/testintegration/benchmarks
+73 -55
View File
@@ -1,60 +1,78 @@
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 3
min-occurrences: 3
version: "2"
linters:
enable-all: true
default: all
disable:
- maligned
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- cyclop
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- errorlint
- exhaustruct
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase
- funlen
- gochecknoglobals
- gochecknoinits
- gocognit
- godot
- godox
- gomoddirectives
- gosmopolitan
- inamedparam
- intrange
- ireturn
- lll
- musttag
- modernize
- nestif
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- recvcheck
- testpackage
- thelper
- tagliatelle
- tparallel
- unparam
- varnamelen
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
gocyclo:
min-complexity: 45
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
+30
View File
@@ -0,0 +1,30 @@
all: false
dir: '{{.InterfaceDir}}'
filename: mocks_test.go
force-file-write: true
formatter: goimports
include-auto-generated: false
log-level: info
structname: '{{.Mock}}{{.InterfaceName}}'
pkgname: '{{.SrcPackageName}}'
recursive: false
require-template-schema-exists: true
template: matryer
template-schema: '{{.Template}}.schema.json'
packages:
github.com/go-openapi/swag/jsonutils/adapters/ifaces:
config:
dir: jsonutils/adapters/ifaces/mocks
filename: mocks.go
pkgname: 'mocks'
force-file-write: true
all: true
github.com/go-openapi/swag/jsonutils/adapters/testintegration:
config:
inpackage: true
dir: jsonutils/adapters/testintegration
force-file-write: true
all: true
interfaces:
EJMarshaler:
EJUnmarshaler:
+227 -11
View File
@@ -1,23 +1,239 @@
# Swag [![Build Status](https://github.com/go-openapi/swag/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/swag/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/swag/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/swag)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE)
[![license](https://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/swag.svg)](https://pkg.go.dev/github.com/go-openapi/swag)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/swag)](https://goreportcard.com/report/github.com/go-openapi/swag)
Contains a bunch of helper functions for go-openapi and go-swagger projects.
Package `swag` contains a bunch of helper functions for go-openapi and go-swagger projects.
You may also use it standalone for your projects.
* convert between value and pointers for builtin types
* convert from string to builtin types (wraps strconv)
* fast json concatenation
* search in path
* load from file or http
* name mangling
> **NOTE**
> `swag` is one of the foundational building blocks of the go-openapi initiative.
> Most repositories in `github.com/go-openapi/...` depend on it in some way.
> And so does our CLI tool `github.com/go-swagger/go-swagger`,
> as well as the code generated by this tool.
* [Contents](#contents)
* [Dependencies](#dependencies)
* [Release Notes](#release-notes)
* [Licensing](#licensing)
* [Note to contributors](#note-to-contributors)
* [TODOs, suggestions and plans](#todos-suggestions-and-plans)
## Contents
`go-openapi/swag` exposes a collection of relatively independent modules.
Moving forward, no additional feature will be added to the `swag` API directly at the root package level,
which remains there for backward-compatibility purposes. All exported top-level features are now deprecated.
Child modules will continue to evolve and some new ones may be added in the future.
| Module | Content | Main features |
|---------------|---------|---------------|
| `cmdutils` | utilities to work with CLIs ||
| `conv` | type conversion utilities | convert between values and pointers for any types<br />convert from string to builtin types (wraps `strconv`)<br />require `./typeutils` (test dependency)<br /> |
| `fileutils` | file utilities | |
| `jsonname` | JSON utilities | infer JSON names from `go` properties<br /> |
| `jsonutils` | JSON utilities | fast json concatenation<br />read and write JSON from and to dynamic `go` data structures<br />~require `github.com/mailru/easyjson`~<br /> |
| `loading` | file loading | load from file or http<br />require `./yamlutils`<br /> |
| `mangling` | safe name generation | name mangling for `go`<br /> |
| `netutils` | networking utilities | host, port from address<br /> |
| `stringutils` | `string` utilities | search in slice (with case-insensitive)<br />split/join query parameters as arrays<br /> |
| `typeutils` | `go` types utilities | check the zero value for any type<br />safe check for a nil value<br /> |
| `yamlutils` | YAML utilities | converting YAML to JSON<br />loading YAML into a dynamic YAML document<br />maintaining the original order of keys in YAML objects<br />require `./jsonutils`<br />~require `github.com/mailru/easyjson`~<br />require `go.yaml.in/yaml/v3`<br /> |
---
## Dependencies
The root module `github.com/go-openapi/swag` at the repo level maintains a few
dependencies outside of the standard library.
* YAML utilities depend on `go.yaml.in/yaml/v3`
* JSON utilities depend on their registered adapter module:
* by default, only the standard library is used
* `github.com/mailru/easyjson` is now only a dependency for module
`github.com/go-openapi/swag/jsonutils/adapters/easyjson/json`,
for users willing to import that module.
* integration tests and benchmarks use all the dependencies are published as their own module
* other dependencies are test dependencies drawn from `github.com/stretchr/testify`
## Release notes
### v0.25.4
** mangling**
Bug fix
* [x] mangler may panic with pluralized overlapping initialisms
Tests
* [x] introduced fuzz tests
### v0.25.3
** mangling**
Bug fix
* [x] mangler may panic with pluralized initialisms
### v0.25.2
Minor changes due to internal maintenance that don't affect the behavior of the library.
* [x] removed indirect test dependencies by switching all tests to `go-openapi/testify`,
a fork of `stretch/testify` with zero-dependencies.
* [x] improvements to CI to catch test reports.
* [x] modernized licensing annotations in source code, using the more compact SPDX annotations
rather than the full license terms.
* [x] simplified a bit JSON & YAML testing by using newly available assertions
* started the journey to an OpenSSF score card badge:
* [x] explicited permissions in CI workflows
* [x] published security policy
* pinned dependencies to github actions
* introduced fuzzing in tests
### v0.25.1
* fixes a data race that could occur when using the standard library implementation of a JSON ordered map
### v0.25.0
**New with this release**:
* requires `go1.24`, as iterators are being introduced
* removes the dependency to `mailru/easyjson` by default (#68)
* functionality remains the same, but performance may somewhat degrade for applications
that relied on `easyjson`
* users of the JSON or YAML utilities who want to use `easyjson` as their preferred JSON serializer library
will be able to do so by registering this the corresponding JSON adapter at runtime. See below.
* ordered keys in JSON and YAML objects: this feature used to rely solely on `easyjson`.
With this release, an implementation relying on the standard `encoding/json` is provided.
* an independent [benchmark](./jsonutils/adapters/testintegration/benchmarks/README.md) to compare the different adapters
* improves the "float is integer" check (`conv.IsFloat64AJSONInteger`) (#59)
* removes the _direct_ dependency to `gopkg.in/yaml.v3` (indirect dependency is still incurred through `stretchr/testify`) (#127)
* exposed `conv.IsNil()` (previously kept private): a safe nil check (accounting for the "non-nil interface with nil value" nonsensical go trick)
**What coming next?**
Moving forward, we want to :
* provide an implementation of the JSON adapter based on `encoding/json/v2`, for `go1.25` builds.
* provide similar implementations for `goccy/go-json` and `jsoniterator/go`, and perhaps some other
similar libraries may be interesting too.
This repo has only few dependencies outside of the standard library:
**How to explicitly register a dependency at runtime**?
* YAML utilities depend on `gopkg.in/yaml.v3`
* `github.com/mailru/easyjson v0.7.7`
The following would maintain how JSON utilities proposed by `swag` used work, up to `v0.24.1`.
```go
import (
"github.com/go-openapi/swag/jsonutils/adapters"
easyjson "github.com/go-openapi/swag/jsonutils/adapters/easyjson/json"
)
func init() {
easyjson.Register(adapters.Registry)
}
```
Subsequent calls to `jsonutils.ReadJSON()` or `jsonutils.WriteJSON()` will switch to `easyjson`
whenever the passed data structures implement the `easyjson.Unmarshaler` or `easyjson.Marshaler` respectively,
or fallback to the standard library.
For more details, you may also look at our
[integration tests](jsonutils/adapters/testintegration/integration_suite_test.go#29).
### v0.24.0
With this release, we have largely modernized the API of `swag`:
* The traditional `swag` API is still supported: code that imports `swag` will still
compile and work the same.
* A deprecation notice is published to encourage consumers of this library to adopt
the newer API
* **Deprecation notice**
* configuration through global variables is now deprecated, in favor of options passed as parameters
* all helper functions are moved to more specialized packages, which are exposed as
go modules. Importing such a module would reduce the footprint of dependencies.
* _all_ functions, variables, constants exposed by the deprecated API have now moved, so
that consumers of the new API no longer need to import github.com/go-openapi/swag, but
should import the desired sub-module(s).
**New with this release**:
* [x] type converters and pointer to value helpers now support generic types
* [x] name mangling now support pluralized initialisms (issue #46)
Strings like "contact IDs" are now recognized as such a plural form and mangled as a linter would expect.
* [x] performance: small improvements to reduce the overhead of convert/format wrappers (see issues #110, or PR #108)
* [x] performance: name mangling utilities run ~ 10% faster (PR #115)
---
## Licensing
This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE).
## Note to contributors
A mono-repo structure comes with some unavoidable extra pains...
* Testing
> The usual `go test ./...` command, run from the root of this repo won't work any longer to test all submodules.
>
> Each module constitutes an independant unit of test. So you have to run `go test` inside each module.
> Or you may take a look at how this is achieved by CI
> [here] https://github.com/go-openapi/swag/blob/master/.github/workflows/go-test.yml).
>
> There are also some alternative tricks using `go work`, for local development, if you feel comfortable with
> go workspaces. Perhaps some day, we'll have a `go work test` to run all tests without any hack.
* Releasing
> Each module follows its own independant module versioning.
>
> So you have tags like `mangling/v0.24.0`, `fileutils/v0.24.0` etc that are used by `go mod` and `go get`
> to refer to the tagged version of each module specifically.
>
> This means we may release patches etc to each module independently.
>
> We'd like to adopt the rule that modules in this repo would only differ by a patch version
> (e.g. `v0.24.5` vs `v0.24.3`), and we'll level all modules whenever a minor version is introduced.
>
> A script in `./hack` is provided to tag all modules with the same version in one go.
* Continuous integration
> At this moment, all tests in all modules are systematically run over the full test matrix (3 platform x 2 go releases).
> This generates quite a lot of jobs.
>
> We ought to reduce the number of jobs required to test a PR focused on only a few modules.
## Todos, suggestions and plans
All kinds of contributions are welcome.
A few ideas:
* [x] Complete the split of dependencies to isolate easyjson from the rest
* [x] Improve CI to reduce needed tests
* [x] Replace dependency to `gopkg.in/yaml.v3` (`yamlutil`)
* [ ] Improve mangling utilities (improve readability, support for capitalized words,
better word substitution for non-letter symbols...)
* [ ] Move back to this common shared pot a few of the technical features introduced by go-swagger independently
(e.g. mangle go package names, search package with go modules support, ...)
* [ ] Apply a similar mono-repo approach to go-openapi/strfmt which suffer from similar woes: bloated API,
imposed dependency to some database driver.
* [ ] Adapt `go-swagger` (incl. generated code) to the new `swag` API.
* [ ] Factorize some tests, as there is a lot of redundant testing code in `jsonutils`
* [ ] Benchmark & profiling: publish independently the tool built to analyze and chart benchmarks (e.g. similar to `benchvisual`)
* [ ] more thorough testing for nil / null case
* [ ] ci pipeline to manage releases
* [ ] cleaner mockery generation (doesn't work out of the box for all sub-modules)
+19
View File
@@ -0,0 +1,19 @@
# Security Policy
This policy outlines the commitment and practices of the go-openapi maintainers regarding security.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.25.x | :white_check_mark: |
## Reporting a vulnerability
If you become aware of a security vulnerability that affects the current repository,
please report it privately to the maintainers.
Please follow the instructions provided by github to
[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability).
TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability".

Some files were not shown because too many files have changed in this diff Show More