Compare commits

...
35 Commits
Author SHA1 Message Date
Ethan Mosbaugh 4c6af55e7c fix: correct RBAC verb for pods/exec from get to create (#2037)
fix: correct RBAC verb and WebSocket fallback for pods/exec

This commit fixes three related issues that prevented exec collectors from
working with minimal RBAC permissions:

1. RBAC preflight check used wrong verb for pods/exec
   Changed from "get" to "create" in v1beta1 and v1beta2 AccessReviewSpecs.
   The pods/exec subresource requires "create" to execute commands.

2. WebSocket fallback used wrong httpstream package import
   The fallback executor checked IsUpgradeFailure using the apimachinery
   httpstream package, but the roundtripper creates UpgradeFailureError using
   the streaming httpstream package. These are different Go types, so
   errors.As always returned false and fallback to SPDY never triggered.
   Changed import to k8s.io/streaming/pkg/httpstream.

3. Stdin mismatch caused SPDY fallback to hang
   PodExecOptions always set Stdin:true but StreamOptions always passed
   Stdin:nil. When WebSocket failed and fell back to SPDY, the server
   waited for stdin data that never arrived. Changed Stdin to false in
   PodExecOptions for exec, copy, and copy_from_host collectors.
2026-05-01 13:03:42 -07:00
Xav Paice a4ce199005 refactor(collect): replace go.podman.io/image with go-containerregistry (#2034)
* refactor(collect): replace go.podman.io/image with go-containerregistry

Migrate pkg/collect/registry.go off go.podman.io/image/v5 (containers/image)
onto github.com/google/go-containerregistry, eliminating the transitive
github.com/docker/docker +incompatible chain that blocks Docker v29 SDK
adoption.

The RegistryImages collector probes whether each requested image exists in
its registry by issuing a manifest HEAD. We replace alltransports.ParseImageName
+ imageRef.NewImage with name.ParseReference + remote.Head. Auth from a
kubernetes.io/dockerconfigjson Secret is converted to an authn.Authenticator;
error classification switches from errcode.Errors string matching to
transport.Error.StatusCode.

Add httptest.NewTLSServer-backed tests for imageExists covering the
found/not-found/unauthorized/EOF-retry paths to lock in protocol-level
behavior across the migration.

Promote go-containerregistry to a direct require at v0.21.5; go mod tidy
drops go.podman.io/image, distribution/distribution/v3, and the bare
docker/docker entry from the module graph.

Issue: tr-dit

* Removed the unreachable strings.Contains branch so we no longer pretend there is special “architecture mismatch → treat as exists” behavior.
Documented next to remote.Get that the check is manifest presence for the reference, not “runnable on this arch.”

* fix(collect): use DefaultKeychain when registry auth config is nil

Restore ambient credential behavior (~/.docker/config.json, credential
helpers) for registry image checks. Without WithAuthFromKeychain,
remote.Get defaulted to anonymous auth, diverging from containers/image
and from documented nil authConfig semantics.

Made-with: Cursor
2026-04-30 10:12:53 +01:00
Ethan MosbaughandClaude Sonnet 4.6 48c45d7b0a refactor: replace SPDY executor with WebSocket-first fallback executor (#2031)
* docs: add design spec for SPDY executor removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add implementation plan for SPDY executor removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(k8sutil): add NewFallbackExecutor helper

* fix(k8sutil): rename url param to u to avoid shadowing net/url

* refactor(collect): use fallback executor in exec collector

* refactor(collect): use fallback executor in copy collector

* refactor(collect): use fallback executor in copy_from_host collector

* refactor(collect): use fallback executor in sonobuoy_results collector

* refactor(collect): use fallback executor in etcd collector

* refactor(collect): use fallback executor in longhorn collector

* refactor(supportbundle): use fallback executor in collect

* chore(k8sutil): delete unused PortForward function

* fix(k8sutil): use GET for WebSocket executor and broaden fallback predicate

WebSocket upgrade requires GET per RFC 6455. Also add IsHTTPSProxyError to
the fallback predicate so HTTPS proxy environments fall back to SPDY correctly,
matching kubectl's implementation. Remove method param from NewFallbackExecutor
since the methods are now transport-specific and not caller-controlled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove superpowers docs artifacts

* fix(collect): enable stdout capture in exec collector

* fix(collect): enable stdout capture in copy collectors

PodExecOptions.Stdout: false causes the WebSocket API server to discard
stdout entirely, silently breaking tar output for file copy operations.
With SPDY this mismatch was harmless; WebSocket strictly respects the field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 06:21:42 -07:00
Evans Mungai 2fdd21fca1 feat(analyze): warn when nodeResources has no node data, add ignoreIfNoFiles (#2035)
* feat(analyze): warn when nodeResources has no node data, add ignoreIfNoFiles

The nodeResources analyzer previously failed silently when
cluster-resources/nodes.json was not collected (e.g. when the
clusterResources collector was excluded or could not list nodes).
It now emits a warn outcome per nodeResources entry in the spec.

Add an ignoreIfNoFiles top-level field to nodeResources, mirroring
textAnalyze, so users can opt out of the new warning when the
analyzer is intentionally optional.

- Add IgnoreIfNoFiles to v1beta2.NodeResources
- Update CRDs and JSON schemas
- Unit test the warn / ignore paths
- Add an e2e fixture and test that excludes clusterResources and
asserts the analyze output

Signed-off-by: Evans Mungai <evans@replicated.com>

* Fix review comment

Signed-off-by: Evans Mungai <evans@replicated.com>

---------

Signed-off-by: Evans Mungai <evans@replicated.com>
2026-04-29 13:13:24 +01:00
572cb8e31d chore(deps): bump the security group across 1 directory with 12 updates (#2030)
* chore(deps): bump the security group across 1 directory with 12 updates

Bumps the security group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/apparentlymart/go-cidr](https://github.com/apparentlymart/go-cidr) | `1.1.0` | `1.1.1` |
| [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) | `1.99.1` | `1.100.0` |
| [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) | `0.0.21` | `0.0.22` |
| [k8s.io/api](https://github.com/kubernetes/api) | `0.35.4` | `0.36.0` |
| [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.35.4` | `0.36.0` |
| [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.35.4` | `0.36.0` |
| [k8s.io/kubelet](https://github.com/kubernetes/kubelet) | `0.35.4` | `0.36.0` |
| [k8s.io/metrics](https://github.com/kubernetes/metrics) | `0.35.4` | `0.36.0` |



Updates `github.com/apparentlymart/go-cidr` from 1.1.0 to 1.1.1
- [Release notes](https://github.com/apparentlymart/go-cidr/releases)
- [Commits](https://github.com/apparentlymart/go-cidr/compare/v1.1.0...v1.1.1)

Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.99.1 to 1.100.0
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.99.1...service/s3/v1.100.0)

Updates `github.com/mattn/go-isatty` from 0.0.21 to 0.0.22
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.21...v0.0.22)

Updates `k8s.io/api` from 0.35.4 to 0.36.0
- [Commits](https://github.com/kubernetes/api/compare/v0.35.4...v0.36.0)

Updates `k8s.io/apiextensions-apiserver` from 0.35.4 to 0.36.0
- [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases)
- [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.35.4...v0.36.0)

Updates `k8s.io/apimachinery` from 0.35.4 to 0.36.0
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.4...v0.36.0)

Updates `k8s.io/apiserver` from 0.35.4 to 0.36.0
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.35.4...v0.36.0)

Updates `k8s.io/cli-runtime` from 0.35.4 to 0.36.0
- [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.35.4...v0.36.0)

Updates `k8s.io/client-go` from 0.35.4 to 0.36.0
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.35.4...v0.36.0)

Updates `k8s.io/kubelet` from 0.35.4 to 0.36.0
- [Commits](https://github.com/kubernetes/kubelet/compare/v0.35.4...v0.36.0)

Updates `k8s.io/metrics` from 0.35.4 to 0.36.0
- [Commits](https://github.com/kubernetes/metrics/compare/v0.35.4...v0.36.0)

Updates `k8s.io/utils` from 0.0.0-20251002143259-bc988d571ff4 to 0.0.0-20260210185600-b8788abfbbc2
- [Commits](https://github.com/kubernetes/utils/commits)

---
updated-dependencies:
- dependency-name: github.com/apparentlymart/go-cidr
  dependency-version: 1.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.100.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: github.com/mattn/go-isatty
  dependency-version: 0.0.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/api
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/apiextensions-apiserver
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/apiserver
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/cli-runtime
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/client-go
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/kubelet
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/metrics
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/utils
  dependency-version: 0.0.0-20260210185600-b8788abfbbc2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>

* k8s.io/kubectl v0.36.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Andrew Lavery <laverya@umich.edu>
2026-04-29 08:12:16 -04:00
Evans MungaiandClaude Opus 4.6 21948fe959 feat: add host registryImages collector and analyzer (#2029)
* feat: add host registryImages collector and analyzer

Adds a host-level registryImages collector and analyzer that can check
image existence in registries without requiring a Kubernetes cluster.
Supports inline username/password auth or ambient credentials from
~/.docker/config.json.

Refactors imageExistsWithAuth from the cluster-level registry collector
to share the core image existence check logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fixes from manual tests

Signed-off-by: Evans Mungai <evans@replicated.com>

* Updates from manual tests

Signed-off-by: Evans Mungai <evans@replicated.com>

* Sort template lists

Signed-off-by: Evans Mungai <evans@replicated.com>

* More changes

Signed-off-by: Evans Mungai <evans@replicated.com>

---------

Signed-off-by: Evans Mungai <evans@replicated.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:26:18 +01:00
Xav Paice b56b98742e Revert "refactor(collect): replace go.podman.io/image with go-containerregistry" (#2033)
This reverts commit b105fa62c7.
2026-04-29 13:29:35 +12:00
furiosa b105fa62c7 refactor(collect): replace go.podman.io/image with go-containerregistry
Migrate pkg/collect/registry.go off go.podman.io/image/v5 (containers/image)
onto github.com/google/go-containerregistry, eliminating the transitive
github.com/docker/docker +incompatible chain that blocks Docker v29 SDK
adoption.

The RegistryImages collector probes whether each requested image exists in
its registry by issuing a manifest HEAD. We replace alltransports.ParseImageName
+ imageRef.NewImage with name.ParseReference + remote.Head. Auth from a
kubernetes.io/dockerconfigjson Secret is converted to an authn.Authenticator;
error classification switches from errcode.Errors string matching to
transport.Error.StatusCode.

Add httptest.NewTLSServer-backed tests for imageExists covering the
found/not-found/unauthorized/EOF-retry paths to lock in protocol-level
behavior across the migration.

Promote go-containerregistry to a direct require at v0.21.5; go mod tidy
drops go.podman.io/image, distribution/distribution/v3, and the bare
docker/docker entry from the module graph.

Issue: tr-dit
2026-04-28 16:14:55 +12:00
Xav PaiceandClaude Opus 4.7 558a8f5110 chore(deps): bump docker/docker to v28.5.2 and go-containerregistry to v0.21.5 (#2028)
Addresses vulnerabilities in github.com/docker/docker v28.5.1. Both are
indirect deps; no source imports them directly. Pulled v0.21.5 of
go-containerregistry via `go get ...@latest`, then forced docker/docker
to v28.5.2 since go.podman.io/image/v5 still pins v28.5.1 at its latest
release (v5.39.2).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:59:37 +12:00
dependabot[bot]andlnx01 070fd9bab4 chore(deps): bump the security group across 1 directory with 12 updates (#2026)
Bumps the security group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) | `1.41.5` | `1.41.6` |
| [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) | `1.19.14` | `1.19.15` |
| [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) | `1.99.0` | `1.99.1` |
| [k8s.io/api](https://github.com/kubernetes/api) | `0.35.3` | `0.35.4` |
| [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.35.3` | `0.35.4` |
| [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.35.3` | `0.35.4` |
| [sigs.k8s.io/e2e-framework](https://github.com/kubernetes-sigs/e2e-framework) | `0.6.0` | `0.7.0` |
| [k8s.io/kubelet](https://github.com/kubernetes/kubelet) | `0.35.3` | `0.35.4` |
| [k8s.io/metrics](https://github.com/kubernetes/metrics) | `0.35.3` | `0.35.4` |



Updates `github.com/aws/aws-sdk-go-v2` from 1.41.5 to 1.41.6
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.41.5...v1.41.6)

Updates `github.com/aws/aws-sdk-go-v2/credentials` from 1.19.14 to 1.19.15
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.19.14...credentials/v1.19.15)

Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.99.0 to 1.99.1
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.99.0...service/s3/v1.99.1)

Updates `k8s.io/api` from 0.35.3 to 0.35.4
- [Commits](https://github.com/kubernetes/api/compare/v0.35.3...v0.35.4)

Updates `k8s.io/apiextensions-apiserver` from 0.35.3 to 0.35.4
- [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases)
- [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.35.3...v0.35.4)

Updates `k8s.io/apimachinery` from 0.35.3 to 0.35.4
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.3...v0.35.4)

Updates `k8s.io/apiserver` from 0.35.3 to 0.35.4
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.35.3...v0.35.4)

Updates `k8s.io/cli-runtime` from 0.35.3 to 0.35.4
- [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.35.3...v0.35.4)

Updates `k8s.io/client-go` from 0.35.3 to 0.35.4
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.35.3...v0.35.4)

Updates `sigs.k8s.io/e2e-framework` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/kubernetes-sigs/e2e-framework/releases)
- [Changelog](https://github.com/kubernetes-sigs/e2e-framework/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/e2e-framework/compare/v0.6.0...v0.7.0)

Updates `k8s.io/kubelet` from 0.35.3 to 0.35.4
- [Commits](https://github.com/kubernetes/kubelet/compare/v0.35.3...v0.35.4)

Updates `k8s.io/metrics` from 0.35.3 to 0.35.4
- [Commits](https://github.com/kubernetes/metrics/compare/v0.35.3...v0.35.4)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-version: 1.41.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-version: 1.19.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.99.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/api
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/apiextensions-apiserver
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/apiserver
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/cli-runtime
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/client-go
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: sigs.k8s.io/e2e-framework
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/kubelet
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/metrics
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 08:21:06 +12:00
dependabot[bot]andlnx01 e89c858bd2 chore(deps): bump docker/setup-qemu-action from 3 to 4 (#2023)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-22 13:37:56 +12:00
Evans Mungai 483467d8b9 chore: update preflight cli help text (#2027)
Signed-off-by: Evans Mungai <evans@replicated.com>
2026-04-21 17:38:16 +01:00
Ash 27903af638 [chore] bump pgx (#2025) 2026-04-21 11:40:02 +01:00
dependabot[bot]andlnx01 6eb09ee9d5 chore(deps): bump github.com/moby/spdystream from 0.5.0 to 0.5.1 (#2022)
Bumps [github.com/moby/spdystream](https://github.com/moby/spdystream) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/moby/spdystream/releases)
- [Commits](https://github.com/moby/spdystream/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: github.com/moby/spdystream
  dependency-version: 0.5.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 13:34:58 +01:00
Evans Mungai 845fdcc8c8 Move REVIEW.md to .cursor/BUGBOT.md (#2021)
* Move REVIEW.md to .cursor/BUGBOT.md

Signed-off-by: Evans Mungai <evans@replicated.com>

* Comment update

Signed-off-by: Evans Mungai <evans@replicated.com>

---------

Signed-off-by: Evans Mungai <evans@replicated.com>
2026-04-16 15:40:45 +01:00
Evans Mungai a3c453d1d6 fix: record skipped collectors in bundle and improve CLI warnings (#2019)
* fix: record skipped collectors in bundle and improve CLI warnings

- Write `skipped-collectors.json` to the support bundle and preflight bundle recording which collectors were skipped (RBAC permissions or spec exclusion), with reason, errors, and timestamp
- Surface RBAC errors and skipped-collector messages as `klog.Warningf` in non-interactive mode so they're visible without `-v` flags
- Exec collector file naming now falls back to the resolved container name (from the pod spec) when `collectorName` is not set, instead of only checking `containerName` from the spec

Signed-off-by: Evans Mungai <evans@replicated.com>

* Address comment

Signed-off-by: Evans Mungai <evans@replicated.com>

* fix bugbot comment

Signed-off-by: Evans Mungai <evans@replicated.com>

* Remove unused var

Signed-off-by: Evans Mungai <evans@replicated.com>

* Address comment

Signed-off-by: Evans Mungai <evans@replicated.com>

* Use results.SaveResult and add automated tests

Signed-off-by: Evans Mungai <evans@replicated.com>

* Improvements

Signed-off-by: Evans Mungai <evans@replicated.com>

* Fix bugbot

Signed-off-by: Evans Mungai <evans@replicated.com>

* Handle bugbot

Signed-off-by: Evans Mungai <evans@replicated.com>

---------

Signed-off-by: Evans Mungai <evans@replicated.com>
2026-04-15 22:29:35 +01:00
Evans Mungai 35d5ad17af feat: add CLAUDE.md and AGENTS.md to project (#2020)
* feat: add CLAUDE.md and AGENTS.md to project

Signed-off-by: Evans Mungai <evans@replicated.com>

* Add missed file

Signed-off-by: Evans Mungai <evans@replicated.com>

* Updates

Signed-off-by: Evans Mungai <evans@replicated.com>

---------

Signed-off-by: Evans Mungai <evans@replicated.com>
2026-04-15 18:46:07 +01:00
Evans MungaiandClaude Opus 4.6 daeab2dc20 fix: improve collector output discoverability (#2018)
* fix: document .tar.gz auto-append in --output flag help text

The --output flag silently appends .tar.gz to the provided path, which
was not mentioned in the help text, causing confusion for users who
expected the exact filename they specified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: exec collector falls back to containerName for file naming

When collectorName is not set, the exec collector now uses containerName
as the output file prefix instead of producing bare -stdout.txt filenames.
This makes output files identifiable without requiring collectorName to
be explicitly set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:42:30 +01:00
Andrew LaveryandDaniel Lipovetsky 5a611b3a6a feat: Collect validating and mutating webhook configurations (#2017)
* feat: Collect validating and mutating webhook configurations

* fixup! feat: Collect validating and mutating webhook configurations

Add metadata before marshaling

* explicitly test webhook configuration collection in TestClusterResources

---------

Co-authored-by: Daniel Lipovetsky <daniel.lipovetsky@nutanix.com>
2026-04-14 11:22:55 -04:00
Ludovic HenryandAndrew Lavery fc72539585 ci: Add releases for linux-riscv64 (#2010)
Add releases for linux-riscv64

This project is a dependency of `k0s` which is working on being built, tested and released on `linux-riscv64` [1].

RISC-V is gaining momentum especially in the embedded and edge world. 

[1] https://github.com/k0sproject/k0s/pull/7414

Co-authored-by: Andrew Lavery <laverya@umich.edu>
2026-04-14 11:03:26 -04:00
Andrew Lavery 69743729a3 smoke test all linux target architectures (#2016)
* smoke test all linux target architectures

* go mod update

* use 'real' target cluster

* better logging

* newer alpine
2026-04-14 07:30:26 -07:00
dependabot[bot]andlnx01 6d1b1eba49 chore(deps): bump golang.org/x/net from 0.52.0 to 0.53.0 in the security group (#2015)
chore(deps): bump golang.org/x/net in the security group

Bumps the security group with 1 update: [golang.org/x/net](https://github.com/golang/net).


Updates `golang.org/x/net` from 0.52.0 to 0.53.0
- [Commits](https://github.com/golang/net/compare/v0.52.0...v0.53.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 06:02:54 -04:00
dependabot[bot]andlnx01 db137b1c33 chore(deps): bump helm.sh/helm/v3 from 3.20.1 to 3.20.2 in /examples/sdk/helm-template (#2012)
chore(deps): bump helm.sh/helm/v3 in /examples/sdk/helm-template

Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.20.1 to 3.20.2.
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.20.1...v3.20.2)

---
updated-dependencies:
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.20.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 09:48:28 -04:00
dependabot[bot]andlnx01 a6915eac63 chore(deps): bump helm.sh/helm/v3 from 3.20.1 to 3.20.2 (#2013)
Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.20.1 to 3.20.2.
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.20.1...v3.20.2)

---
updated-dependencies:
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.20.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 09:48:04 -04:00
replicated-ciandlnx01 95ef65b08d Bump Go from 1.26.1 to 1.26.2 (#2014)
Bump Go to version from 1.26.1 to 1.26.2

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 09:47:45 -04:00
Ethan MosbaughandClaude Sonnet 4.6 c096e5d075 fix(supportbundle): ExtractLicenseFromBundle prefers license.json regardless of tar order (#2011)
license.json was being ignored when configmaps containing a license ID
appeared earlier in the tar archive. The function now collects both
candidates in a single pass and returns the license.json result if
present, falling back to the configmap scan only when license.json is
absent.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:59:53 -07:00
dependabot[bot]andlnx01 e3903add9f chore(deps): bump the security group across 1 directory with 14 updates (#2009)
Bumps the security group with 14 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) | `1.41.1` | `1.41.5` |
| [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) | `1.19.9` | `1.19.14` |
| [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) | `1.96.0` | `1.99.0` |
| [github.com/distribution/distribution/v3](https://github.com/distribution/distribution) | `3.0.0` | `3.1.0` |
| [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) | `1.8.5` | `1.8.6` |
| [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) | `0.0.20` | `0.0.21` |
| [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) | `4.26.2` | `4.26.3` |
| [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) | `1.42.0` | `1.43.0` |
| [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) | `1.42.0` | `1.43.0` |
| [go.podman.io/image/v5](https://github.com/containers/container-libs) | `5.39.1` | `5.39.2` |
| [golang.org/x/mod](https://github.com/golang/mod) | `0.34.0` | `0.35.0` |
| [github.com/hashicorp/go-version](https://github.com/hashicorp/go-version) | `1.8.0` | `1.9.0` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.42.0` | `0.43.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.35.0` | `0.36.0` |



Updates `github.com/aws/aws-sdk-go-v2` from 1.41.1 to 1.41.5
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.41.1...v1.41.5)

Updates `github.com/aws/aws-sdk-go-v2/credentials` from 1.19.9 to 1.19.14
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/m2/v1.19.9...credentials/v1.19.14)

Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.96.0 to 1.99.0
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.96.0...service/s3/v1.99.0)

Updates `github.com/distribution/distribution/v3` from 3.0.0 to 3.1.0
- [Release notes](https://github.com/distribution/distribution/releases)
- [Commits](https://github.com/distribution/distribution/compare/v3.0.0...v3.1.0)

Updates `github.com/hashicorp/go-getter` from 1.8.5 to 1.8.6
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.8.5...v1.8.6)

Updates `github.com/mattn/go-isatty` from 0.0.20 to 0.0.21
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.20...v0.0.21)

Updates `github.com/shirou/gopsutil/v4` from 4.26.2 to 4.26.3
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.26.2...v4.26.3)

Updates `go.opentelemetry.io/otel` from 1.42.0 to 1.43.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.42.0...v1.43.0)

Updates `go.opentelemetry.io/otel/sdk` from 1.42.0 to 1.43.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.42.0...v1.43.0)

Updates `go.podman.io/image/v5` from 5.39.1 to 5.39.2
- [Release notes](https://github.com/containers/container-libs/releases)
- [Commits](https://github.com/containers/container-libs/compare/image/v5.39.1...image/v5.39.2)

Updates `golang.org/x/mod` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/mod/compare/v0.34.0...v0.35.0)

Updates `github.com/hashicorp/go-version` from 1.8.0 to 1.9.0
- [Release notes](https://github.com/hashicorp/go-version/releases)
- [Changelog](https://github.com/hashicorp/go-version/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-version/compare/v1.8.0...v1.9.0)

Updates `golang.org/x/sys` from 0.42.0 to 0.43.0
- [Commits](https://github.com/golang/sys/compare/v0.42.0...v0.43.0)

Updates `golang.org/x/text` from 0.35.0 to 0.36.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-version: 1.41.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-version: 1.19.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.99.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: github.com/distribution/distribution/v3
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/mattn/go-isatty
  dependency-version: 0.0.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-version: 4.26.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: go.podman.io/image/v5
  dependency-version: 5.39.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: golang.org/x/mod
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: github.com/hashicorp/go-version
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: golang.org/x/sys
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: golang.org/x/text
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 14:32:09 -04:00
Andrew Lavery ad7d52f7e5 add a collector that checks s3 access (#2007)
* add a collector that checks s3 access

* testing and analyzer

* analyzer test

* fmt
2026-04-09 11:12:34 -07:00
Evans Mungai 670a510a2d feat(analyze): optional additionalDeviceTypes parameter for blockDevices (#2002)
feat(analyze): optional additionalDeviceTypes for blockDevices; refactor match config and tests

Allow preflights to count extra lsblk TYPE values (e.g. loop, lvm) by listing them in
blockDevices.additionalDeviceTypes on BlockDevicesAnalyze. Types in this list are
eligible whether or not includeUnmountedPartitions is set; disk and optional
partitions behave as before.

Refactor matching to use blockDevicesMatchConfig and document eligibility on that
type. Add host_block_devices_match_test.go for type-rule tables and preflight-style
integration cases; keep classic scenarios in host_block_devices_test.go with a
shared analyzeHostBlockDevicesOutput helper.
Regenerate CRDs and deepcopy for the new API field.

Signed-off-by: Evans Mungai <evans@replicated.com>
2026-03-31 20:29:51 +01:00
Andrew Lavery cb1e39cb61 run govulncheck and then grype (#2000)
* run govulncheck and then grype

* cleanup

* exclude bin/ from grype scans

* show path to file containing vuln in grype output
2026-03-24 13:29:12 -07:00
dependabot[bot]andlnx01 a697d59040 chore(deps): bump the security group with 10 updates (#1999)
Bumps the security group with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/fatih/color](https://github.com/fatih/color) | `1.18.0` | `1.19.0` |
| [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) | `5.8.0` | `5.9.1` |
| [k8s.io/api](https://github.com/kubernetes/api) | `0.35.2` | `0.35.3` |
| [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.35.2` | `0.35.3` |
| [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.35.2` | `0.35.3` |
| [k8s.io/apiserver](https://github.com/kubernetes/apiserver) | `0.35.2` | `0.35.3` |
| [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.35.2` | `0.35.3` |
| [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.35.2` | `0.35.3` |
| [k8s.io/kubelet](https://github.com/kubernetes/kubelet) | `0.35.2` | `0.35.3` |
| [k8s.io/metrics](https://github.com/kubernetes/metrics) | `0.35.2` | `0.35.3` |


Updates `github.com/fatih/color` from 1.18.0 to 1.19.0
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.18.0...v1.19.0)

Updates `github.com/jackc/pgx/v5` from 5.8.0 to 5.9.1
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.8.0...v5.9.1)

Updates `k8s.io/api` from 0.35.2 to 0.35.3
- [Commits](https://github.com/kubernetes/api/compare/v0.35.2...v0.35.3)

Updates `k8s.io/apiextensions-apiserver` from 0.35.2 to 0.35.3
- [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases)
- [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.35.2...v0.35.3)

Updates `k8s.io/apimachinery` from 0.35.2 to 0.35.3
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.2...v0.35.3)

Updates `k8s.io/apiserver` from 0.35.2 to 0.35.3
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.35.2...v0.35.3)

Updates `k8s.io/cli-runtime` from 0.35.2 to 0.35.3
- [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.35.2...v0.35.3)

Updates `k8s.io/client-go` from 0.35.2 to 0.35.3
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.35.2...v0.35.3)

Updates `k8s.io/kubelet` from 0.35.2 to 0.35.3
- [Commits](https://github.com/kubernetes/kubelet/compare/v0.35.2...v0.35.3)

Updates `k8s.io/metrics` from 0.35.2 to 0.35.3
- [Commits](https://github.com/kubernetes/metrics/compare/v0.35.2...v0.35.3)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-version: 1.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: github.com/jackc/pgx/v5
  dependency-version: 5.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: k8s.io/api
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/apiextensions-apiserver
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/apiserver
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/cli-runtime
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/client-go
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/kubelet
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: k8s.io/metrics
  dependency-version: 0.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-24 16:07:27 -04:00
dependabot[bot]andlnx01 b4fd76a5e1 chore(deps): bump helm.sh/helm/v3 from 3.20.0 to 3.20.1 in /examples/sdk/helm-template in the security group (#1996)
chore(deps): bump helm.sh/helm/v3

Bumps the security group in /examples/sdk/helm-template with 1 update: [helm.sh/helm/v3](https://github.com/helm/helm).


Updates `helm.sh/helm/v3` from 3.20.0 to 3.20.1
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.20.0...v3.20.1)

---
updated-dependencies:
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 12:36:28 -04:00
dependabot[bot]andlnx01 11e06c7b37 chore(deps): bump google.golang.org/grpc from 1.77.0 to 1.79.3 (#1998)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.77.0 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.77.0...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 12:33:32 -04:00
dependabot[bot]andlnx01 af3e5ab501 chore(deps): bump dorny/paths-filter from 3 to 4 (#1994)
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 3 to 4.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/v3...v4)

---
updated-dependencies:
- dependency-name: dorny/paths-filter
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 12:29:37 -04:00
dependabot[bot]andlnx01 3ded294ae8 chore(deps): bump the security group with 5 updates (#1995)
Bumps the security group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) | `1.8.4` | `1.8.5` |
| [golang.org/x/mod](https://github.com/golang/mod) | `0.33.0` | `0.34.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.51.0` | `0.52.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.34.0` | `0.35.0` |
| [helm.sh/helm/v3](https://github.com/helm/helm) | `3.20.0` | `3.20.1` |


Updates `github.com/hashicorp/go-getter` from 1.8.4 to 1.8.5
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.8.4...v1.8.5)

Updates `golang.org/x/mod` from 0.33.0 to 0.34.0
- [Commits](https://github.com/golang/mod/compare/v0.33.0...v0.34.0)

Updates `golang.org/x/net` from 0.51.0 to 0.52.0
- [Commits](https://github.com/golang/net/compare/v0.51.0...v0.52.0)

Updates `golang.org/x/text` from 0.34.0 to 0.35.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.34.0...v0.35.0)

Updates `helm.sh/helm/v3` from 3.20.0 to 3.20.1
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.20.0...v3.20.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
- dependency-name: golang.org/x/mod
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: golang.org/x/net
  dependency-version: 0.52.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: golang.org/x/text
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 12:29:08 -04:00
74 changed files with 4806 additions and 798 deletions
+22
View File
@@ -0,0 +1,22 @@
# Code Review Guidelines
## Basic Review
- **Breaking API changes** — Check for removed/renamed fields in `pkg/apis/`, changed function signatures in public packages, or modified CLI flags and output formats.
- **Pattern violations** — New code should follow existing patterns in the codebase (e.g., collector/analyzer structure, error handling conventions, interface usage).
- **Security** — Watch for command injection in exec-based collectors, path traversal in file operations, unsanitized user input in specs, and leaked credentials in collected data.
- **Go standards** — Issues that linters like `go vet`, `staticcheck`, and `modernize` would catch: deprecated API usage, unnecessary allocations, error shadowing, unchecked errors.
- **Test coverage** — New functionality should have tests. Changes to existing code should not reduce coverage compared to the last test run on `main`.
- **Error handling** — Errors should wrap context (`fmt.Errorf("... : %w", err)`), not be silently swallowed, and provide actionable messages for operator-facing output.
- **Concurrency safety** — Collectors run concurrently. Shared state must be protected. `CollectorResult` map writes from goroutines need synchronization.
- **Bundle storage** — Collectors must save data using `CollectorResult.SaveResult` and related methods (`SaveResults`, `SymLinkResult`). Never write files directly — `CollectorResult` handles dual-mode storage (in-memory for preflights, on-disk for support bundles). See `pkg/collect/result.go`.
## Advanced Review
- **Cross-feature impact** — Consider whether a change to one collector/analyzer could affect the broader collection pipeline, redaction, or output archive structure.
- **CLI vs SDK consumers** — This project is consumed both as CLI tools and as Go packages (SDK). Changes targeting a CLI use case must not break SDK consumers who import `pkg/collect`, `pkg/analyze`, or API types directly.
- **Documentation** — Does the change add, modify, or remove user-facing behavior? Check whether https://troubleshoot.sh needs updates. Use https://troubleshoot.sh/llms.txt or https://troubleshoot.sh/llms-full.txt to review current docs.
- **Dedicated documentation needs** — For large or complex changes, consider whether CLI users or SDK consumers need standalone documentation (migration guides, new feature walkthroughs, updated examples).
- **Backwards compatibility** — Spec changes must consider existing specs in the wild. New fields should have sensible zero-value defaults. Removed fields should not cause parse failures.
- **Downstream impact on sbctl** — Changes to public Go packages (`pkg/collect`, `pkg/analyze`, API types, etc.) may require follow-up changes in [replicatedhq/sbctl](https://github.com/replicatedhq/sbctl), which imports this project as a dependency. Flag any breaking or behavioral changes that could affect sbctl.
- **Repository docs** — If your changes affect build commands, architecture, project conventions, or review guidelines, update `CLAUDE.md`, `README.md`, and this file accordingly.
+132 -2
View File
@@ -22,7 +22,7 @@ jobs:
examples: ${{ steps.filter.outputs.examples }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
@@ -104,6 +104,134 @@ jobs:
path: bin/
retention-days: 1
# Minimal linux architecture smoke tests
linux-arch-smoke:
if: needs.changes.outputs.go-files == 'true'
needs: [changes, lint]
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- goarch: amd64
docker_platform: linux/amd64
expected_machine: x86_64
- goarch: arm64
docker_platform: linux/arm64
expected_machine: aarch64
- goarch: arm
goarm: "7"
docker_platform: linux/arm/v7
expected_machine: armv7l
- goarch: riscv64
docker_platform: linux/riscv64
expected_machine: riscv64
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Setup K3s
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- uses: docker/setup-qemu-action@v4
with:
platforms: arm64,arm,riscv64
- name: Build linux/${{ matrix.goarch }} binaries
env:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
run: |
if [ -n "${GOARM}" ]; then
echo "Building for GOARCH=${GOARCH} GOARM=${GOARM}"
else
echo "Building for GOARCH=${GOARCH}"
fi
make build
- name: Collect a minimal support bundle on ${{ matrix.docker_platform }}
env:
PREFLIGHT_AUTO_UPDATE: "false"
TROUBLESHOOT_AUTO_UPDATE: "false"
run: |
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
kubeconfig_path="${KUBECONFIG:-$HOME/.kube/config}"
assert_tar_member() {
local pattern="$1"
local label="$2"
local member
member="$(grep -m1 "$pattern" "$tmpdir/archive-members.txt" || true)"
if [ -z "$member" ]; then
echo "Expected ${label} was not found in ${archive}"
exit 1
fi
echo "Found ${label}: ${member}"
}
echo "Using kubeconfig at ${kubeconfig_path}"
test -f "${kubeconfig_path}"
cp "${kubeconfig_path}" "$tmpdir/kubeconfig"
echo "Verifying K3s cluster is reachable on the host"
kubectl --kubeconfig "$tmpdir/kubeconfig" get nodes -o wide
docker run --rm \
--platform ${{ matrix.docker_platform }} \
--network host \
-v "$PWD:/src" \
-v "$tmpdir:/tmp/linux-arch-smoke" \
-w /src \
alpine:3.23 \
sh -ec '
export PREFLIGHT_AUTO_UPDATE=false
export TROUBLESHOOT_AUTO_UPDATE=false
/src/bin/support-bundle \
--kubeconfig /tmp/linux-arch-smoke/kubeconfig \
--interactive=false \
/src/test/e2e/support-bundle/spec/linuxArchSmokeLocalHostCollectors.yaml \
--output /tmp/linux-arch-smoke/linux-arch-support-bundle.tar.gz
'
archive="$tmpdir/linux-arch-support-bundle.tar.gz"
test -f "$archive"
echo "Generated support bundle archive: $archive"
tar -tf "$archive" > "$tmpdir/archive-members.txt"
assert_tar_member '/host-collectors/system/cpu.json$' 'cpu.json'
assert_tar_member '/host-collectors/system/memory.json$' 'memory.json'
assert_tar_member '/host-collectors/run-host/uname.txt$' 'uname.txt'
assert_tar_member '/cluster-resources/nodes.json$' 'cluster-resources nodes.json'
assert_tar_member '/cluster-resources/resources.json$' 'cluster-resources resources.json'
assert_tar_member '/analysis.json$' 'analysis.json'
echo "Checking uname -m output"
uname_path="$(grep -m1 '/host-collectors/run-host/uname.txt$' "$tmpdir/archive-members.txt")"
uname_machine="$(tar -xOf "$archive" "$uname_path" | tr -d '\r\n')"
if [ "$uname_machine" != '${{ matrix.expected_machine }}' ]; then
echo "Unexpected uname -m output: ${uname_machine}"
exit 1
fi
echo "Analyzer input check passed: uname -m reported ${uname_machine}"
echo "Checking analysis.json"
analysis_path="$(grep -m1 '/analysis.json$' "$tmpdir/archive-members.txt")"
tar -xOf "$archive" "$analysis_path" > "$tmpdir/analysis.json"
jq -e '
.[]
| select(.insight.primary == "Linux arch smoke node count")
| select(.insight.detail == "This cluster has exactly 1 node")
| select(.severity == "debug")
' "$tmpdir/analysis.json" >/dev/null
echo "Found expected analyzer result: Linux arch smoke node count -> This cluster has exactly 1 node"
# E2E tests
e2e:
if: needs.changes.outputs.go-files == 'true' || github.event_name == 'push'
@@ -143,7 +271,7 @@ jobs:
# Success summary
success:
if: always()
needs: [lint, test, build, e2e]
needs: [lint, test, build, linux-arch-smoke, e2e]
runs-on: ubuntu-latest
steps:
- name: Check results
@@ -152,6 +280,7 @@ jobs:
if [[ "${{ needs.lint.result }}" == "failure" ]] || \
[[ "${{ needs.test.result }}" == "failure" ]] || \
[[ "${{ needs.build.result }}" == "failure" ]] || \
[[ "${{ needs.linux-arch-smoke.result }}" == "failure" ]] || \
[[ "${{ needs.e2e.result }}" == "failure" ]]; then
echo "::error::Some jobs failed or were cancelled"
exit 1
@@ -161,6 +290,7 @@ jobs:
if [[ "${{ needs.lint.result }}" == "cancelled" ]] || \
[[ "${{ needs.test.result }}" == "cancelled" ]] || \
[[ "${{ needs.build.result }}" == "cancelled" ]] || \
[[ "${{ needs.linux-arch-smoke.result }}" == "cancelled" ]] || \
[[ "${{ needs.e2e.result }}" == "cancelled" ]]; then
echo "::error::Some jobs failed or were cancelled"
exit 1
+4
View File
@@ -0,0 +1,4 @@
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY EPSS RISK PATH
{{- range .Matches}}
{{.Artifact.Name}} {{.Artifact.Version}} {{join .Vulnerability.Fix.Versions ","}} {{.Artifact.Type}} {{.Vulnerability.ID}} {{.Vulnerability.Severity}} {{.Vulnerability.EPSS}} {{.Vulnerability.Risk}} {{range .Artifact.Locations}}{{.AccessPath}}{{end}}
{{- end}}
+3
View File
@@ -0,0 +1,3 @@
exclude:
- ./examples/**
- ./bin/**
+3
View File
@@ -0,0 +1,3 @@
# AGENTS.md
ALWAYS read and follow the instructions in CLAUDE.md before starting any work in this repository.
+30
View File
@@ -0,0 +1,30 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Replicated Troubleshoot is a Kubernetes diagnostic framework providing two kubectl plugins: `preflight` (pre-installation cluster validation) and `support-bundle` (post-installation diagnostics with log collection, redaction, and analysis). Specs use the Kubernetes custom resource format (as a serialization convention, not installed in-cluster) and are defined by application vendors and executed by cluster operators.
## Build & Test Commands
```bash
make build # Build bin/support-bundle and bin/preflight
make test # Unit tests (includes generate, fmt, vet)
make test RUN=TestMyFunction # Run a single test
make test-integration # Integration tests (requires k8s cluster)
make e2e # All e2e tests
make generate # Regenerate types/clients after modifying pkg/apis/
```
## Architecture
The core data flow is: **Spec loading → Collection → Redaction → Analysis → Results**. The two main workflows are orchestrated by `pkg/supportbundle/` and `pkg/preflight/`.
Three API versions coexist in `pkg/apis/troubleshoot/`: v1beta1, v1beta2 (primary, all types defined here), and v1beta3 (in-progress, adds `StringOrValueFrom` for Secret/ConfigMap references, converts to v1beta2 at runtime).
Collectors live in `pkg/collect/`, analyzers in `pkg/analyze/`. When adding either, follow the pattern of existing implementations and run `make generate` after modifying API types.
## Code Review
See [.cursor/BUGBOT.md](.cursor/BUGBOT.md) for the full review checklist covering basic checks (API breaks, pattern violations, security, test coverage) and advanced checks (cross-feature impact, CLI vs SDK consumers, documentation needs).
+16 -8
View File
@@ -61,6 +61,9 @@ test: generate fmt vet
test-integration: generate fmt vet
go test -v --tags="integration exclude_graphdriver_devicemapper exclude_graphdriver_btrfs" ${BUILDPATHS}
.PHONY: e2e
e2e: preflight-e2e-test support-bundle-e2e-test support-bundle-e2e-go-test
.PHONY: preflight-e2e-test
preflight-e2e-test:
./test/validate-preflight-e2e.sh
@@ -255,14 +258,19 @@ sbom: sbom/assets/troubleshoot-sbom.tgz
sbom/assets/troubleshoot-sbom.tgz > sbom/assets/troubleshoot-sbom.tgz.sig
cosign public-key --key cosign.key --outfile sbom/assets/key.pub
.PHONY: get-govulncheck
get-govulncheck:
@command -v govulncheck >/dev/null 2>&1 || go install golang.org/x/vuln/cmd/govulncheck@latest
.PHONY: get-grype
get-grype:
@command -v grype >/dev/null 2>&1 || curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $(GOPATH)/bin
.PHONY: scan
scan:
trivy fs \
--scanners vuln \
--exit-code=1 \
--severity="HIGH,CRITICAL" \
--ignore-unfixed \
./
scan: get-govulncheck get-grype
govulncheck ./...
grype db update
grype dir:. --only-fixed --fail-on high -o template -t .grype.tmpl
.PHONY: watch
watch: npm-install
@@ -296,4 +304,4 @@ longhorn:
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/k8s\/pkg/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn/g"
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/types/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/types/g"
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/util/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/util/g"
rm -rf longhorn-manager
rm -rf longhorn-manager
+7 -1
View File
@@ -25,7 +25,13 @@ func RootCmd() *cobra.Command {
Args: cobra.MinimumNArgs(1),
Short: "Run and retrieve preflight checks in a cluster",
Long: `A preflight check is a set of validations that can and should be run to ensure
that a cluster meets the requirements to run an application.`,
that a cluster meets the requirements to run an application.
Unlike support-bundle, preflight does not support --load-cluster-specs because
preflight checks are designed to run before an application is installed or
upgraded. Since no deployment has occurred yet, there are no in-cluster specs
to discover. Preflight specs must be provided via a URL, local file path, or
stdin (e.g. "helm template ... | kubectl preflight -").`,
SilenceUsage: true,
SilenceErrors: true,
PreRun: func(cmd *cobra.Command, args []string) {
+1 -1
View File
@@ -133,7 +133,7 @@ If no arguments are provided, specs are automatically loaded from the cluster by
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
cmd.Flags().Int("remote-host-collect-timeout", 30, "timeout in seconds for remote host collect operations (e.g. waiting for pods/daemonsets)")
cmd.Flags().StringP("output", "o", "", "specify the output file path for the support bundle")
cmd.Flags().StringP("output", "o", "", "specify the output file path for the support bundle (.tar.gz extension is added automatically)")
cmd.Flags().Bool("debug", false, "enable debug logging. This is equivalent to --v=0")
cmd.Flags().Bool("dry-run", false, "print support bundle spec without collecting anything")
cmd.Flags().Bool("auto-update", true, "enable automatic binary self-update check and install")
+12 -1
View File
@@ -166,7 +166,18 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
go func() {
defer wg.Done()
for msg := range progressChan {
klog.Infof("Collecting support bundle: %v", msg)
switch msg := msg.(type) {
case error:
klog.Warningf("Collecting support bundle: %v", msg)
case string:
if strings.Contains(msg, "skipping collector") {
klog.Warningf("Collecting support bundle: %s", msg)
} else {
klog.Infof("Collecting support bundle: %s", msg)
}
default:
klog.Infof("Collecting support bundle: %v", msg)
}
}
}()
} else {
+161
View File
@@ -880,6 +880,55 @@ spec:
- namespace
- outcomes
type: object
ingressClass:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
exclude:
type: BoolString
ingressClassName:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
jobStatus:
properties:
annotations:
@@ -1311,6 +1360,8 @@ spec:
- key
type: object
type: object
ignoreIfNoFiles:
type: boolean
outcomes:
items:
properties:
@@ -1563,6 +1614,58 @@ spec:
- outcomes
- selector
type: object
s3Status:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
fileName:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- collectorName
- outcomes
type: object
secret:
properties:
annotations:
@@ -1922,7 +2025,16 @@ spec:
items:
properties:
blockDevices:
description: BlockDevicesAnalyze evaluates host-collected block
device listings (lsblk-based).
properties:
additionalDeviceTypes:
description: |-
AdditionalDeviceTypes are extra lsblk TYPE values (e.g. loop, lvm) that may count toward outcomes,
in addition to whole disks and (when IncludeUnmountedPartitions is set) partitions.
items:
type: string
type: array
annotations:
additionalProperties:
type: string
@@ -2725,6 +2837,55 @@ spec:
required:
- outcomes
type: object
registryImages:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
subnetAvailable:
properties:
annotations:
+185 -12
View File
@@ -2674,7 +2674,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -5933,7 +5935,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -6723,8 +6727,8 @@ spec:
will be made available to those containers which consume them
by name.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This is a stable field but requires that the
DynamicResourceAllocation feature gate is enabled.
This field is immutable.
items:
@@ -7184,9 +7188,10 @@ spec:
operator:
description: |-
Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal.
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
Exists is equivalent to wildcard for value, so that a pod can
tolerate all taints of a particular category.
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
type: string
tolerationSeconds:
description: |-
@@ -7987,7 +7992,7 @@ spec:
resources:
description: |-
resources represents the minimum resources the volume should have.
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
Users are allowed to specify resource requirements
that are lower than previous value but must still be higher than capacity recorded in the
status field of the claim.
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
@@ -8888,6 +8893,24 @@ spec:
CSRs will be addressed to this
signer.
type: string
userAnnotations:
additionalProperties:
type: string
description: |-
userAnnotations allow pod authors to pass additional information to
the signer implementation. Kubernetes does not restrict or validate this
metadata in any way.
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
the PodCertificateRequest objects that Kubelet creates.
Entries are subject to the same validation as object metadata annotations,
with the addition that all keys must be domain-prefixed. No restrictions
are placed on values, except an overall size limitation on the entire field.
Signers should document the keys and values they support. Signers should
deny requests that contain keys they do not recognize.
type: object
required:
- keyType
- signerName
@@ -9318,6 +9341,42 @@ spec:
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
workloadRef:
description: |-
WorkloadRef provides a reference to the Workload object that this Pod belongs to.
This field is used by the scheduler to identify the PodGroup and apply the
correct group scheduling policies. The Workload object referenced
by this field may not exist at the time the Pod is created.
This field is immutable, but a Workload object with the same name
may be recreated with different policies. Doing this during pod scheduling
may result in the placement not conforming to the expected policies.
properties:
name:
description: |-
Name defines the name of the Workload object this Pod belongs to.
Workload must be in the same namespace as the Pod.
If it doesn't match any existing Workload, the Pod will remain unschedulable
until a Workload object is created and observed by the kube-scheduler.
It must be a DNS subdomain.
type: string
podGroup:
description: |-
PodGroup is the name of the PodGroup within the Workload that this Pod
belongs to. If it doesn't match any existing PodGroup within the Workload,
the Pod will remain unschedulable until the Workload object is recreated
and observed by the kube-scheduler. It must be a DNS label.
type: string
podGroupReplicaKey:
description: |-
PodGroupReplicaKey specifies the replica key of the PodGroup to which this
Pod belongs. It is used to distinguish pods belonging to different replicas
of the same pod group. The pod group policy is applied separately to each replica.
When set, it must be a DNS label.
type: string
required:
- name
- podGroup
type: object
required:
- containers
type: object
@@ -11199,7 +11258,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -14458,7 +14519,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -15248,8 +15311,8 @@ spec:
will be made available to those containers which consume them
by name.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This is a stable field but requires that the
DynamicResourceAllocation feature gate is enabled.
This field is immutable.
items:
@@ -15709,9 +15772,10 @@ spec:
operator:
description: |-
Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal.
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
Exists is equivalent to wildcard for value, so that a pod can
tolerate all taints of a particular category.
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
type: string
tolerationSeconds:
description: |-
@@ -16512,7 +16576,7 @@ spec:
resources:
description: |-
resources represents the minimum resources the volume should have.
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
Users are allowed to specify resource requirements
that are lower than previous value but must still be higher than capacity recorded in the
status field of the claim.
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
@@ -17413,6 +17477,24 @@ spec:
CSRs will be addressed to this
signer.
type: string
userAnnotations:
additionalProperties:
type: string
description: |-
userAnnotations allow pod authors to pass additional information to
the signer implementation. Kubernetes does not restrict or validate this
metadata in any way.
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
the PodCertificateRequest objects that Kubelet creates.
Entries are subject to the same validation as object metadata annotations,
with the addition that all keys must be domain-prefixed. No restrictions
are placed on values, except an overall size limitation on the entire field.
Signers should document the keys and values they support. Signers should
deny requests that contain keys they do not recognize.
type: object
required:
- keyType
- signerName
@@ -17843,6 +17925,42 @@ spec:
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
workloadRef:
description: |-
WorkloadRef provides a reference to the Workload object that this Pod belongs to.
This field is used by the scheduler to identify the PodGroup and apply the
correct group scheduling policies. The Workload object referenced
by this field may not exist at the time the Pod is created.
This field is immutable, but a Workload object with the same name
may be recreated with different policies. Doing this during pod scheduling
may result in the placement not conforming to the expected policies.
properties:
name:
description: |-
Name defines the name of the Workload object this Pod belongs to.
Workload must be in the same namespace as the Pod.
If it doesn't match any existing Workload, the Pod will remain unschedulable
until a Workload object is created and observed by the kube-scheduler.
It must be a DNS subdomain.
type: string
podGroup:
description: |-
PodGroup is the name of the PodGroup within the Workload that this Pod
belongs to. If it doesn't match any existing PodGroup within the Workload,
the Pod will remain unschedulable until the Workload object is recreated
and observed by the kube-scheduler. It must be a DNS label.
type: string
podGroupReplicaKey:
description: |-
PodGroupReplicaKey specifies the replica key of the PodGroup to which this
Pod belongs. It is used to distinguish pods belonging to different replicas
of the same pod group. The pod group policy is applied separately to each replica.
When set, it must be a DNS label.
type: string
required:
- name
- podGroup
type: object
required:
- containers
type: object
@@ -17851,6 +17969,29 @@ spec:
required:
- namespace
type: object
s3Status:
properties:
accessKeyID:
type: string
bucketName:
type: string
collectorName:
type: string
endpoint:
type: string
exclude:
type: BoolString
insecure:
type: boolean
region:
type: string
secretAccessKey:
type: string
usePathStyle:
type: boolean
required:
- bucketName
type: object
secret:
properties:
collectorName:
@@ -17881,6 +18022,17 @@ spec:
namespace:
type: string
type: object
supportBundleMetadata:
properties:
collectorName:
type: string
exclude:
type: BoolString
namespace:
type: string
required:
- namespace
type: object
sysctl:
properties:
collectorName:
@@ -18333,6 +18485,27 @@ spec:
- port
- toCIDR
type: object
registryImages:
description: |-
HostRegistryImages checks whether images are accessible from the host,
without requiring a Kubernetes cluster. Auth can be supplied inline via
Username/Password or omitted to rely on ambient credentials (e.g. ~/.docker/config.json).
properties:
collectorName:
type: string
exclude:
type: BoolString
images:
items:
type: string
type: array
password:
type: string
username:
type: string
required:
- images
type: object
run:
properties:
args:
@@ -43,7 +43,16 @@ spec:
items:
properties:
blockDevices:
description: BlockDevicesAnalyze evaluates host-collected block
device listings (lsblk-based).
properties:
additionalDeviceTypes:
description: |-
AdditionalDeviceTypes are extra lsblk TYPE values (e.g. loop, lvm) that may count toward outcomes,
in addition to whole disks and (when IncludeUnmountedPartitions is set) partitions.
items:
type: string
type: array
annotations:
additionalProperties:
type: string
@@ -846,6 +855,55 @@ spec:
required:
- outcomes
type: object
registryImages:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
subnetAvailable:
properties:
annotations:
@@ -1775,6 +1833,27 @@ spec:
- port
- toCIDR
type: object
registryImages:
description: |-
HostRegistryImages checks whether images are accessible from the host,
without requiring a Kubernetes cluster. Auth can be supplied inline via
Username/Password or omitted to rely on ambient credentials (e.g. ~/.docker/config.json).
properties:
collectorName:
type: string
exclude:
type: BoolString
images:
items:
type: string
type: array
password:
type: string
username:
type: string
required:
- images
type: object
run:
properties:
args:
@@ -43,7 +43,16 @@ spec:
items:
properties:
blockDevices:
description: BlockDevicesAnalyze evaluates host-collected block
device listings (lsblk-based).
properties:
additionalDeviceTypes:
description: |-
AdditionalDeviceTypes are extra lsblk TYPE values (e.g. loop, lvm) that may count toward outcomes,
in addition to whole disks and (when IncludeUnmountedPartitions is set) partitions.
items:
type: string
type: array
annotations:
additionalProperties:
type: string
@@ -846,6 +855,55 @@ spec:
required:
- outcomes
type: object
registryImages:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
subnetAvailable:
properties:
annotations:
@@ -1775,6 +1833,27 @@ spec:
- port
- toCIDR
type: object
registryImages:
description: |-
HostRegistryImages checks whether images are accessible from the host,
without requiring a Kubernetes cluster. Auth can be supplied inline via
Username/Password or omitted to rely on ambient credentials (e.g. ~/.docker/config.json).
properties:
collectorName:
type: string
exclude:
type: BoolString
images:
items:
type: string
type: array
password:
type: string
username:
type: string
required:
- images
type: object
run:
properties:
args:
+267 -12
View File
@@ -880,6 +880,55 @@ spec:
- namespace
- outcomes
type: object
ingressClass:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
exclude:
type: BoolString
ingressClassName:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
jobStatus:
properties:
annotations:
@@ -1311,6 +1360,8 @@ spec:
- key
type: object
type: object
ignoreIfNoFiles:
type: boolean
outcomes:
items:
properties:
@@ -1563,6 +1614,58 @@ spec:
- outcomes
- selector
type: object
s3Status:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
fileName:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- collectorName
- outcomes
type: object
secret:
properties:
annotations:
@@ -4522,7 +4625,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -7781,7 +7886,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -8571,8 +8678,8 @@ spec:
will be made available to those containers which consume them
by name.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This is a stable field but requires that the
DynamicResourceAllocation feature gate is enabled.
This field is immutable.
items:
@@ -9032,9 +9139,10 @@ spec:
operator:
description: |-
Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal.
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
Exists is equivalent to wildcard for value, so that a pod can
tolerate all taints of a particular category.
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
type: string
tolerationSeconds:
description: |-
@@ -9835,7 +9943,7 @@ spec:
resources:
description: |-
resources represents the minimum resources the volume should have.
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
Users are allowed to specify resource requirements
that are lower than previous value but must still be higher than capacity recorded in the
status field of the claim.
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
@@ -10736,6 +10844,24 @@ spec:
CSRs will be addressed to this
signer.
type: string
userAnnotations:
additionalProperties:
type: string
description: |-
userAnnotations allow pod authors to pass additional information to
the signer implementation. Kubernetes does not restrict or validate this
metadata in any way.
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
the PodCertificateRequest objects that Kubelet creates.
Entries are subject to the same validation as object metadata annotations,
with the addition that all keys must be domain-prefixed. No restrictions
are placed on values, except an overall size limitation on the entire field.
Signers should document the keys and values they support. Signers should
deny requests that contain keys they do not recognize.
type: object
required:
- keyType
- signerName
@@ -11166,6 +11292,42 @@ spec:
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
workloadRef:
description: |-
WorkloadRef provides a reference to the Workload object that this Pod belongs to.
This field is used by the scheduler to identify the PodGroup and apply the
correct group scheduling policies. The Workload object referenced
by this field may not exist at the time the Pod is created.
This field is immutable, but a Workload object with the same name
may be recreated with different policies. Doing this during pod scheduling
may result in the placement not conforming to the expected policies.
properties:
name:
description: |-
Name defines the name of the Workload object this Pod belongs to.
Workload must be in the same namespace as the Pod.
If it doesn't match any existing Workload, the Pod will remain unschedulable
until a Workload object is created and observed by the kube-scheduler.
It must be a DNS subdomain.
type: string
podGroup:
description: |-
PodGroup is the name of the PodGroup within the Workload that this Pod
belongs to. If it doesn't match any existing PodGroup within the Workload,
the Pod will remain unschedulable until the Workload object is recreated
and observed by the kube-scheduler. It must be a DNS label.
type: string
podGroupReplicaKey:
description: |-
PodGroupReplicaKey specifies the replica key of the PodGroup to which this
Pod belongs. It is used to distinguish pods belonging to different replicas
of the same pod group. The pod group policy is applied separately to each replica.
When set, it must be a DNS label.
type: string
required:
- name
- podGroup
type: object
required:
- containers
type: object
@@ -13047,7 +13209,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -16306,7 +16470,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -17096,8 +17262,8 @@ spec:
will be made available to those containers which consume them
by name.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This is a stable field but requires that the
DynamicResourceAllocation feature gate is enabled.
This field is immutable.
items:
@@ -17557,9 +17723,10 @@ spec:
operator:
description: |-
Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal.
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
Exists is equivalent to wildcard for value, so that a pod can
tolerate all taints of a particular category.
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
type: string
tolerationSeconds:
description: |-
@@ -18360,7 +18527,7 @@ spec:
resources:
description: |-
resources represents the minimum resources the volume should have.
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
Users are allowed to specify resource requirements
that are lower than previous value but must still be higher than capacity recorded in the
status field of the claim.
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
@@ -19261,6 +19428,24 @@ spec:
CSRs will be addressed to this
signer.
type: string
userAnnotations:
additionalProperties:
type: string
description: |-
userAnnotations allow pod authors to pass additional information to
the signer implementation. Kubernetes does not restrict or validate this
metadata in any way.
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
the PodCertificateRequest objects that Kubelet creates.
Entries are subject to the same validation as object metadata annotations,
with the addition that all keys must be domain-prefixed. No restrictions
are placed on values, except an overall size limitation on the entire field.
Signers should document the keys and values they support. Signers should
deny requests that contain keys they do not recognize.
type: object
required:
- keyType
- signerName
@@ -19691,6 +19876,42 @@ spec:
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
workloadRef:
description: |-
WorkloadRef provides a reference to the Workload object that this Pod belongs to.
This field is used by the scheduler to identify the PodGroup and apply the
correct group scheduling policies. The Workload object referenced
by this field may not exist at the time the Pod is created.
This field is immutable, but a Workload object with the same name
may be recreated with different policies. Doing this during pod scheduling
may result in the placement not conforming to the expected policies.
properties:
name:
description: |-
Name defines the name of the Workload object this Pod belongs to.
Workload must be in the same namespace as the Pod.
If it doesn't match any existing Workload, the Pod will remain unschedulable
until a Workload object is created and observed by the kube-scheduler.
It must be a DNS subdomain.
type: string
podGroup:
description: |-
PodGroup is the name of the PodGroup within the Workload that this Pod
belongs to. If it doesn't match any existing PodGroup within the Workload,
the Pod will remain unschedulable until the Workload object is recreated
and observed by the kube-scheduler. It must be a DNS label.
type: string
podGroupReplicaKey:
description: |-
PodGroupReplicaKey specifies the replica key of the PodGroup to which this
Pod belongs. It is used to distinguish pods belonging to different replicas
of the same pod group. The pod group policy is applied separately to each replica.
When set, it must be a DNS label.
type: string
required:
- name
- podGroup
type: object
required:
- containers
type: object
@@ -19699,6 +19920,29 @@ spec:
required:
- namespace
type: object
s3Status:
properties:
accessKeyID:
type: string
bucketName:
type: string
collectorName:
type: string
endpoint:
type: string
exclude:
type: BoolString
insecure:
type: boolean
region:
type: string
secretAccessKey:
type: string
usePathStyle:
type: boolean
required:
- bucketName
type: object
secret:
properties:
collectorName:
@@ -19729,6 +19973,17 @@ spec:
namespace:
type: string
type: object
supportBundleMetadata:
properties:
collectorName:
type: string
exclude:
type: BoolString
namespace:
type: string
required:
- namespace
type: object
sysctl:
properties:
collectorName:
+346 -12
View File
@@ -911,6 +911,55 @@ spec:
- namespace
- outcomes
type: object
ingressClass:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
exclude:
type: BoolString
ingressClassName:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
jobStatus:
properties:
annotations:
@@ -1342,6 +1391,8 @@ spec:
- key
type: object
type: object
ignoreIfNoFiles:
type: boolean
outcomes:
items:
properties:
@@ -1594,6 +1645,58 @@ spec:
- outcomes
- selector
type: object
s3Status:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
fileName:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- collectorName
- outcomes
type: object
secret:
properties:
annotations:
@@ -4553,7 +4656,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -7812,7 +7917,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -8602,8 +8709,8 @@ spec:
will be made available to those containers which consume them
by name.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This is a stable field but requires that the
DynamicResourceAllocation feature gate is enabled.
This field is immutable.
items:
@@ -9063,9 +9170,10 @@ spec:
operator:
description: |-
Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal.
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
Exists is equivalent to wildcard for value, so that a pod can
tolerate all taints of a particular category.
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
type: string
tolerationSeconds:
description: |-
@@ -9866,7 +9974,7 @@ spec:
resources:
description: |-
resources represents the minimum resources the volume should have.
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
Users are allowed to specify resource requirements
that are lower than previous value but must still be higher than capacity recorded in the
status field of the claim.
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
@@ -10767,6 +10875,24 @@ spec:
CSRs will be addressed to this
signer.
type: string
userAnnotations:
additionalProperties:
type: string
description: |-
userAnnotations allow pod authors to pass additional information to
the signer implementation. Kubernetes does not restrict or validate this
metadata in any way.
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
the PodCertificateRequest objects that Kubelet creates.
Entries are subject to the same validation as object metadata annotations,
with the addition that all keys must be domain-prefixed. No restrictions
are placed on values, except an overall size limitation on the entire field.
Signers should document the keys and values they support. Signers should
deny requests that contain keys they do not recognize.
type: object
required:
- keyType
- signerName
@@ -11197,6 +11323,42 @@ spec:
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
workloadRef:
description: |-
WorkloadRef provides a reference to the Workload object that this Pod belongs to.
This field is used by the scheduler to identify the PodGroup and apply the
correct group scheduling policies. The Workload object referenced
by this field may not exist at the time the Pod is created.
This field is immutable, but a Workload object with the same name
may be recreated with different policies. Doing this during pod scheduling
may result in the placement not conforming to the expected policies.
properties:
name:
description: |-
Name defines the name of the Workload object this Pod belongs to.
Workload must be in the same namespace as the Pod.
If it doesn't match any existing Workload, the Pod will remain unschedulable
until a Workload object is created and observed by the kube-scheduler.
It must be a DNS subdomain.
type: string
podGroup:
description: |-
PodGroup is the name of the PodGroup within the Workload that this Pod
belongs to. If it doesn't match any existing PodGroup within the Workload,
the Pod will remain unschedulable until the Workload object is recreated
and observed by the kube-scheduler. It must be a DNS label.
type: string
podGroupReplicaKey:
description: |-
PodGroupReplicaKey specifies the replica key of the PodGroup to which this
Pod belongs. It is used to distinguish pods belonging to different replicas
of the same pod group. The pod group policy is applied separately to each replica.
When set, it must be a DNS label.
type: string
required:
- name
- podGroup
type: object
required:
- containers
type: object
@@ -13078,7 +13240,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -16337,7 +16501,9 @@ spec:
type: integer
type: object
resizePolicy:
description: Resources resize policy for the container.
description: |-
Resources resize policy for the container.
This field cannot be set on ephemeral containers.
items:
description: ContainerResizePolicy represents
resource resize policy for the container.
@@ -17127,8 +17293,8 @@ spec:
will be made available to those containers which consume them
by name.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This is a stable field but requires that the
DynamicResourceAllocation feature gate is enabled.
This field is immutable.
items:
@@ -17588,9 +17754,10 @@ spec:
operator:
description: |-
Operator represents a key's relationship to the value.
Valid operators are Exists and Equal. Defaults to Equal.
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
Exists is equivalent to wildcard for value, so that a pod can
tolerate all taints of a particular category.
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
type: string
tolerationSeconds:
description: |-
@@ -18391,7 +18558,7 @@ spec:
resources:
description: |-
resources represents the minimum resources the volume should have.
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
Users are allowed to specify resource requirements
that are lower than previous value but must still be higher than capacity recorded in the
status field of the claim.
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
@@ -19292,6 +19459,24 @@ spec:
CSRs will be addressed to this
signer.
type: string
userAnnotations:
additionalProperties:
type: string
description: |-
userAnnotations allow pod authors to pass additional information to
the signer implementation. Kubernetes does not restrict or validate this
metadata in any way.
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
the PodCertificateRequest objects that Kubelet creates.
Entries are subject to the same validation as object metadata annotations,
with the addition that all keys must be domain-prefixed. No restrictions
are placed on values, except an overall size limitation on the entire field.
Signers should document the keys and values they support. Signers should
deny requests that contain keys they do not recognize.
type: object
required:
- keyType
- signerName
@@ -19722,6 +19907,42 @@ spec:
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
workloadRef:
description: |-
WorkloadRef provides a reference to the Workload object that this Pod belongs to.
This field is used by the scheduler to identify the PodGroup and apply the
correct group scheduling policies. The Workload object referenced
by this field may not exist at the time the Pod is created.
This field is immutable, but a Workload object with the same name
may be recreated with different policies. Doing this during pod scheduling
may result in the placement not conforming to the expected policies.
properties:
name:
description: |-
Name defines the name of the Workload object this Pod belongs to.
Workload must be in the same namespace as the Pod.
If it doesn't match any existing Workload, the Pod will remain unschedulable
until a Workload object is created and observed by the kube-scheduler.
It must be a DNS subdomain.
type: string
podGroup:
description: |-
PodGroup is the name of the PodGroup within the Workload that this Pod
belongs to. If it doesn't match any existing PodGroup within the Workload,
the Pod will remain unschedulable until the Workload object is recreated
and observed by the kube-scheduler. It must be a DNS label.
type: string
podGroupReplicaKey:
description: |-
PodGroupReplicaKey specifies the replica key of the PodGroup to which this
Pod belongs. It is used to distinguish pods belonging to different replicas
of the same pod group. The pod group policy is applied separately to each replica.
When set, it must be a DNS label.
type: string
required:
- name
- podGroup
type: object
required:
- containers
type: object
@@ -19730,6 +19951,29 @@ spec:
required:
- namespace
type: object
s3Status:
properties:
accessKeyID:
type: string
bucketName:
type: string
collectorName:
type: string
endpoint:
type: string
exclude:
type: BoolString
insecure:
type: boolean
region:
type: string
secretAccessKey:
type: string
usePathStyle:
type: boolean
required:
- bucketName
type: object
secret:
properties:
collectorName:
@@ -19760,6 +20004,17 @@ spec:
namespace:
type: string
type: object
supportBundleMetadata:
properties:
collectorName:
type: string
exclude:
type: BoolString
namespace:
type: string
required:
- namespace
type: object
sysctl:
properties:
collectorName:
@@ -19797,7 +20052,16 @@ spec:
items:
properties:
blockDevices:
description: BlockDevicesAnalyze evaluates host-collected block
device listings (lsblk-based).
properties:
additionalDeviceTypes:
description: |-
AdditionalDeviceTypes are extra lsblk TYPE values (e.g. loop, lvm) that may count toward outcomes,
in addition to whole disks and (when IncludeUnmountedPartitions is set) partitions.
items:
type: string
type: array
annotations:
additionalProperties:
type: string
@@ -20600,6 +20864,55 @@ spec:
required:
- outcomes
type: object
registryImages:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
collectorName:
type: string
exclude:
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- outcomes
type: object
subnetAvailable:
properties:
annotations:
@@ -21529,6 +21842,27 @@ spec:
- port
- toCIDR
type: object
registryImages:
description: |-
HostRegistryImages checks whether images are accessible from the host,
without requiring a Kubernetes cluster. Auth can be supplied inline via
Username/Password or omitted to rely on ambient credentials (e.g. ~/.docker/config.json).
properties:
collectorName:
type: string
exclude:
type: BoolString
images:
items:
type: string
type: array
password:
type: string
username:
type: string
required:
- images
type: object
run:
properties:
args:
+15 -3
View File
@@ -9,10 +9,14 @@ builds:
main: ./cmd/preflight/main.go
env: [CGO_ENABLED=0]
goos: [linux, darwin, windows]
goarch: [amd64, arm, arm64]
goarch: [amd64, arm, arm64, riscv64]
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: riscv64
- goos: darwin
goarch: riscv64
ldflags:
- -s -w
- -X github.com/replicatedhq/troubleshoot/pkg/version.version={{ .Version }}
@@ -32,10 +36,14 @@ builds:
main: ./cmd/troubleshoot/main.go
env: [CGO_ENABLED=0]
goos: [linux, darwin, windows]
goarch: [amd64, arm, arm64]
goarch: [amd64, arm, arm64, riscv64]
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: riscv64
- goos: darwin
goarch: riscv64
ldflags:
- -s -w
- -X github.com/replicatedhq/troubleshoot/pkg/version.version={{ .Version }}
@@ -55,10 +63,14 @@ builds:
main: ./cmd/collect/main.go
env: [CGO_ENABLED=0]
goos: [linux, darwin, windows]
goarch: [amd64, arm, arm64]
goarch: [amd64, arm, arm64, riscv64]
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: riscv64
- goos: darwin
goarch: riscv64
ldflags:
- -s -w
- -X github.com/replicatedhq/troubleshoot/pkg/version.version={{ .Version }}
+17 -17
View File
@@ -1,6 +1,6 @@
module helm-template
go 1.25.5
go 1.26.1
// Always use the local version of troubleshoot so as to build using
// the latest version of the library. This will ensure the example
@@ -9,7 +9,7 @@ replace github.com/replicatedhq/troubleshoot v0.0.0 => ../../../
require (
github.com/replicatedhq/troubleshoot v0.0.0
helm.sh/helm/v3 v3.20.0
helm.sh/helm/v3 v3.20.2
sigs.k8s.io/yaml v1.6.0
)
@@ -47,27 +47,27 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.0 // indirect
k8s.io/apiextensions-apiserver v0.35.0 // indirect
k8s.io/apimachinery v0.35.0 // indirect
k8s.io/client-go v0.35.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/api v0.35.3 // indirect
k8s.io/apiextensions-apiserver v0.35.3 // indirect
k8s.io/apimachinery v0.35.3 // indirect
k8s.io/client-go v0.35.3 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/controller-runtime v0.22.4 // indirect
sigs.k8s.io/controller-runtime v0.23.3 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect
)
+38 -38
View File
@@ -102,28 +102,28 @@ 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=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
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/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
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.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -135,29 +135,29 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
helm.sh/helm/v3 v3.20.0 h1:2M+0qQwnbI1a2CxN7dbmfsWHg/MloeaFMnZCY56as50=
helm.sh/helm/v3 v3.20.0/go.mod h1:rTavWa0lagZOxGfdhu4vgk1OjH2UYCnrDKE2PVC4N0o=
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=
k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
helm.sh/helm/v3 v3.20.2 h1:binM4rvPx5DcNsa1sIt7UZi55lRbu3pZUFmQkSoRh48=
helm.sh/helm/v3 v3.20.2/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww=
k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ=
k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4=
k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU=
k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8=
k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=
k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=
k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
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/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
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-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+96 -117
View File
@@ -1,38 +1,41 @@
module github.com/replicatedhq/troubleshoot
go 1.26.1
go 1.26.2
require (
github.com/Masterminds/sprig/v3 v3.3.0
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
github.com/apparentlymart/go-cidr v1.1.0
github.com/apparentlymart/go-cidr v1.1.1
github.com/aws/aws-sdk-go-v2 v1.41.6
github.com/aws/aws-sdk-go-v2/credentials v1.19.15
github.com/aws/aws-sdk-go-v2/service/s3 v1.100.0
github.com/blang/semver/v4 v4.0.0
github.com/casbin/govaluate v1.10.0
github.com/cilium/ebpf v0.21.0
github.com/containerd/cgroups/v3 v3.1.3
github.com/distribution/distribution/v3 v3.0.0
github.com/fatih/color v1.18.0
github.com/fatih/color v1.19.0
github.com/go-logr/logr v1.4.3
github.com/go-redis/redis/v7 v7.4.1
github.com/go-sql-driver/mysql v1.9.3
github.com/gobwas/glob v0.2.3
github.com/godbus/dbus/v5 v5.2.2
github.com/google/go-containerregistry v0.21.5
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/hashicorp/go-getter v1.8.4
github.com/hashicorp/go-getter v1.8.6
github.com/hashicorp/go-multierror v1.1.1
github.com/jackc/pgx/v5 v5.8.0
github.com/jackc/pgx/v5 v5.9.2
github.com/longhorn/go-iscsi-helper v0.0.0-20210330030558-49a327fb024e
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-isatty v0.0.22
github.com/microsoft/go-mssqldb v1.9.8
github.com/miekg/dns v1.1.72
github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
github.com/segmentio/ksuid v1.0.4
github.com/shirou/gopsutil/v4 v4.26.2
github.com/shirou/gopsutil/v4 v4.26.3
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
@@ -41,91 +44,85 @@ require (
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
github.com/vmware-tanzu/velero v1.18.0
go.opentelemetry.io/otel v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.podman.io/image/v5 v5.39.1
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
golang.org/x/mod v0.33.0
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/mod v0.35.0
golang.org/x/sync v0.20.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.35.2
k8s.io/apiextensions-apiserver v0.35.2
k8s.io/apimachinery v0.35.2
k8s.io/apiserver v0.35.2
k8s.io/cli-runtime v0.35.2
k8s.io/client-go v0.35.2
k8s.io/api v0.36.0
k8s.io/apiextensions-apiserver v0.36.0
k8s.io/apimachinery v0.36.0
k8s.io/apiserver v0.36.0
k8s.io/cli-runtime v0.36.0
k8s.io/client-go v0.36.0
k8s.io/klog/v2 v2.140.0
k8s.io/streaming v0.36.0
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.23.3
sigs.k8s.io/e2e-framework v0.6.0
sigs.k8s.io/e2e-framework v0.7.0
)
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect
github.com/aws/smithy-go v1.25.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/distribution/distribution/v3 v3.1.0 // indirect
github.com/docker/cli v29.4.0+incompatible // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
@@ -134,13 +131,9 @@ require (
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mistifyio/go-zfs/v4 v4.0.0 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/rubenv/sql-migrate v1.8.1 // indirect
@@ -152,37 +145,36 @@ require (
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/sylabs/sif/v2 v2.22.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/vladimirvivien/gexe v0.4.1 // indirect
github.com/vladimirvivien/gexe v0.5.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.39.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.67.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
k8s.io/component-base v0.35.2 // indirect
k8s.io/kubectl v0.35.0 // indirect
gotest.tools/v3 v3.5.2 // indirect
k8s.io/component-base v0.36.0 // indirect
k8s.io/kubectl v0.36.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
)
require (
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/storage v1.58.0 // indirect
cloud.google.com/go/storage v1.61.3 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f // indirect
@@ -190,13 +182,8 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/containerd v1.7.30 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/docker v28.5.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.4 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
@@ -206,27 +193,21 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-version v1.8.0
github.com/hashicorp/go-version v1.9.0
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/spdystream v0.5.1 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
@@ -235,14 +216,13 @@ require (
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runtime-spec v1.3.0
github.com/opencontainers/selinux v1.13.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -252,29 +232,28 @@ require (
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.podman.io/storage v1.62.1-0.20260218215809-4bd29ff8b87e // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0
golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sys v0.42.0
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0
golang.org/x/time v0.14.0 // indirect
google.golang.org/api v0.256.0 // indirect
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/net v0.53.0
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.43.0
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0
golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.271.0 // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v3 v3.20.0
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/kubelet v0.35.2
k8s.io/metrics v0.35.2
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
helm.sh/helm/v3 v3.20.2
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/kubelet v0.36.0
k8s.io/metrics v0.36.0
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
periph.io/x/host/v3 v3.8.5
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.20.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/kustomize/api v0.21.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect
sigs.k8s.io/yaml v1.6.0
)
+244 -297
View File
@@ -1,27 +1,25 @@
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.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
@@ -49,12 +47,12 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
@@ -65,68 +63,66 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412 h1:vOVO0ypMfTt6tZacyI0kp+iCZb1XSNiYDqnzBWYgfe4=
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412/go.mod h1:AI9hp1tkp10pAlK5TCwL+7yWbRgtDm9jhToq6qij2xs=
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/apparentlymart/go-cidr v1.1.1 h1:oEEk8CE0HP0YpHxsegk/TaOtR2FLHdWv4p3eM4ceUwg=
github.com/apparentlymart/go-cidr v1.1.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg=
github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 h1:adBsCIIpLbLmYnkQU+nAChU5yhVTvu5PerROm+/Kq2A=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9/go.mod h1:uOYhgfgThm/ZyAuJGNQ5YgNyOlYfqnGpTHXvk3cpykg=
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4=
github.com/aws/aws-sdk-go-v2/credentials v1.19.15/go.mod h1:gJiYyMOjNg8OEdRWOf3CrFQxM2a98qmrtjx1zuiQfB8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 h1:IOGsJ1xVWhsi+ZO7/NW8OuZZBtMJLZbk4P5HDjJO0jQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22/go.mod h1:b+hYdbU+jGKfXE8kKM6g1+h+L/Go3vMvzlxBsiuGsxg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 h1:FPXsW9+gMuIeKmz7j6ENWcWtBGTe1kH8r9thNt5Uxx4=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23/go.mod h1:7J8iGMdRKk6lw2C+cMIphgAnT8uTwBwNOsGkyOCm80U=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 h1:HtOTYcbVcGABLOVuPYaIihj6IlkqubBwFj10K5fxRek=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8/go.mod h1:VsK9abqQeGlzPgUr+isNWzPlK2vKe9INMLWnY65f5Xs=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14 h1:xnvDEnw+pnj5mctWiYuFbigrEzSm35x7k4KS/ZkCANg=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14/go.mod h1:yS5rNogD8e0Wu9+l3MUwr6eENBzEeGejvINpN5PAYfY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 h1:PUmZeJU6Y1Lbvt9WFuJ0ugUK2xn6hIWUBBbKuOWF30s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22/go.mod h1:nO6egFBoAaoXze24a2C0NjQCvdpk8OueRoYimvEB9jo=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22 h1:SE+aQ4DEqG53RRCAIHlCf//B2ycxGH7jFkpnAh/kKPM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22/go.mod h1:ES3ynECd7fYeJIL6+oax+uIEljmfps0S70BaQzbMd/o=
github.com/aws/aws-sdk-go-v2/service/s3 v1.100.0 h1:7G26Sae6PMKn4kMcU5JzNfrm1YrKwyOhowXPYR2WiWY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.100.0/go.mod h1:Fw9aqhJicIVee1VytBBjH+l+5ov6/PhbtIK/u3rt/ls=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.10/go.mod h1:p6+MXNxW7IA6dMgHfTAzljuwSKD0NCm/4lbS4t6+7vI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 h1:x6bKbmDhsgSZwv6q19wY/u3rLk/3FGjJWyqKcIRufpE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.16/go.mod h1:CudnEVKRtLn0+3uMV0yEXZ+YZOKnAtUJ5DmDhilVnIw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 h1:oK/njaL8GtyEihkWMD4k3VgHCT64RQKkZwh0DG5j8ak=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20/go.mod h1:JHs8/y1f3zY7U5WcuzoJ/yAYGYtNIVPKLIbp61euvmg=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcuxk8l+PG4+A0exds=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo=
github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U=
github.com/aws/smithy-go v1.25.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4=
github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0=
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f h1:tRk+aBit+q3oqnj/1mF5HHhP2yxJM2lSa0afOJxQ3nE=
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
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/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
@@ -142,28 +138,22 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k=
github.com/cilium/ebpf v0.21.0/go.mod h1:1kHKv6Kvh5a6TePP5vvvoMa1bclRyzUXELSs272fmIQ=
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ=
github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
github.com/containerd/containerd v1.7.30 h1:/2vezDpLDVGGmkUXmlNPLCCNKHJ5BbC5tJB5JNzQhqE=
github.com/containerd/containerd v1.7.30/go.mod h1:fek494vwJClULlTpExsmOyKCMUAbuVjlFsJQc4/j44M=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
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/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -175,48 +165,40 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM=
github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=
github.com/distribution/distribution/v3 v3.1.0 h1:u1v788HreKTLGdNY6s7px8Exgrs9mZ9UrCDjSrpCM8g=
github.com/distribution/distribution/v3 v3.1.0/go.mod h1:73BuF5/ziMHNVt7nnL1roYpH4Eg/FgUlKZm3WryIx/o=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4=
github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
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/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=
github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=
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.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
@@ -232,8 +214,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -262,7 +244,6 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
@@ -282,26 +263,23 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
github.com/google/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=
github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.2.0/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=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -310,23 +288,21 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 h1:0HADrxxqaQkGycO1JoUUA+B4FnIkuo8d2bz/hSaTFFQ=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70/go.mod h1:fm2FdDCzJdtbXF7WKAMvBb5NEPouXPHFbGNYs9ShFns=
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/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.8.4 h1:hGEd2xsuVKgwkMtPVufq73fAmZU/x65PPcqH3cb0D9A=
github.com/hashicorp/go-getter v1.8.4/go.mod h1:x27pPGSg9kzoB147QXI8d/nDvp2IgYGcwuRjpaXE9Yg=
github.com/hashicorp/go-getter v1.8.6 h1:9sQboWULaydVphxc4S64oAI4YqpuCk7nPmvbk131ebY=
github.com/hashicorp/go-getter v1.8.6/go.mod h1:nVH12eOV2P58dIiL3rsU6Fh3wLeJEKBOJzhMmzlSWoo=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=
github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
@@ -341,8 +317,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
@@ -355,10 +331,8 @@ github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCX
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -388,8 +362,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@@ -404,8 +378,6 @@ github.com/microsoft/go-mssqldb v1.9.8 h1:d4IFMvF/o+HdpXUqbBfzHvn/NlFA75YGcfHUUv
github.com/microsoft/go-mssqldb v1.9.8/go.mod h1:eGSRSGAW4hKMy5YcAenhCDjIRm2rhqIdmmwgciMzLus=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4=
github.com/mistifyio/go-zfs/v4 v4.0.0/go.mod h1:weotFtXTHvBwhr9Mv96KYnDkTPBOHFUbm9cBmQpesL0=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -415,20 +387,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -439,30 +399,24 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
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/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
@@ -487,10 +441,12 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
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/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho=
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
@@ -512,14 +468,12 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -552,10 +506,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/sylabs/sif/v2 v2.22.0 h1:Y+xXufp4RdgZe02SR3nWEg7S6q4tPWN237WHYzkDSKA=
github.com/sylabs/sif/v2 v2.22.0/go.mod h1:W1XhWTmG1KcG7j5a3KSYdMcUIFvbs240w/MMVW627hs=
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
@@ -570,8 +520,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY=
github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E=
github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A=
github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E=
github.com/vmware-tanzu/velero v1.18.0 h1:szADU7zjNF5vhWM3tX/ttzkODclhShOfu8G12/fnmFE=
github.com/vmware-tanzu/velero v1.18.0/go.mod h1:MrbDXkA39TFvLzpznrhdWs0QFcbFbQauTHqYLcrzSj4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@@ -582,58 +532,54 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
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/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
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/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.podman.io/image/v5 v5.39.1 h1:loIw4qHzZzBlUguYZau40u8HbR5MrTPQhwT4Hy6sCm0=
go.podman.io/image/v5 v5.39.1/go.mod h1:SlaR6Pra1ATIx4BcuZ16oafb3QcCHISaKcJbtlN/G/0=
go.podman.io/storage v1.62.1-0.20260218215809-4bd29ff8b87e h1:GsWzvpmQucylaCaYFJ/siGJzYek/XBfhDXC7kzPZYIA=
go.podman.io/storage v1.62.1-0.20260218215809-4bd29ff8b87e/go.mod h1:B83Ad8mtO0GZs7rEwb66f0Ed5G57NyKI/iJZHoJrpUE=
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
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.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
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/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
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/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
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.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
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=
@@ -646,18 +592,18 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
@@ -672,36 +618,35 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
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/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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=
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc=
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc=
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY=
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
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-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -724,51 +669,53 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
helm.sh/helm/v3 v3.20.0 h1:2M+0qQwnbI1a2CxN7dbmfsWHg/MloeaFMnZCY56as50=
helm.sh/helm/v3 v3.20.0/go.mod h1:rTavWa0lagZOxGfdhu4vgk1OjH2UYCnrDKE2PVC4N0o=
k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
k8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=
k8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=
k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/apiserver v0.35.2 h1:rb52v0CZGEL0FkhjS+I6jHflAp7fZ4MIaKcEHX7wmDk=
k8s.io/apiserver v0.35.2/go.mod h1:CROJUAu0tfjZLyYgSeBsBan2T7LUJGh0ucWwTCSSk7g=
k8s.io/cli-runtime v0.35.2 h1:3DNctzpPNXavqyrm/FFiT60TLk4UjUxuUMYbKOE970E=
k8s.io/cli-runtime v0.35.2/go.mod h1:G2Ieu0JidLm5m1z9b0OkFhnykvJ1w+vjbz1tR5OFKL0=
k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
k8s.io/component-base v0.35.2 h1:btgR+qNrpWuRSuvWSnQYsZy88yf5gVwemvz0yw79pGc=
k8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0=
helm.sh/helm/v3 v3.20.2 h1:binM4rvPx5DcNsa1sIt7UZi55lRbu3pZUFmQkSoRh48=
helm.sh/helm/v3 v3.20.2/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww=
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc=
k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE=
k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho=
k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg=
k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA=
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo=
k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc=
k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo=
k8s.io/kubelet v0.35.2 h1:qF9jOe1j6vT4bVQZ6nnTTA5uu5NCnyR10o9IkW8Z0JQ=
k8s.io/kubelet v0.35.2/go.mod h1:2pyCVLDfm7ErNwWZw2mutCloAXX76gfOToIMCHCq/8s=
k8s.io/metrics v0.35.2 h1:PJRP88qeadR5evg4ZKJAh3NR3ICchwM51/Aidd0LHjc=
k8s.io/metrics v0.35.2/go.mod h1:w1pJmSu2j8ftVI26MGcJtMnpmZ06oKwb4Enm+xVl06Q=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms=
k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo=
k8s.io/kubelet v0.36.0 h1:zWeevZeGl80DInNU6WUo13yWmgbEajkRaBFqeKqkweA=
k8s.io/kubelet v0.36.0/go.mod h1:PLROV2RwWJkSbAkdZ8HeJWsbsjEEEMlhRIEzAwGeU9c=
k8s.io/metrics v0.36.0 h1:VF41Mv9ZWKKQ4jEiJ0n3Tp6jdyO+oM6dbKcJn6Y/DVg=
k8s.io/metrics v0.36.0/go.mod h1:FY1dgPJZqnSfnOYbVdBEdRNUdy0n1nUCU6yxSMUrVG4=
k8s.io/streaming v0.36.0 h1:agnTxU+NFulUrtYzXUGKO3ndEa8jKwht1Kwn9nu9x+4=
k8s.io/streaming v0.36.0/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
periph.io/x/host/v3 v3.8.5 h1:g4g5xE1XZtDiGl1UAJaUur1aT7uNiFLMkyMEiZ7IHII=
periph.io/x/host/v3 v3.8.5/go.mod h1:hPq8dISZIc+UNfWoRj+bPH3XEBQqJPdFdx218W92mdc=
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
sigs.k8s.io/e2e-framework v0.6.0 h1:p7hFzHnLKO7eNsWGI2AbC1Mo2IYxidg49BiT4njxkrM=
sigs.k8s.io/e2e-framework v0.6.0/go.mod h1:IREnCHnKgRCioLRmNi0hxSJ1kJ+aAdjEKK/gokcZu4k=
sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM=
sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw=
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/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI=
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
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
@@ -262,6 +262,8 @@ func GetAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
return &AnalyzeNodeMetrics{analyzer: analyzer.NodeMetrics}
case analyzer.HTTP != nil:
return &AnalyzeHTTPAnalyze{analyzer: analyzer.HTTP}
case analyzer.S3Status != nil:
return &AnalyzeS3Status{analyzer: analyzer.S3Status}
default:
return nil
}
+2
View File
@@ -67,6 +67,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
return &AnalyzeHostNetworkNamespaceConnectivity{analyzer.NetworkNamespaceConnectivity}, true
case analyzer.Sysctl != nil:
return &AnalyzeHostSysctl{analyzer.Sysctl}, true
case analyzer.RegistryImages != nil:
return &AnalyzeHostRegistryImages{analyzer.RegistryImages}, true
default:
return nil, false
}
+40 -18
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"slices"
"strconv"
"strings"
@@ -16,6 +17,25 @@ type AnalyzeHostBlockDevices struct {
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
}
// blockDevicesMatchConfig carries analyzer settings for counting devices toward a blockDevices when-clause
// ("<name-regex> <op> <count>" against host block_devices.json). A device is eligible when its name matches
// the regex; its type is "disk", or "part" if includeUnmountedPartitions, or listed in additionalDeviceTypes;
// size >= minimumAcceptableSize when that is non-zero; it has no mountpoint or filesystem; it is not
// read-only or removable; and no other row has ParentKernelName equal to its KernelName.
type blockDevicesMatchConfig struct {
minimumAcceptableSize uint64
includeUnmountedPartitions bool
additionalDeviceTypes []string
}
func matchConfigFromAnalyzer(a *troubleshootv1beta2.BlockDevicesAnalyze) blockDevicesMatchConfig {
return blockDevicesMatchConfig{
minimumAcceptableSize: a.MinimumAcceptableSize,
includeUnmountedPartitions: a.IncludeUnmountedPartitions,
additionalDeviceTypes: a.AdditionalDeviceTypes,
}
}
func (a *AnalyzeHostBlockDevices) Title() string {
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Block Devices")
}
@@ -49,7 +69,7 @@ func (a *AnalyzeHostBlockDevices) Analyze(
// <regexp> <op> <count>
// example: sdb > 0
func compareHostBlockDevicesConditionalToActual(conditional string, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) (res bool, err error) {
func compareHostBlockDevicesConditionalToActual(conditional string, cfg blockDevicesMatchConfig, devices []collect.BlockDeviceInfo) (res bool, err error) {
parts := strings.Split(conditional, " ")
if len(parts) != 3 {
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
@@ -59,7 +79,7 @@ func compareHostBlockDevicesConditionalToActual(conditional string, minimumAccep
if err != nil {
return false, errors.Wrapf(err, "failed to compile regex %q", parts[0])
}
count := countEligibleBlockDevices(rx, minimumAcceptableSize, includeUnmountedPartitions, devices)
count := countEligibleBlockDevices(rx, cfg, devices)
desiredInt, err := strconv.Atoi(parts[2])
if err != nil {
@@ -82,11 +102,11 @@ func compareHostBlockDevicesConditionalToActual(conditional string, minimumAccep
return false, fmt.Errorf("Unexpected operator %q", parts[1])
}
func countEligibleBlockDevices(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, devices []collect.BlockDeviceInfo) int {
func countEligibleBlockDevices(rx *regexp.Regexp, cfg blockDevicesMatchConfig, devices []collect.BlockDeviceInfo) int {
count := 0
for _, device := range devices {
if isEligibleBlockDevice(rx, minimumAcceptableSize, includeUnmountedPartitions, device, devices) {
if isEligibleBlockDevice(rx, cfg, device, devices) {
count++
}
}
@@ -94,23 +114,27 @@ func countEligibleBlockDevices(rx *regexp.Regexp, minimumAcceptableSize uint64,
return count
}
func isEligibleBlockDevice(rx *regexp.Regexp, minimumAcceptableSize uint64, includeUnmountedPartitions bool, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
func isEligibleDeviceType(deviceType string, cfg blockDevicesMatchConfig) bool {
if deviceType == "disk" {
return true
}
if cfg.includeUnmountedPartitions && deviceType == "part" {
return true
}
return slices.Contains(cfg.additionalDeviceTypes, deviceType)
}
func isEligibleBlockDevice(rx *regexp.Regexp, cfg blockDevicesMatchConfig, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
if !rx.MatchString(device.Name) {
return false
}
if includeUnmountedPartitions {
if device.Type != "disk" && device.Type != "part" {
return false
}
} else {
if device.Type != "disk" {
return false
}
if !isEligibleDeviceType(device.Type, cfg) {
return false
}
if minimumAcceptableSize != 0 {
if device.Size < minimumAcceptableSize {
if cfg.minimumAcceptableSize != 0 {
if device.Size < cfg.minimumAcceptableSize {
return false
}
}
@@ -141,12 +165,10 @@ func isEligibleBlockDevice(rx *regexp.Regexp, minimumAcceptableSize uint64, incl
}
func (a *AnalyzeHostBlockDevices) CheckCondition(when string, data []byte) (bool, error) {
var devices []collect.BlockDeviceInfo
if err := json.Unmarshal(data, &devices); err != nil {
return false, errors.Wrap(err, "failed to unmarshal block devices info")
}
return compareHostBlockDevicesConditionalToActual(when, a.hostAnalyzer.MinimumAcceptableSize, a.hostAnalyzer.IncludeUnmountedPartitions, devices)
return compareHostBlockDevicesConditionalToActual(when, matchConfigFromAnalyzer(a.hostAnalyzer), devices)
}
@@ -0,0 +1,102 @@
package analyzer
import (
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestHostBlockDevices_deviceTypeEligibility documents type rules in isolation (see blockDevicesMatchConfig in host_block_devices.go).
func TestHostBlockDevices_deviceTypeEligibility(t *testing.T) {
tests := []struct {
name string
cfg blockDevicesMatchConfig
devType string
want bool
}{
{name: "disk always", cfg: blockDevicesMatchConfig{}, devType: "disk", want: true},
{name: "part without flag", cfg: blockDevicesMatchConfig{}, devType: "part", want: false},
{name: "part with flag", cfg: blockDevicesMatchConfig{includeUnmountedPartitions: true}, devType: "part", want: true},
{name: "loop without additional", cfg: blockDevicesMatchConfig{}, devType: "loop", want: false},
{name: "loop with additional", cfg: blockDevicesMatchConfig{additionalDeviceTypes: []string{"loop"}}, devType: "loop", want: true},
{name: "lvm with additional", cfg: blockDevicesMatchConfig{additionalDeviceTypes: []string{"lvm"}}, devType: "lvm", want: true},
{name: "crypt with additional", cfg: blockDevicesMatchConfig{additionalDeviceTypes: []string{"crypt"}}, devType: "crypt", want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isEligibleDeviceType(tt.devType, tt.cfg)
assert.Equal(t, tt.want, got)
})
}
}
// TestHostBlockDevices_additionalDeviceTypes covers end-to-end analyze behavior for AdditionalDeviceTypes and representative preflights.
func TestHostBlockDevices_additionalDeviceTypes(t *testing.T) {
const rawStoragePass = "At least one raw block device is available for storage."
const rawStorageFail = "No raw block devices found. At least one unformatted, unmounted disk is required for storage. Attach a raw disk and ensure it has no filesystem or mount point."
rawStorageOutcomes := []*troubleshootv1beta2.Outcome{
{Fail: &troubleshootv1beta2.SingleOutcome{When: ".* == 0", Message: rawStorageFail}},
{Pass: &troubleshootv1beta2.SingleOutcome{When: ".* >= 1", Message: rawStoragePass}},
}
tests := []struct {
name string
devices []collect.BlockDeviceInfo
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
want []*AnalyzeResult
}{
{
name: "preflight-style 10GiB loop0 with includeUnmountedPartitions + additionalDeviceTypes loop",
devices: []collect.BlockDeviceInfo{{
Name: "loop0", KernelName: "loop0", Type: "loop", Major: 7, Minor: 0,
Size: 10737418240, ReadOnly: false, Removable: false,
}},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
IncludeUnmountedPartitions: true,
MinimumAcceptableSize: 10737418240,
AdditionalDeviceTypes: []string{"loop"},
Outcomes: rawStorageOutcomes,
},
want: []*AnalyzeResult{{Title: "Block Devices", IsPass: true, Message: rawStoragePass}},
},
{
name: "preflight-style 10GiB LVM with includeUnmountedPartitions + additionalDeviceTypes lvm",
devices: []collect.BlockDeviceInfo{{
Name: "ceph--vg-lv--osd0", KernelName: "dm-0", Type: "lvm", Major: 252, Minor: 0,
Size: 10737418240, ReadOnly: false, Removable: false,
}},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
IncludeUnmountedPartitions: true,
MinimumAcceptableSize: 10737418240,
AdditionalDeviceTypes: []string{"lvm"},
Outcomes: rawStorageOutcomes,
},
want: []*AnalyzeResult{{Title: "Block Devices", IsPass: true, Message: rawStoragePass}},
},
{
name: "loop counts with only additionalDeviceTypes (includeUnmountedPartitions false)",
devices: []collect.BlockDeviceInfo{{
Name: "loop0", KernelName: "loop0", Type: "loop", Major: 7,
}},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
AdditionalDeviceTypes: []string{"loop"},
Outcomes: []*troubleshootv1beta2.Outcome{
{Pass: &troubleshootv1beta2.SingleOutcome{When: ".* > 0", Message: "Block device available"}},
{Fail: &troubleshootv1beta2.SingleOutcome{Message: "No block device available"}},
},
},
want: []*AnalyzeResult{{Title: "Block Devices", IsPass: true, Message: "Block device available"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := analyzeHostBlockDevicesOutput(t, tt.devices, tt.hostAnalyzer)
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
+10 -11
View File
@@ -256,23 +256,22 @@ func TestAnalyzeBlockDevices(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.devices)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := (&AnalyzeHostBlockDevices{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil)
result, err := analyzeHostBlockDevicesOutput(t, test.devices, test.hostAnalyzer)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
// analyzeHostBlockDevicesOutput runs the host block device analyzer on marshaled fixture data (shared by match tests).
func analyzeHostBlockDevicesOutput(t *testing.T, devices []collect.BlockDeviceInfo, hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze) ([]*AnalyzeResult, error) {
t.Helper()
b, err := json.Marshal(devices)
require.NoError(t, err)
getCollectedFileContents := func(string) ([]byte, error) { return b, nil }
return (&AnalyzeHostBlockDevices{hostAnalyzer}).Analyze(getCollectedFileContents, nil)
}
+199
View File
@@ -0,0 +1,199 @@
package analyzer
import (
"encoding/json"
"fmt"
"slices"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/klog/v2"
)
// RegistryImagesSummary is passed as template data when rendering outcome messages.
// Fields are exported so Go templates can reference them.
//
// - Verified: images confirmed to exist in the registry.
// - Missing: images confirmed not to exist in the registry.
// - Errors: images that could not be checked (parse failures, timeouts, auth errors, etc).
// - UnverifiedReasons: map of image name to reason string for every unverified image
// (union of Missing and Errors).
//
// The `when` conditions follow the existing registry images analyzer nomenclature:
// "verified", "missing", and "errors" (see https://troubleshoot.sh/docs/analyze/registry-images).
type RegistryImagesSummary struct {
Verified []string
Missing []string
Errors []string
UnverifiedReasons map[string]string
}
type AnalyzeHostRegistryImages struct {
hostAnalyzer *troubleshootv1beta2.HostRegistryImagesAnalyze
}
func (a *AnalyzeHostRegistryImages) Title() string {
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Registry Images")
}
func (a *AnalyzeHostRegistryImages) IsExcluded() (bool, error) {
return isExcluded(a.hostAnalyzer.Exclude)
}
func (a *AnalyzeHostRegistryImages) Analyze(
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
) ([]*AnalyzeResult, error) {
collectorName := a.hostAnalyzer.CollectorName
if collectorName == "" {
collectorName = "images"
}
const nodeBaseDir = "host-collectors/registry-images"
localPath := fmt.Sprintf("%s/%s.json", nodeBaseDir, collectorName)
fileName := fmt.Sprintf("%s.json", collectorName)
collectedContents, err := retrieveCollectedContents(
getCollectedFileContents,
localPath,
nodeBaseDir,
fileName,
)
if err != nil {
return []*AnalyzeResult{{Title: a.Title()}}, err
}
var results []*AnalyzeResult
for _, content := range collectedContents {
currentTitle := a.Title()
if content.NodeName != "" {
currentTitle = fmt.Sprintf("%s - Node %s", a.Title(), content.NodeName)
}
result, err := a.evaluateOutcomesWithTemplate(content.Data, currentTitle)
if err != nil {
return nil, errors.Wrap(err, "failed to analyze host registry images")
}
if result != nil {
klog.V(2).Infof("registry images analysis result: title=%q pass=%t warn=%t fail=%t message=%q",
result.Title, result.IsPass, result.IsWarn, result.IsFail, result.Message)
results = append(results, result)
}
}
return results, nil
}
func (a *AnalyzeHostRegistryImages) evaluateOutcomesWithTemplate(data []byte, title string) (*AnalyzeResult, error) {
summary, err := buildRegistryImagesSummary(data)
if err != nil {
return nil, err
}
for _, outcome := range a.hostAnalyzer.Outcomes {
result := &AnalyzeResult{Title: title}
switch {
case outcome.Fail != nil:
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = renderRegistryMessage(outcome.Fail.Message, summary)
result.URI = outcome.Fail.URI
return result, nil
}
isMatch, err := compareRegistryConditionalToActual(outcome.Fail.When, len(summary.Verified), len(summary.Missing), len(summary.Errors))
if err != nil {
return result, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
}
if isMatch {
result.IsFail = true
result.Message = renderRegistryMessage(outcome.Fail.Message, summary)
result.URI = outcome.Fail.URI
return result, nil
}
case outcome.Warn != nil:
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = renderRegistryMessage(outcome.Warn.Message, summary)
result.URI = outcome.Warn.URI
return result, nil
}
isMatch, err := compareRegistryConditionalToActual(outcome.Warn.When, len(summary.Verified), len(summary.Missing), len(summary.Errors))
if err != nil {
return result, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
}
if isMatch {
result.IsWarn = true
result.Message = renderRegistryMessage(outcome.Warn.Message, summary)
result.URI = outcome.Warn.URI
return result, nil
}
case outcome.Pass != nil:
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = renderRegistryMessage(outcome.Pass.Message, summary)
result.URI = outcome.Pass.URI
return result, nil
}
isMatch, err := compareRegistryConditionalToActual(outcome.Pass.When, len(summary.Verified), len(summary.Missing), len(summary.Errors))
if err != nil {
return result, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
}
if isMatch {
result.IsPass = true
result.Message = renderRegistryMessage(outcome.Pass.Message, summary)
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return nil, nil
}
func buildRegistryImagesSummary(data []byte) (*RegistryImagesSummary, error) {
var registryInfo collect.RegistryInfo
if err := json.Unmarshal(data, &registryInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal registry info")
}
summary := &RegistryImagesSummary{
UnverifiedReasons: map[string]string{},
}
for image, info := range registryInfo.Images {
if info.Error != "" {
summary.Errors = append(summary.Errors, image)
summary.UnverifiedReasons[image] = info.Error
} else if !info.Exists {
summary.Missing = append(summary.Missing, image)
summary.UnverifiedReasons[image] = "image not found in registry"
} else {
summary.Verified = append(summary.Verified, image)
}
}
slices.Sort(summary.Verified)
slices.Sort(summary.Missing)
slices.Sort(summary.Errors)
return summary, nil
}
func renderRegistryMessage(message string, summary *RegistryImagesSummary) string {
rendered, err := util.RenderTemplate(message, summary)
if err != nil {
klog.V(2).Infof("Failed to render registry message template: %v", err)
return message
}
return rendered
}
func (a *AnalyzeHostRegistryImages) CheckCondition(when string, data []byte) (bool, error) {
summary, err := buildRegistryImagesSummary(data)
if err != nil {
return false, err
}
return compareRegistryConditionalToActual(when, len(summary.Verified), len(summary.Missing), len(summary.Errors))
}
+412
View File
@@ -0,0 +1,412 @@
package analyzer
import (
"encoding/json"
"testing"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeHostRegistryImagesCheckCondition(t *testing.T) {
tests := []struct {
name string
conditional string
data collect.RegistryInfo
expected bool
expectErr string
}{
{
name: "all images found",
conditional: "missing == 0",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
"registry.example.com/app:v2": {Exists: true},
},
},
expected: true,
},
{
name: "some images not found",
conditional: "missing > 0",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
"registry.example.com/app:v2": {Exists: false},
},
},
expected: true,
},
{
name: "verified count matches found",
conditional: "verified == 2",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
"registry.example.com/app:v2": {Exists: true},
},
},
expected: true,
},
{
name: "errored images counted under errors",
conditional: "errors > 0",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Error: "connection refused"},
},
},
expected: true,
},
{
name: "no errors when all found",
conditional: "missing == 0",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
},
},
expected: true,
},
{
name: "mixed results - missing and errors counted separately",
conditional: "missing == 1",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
"registry.example.com/app:v2": {Exists: false},
"registry.example.com/app:v3": {Error: "timeout"},
},
},
expected: true,
},
{
name: "invalid conditional format",
conditional: "missing",
data: collect.RegistryInfo{
Images: map[string]collect.RegistryImage{},
},
expected: false,
expectErr: "unable to parse conditional",
},
{
name: "unmarshal error",
conditional: "missing == 0",
expected: false,
expectErr: "failed to unmarshal registry info",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
a := &AnalyzeHostRegistryImages{}
var data []byte
if test.expectErr == "failed to unmarshal registry info" {
data = []byte(`{not valid json}`)
} else {
var err error
data, err = json.Marshal(test.data)
req.NoError(err)
}
result, err := a.CheckCondition(test.conditional, data)
if test.expectErr != "" {
req.ErrorContains(err, test.expectErr)
} else {
req.NoError(err)
}
assert.Equal(t, test.expected, result)
})
}
}
func TestAnalyzeHostRegistryImages(t *testing.T) {
tests := []struct {
name string
hostAnalyzer *troubleshootv1beta2.HostRegistryImagesAnalyze
getCollectedFileContents func(string) ([]byte, error)
expectedResults []*AnalyzeResult
expectedError string
}{
{
name: "pass when all images found",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "missing == 0",
Message: "All images are available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/images.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsPass: true,
Message: "All images are available",
},
},
},
{
name: "fail when images not found",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "missing > 0",
Message: "Some images are not available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/images.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: false},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsFail: true,
Message: "Some images are not available",
},
},
},
{
name: "errored images matched by errors condition",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "errors > 0",
Message: "Some images are not available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/images.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Error: "connection refused"},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsFail: true,
Message: "Some images are not available",
},
},
},
{
name: "custom collector name used in path",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
CollectorName: "my-registry",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "missing == 0",
Message: "All images are available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/my-registry.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsPass: true,
Message: "All images are available",
},
},
},
{
name: "return error when collection data missing",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "missing == 0",
Message: "All images are available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
},
},
expectedError: "file not found",
},
{
name: "template rendering with NotFound list",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "missing > 0",
Message: "Missing: {{ .Missing | join \", \" }}",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/images.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: false},
"registry.example.com/app:v2": {Exists: true},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsFail: true,
Message: "Missing: registry.example.com/app:v1",
},
},
},
{
name: "template rendering with NotFoundReasons map",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "errors > 0",
Message: `{{ range $image, $reason := .UnverifiedReasons }}{{ $image }}: {{ $reason }}; {{ end }}`,
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/images.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Error: "connection refused"},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsFail: true,
Message: "registry.example.com/app:v1: connection refused; ",
},
},
},
{
name: "template rendering with Found count",
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "missing == 0",
Message: "All {{ len .Verified }} images are available",
},
},
},
},
getCollectedFileContents: func(path string) ([]byte, error) {
if path == "host-collectors/registry-images/images.json" {
return json.Marshal(collect.RegistryInfo{
Images: map[string]collect.RegistryImage{
"registry.example.com/app:v1": {Exists: true},
"registry.example.com/app:v2": {Exists: true},
},
})
}
return nil, errors.New("file not found")
},
expectedResults: []*AnalyzeResult{
{
Title: "Registry Images",
IsPass: true,
Message: "All 2 images are available",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
a := &AnalyzeHostRegistryImages{
hostAnalyzer: test.hostAnalyzer,
}
results, err := a.Analyze(test.getCollectedFileContents, nil)
if test.expectedError != "" {
req.ErrorContains(err, test.expectedError)
} else {
req.NoError(err)
}
req.Equal(test.expectedResults, results)
})
}
}
func TestAnalyzeHostRegistryImagesTitle(t *testing.T) {
t.Run("default title", func(t *testing.T) {
a := &AnalyzeHostRegistryImages{
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{},
}
assert.Equal(t, "Registry Images", a.Title())
})
t.Run("custom title", func(t *testing.T) {
a := &AnalyzeHostRegistryImages{
hostAnalyzer: &troubleshootv1beta2.HostRegistryImagesAnalyze{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "My Registry Check",
},
},
}
assert.Equal(t, "My Registry Check", a.Title())
})
}
+20 -18
View File
@@ -17,24 +17,26 @@ import (
)
var Filemap = map[string]string{
"deployment": constants.CLUSTER_RESOURCES_DEPLOYMENTS,
"daemonset": constants.CLUSTER_RESOURCES_DAEMONSETS,
"statefulset": constants.CLUSTER_RESOURCES_STATEFULSETS,
"networkpolicy": constants.CLUSTER_RESOURCES_NETWORK_POLICY,
"pod": constants.CLUSTER_RESOURCES_PODS,
"ingress": constants.CLUSTER_RESOURCES_INGRESS,
"service": constants.CLUSTER_RESOURCES_SERVICES,
"resourcequota": constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
"job": constants.CLUSTER_RESOURCES_JOBS,
"persistentvolumeclaim": constants.CLUSTER_RESOURCES_PVCS,
"pvc": constants.CLUSTER_RESOURCES_PVCS,
"replicaset": constants.CLUSTER_RESOURCES_REPLICASETS,
"configmap": constants.CLUSTER_RESOURCES_CONFIGMAPS,
"namespace": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
"persistentvolume": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
"pv": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
"node": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
"storageclass": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
"deployment": constants.CLUSTER_RESOURCES_DEPLOYMENTS,
"daemonset": constants.CLUSTER_RESOURCES_DAEMONSETS,
"statefulset": constants.CLUSTER_RESOURCES_STATEFULSETS,
"networkpolicy": constants.CLUSTER_RESOURCES_NETWORK_POLICY,
"pod": constants.CLUSTER_RESOURCES_PODS,
"ingress": constants.CLUSTER_RESOURCES_INGRESS,
"service": constants.CLUSTER_RESOURCES_SERVICES,
"resourcequota": constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
"job": constants.CLUSTER_RESOURCES_JOBS,
"persistentvolumeclaim": constants.CLUSTER_RESOURCES_PVCS,
"pvc": constants.CLUSTER_RESOURCES_PVCS,
"replicaset": constants.CLUSTER_RESOURCES_REPLICASETS,
"configmap": constants.CLUSTER_RESOURCES_CONFIGMAPS,
"validatingwebhookconfiguration": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_VALIDATING_WEBHOOK_CONFIGURATIONS),
"mutatingwebhookconfiguration": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_MUTATING_WEBHOOK_CONFIGURATIONS),
"namespace": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
"persistentvolume": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
"pv": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
"node": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
"storageclass": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
}
type AnalyzeClusterResource struct {
+17 -1
View File
@@ -17,6 +17,7 @@ import (
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/types"
)
type AnalyzeNodeResources struct {
@@ -45,6 +46,9 @@ func (a *AnalyzeNodeResources) Analyze(getFile getCollectedFileContents, findFil
if err != nil {
return nil, err
}
if result == nil {
return nil, nil
}
result.Strict = a.analyzer.Strict.BoolOrDefaultFalse()
return []*AnalyzeResult{result}, nil
}
@@ -53,7 +57,19 @@ func (a *AnalyzeNodeResources) analyzeNodeResources(analyzer *troubleshootv1beta
collected, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_NODES))
if err != nil {
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
if _, ok := err.(*types.NotFoundError); !ok {
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
}
if analyzer.IgnoreIfNoFiles {
return nil, nil
}
return &AnalyzeResult{
Title: a.Title(),
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
IsWarn: true,
Message: "No node resources were collected, unable to analyze node resources",
}, nil
}
var nodes corev1.NodeList
+55
View File
@@ -1,6 +1,7 @@
package analyzer
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
@@ -10,6 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/types"
)
func Test_compareNodeResourceConditionalToActual(t *testing.T) {
@@ -1684,3 +1686,56 @@ func Test_analyzeNodeResources(t *testing.T) {
})
}
}
func Test_analyzeNodeResources_NoFiles(t *testing.T) {
missingFile := func(name string) ([]byte, error) {
return nil, &types.NotFoundError{Name: name}
}
t.Run("emits warning when nodes.json is not collected", func(t *testing.T) {
req := require.New(t)
analyzer := &troubleshootv1beta2.NodeResources{
Outcomes: []*troubleshootv1beta2.Outcome{
{Pass: &troubleshootv1beta2.SingleOutcome{Message: "ok"}},
},
}
a := AnalyzeNodeResources{analyzer: analyzer}
got, err := a.Analyze(missingFile, nil)
req.NoError(err)
req.Len(got, 1)
req.True(got[0].IsWarn)
req.Equal("Node Resources", got[0].Title)
})
t.Run("ignoreIfNoFiles suppresses the warning", func(t *testing.T) {
req := require.New(t)
analyzer := &troubleshootv1beta2.NodeResources{
IgnoreIfNoFiles: true,
Outcomes: []*troubleshootv1beta2.Outcome{
{Pass: &troubleshootv1beta2.SingleOutcome{Message: "ok"}},
},
}
a := AnalyzeNodeResources{analyzer: analyzer}
got, err := a.Analyze(missingFile, nil)
req.NoError(err)
req.Empty(got)
})
t.Run("non-NotFound errors are propagated", func(t *testing.T) {
req := require.New(t)
analyzer := &troubleshootv1beta2.NodeResources{
IgnoreIfNoFiles: true,
Outcomes: []*troubleshootv1beta2.Outcome{
{Pass: &troubleshootv1beta2.SingleOutcome{Message: "ok"}},
},
}
ioErr := func(string) ([]byte, error) {
return nil, fmt.Errorf("permission denied")
}
a := AnalyzeNodeResources{analyzer: analyzer}
got, err := a.Analyze(ioErr, nil)
req.Error(err)
req.Nil(got)
req.Contains(err.Error(), "permission denied")
})
}
+139
View File
@@ -0,0 +1,139 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
type AnalyzeS3Status struct {
analyzer *troubleshootv1beta2.DatabaseAnalyze
}
func (a *AnalyzeS3Status) Title() string {
title := a.analyzer.CheckName
if title == "" {
title = a.collectorName()
}
return title
}
func (a *AnalyzeS3Status) IsExcluded() (bool, error) {
return isExcluded(a.analyzer.Exclude)
}
func (a *AnalyzeS3Status) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
result, err := a.analyzeS3Status(a.analyzer, getFile)
if err != nil {
return nil, err
}
result.Strict = a.analyzer.Strict.BoolOrDefaultFalse()
return []*AnalyzeResult{result}, nil
}
func (a *AnalyzeS3Status) collectorName() string {
collectorName := a.analyzer.CollectorName
if collectorName == "" {
collectorName = "s3Status"
}
return collectorName
}
func (a *AnalyzeS3Status) analyzeS3Status(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
fullPath := path.Join("s3Status", fmt.Sprintf("%s.json", a.collectorName()))
collected, err := getCollectedFileContents(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
}
databaseConnection := collect.DatabaseConnection{}
if err := json.Unmarshal(collected, &databaseConnection); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal s3 status result")
}
result := &AnalyzeResult{
Title: a.Title(),
IconKey: "kubernetes_s3_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/s3-analyze.svg",
}
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return result, nil
}
isMatch, err := compareDatabaseConditionalToActual(outcome.Fail.When, &databaseConnection)
if err != nil {
return result, errors.Wrap(err, "failed to compare s3 status conditional")
}
if isMatch {
result.IsFail = true
if databaseConnection.Error != "" {
result.Message = outcome.Fail.Message + " " + databaseConnection.Error
} else {
result.Message = outcome.Fail.Message
}
result.URI = outcome.Fail.URI
return result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return result, nil
}
isMatch, err := compareDatabaseConditionalToActual(outcome.Warn.When, &databaseConnection)
if err != nil {
return result, errors.Wrap(err, "failed to compare s3 status conditional")
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
isMatch, err := compareDatabaseConditionalToActual(outcome.Pass.When, &databaseConnection)
if err != nil {
return result, errors.Wrap(err, "failed to compare s3 status conditional")
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return result, nil
}
+175
View File
@@ -0,0 +1,175 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeS3Status(t *testing.T) {
tests := []struct {
name string
analyzer *troubleshootv1beta2.DatabaseAnalyze
collected *collect.DatabaseConnection
wantPass bool
wantFail bool
wantWarn bool
wantMessage string
}{
{
name: "connected, pass",
analyzer: &troubleshootv1beta2.DatabaseAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "connected == false",
Message: "Cannot access the S3 bucket.",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "connected == true",
Message: "S3 bucket is accessible.",
},
},
},
},
collected: &collect.DatabaseConnection{
IsConnected: true,
},
wantPass: true,
wantMessage: "S3 bucket is accessible.",
},
{
name: "not connected, fail with error appended",
analyzer: &troubleshootv1beta2.DatabaseAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "connected == false",
Message: "Cannot access the S3 bucket.",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "connected == true",
Message: "S3 bucket is accessible.",
},
},
},
},
collected: &collect.DatabaseConnection{
IsConnected: false,
Error: "operation error S3: HeadBucket, StatusCode: 403",
},
wantFail: true,
wantMessage: "Cannot access the S3 bucket. operation error S3: HeadBucket, StatusCode: 403",
},
{
name: "not connected, fail without error",
analyzer: &troubleshootv1beta2.DatabaseAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "connected == false",
Message: "Cannot access the S3 bucket.",
},
},
},
},
collected: &collect.DatabaseConnection{
IsConnected: false,
},
wantFail: true,
wantMessage: "Cannot access the S3 bucket.",
},
{
name: "warn outcome",
analyzer: &troubleshootv1beta2.DatabaseAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "connected == false",
Message: "S3 bucket may be inaccessible.",
},
},
},
},
collected: &collect.DatabaseConnection{
IsConnected: false,
},
wantWarn: true,
wantMessage: "S3 bucket may be inaccessible.",
},
{
name: "unconditional fail",
analyzer: &troubleshootv1beta2.DatabaseAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "Always fails.",
},
},
},
},
collected: &collect.DatabaseConnection{
IsConnected: true,
},
wantFail: true,
wantMessage: "Always fails.",
},
{
name: "custom collector name",
analyzer: &troubleshootv1beta2.DatabaseAnalyze{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "My S3 Check",
},
CollectorName: "my-bucket",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "connected == true",
Message: "Bucket OK.",
},
},
},
},
collected: &collect.DatabaseConnection{
IsConnected: true,
},
wantPass: true,
wantMessage: "Bucket OK.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
collectedData, err := json.Marshal(tt.collected)
require.NoError(t, err)
a := &AnalyzeS3Status{analyzer: tt.analyzer}
getFile := func(path string) ([]byte, error) {
return collectedData, nil
}
results, err := a.Analyze(getFile, nil)
require.NoError(t, err)
require.Len(t, results, 1)
result := results[0]
assert.Equal(t, tt.wantPass, result.IsPass)
assert.Equal(t, tt.wantFail, result.IsFail)
assert.Equal(t, tt.wantWarn, result.IsWarn)
assert.Equal(t, tt.wantMessage, result.Message)
if tt.analyzer.CheckName != "" {
assert.Equal(t, tt.analyzer.CheckName, result.Title)
}
})
}
}
@@ -261,7 +261,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: pickNamespaceOrDefault(c.Exec.Namespace, overrideNS),
Verb: "get",
Verb: "create",
Group: "",
Version: "",
Resource: "pods",
@@ -286,7 +286,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: pickNamespaceOrDefault(c.Copy.Namespace, overrideNS),
Verb: "get",
Verb: "create",
Group: "",
Version: "",
Resource: "pods",
@@ -130,9 +130,10 @@ type Distribution struct {
}
type NodeResources struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Filters *NodeResourceFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Filters *NodeResourceFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
IgnoreIfNoFiles bool `json:"ignoreIfNoFiles,omitempty" yaml:"ignoreIfNoFiles,omitempty"`
}
type NodeResourceFilters struct {
@@ -316,4 +317,5 @@ type Analyze struct {
Event *EventAnalyze `json:"event,omitempty" yaml:"event,omitempty"`
NodeMetrics *NodeMetricsAnalyze `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"`
HTTP *HTTPAnalyze `json:"http,omitempty" yaml:"http,omitempty"`
S3Status *DatabaseAnalyze `json:"s3Status,omitempty" yaml:"s3Status,omitempty"`
}
@@ -323,6 +323,17 @@ type SupportBundleMetadata struct {
Namespace string `json:"namespace" yaml:"namespace"`
}
type S3Status struct {
CollectorMeta `json:",inline" yaml:",inline"`
BucketName string `json:"bucketName" yaml:"bucketName"`
Region string `json:"region,omitempty" yaml:"region,omitempty"`
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
AccessKeyID string `json:"accessKeyID,omitempty" yaml:"accessKeyID,omitempty"`
SecretAccessKey string `json:"secretAccessKey,omitempty" yaml:"secretAccessKey,omitempty"`
UsePathStyle bool `json:"usePathStyle,omitempty" yaml:"usePathStyle,omitempty"`
Insecure bool `json:"insecure,omitempty" yaml:"insecure,omitempty"`
}
type Collect struct {
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
@@ -355,6 +366,7 @@ type Collect struct {
DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
Etcd *Etcd `json:"etcd,omitempty" yaml:"etcd,omitempty"`
SupportBundleMetadata *SupportBundleMetadata `json:"supportBundleMetadata,omitempty" yaml:"supportBundleMetadata,omitempty"`
S3Status *S3Status `json:"s3Status,omitempty" yaml:"s3Status,omitempty"`
}
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
@@ -517,7 +529,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: pickNamespaceOrDefault(c.Exec.Namespace, overrideNS),
Verb: "get",
Verb: "create",
Group: "",
Version: "",
Resource: "pods",
@@ -542,7 +554,7 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: pickNamespaceOrDefault(c.Copy.Namespace, overrideNS),
Verb: "get",
Verb: "create",
Group: "",
Version: "",
Resource: "pods",
@@ -587,6 +599,8 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
},
NonResourceAttributes: nil,
})
} else if c.S3Status != nil {
// NOOP
}
return result
@@ -694,6 +708,10 @@ func (c *Collect) GetName() string {
collector = "support-bundle-metadata"
name = c.SupportBundleMetadata.CollectorName
}
if c.S3Status != nil {
collector = "s3Status"
name = c.S3Status.CollectorName
}
if collector == "" {
return "<none>"
@@ -60,12 +60,16 @@ type TimeAnalyze struct {
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
// BlockDevicesAnalyze evaluates host-collected block device listings (lsblk-based).
type BlockDevicesAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
MinimumAcceptableSize uint64 `json:"minimumAcceptableSize" yaml:"minimumAcceptableSize"`
IncludeUnmountedPartitions bool `json:"includeUnmountedPartitions" yaml:"includeUnmountedPartitions"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
MinimumAcceptableSize uint64 `json:"minimumAcceptableSize" yaml:"minimumAcceptableSize"`
IncludeUnmountedPartitions bool `json:"includeUnmountedPartitions" yaml:"includeUnmountedPartitions"`
// AdditionalDeviceTypes are extra lsblk TYPE values (e.g. loop, lvm) that may count toward outcomes,
// in addition to whole disks and (when IncludeUnmountedPartitions is set) partitions.
AdditionalDeviceTypes []string `json:"additionalDeviceTypes,omitempty" yaml:"additionalDeviceTypes,omitempty"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type SystemPackagesAnalyze struct {
@@ -149,6 +153,12 @@ type HostSysctlAnalyze struct {
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type HostRegistryImagesAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type HostAnalyze struct {
CPU *CPUAnalyze `json:"cpu,omitempty" yaml:"cpu,omitempty"`
TCPLoadBalancer *TCPLoadBalancerAnalyze `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
@@ -176,4 +186,5 @@ type HostAnalyze struct {
JsonCompare *JsonCompare `json:"jsonCompare,omitempty" yaml:"jsonCompare,omitempty"`
NetworkNamespaceConnectivity *NetworkNamespaceConnectivityAnalyze `json:"networkNamespaceConnectivity,omitempty" yaml:"networkNamespaceConnectivity,omitempty"`
Sysctl *HostSysctlAnalyze `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
RegistryImages *HostRegistryImagesAnalyze `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
}
@@ -235,6 +235,16 @@ type HostSysctl struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}
// HostRegistryImages checks whether images are accessible from the host,
// without requiring a Kubernetes cluster. Auth can be supplied inline via
// Username/Password or omitted to rely on ambient credentials (e.g. ~/.docker/config.json).
type HostRegistryImages struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
Images []string `json:"images" yaml:"images"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
}
type HostCollect struct {
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
@@ -265,6 +275,7 @@ type HostCollect struct {
HostDNS *HostDNS `json:"dns,omitempty" yaml:"dns,omitempty"`
NetworkNamespaceConnectivity *HostNetworkNamespaceConnectivity `json:"networkNamespaceConnectivity,omitempty" yaml:"networkNamespaceConnectivity,omitempty"`
HostSysctl *HostSysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
RegistryImages *HostRegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
}
// GetName gets the name of the collector
@@ -235,6 +235,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(HTTPAnalyze)
(*in).DeepCopyInto(*out)
}
if in.S3Status != nil {
in, out := &in.S3Status, &out.S3Status
*out = new(DatabaseAnalyze)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
@@ -448,6 +453,11 @@ func (in *AnalyzerStatus) DeepCopy() *AnalyzerStatus {
func (in *BlockDevicesAnalyze) DeepCopyInto(out *BlockDevicesAnalyze) {
*out = *in
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
if in.AdditionalDeviceTypes != nil {
in, out := &in.AdditionalDeviceTypes, &out.AdditionalDeviceTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
@@ -990,6 +1000,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
*out = new(SupportBundleMetadata)
(*in).DeepCopyInto(*out)
}
if in.S3Status != nil {
in, out := &in.S3Status, &out.S3Status
*out = new(S3Status)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
@@ -1984,6 +1999,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
*out = new(HostSysctlAnalyze)
(*in).DeepCopyInto(*out)
}
if in.RegistryImages != nil {
in, out := &in.RegistryImages, &out.RegistryImages
*out = new(HostRegistryImagesAnalyze)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
@@ -2224,6 +2244,11 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
*out = new(HostSysctl)
(*in).DeepCopyInto(*out)
}
if in.RegistryImages != nil {
in, out := &in.RegistryImages, &out.RegistryImages
*out = new(HostRegistryImages)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
@@ -2669,6 +2694,54 @@ func (in *HostPreflightStatus) DeepCopy() *HostPreflightStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostRegistryImages) DeepCopyInto(out *HostRegistryImages) {
*out = *in
in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta)
if in.Images != nil {
in, out := &in.Images, &out.Images
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostRegistryImages.
func (in *HostRegistryImages) DeepCopy() *HostRegistryImages {
if in == nil {
return nil
}
out := new(HostRegistryImages)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostRegistryImagesAnalyze) DeepCopyInto(out *HostRegistryImagesAnalyze) {
*out = *in
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostRegistryImagesAnalyze.
func (in *HostRegistryImagesAnalyze) DeepCopy() *HostRegistryImagesAnalyze {
if in == nil {
return nil
}
out := new(HostRegistryImagesAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostRun) DeepCopyInto(out *HostRun) {
*out = *in
@@ -4708,6 +4781,22 @@ func (in *RunPod) DeepCopy() *RunPod {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *S3Status) DeepCopyInto(out *S3Status) {
*out = *in
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new S3Status.
func (in *S3Status) DeepCopy() *S3Status {
if in == nil {
return nil
}
out := new(S3Status)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Secret) DeepCopyInto(out *Secret) {
*out = *in
+60
View File
@@ -411,6 +411,16 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CONFIGMAPS)), marshalErrors(configMapsErrors))
// Validating Webhook Configurations
validatingWebhookConfigurations, validatingWebhookConfigurationsErrors := validatingWebhookConfigurations(ctx, client)
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_VALIDATING_WEBHOOK_CONFIGURATIONS)), bytes.NewBuffer(validatingWebhookConfigurations))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_VALIDATING_WEBHOOK_CONFIGURATIONS)), marshalErrors(validatingWebhookConfigurationsErrors))
// Mutating Webhook Configurations
mutatingWebhookConfigurations, mutatingWebhookConfigurationsErrors := mutatingWebhookConfigurations(ctx, client)
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_MUTATING_WEBHOOK_CONFIGURATIONS)), bytes.NewBuffer(mutatingWebhookConfigurations))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_MUTATING_WEBHOOK_CONFIGURATIONS)), marshalErrors(mutatingWebhookConfigurationsErrors))
// Replicated License
licenseData, licenseErr := replicatedLicense(ctx, client, namespaceNames)
if licenseErr == nil {
@@ -2234,6 +2244,56 @@ func configMaps(ctx context.Context, client kubernetes.Interface, namespaces []s
return configmapByNamespace, errorsByNamespace
}
func validatingWebhookConfigurations(ctx context.Context, client kubernetes.Interface) ([]byte, []string) {
validatingWebhookConfigurations, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
gvk, err := apiutil.GVKForObject(validatingWebhookConfigurations, scheme.Scheme)
if err == nil {
validatingWebhookConfigurations.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range validatingWebhookConfigurations.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
validatingWebhookConfigurations.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(validatingWebhookConfigurations, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
func mutatingWebhookConfigurations(ctx context.Context, client kubernetes.Interface) ([]byte, []string) {
mutatingWebhookConfigurations, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
gvk, err := apiutil.GVKForObject(mutatingWebhookConfigurations, scheme.Scheme)
if err == nil {
mutatingWebhookConfigurations.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range mutatingWebhookConfigurations.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
mutatingWebhookConfigurations.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(mutatingWebhookConfigurations, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
// storeCustomResource stores a custom resource as JSON and YAML
// We use both formats for backwards compatibility. This way we
// avoid breaking existing tools and analysers that already rely on
+165
View File
@@ -12,6 +12,7 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
certificatesv1 "k8s.io/api/certificates/v1"
v1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
@@ -776,3 +777,167 @@ func createTestCertificateSigningRequests(client kubernetes.Interface, csrNames
}
return nil
}
func Test_ValidatingWebhookConfigurations(t *testing.T) {
tests := []struct {
name string
vwcNames []string
}{
{
name: "single validating webhook configuration",
vwcNames: []string{"test-vwc"},
},
{
name: "multiple validating webhook configurations",
vwcNames: []string{"vwc-1", "vwc-2", "vwc-3"},
},
{
name: "empty list",
vwcNames: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := testclient.NewSimpleClientset()
ctx := context.Background()
err := createTestValidatingWebhookConfigurations(client, tt.vwcNames)
require.NoError(t, err)
data, errs := validatingWebhookConfigurations(ctx, client)
assert.Empty(t, errs)
assert.NotNil(t, data)
var list admissionregistrationv1.ValidatingWebhookConfigurationList
err = json.Unmarshal(data, &list)
require.NoError(t, err)
assert.Len(t, list.Items, len(tt.vwcNames))
for _, item := range list.Items {
assert.Contains(t, tt.vwcNames, item.Name)
}
})
}
}
func Test_ValidatingWebhookConfigurations_PermissionDenied(t *testing.T) {
client := testclient.NewSimpleClientset()
ctx := context.Background()
client.PrependReactor("list", "validatingwebhookconfigurations", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("validatingwebhookconfigurations.admissionregistration.k8s.io is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"validatingwebhookconfigurations\" in API group \"admissionregistration.k8s.io\" at the cluster scope")
})
data, errs := validatingWebhookConfigurations(ctx, client)
assert.Nil(t, data)
require.NotEmpty(t, errs)
assert.Len(t, errs, 1)
assert.Contains(t, errs[0], "forbidden")
}
func createTestValidatingWebhookConfigurations(client kubernetes.Interface, names []string) error {
for _, name := range names {
_, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.Background(), &admissionregistrationv1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1.ValidatingWebhook{
{
Name: "test-webhook.example.com",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
Service: &admissionregistrationv1.ServiceReference{
Namespace: "default",
Name: "webhook-service",
},
},
AdmissionReviewVersions: []string{"v1"},
},
},
}, metav1.CreateOptions{})
if err != nil {
return err
}
}
return nil
}
func Test_MutatingWebhookConfigurations(t *testing.T) {
tests := []struct {
name string
mwcNames []string
}{
{
name: "single mutating webhook configuration",
mwcNames: []string{"test-mwc"},
},
{
name: "multiple mutating webhook configurations",
mwcNames: []string{"mwc-1", "mwc-2"},
},
{
name: "empty list",
mwcNames: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := testclient.NewSimpleClientset()
ctx := context.Background()
err := createTestMutatingWebhookConfigurations(client, tt.mwcNames)
require.NoError(t, err)
data, errs := mutatingWebhookConfigurations(ctx, client)
assert.Empty(t, errs)
assert.NotNil(t, data)
var list admissionregistrationv1.MutatingWebhookConfigurationList
err = json.Unmarshal(data, &list)
require.NoError(t, err)
assert.Len(t, list.Items, len(tt.mwcNames))
for _, item := range list.Items {
assert.Contains(t, tt.mwcNames, item.Name)
}
})
}
}
func Test_MutatingWebhookConfigurations_PermissionDenied(t *testing.T) {
client := testclient.NewSimpleClientset()
ctx := context.Background()
client.PrependReactor("list", "mutatingwebhookconfigurations", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("mutatingwebhookconfigurations.admissionregistration.k8s.io is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"mutatingwebhookconfigurations\" in API group \"admissionregistration.k8s.io\" at the cluster scope")
})
data, errs := mutatingWebhookConfigurations(ctx, client)
assert.Nil(t, data)
require.NotEmpty(t, errs)
assert.Len(t, errs, 1)
assert.Contains(t, errs[0], "forbidden")
}
func createTestMutatingWebhookConfigurations(client kubernetes.Interface, names []string) error {
for _, name := range names {
_, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.Background(), &admissionregistrationv1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1.MutatingWebhook{
{
Name: "test-mutating-webhook.example.com",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
Service: &admissionregistrationv1.ServiceReference{
Namespace: "default",
Name: "webhook-service",
},
},
AdmissionReviewVersions: []string{"v1"},
},
},
}, metav1.CreateOptions{})
if err != nil {
return err
}
}
return nil
}
+37 -1
View File
@@ -1,9 +1,11 @@
package collect
import (
"bytes"
"context"
"encoding/json"
"fmt"
"maps"
"strconv"
"strings"
"time"
@@ -13,6 +15,7 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/multitype"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
)
type Collector interface {
@@ -130,6 +133,8 @@ func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, nam
return &CollectEtcd{collector.Etcd, bundlePath, clientConfig, client, ctx, RBACErrors}, true
case collector.SupportBundleMetadata != nil:
return &CollectSupportBundleMetadata{collector.SupportBundleMetadata, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.S3Status != nil:
return &CollectS3Status{collector.S3Status, bundlePath, RBACErrors}, true
default:
return nil, false
}
@@ -228,10 +233,12 @@ func getCollectorName(c interface{}) string {
case *CollectSupportBundleMetadata:
collector = "support-bundle-metadata"
name = v.Collector.CollectorName
case *CollectS3Status:
collector = "s3Status"
name = v.Collector.CollectorName
default:
collector = "<none>"
}
if name != "" {
return fmt.Sprintf("%s/%s", collector, name)
}
@@ -292,6 +299,35 @@ func DedupCollectors(allCollectors []*troubleshootv1beta2.Collect) []*troublesho
return finalCollectors
}
// SkippedCollector records information about a collector that was skipped during collection.
type SkippedCollector struct {
Collector string `json:"collector"`
Reason string `json:"reason"`
Errors []string `json:"errors"`
Timestamp string `json:"timestamp"`
}
// WriteSkippedCollectors marshals the skipped collectors list and saves it
// using SaveResult which handles both in-memory and on-disk storage.
func WriteSkippedCollectors(skipped []SkippedCollector, allCollectedData map[string][]byte, bundlePath string) {
if len(skipped) == 0 {
return
}
skippedJSON, err := json.Marshal(skipped)
if err != nil {
return
}
// Either write to bundle path or memory
c := CollectorResult{}
if err := c.SaveResult(bundlePath, "skipped-collectors.json", bytes.NewReader(skippedJSON)); err != nil {
klog.Errorf("Failed to save skipped collectors: %v", err)
} else {
// Write to collected data to return downstream
maps.Copy(allCollectedData, c)
}
}
// Ensure Copy collectors are last in the list
// This is because copy collectors are expected to copy files from other collectors such as Exec, RunPod, RunDaemonSet
func EnsureCopyLast(allCollectors []Collector) []Collector {
+88
View File
@@ -1,6 +1,9 @@
package collect
import (
"encoding/json"
"os"
"path/filepath"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
@@ -519,3 +522,88 @@ func TestEnsureCopyLast(t *testing.T) {
})
}
}
func TestWriteSkippedCollectors(t *testing.T) {
tests := []struct {
name string
skipped []SkippedCollector
bundlePath string
useTempDir bool
wantInMap bool
wantOnDisk bool
wantEntries []SkippedCollector
}{
{
name: "empty skipped list does nothing",
skipped: nil,
bundlePath: "",
wantInMap: false,
},
{
name: "in-memory only when bundlePath is empty",
skipped: []SkippedCollector{
{Collector: "clusterResources", Reason: "excluded", Timestamp: "2026-01-01T00:00:00Z"},
},
bundlePath: "",
wantInMap: true,
wantOnDisk: false,
wantEntries: []SkippedCollector{
{Collector: "clusterResources", Reason: "excluded", Timestamp: "2026-01-01T00:00:00Z"},
},
},
{
name: "writes to disk when bundlePath is set",
skipped: []SkippedCollector{
{Collector: "clusterResources", Reason: "excluded", Timestamp: "2026-01-01T00:00:00Z"},
{Collector: "logs", Reason: "insufficient RBAC permissions", Errors: []string{"pods is forbidden"}, Timestamp: "2026-01-01T00:00:01Z"},
},
useTempDir: true,
wantInMap: true,
wantOnDisk: true,
wantEntries: []SkippedCollector{
{Collector: "clusterResources", Reason: "excluded", Timestamp: "2026-01-01T00:00:00Z"},
{Collector: "logs", Reason: "insufficient RBAC permissions", Errors: []string{"pods is forbidden"}, Timestamp: "2026-01-01T00:00:01Z"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bundlePath := tt.bundlePath
if tt.useTempDir {
bundlePath = t.TempDir()
}
result := CollectorResult{}
WriteSkippedCollectors(tt.skipped, result, bundlePath)
if !tt.wantInMap {
assert.Empty(t, result)
return
}
// Verify in-memory entry exists
if bundlePath == "" {
// In-memory mode: data is stored in the map
data, ok := result["skipped-collectors.json"]
require.True(t, ok, "skipped-collectors.json should be in result map")
require.NotNil(t, data)
var got []SkippedCollector
require.NoError(t, json.Unmarshal(data, &got))
assert.Equal(t, tt.wantEntries, got)
} else {
// On-disk mode: map entry exists with nil value, file is on disk
_, ok := result["skipped-collectors.json"]
require.True(t, ok, "skipped-collectors.json should be in result map")
diskData, err := os.ReadFile(filepath.Join(bundlePath, "skipped-collectors.json"))
require.NoError(t, err)
var got []SkippedCollector
require.NoError(t, json.Unmarshal(diskData, &got))
assert.Equal(t, tt.wantEntries, got)
}
})
}
}
+8 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
@@ -97,19 +98,22 @@ func copyFilesFromPod(ctx context.Context, dstPath string, clientConfig *restcli
return nil, nil, errors.Wrap(err, "failed to add runtime scheme")
}
// Stdin must be false because StreamOptions.Stdin is nil below.
// A mismatch causes the SPDY fallback (after WebSocket fails on RBAC)
// to hang: the API server opens a stdin stream but never receives EOF.
parameterCodec := runtime.NewParameterCodec(scheme)
req.VersionedParams(&corev1.PodExecOptions{
Command: command,
Container: containerName,
Stdin: true,
Stdout: false,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
exec, err := k8sutil.NewFallbackExecutor(clientConfig, req.URL())
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create SPDY executor")
return nil, nil, errors.Wrap(err, "failed to create executor")
}
result := NewResult()
+7 -4
View File
@@ -299,19 +299,22 @@ func copyFilesFromHost(ctx context.Context, dstPath string, clientConfig *restcl
return nil, nil, errors.Wrap(err, "failed to add runtime scheme")
}
// Stdin must be false because StreamOptions.Stdin is nil below.
// A mismatch causes the SPDY fallback (after WebSocket fails on RBAC)
// to hang: the API server opens a stdin stream but never receives EOF.
parameterCodec := runtime.NewParameterCodec(scheme)
req.VersionedParams(&corev1.PodExecOptions{
Command: command,
Container: containerName,
Stdin: true,
Stdout: false,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
exec, err := k8sutil.NewFallbackExecutor(clientConfig, req.URL())
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create SPDY executor")
return nil, nil, errors.Wrap(err, "failed to create executor")
}
result := NewResult()
+2 -1
View File
@@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
@@ -325,7 +326,7 @@ func (c *etcdDebug) executeCommand(command string) ([]byte, []byte, error) {
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(c.clientConfig, "POST", req.URL())
exec, err := k8sutil.NewFallbackExecutor(c.clientConfig, req.URL())
if err != nil {
return nil, nil, err
}
+20 -6
View File
@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
@@ -87,16 +88,26 @@ func execWithoutTimeout(clientConfig *rest.Config, bundlePath string, execCollec
pod := pods[0]
stdout, stderr, execErrors := getExecOutputs(ctx, clientConfig, client, pod, execCollector)
container := pod.Spec.Containers[0].Name
if execCollector.ContainerName != "" {
container = execCollector.ContainerName
}
filePrefix := execCollector.CollectorName
if filePrefix == "" {
filePrefix = container
}
path := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
if len(stdout) > 0 {
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
output.SaveResult(bundlePath, filepath.Join(path, filePrefix+"-stdout.txt"), bytes.NewBuffer(stdout))
}
if len(stderr) > 0 {
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
output.SaveResult(bundlePath, filepath.Join(path, filePrefix+"-stderr.txt"), bytes.NewBuffer(stderr))
}
if len(execErrors) > 0 {
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
output.SaveResult(bundlePath, filepath.Join(path, filePrefix+"-errors.json"), marshalErrors(execErrors))
}
}
@@ -118,16 +129,19 @@ func getExecOutputs(
}
parameterCodec := runtime.NewParameterCodec(scheme)
// Stdin must be false because StreamOptions.Stdin is nil below.
// A mismatch causes the SPDY fallback (after WebSocket fails on RBAC)
// to hang: the API server opens a stdin stream but never receives EOF.
req.VersionedParams(&corev1.PodExecOptions{
Command: append(execCollector.Command, execCollector.Args...),
Container: container,
Stdin: true,
Stdout: false,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
exec, err := k8sutil.NewFallbackExecutor(clientConfig, req.URL())
if err != nil {
return nil, nil, []string{err.Error()}
}
+2
View File
@@ -105,6 +105,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str
return &CollectHostNetworkNamespaceConnectivity{collector.NetworkNamespaceConnectivity, bundlePath}, true
case collector.HostSysctl != nil:
return &CollectHostSysctl{collector.HostSysctl, bundlePath}, true
case collector.RegistryImages != nil:
return &CollectHostRegistryImages{collector.RegistryImages, bundlePath}, true
default:
return nil, false
}
+90
View File
@@ -0,0 +1,90 @@
package collect
import (
"bytes"
"encoding/json"
"path/filepath"
"time"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/klog/v2"
)
type CollectHostRegistryImages struct {
hostCollector *troubleshootv1beta2.HostRegistryImages
BundlePath string
}
func (c *CollectHostRegistryImages) Title() string {
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Registry Images")
}
func (c *CollectHostRegistryImages) IsExcluded() (bool, error) {
return isExcluded(c.hostCollector.Exclude)
}
func (c *CollectHostRegistryImages) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
registryInfo := RegistryInfo{
Images: map[string]RegistryImage{},
}
auth := c.resolveAuth()
if auth != nil {
klog.V(2).Infof("using inline credentials for registry check (username=%s)", c.hostCollector.Username)
} else {
klog.V(2).Info("no inline credentials provided, using ambient auth")
}
klog.V(2).Infof("checking %d images", len(c.hostCollector.Images))
for _, image := range c.hostCollector.Images {
klog.V(2).Infof("checking image: %s", image)
imageRef, err := parseImageRef(image)
if err != nil {
klog.Errorf("failed to parse image ref %s: %v", image, err)
registryInfo.Images[image] = RegistryImage{Error: err.Error()}
continue
}
exists, err := imageExistsWithAuth(auth, imageRef, image, 10*time.Second)
if err != nil {
klog.Errorf("image check failed for %s: %v", image, err)
registryInfo.Images[image] = RegistryImage{Error: err.Error()}
} else {
klog.V(2).Infof("image %s exists=%t", image, exists)
registryInfo.Images[image] = RegistryImage{Exists: exists}
}
}
b, err := json.MarshalIndent(registryInfo, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal registry info")
}
collectorName := c.hostCollector.CollectorName
if collectorName == "" {
collectorName = "images"
}
name := filepath.Join("host-collectors/registry-images", collectorName+".json")
output := NewResult()
output.SaveResult(c.BundlePath, name, bytes.NewBuffer(b))
return output, nil
}
func (c *CollectHostRegistryImages) RemoteCollect(progressChan chan<- interface{}) (map[string][]byte, error) {
return nil, ErrRemoteCollectorNotImplemented
}
// resolveAuth returns auth config from inline credentials or nil for ambient auth.
func (c *CollectHostRegistryImages) resolveAuth() *registryAuthConfig {
if c.hostCollector.Username != "" {
return &registryAuthConfig{
username: c.hostCollector.Username,
password: c.hostCollector.Password,
}
}
// No credentials: rely on ambient auth (~/.docker/config.json)
return nil
}
+97
View File
@@ -0,0 +1,97 @@
package collect
import (
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCollectHostRegistryImagesTitle(t *testing.T) {
tests := []struct {
name string
meta troubleshootv1beta2.HostCollectorMeta
expected string
}{
{
name: "default title",
meta: troubleshootv1beta2.HostCollectorMeta{},
expected: "Registry Images",
},
{
name: "custom title",
meta: troubleshootv1beta2.HostCollectorMeta{
CollectorName: "My Registry",
},
expected: "My Registry",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := &CollectHostRegistryImages{
hostCollector: &troubleshootv1beta2.HostRegistryImages{
HostCollectorMeta: test.meta,
},
}
assert.Equal(t, test.expected, c.Title())
})
}
}
func TestCollectHostRegistryImagesResolveAuth(t *testing.T) {
tests := []struct {
name string
username string
password string
expected *registryAuthConfig
}{
{
name: "nil when no credentials",
username: "",
password: "",
expected: nil,
},
{
name: "returns auth with credentials",
username: "user",
password: "pass",
expected: &registryAuthConfig{
username: "user",
password: "pass",
},
},
{
name: "returns auth with username only",
username: "user",
password: "",
expected: &registryAuthConfig{
username: "user",
password: "",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := &CollectHostRegistryImages{
hostCollector: &troubleshootv1beta2.HostRegistryImages{
Username: test.username,
Password: test.password,
},
}
result := c.resolveAuth()
assert.Equal(t, test.expected, result)
})
}
}
func TestCollectHostRegistryImagesRemoteCollect(t *testing.T) {
c := &CollectHostRegistryImages{
hostCollector: &troubleshootv1beta2.HostRegistryImages{},
}
result, err := c.RemoteCollect(nil)
require.ErrorIs(t, err, ErrRemoteCollectorNotImplemented)
assert.Nil(t, result)
}
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
longhornv1beta1types "github.com/replicatedhq/troubleshoot/pkg/longhorn/apis/longhorn/v1beta1"
longhornv1beta1 "github.com/replicatedhq/troubleshoot/pkg/longhorn/client/clientset/versioned/typed/longhorn/v1beta1"
longhorntypes "github.com/replicatedhq/troubleshoot/pkg/longhorn/types"
@@ -391,7 +392,7 @@ func GetLonghornReplicaChecksum(clientConfig *rest.Config, replica longhornv1bet
Param("command", "-c").
Param("command", fmt.Sprintf("if [ -d %s ]; then md5sum %s/*; fi", dir, dir))
executor, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
executor, err := k8sutil.NewFallbackExecutor(clientConfig, req.URL())
if err != nil {
return "", errors.Wrapf(err, "create remote exec")
}
+83 -55
View File
@@ -3,21 +3,22 @@ package collect
import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
stderrors "errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/distribution/distribution/v3/registry/api/errcode"
registryv2 "github.com/distribution/distribution/v3/registry/api/v2"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
imagedocker "go.podman.io/image/v5/docker"
dockerref "go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/image/v5/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@@ -91,9 +92,9 @@ func (c *CollectRegistry) Collect(progressChan chan<- interface{}) (CollectorRes
}
func imageExists(namespace string, clientConfig *rest.Config, registryCollector *troubleshootv1beta2.RegistryImages, image string, deadline time.Duration) (bool, error) {
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", image))
imageRef, err := parseImageRef(image)
if err != nil {
return false, errors.Wrapf(err, "failed to parse image name %s", image)
return false, err
}
authConfig, err := getImageAuthConfig(namespace, clientConfig, registryCollector, imageRef)
@@ -102,16 +103,29 @@ func imageExists(namespace string, clientConfig *rest.Config, registryCollector
return false, errors.Wrap(err, "failed to get auth config")
}
sysCtx := types.SystemContext{
DockerDisableV1Ping: true,
DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
return imageExistsWithAuth(authConfig, imageRef, image, deadline)
}
func parseImageRef(image string) (name.Reference, error) {
ref, err := name.ParseReference(image)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse image name %s", image)
}
if authConfig != nil {
sysCtx.DockerAuthConfig = &types.DockerAuthConfig{
Username: authConfig.username,
Password: authConfig.password,
}
return ref, nil
}
// imageExistsWithAuth checks if an image exists in a registry using optional auth credentials.
// authConfig may be nil for ambient credentials (e.g. ~/.docker/config.json).
// This is the shared core used by both the cluster-level and host-level registry collectors.
func imageExistsWithAuth(authConfig *registryAuthConfig, ref name.Reference, image string, deadline time.Duration) (bool, error) {
// remote.DefaultTransport includes Proxy (HTTP_PROXY/HTTPS_PROXY), dial/TLS
// timeouts, and keepalive; clone it so InsecureSkipVerify does not drop those.
defaultTR, ok := remote.DefaultTransport.(*http.Transport)
if !ok {
return false, errors.New("remote.DefaultTransport is not *http.Transport")
}
insecureTransport := defaultTR.Clone()
insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
if deadline == 0 {
deadline = 10 * time.Second
@@ -122,12 +136,30 @@ func imageExists(namespace string, clientConfig *rest.Config, registryCollector
err := func() error {
ctx, cancel := context.WithTimeout(context.Background(), deadline)
defer cancel()
remoteImage, err := imageRef.NewImage(ctx, &sysCtx)
if err != nil {
return err
opts := []remote.Option{
remote.WithContext(ctx),
remote.WithTransport(insecureTransport),
}
remoteImage.Close()
return nil
if authConfig != nil {
opts = append(opts, remote.WithAuth(&authn.Basic{
Username: authConfig.username,
Password: authConfig.password,
}))
} else {
opts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain))
}
// Use Get (not Head) so 404 responses include a JSON body; the registry
// API encodes MANIFEST_UNKNOWN vs NAME_UNKNOWN there, which we need to
// distinguish. Head 404s typically have no body, so *transport.Error has
// empty Errors and we cannot classify the failure.
//
// Get fetches the manifest (or list/index) for the tag or digest and does
// not pick a per-platform child image, so this checks presence only, not
// whether the image runs on a given architecture.
_, err := remote.Get(ref, opts...)
return err
}()
if err == nil {
klog.V(2).Infof("image %s exists", image)
@@ -136,18 +168,10 @@ func imageExists(namespace string, clientConfig *rest.Config, registryCollector
klog.Errorf("failed to get image %s: %v", image, err)
// if this is a context timeout, stop here so we dont run this check for too long
if errors.Is(err, context.DeadlineExceeded) {
if stderrors.Is(err, context.DeadlineExceeded) {
return false, errors.Wrap(err, "failed to get image manifest")
}
if strings.Contains(err.Error(), "no image found in manifest list for architecture") {
// manifest was downloaded, but no matching architecture found in manifest
// should this count as image does not exist?
// this binary's architecture is not necessarily what will run in the cluster
return true, nil
}
if isNotFound(err) {
return false, nil
}
@@ -164,7 +188,7 @@ func imageExists(namespace string, clientConfig *rest.Config, registryCollector
return false, errors.Wrap(lastErr, "failed to retry")
}
func getImageAuthConfig(namespace string, clientConfig *rest.Config, registryCollector *troubleshootv1beta2.RegistryImages, imageRef types.ImageReference) (*registryAuthConfig, error) {
func getImageAuthConfig(namespace string, clientConfig *rest.Config, registryCollector *troubleshootv1beta2.RegistryImages, imageRef name.Reference) (*registryAuthConfig, error) {
if registryCollector.ImagePullSecrets == nil {
return nil, nil
}
@@ -195,13 +219,13 @@ func getImageAuthConfig(namespace string, clientConfig *rest.Config, registryCol
return nil, errors.New("image pull secret spec is not valid")
}
func getImageAuthConfigFromData(imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets) (*registryAuthConfig, error) {
func getImageAuthConfigFromData(imageRef name.Reference, pullSecrets *v1beta2.ImagePullSecrets) (*registryAuthConfig, error) {
if pullSecrets.SecretType != "kubernetes.io/dockerconfigjson" {
return nil, errors.Errorf("secret type is not supported: %s", pullSecrets.SecretType)
}
configJsonBase64 := pullSecrets.Data[".dockerconfigjson"]
registry := dockerref.Domain(imageRef.DockerReference())
registry := imageRef.Context().RegistryStr()
configJson, err := base64.StdEncoding.DecodeString(configJsonBase64)
if err != nil {
@@ -222,8 +246,14 @@ func getImageAuthConfigFromData(imageRef types.ImageReference, pullSecrets *v1be
}
auth, ok := dockerCfgJSON.Auths[registry]
// go-containerregistry normalizes "docker.io" to "index.docker.io"
// (name.DefaultRegistry); many dockerconfigjson files key on "docker.io"
// instead. Fall back to the alias so existing user secrets keep working.
if !ok && registry == name.DefaultRegistry {
auth, ok = dockerCfgJSON.Auths["docker.io"]
}
if !ok {
// Suport a mix of public and private images
// Support a mix of public and private images
return nil, nil
}
@@ -258,7 +288,7 @@ func getImageAuthConfigFromData(imageRef types.ImageReference, pullSecrets *v1be
return &authConfig, nil
}
func getImageAuthConfigFromSecret(clientConfig *rest.Config, imageRef types.ImageReference, pullSecrets *v1beta2.ImagePullSecrets, namespace string) (*registryAuthConfig, error) {
func getImageAuthConfigFromSecret(clientConfig *rest.Config, imageRef name.Reference, pullSecrets *v1beta2.ImagePullSecrets, namespace string) (*registryAuthConfig, error) {
ctx := context.Background()
client, err := kubernetes.NewForConfig(clientConfig)
@@ -287,30 +317,28 @@ func getImageAuthConfigFromSecret(clientConfig *rest.Config, imageRef types.Imag
return config, nil
}
// isNotFound returns true only when the registry reports MANIFEST_UNKNOWN: the
// repository exists but the tag or digest has no manifest. A 404 with
// NAME_UNKNOWN (repository missing) is not "not found" in that sense; callers
// should see the error to diagnose a wrong image path. Unstructured 404s
// (e.g. empty body on HEAD) are not treated as a known-missing image.
func isNotFound(err error) bool {
switch err := err.(type) {
case errcode.Errors:
for _, e := range err {
if isNotFound(e) {
return true
}
}
return false
case errcode.Error:
return err.Message == registryv2.ErrorCodeManifestUnknown.Message()
}
// this type will cause panic when compared to error type
if _, ok := err.(imagedocker.ErrUnauthorizedForCredentials); ok {
var terr *transport.Error
if !stderrors.As(err, &terr) || terr.StatusCode != http.StatusNotFound {
return false
}
cause := errors.Cause(err)
if cause, ok := cause.(error); ok {
if cause == err {
if len(terr.Errors) == 0 {
return false
}
for _, d := range terr.Errors {
if d.Code == transport.NameUnknownErrorCode {
return false
}
}
return isNotFound(cause)
for _, d := range terr.Errors {
if d.Code == transport.ManifestUnknownErrorCode {
return true
}
}
return false
}
+140 -2
View File
@@ -5,15 +5,55 @@ import (
"encoding/base64"
"fmt"
"net"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/google/go-containerregistry/pkg/name"
"github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"go.podman.io/image/v5/transports/alltransports"
"k8s.io/client-go/rest"
)
// fakeRegistry is a minimal Docker Registry v2 TLS stand-in for unit testing
// imageExists. The handler returns whatever the caller puts in `manifest`
// for /v2/{name}/manifests/{ref}. /v2/ is always a 200.
//
// We use NewTLSServer so the same tests work against both the current
// containers/image implementation (DockerInsecureSkipTLSVerify) and the
// new go-containerregistry implementation (InsecureSkipVerify transport).
// Plain HTTP would break after Task 3 because go-containerregistry does not
// treat 127.0.0.1 as an insecure registry by default.
type fakeRegistry struct {
server *httptest.Server
manifest http.HandlerFunc
}
func newFakeRegistry(t *testing.T, manifest http.HandlerFunc) *fakeRegistry {
t.Helper()
mux := http.NewServeMux()
mux.HandleFunc("/v2/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v2/" || r.URL.Path == "/v2" {
w.Header().Set("Docker-Distribution-Api-Version", "registry/2.0")
w.WriteHeader(http.StatusOK)
return
}
manifest(w, r)
})
srv := httptest.NewTLSServer(mux)
t.Cleanup(srv.Close)
return &fakeRegistry{server: srv, manifest: manifest}
}
// hostPort strips "https://" from the test server URL, leaving "127.0.0.1:NNNN"
// suitable for use as the registry portion of an image reference.
func (f *fakeRegistry) hostPort() string {
return strings.TrimPrefix(f.server.URL, "https://")
}
func TestGetImageAuthConfigFromData(t *testing.T) {
tests := []struct {
name string
@@ -57,7 +97,7 @@ func TestGetImageAuthConfigFromData(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", test.imageName))
imageRef, err := name.ParseReference(test.imageName)
assert.NoError(t, err)
pullSecrets := &v1beta2.ImagePullSecrets{
@@ -119,3 +159,101 @@ func TestImageExists_ContextDeadlineExceeded(t *testing.T) {
assert.ErrorIs(t, err, context.DeadlineExceeded)
assert.False(t, exists)
}
func TestImageExists_Found(t *testing.T) {
fr := newFakeRegistry(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
w.Header().Set("Docker-Content-Digest", "sha256:1111111111111111111111111111111111111111111111111111111111111111")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","size":1,"digest":"sha256:2222222222222222222222222222222222222222222222222222222222222222"},"layers":[]}`))
})
collector := &v1beta2.RegistryImages{
Images: []string{fmt.Sprintf("%s/test:latest", fr.hostPort())},
}
exists, err := imageExists("default", &rest.Config{}, collector, fmt.Sprintf("%s/test:latest", fr.hostPort()), 5*time.Second)
assert.NoError(t, err)
assert.True(t, exists)
}
func TestImageExists_NotFound(t *testing.T) {
fr := newFakeRegistry(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"errors":[{"code":"MANIFEST_UNKNOWN","message":"manifest unknown"}]}`))
})
collector := &v1beta2.RegistryImages{
Images: []string{fmt.Sprintf("%s/test:latest", fr.hostPort())},
}
exists, err := imageExists("default", &rest.Config{}, collector, fmt.Sprintf("%s/test:latest", fr.hostPort()), 5*time.Second)
assert.NoError(t, err)
assert.False(t, exists)
}
func TestImageExists_NameUnknown_PropagatesError(t *testing.T) {
fr := newFakeRegistry(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"errors":[{"code":"NAME_UNKNOWN","message":"repository name not known to registry"}]}`))
})
collector := &v1beta2.RegistryImages{
Images: []string{fmt.Sprintf("%s/nonexistentrepo/image:latest", fr.hostPort())},
}
exists, err := imageExists("default", &rest.Config{}, collector, fmt.Sprintf("%s/nonexistentrepo/image:latest", fr.hostPort()), 5*time.Second)
assert.Error(t, err)
assert.False(t, exists)
}
func TestImageExists_Unauthorized(t *testing.T) {
fr := newFakeRegistry(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Www-Authenticate", `Basic realm="registry"`)
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}`))
})
collector := &v1beta2.RegistryImages{
Images: []string{fmt.Sprintf("%s/test:latest", fr.hostPort())},
}
exists, err := imageExists("default", &rest.Config{}, collector, fmt.Sprintf("%s/test:latest", fr.hostPort()), 5*time.Second)
assert.Error(t, err)
assert.False(t, exists)
}
func TestImageExists_RetriesOnEOF(t *testing.T) {
var attempts int32
fr := newFakeRegistry(t, func(w http.ResponseWriter, r *http.Request) {
n := atomic.AddInt32(&attempts, 1)
if n < 3 {
// hijack the connection and slam it shut to cause an EOF on the client
hj, ok := w.(http.Hijacker)
if !ok {
t.Fatalf("response writer does not support hijacking")
}
conn, _, err := hj.Hijack()
if err != nil {
t.Fatalf("hijack: %v", err)
}
_ = conn.Close()
return
}
w.Header().Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
w.Header().Set("Docker-Content-Digest", "sha256:1111111111111111111111111111111111111111111111111111111111111111")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","size":1,"digest":"sha256:2222222222222222222222222222222222222222222222222222222222222222"},"layers":[]}`))
})
collector := &v1beta2.RegistryImages{
Images: []string{fmt.Sprintf("%s/test:latest", fr.hostPort())},
}
exists, err := imageExists("default", &rest.Config{}, collector, fmt.Sprintf("%s/test:latest", fr.hostPort()), 5*time.Second)
assert.NoError(t, err)
assert.True(t, exists)
assert.GreaterOrEqual(t, atomic.LoadInt32(&attempts), int32(3))
}
+2 -3
View File
@@ -6,6 +6,7 @@ import (
"compress/gzip"
"fmt"
"io"
"maps"
"os"
"path"
"path/filepath"
@@ -87,9 +88,7 @@ func (r CollectorResult) SymLinkResult(bundlePath, relativeLinkPath, relativeFil
// It also ensures that when operating on the results in memory (e.g preflights),
// all files are included.
func (r CollectorResult) AddResult(other CollectorResult) {
for k, v := range other {
r[k] = v
}
maps.Copy(r, other)
}
// SaveResult saves the collector result to relativePath file on disk. If bundlePath is
+103
View File
@@ -0,0 +1,103 @@
package collect
import (
"bytes"
"context"
"encoding/json"
"fmt"
"time"
"crypto/tls"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"net/http"
)
type S3StatusResult struct {
BucketName string `json:"bucketName"`
Endpoint string `json:"endpoint,omitempty"`
Region string `json:"region,omitempty"`
IsConnected bool `json:"isConnected"`
Error string `json:"error,omitempty"`
}
type CollectS3Status struct {
Collector *troubleshootv1beta2.S3Status
BundlePath string
RBACErrors
}
func (c *CollectS3Status) Title() string {
return getCollectorName(c)
}
func (c *CollectS3Status) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectS3Status) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
result := S3StatusResult{
BucketName: c.Collector.BucketName,
Endpoint: c.Collector.Endpoint,
Region: c.Collector.Region,
}
region := c.Collector.Region
if region == "" {
region = "us-east-1"
}
opts := s3.Options{
Region: region,
Credentials: credentials.NewStaticCredentialsProvider(
c.Collector.AccessKeyID,
c.Collector.SecretAccessKey,
"",
),
UsePathStyle: c.Collector.UsePathStyle,
}
if c.Collector.Endpoint != "" {
opts.BaseEndpoint = aws.String(c.Collector.Endpoint)
}
if c.Collector.Insecure {
opts.HTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
}
client := s3.New(opts)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_, err := client.HeadBucket(ctx, &s3.HeadBucketInput{
Bucket: aws.String(c.Collector.BucketName),
})
if err != nil {
result.Error = err.Error()
} else {
result.IsConnected = true
}
b, err := json.Marshal(result)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal s3 status result")
}
collectorName := c.Collector.CollectorName
if collectorName == "" {
collectorName = "s3Status"
}
output := NewResult()
output.SaveResult(c.BundlePath, fmt.Sprintf("s3Status/%s.json", collectorName), bytes.NewBuffer(b))
return output, nil
}
+108
View File
@@ -0,0 +1,108 @@
package collect
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCollectS3Status_Collect(t *testing.T) {
tests := []struct {
name string
handler http.HandlerFunc
collectorName string
wantConnected bool
wantErrContains string
}{
{
name: "bucket exists",
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead && r.URL.Path == "/test-bucket" {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusNotFound)
}),
collectorName: "mybucket",
wantConnected: true,
},
{
name: "bucket not found",
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}),
collectorName: "mybucket",
wantConnected: false,
wantErrContains: "StatusCode: 404",
},
{
name: "access denied",
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}),
collectorName: "mybucket",
wantConnected: false,
wantErrContains: "StatusCode: 403",
},
{
name: "default collector name",
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}),
collectorName: "",
wantConnected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := httptest.NewServer(tt.handler)
defer ts.Close()
collector := &CollectS3Status{
Collector: &troubleshootv1beta2.S3Status{
CollectorMeta: troubleshootv1beta2.CollectorMeta{
CollectorName: tt.collectorName,
},
BucketName: "test-bucket",
Endpoint: ts.URL,
Region: "us-east-1",
AccessKeyID: "test-key",
SecretAccessKey: "test-secret",
UsePathStyle: true,
},
BundlePath: "",
}
result, err := collector.Collect(nil)
require.NoError(t, err)
expectedName := tt.collectorName
if expectedName == "" {
expectedName = "s3Status"
}
key := "s3Status/" + expectedName + ".json"
raw, ok := result[key]
require.True(t, ok, "expected key %s in result, got keys: %v", key, result)
var s3Result S3StatusResult
err = json.Unmarshal(raw, &s3Result)
require.NoError(t, err)
assert.Equal(t, tt.wantConnected, s3Result.IsConnected)
assert.Equal(t, "test-bucket", s3Result.BucketName)
if tt.wantErrContains != "" {
assert.Contains(t, s3Result.Error, tt.wantErrContains)
} else {
assert.Empty(t, s3Result.Error)
}
})
}
}
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -147,7 +148,7 @@ func sonobuoyRetrieveResults(
Stdout: true,
Stderr: false,
}, scheme.ParameterCodec)
executor, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
executor, err := k8sutil.NewFallbackExecutor(restConfig, req.URL())
if err != nil {
return nil, ec, err
}
+43 -41
View File
@@ -23,47 +23,49 @@ const (
ANALYSIS_FILENAME = "analysis.json"
// Cluster Resources Collector Directories
CLUSTER_RESOURCES_DIR = "cluster-resources"
CLUSTER_RESOURCES_NAMESPACES = "namespaces"
CLUSTER_RESOURCES_AUTH_CANI = "auth-cani-list"
CLUSTER_RESOURCES_PODS = "pods"
CLUSTER_RESOURCES_PODS_LOGS = "pods/logs"
CLUSTER_RESOURCES_POD_DISRUPTION_BUDGETS = "pod-disruption-budgets"
CLUSTER_RESOURCES_SERVICES = "services"
CLUSTER_RESOURCES_DEPLOYMENTS = "deployments"
CLUSTER_RESOURCES_REPLICASETS = "replicasets"
CLUSTER_RESOURCES_STATEFULSETS = "statefulsets"
CLUSTER_RESOURCES_DAEMONSETS = "daemonsets"
CLUSTER_RESOURCES_JOBS = "jobs"
CLUSTER_RESOURCES_CRONJOBS = "cronjobs"
CLUSTER_RESOURCES_INGRESS = "ingress"
CLUSTER_RESOURCES_NETWORK_POLICY = "network-policy"
CLUSTER_RESOURCES_RESOURCE_QUOTA = "resource-quota"
CLUSTER_RESOURCES_STORAGE_CLASS = "storage-classes"
CLUSTER_RESOURCES_INGRESS_CLASS = "ingress-classes"
CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS = "custom-resource-definitions"
CLUSTER_RESOURCES_CUSTOM_RESOURCES = "custom-resources"
CLUSTER_RESOURCES_IMAGE_PULL_SECRETS = "image-pull-secrets" // nolint:gosec
CLUSTER_RESOURCES_NODES = "nodes"
CLUSTER_RESOURCES_GROUPS = "groups"
CLUSTER_RESOURCES_RESOURCES = "resources"
CLUSTER_RESOURCES_LIMITRANGES = "limitranges"
CLUSTER_RESOURCES_EVENTS = "events"
CLUSTER_RESOURCES_PVS = "pvs"
CLUSTER_RESOURCES_PVCS = "pvcs"
CLUSTER_RESOURCES_ROLES = "roles"
CLUSTER_RESOURCES_ROLE_BINDINGS = "rolebindings"
CLUSTER_RESOURCES_CLUSTER_ROLES = "clusterroles"
CLUSTER_RESOURCES_CLUSTER_ROLE_BINDINGS = "clusterrolebindings"
CLUSTER_RESOURCES_PRIORITY_CLASS = "priorityclasses"
CLUSTER_RESOURCES_ENDPOINTS = "endpoints"
CLUSTER_RESOURCES_ENDPOINTSLICES = "endpointslices"
CLUSTER_RESOURCES_SERVICE_ACCOUNTS = "serviceaccounts"
CLUSTER_RESOURCES_LEASES = "leases"
CLUSTER_RESOURCES_VOLUME_ATTACHMENTS = "volumeattachments"
CLUSTER_RESOURCES_CONFIGMAPS = "configmaps"
CLUSTER_RESOURCES_REPLICATED_LICENSE = "license.json"
CLUSTER_RESOURCES_CERTIFICATE_SIGNING_REQUESTS = "certificatesigningrequests"
CLUSTER_RESOURCES_DIR = "cluster-resources"
CLUSTER_RESOURCES_NAMESPACES = "namespaces"
CLUSTER_RESOURCES_AUTH_CANI = "auth-cani-list"
CLUSTER_RESOURCES_PODS = "pods"
CLUSTER_RESOURCES_PODS_LOGS = "pods/logs"
CLUSTER_RESOURCES_POD_DISRUPTION_BUDGETS = "pod-disruption-budgets"
CLUSTER_RESOURCES_SERVICES = "services"
CLUSTER_RESOURCES_DEPLOYMENTS = "deployments"
CLUSTER_RESOURCES_REPLICASETS = "replicasets"
CLUSTER_RESOURCES_STATEFULSETS = "statefulsets"
CLUSTER_RESOURCES_DAEMONSETS = "daemonsets"
CLUSTER_RESOURCES_JOBS = "jobs"
CLUSTER_RESOURCES_CRONJOBS = "cronjobs"
CLUSTER_RESOURCES_INGRESS = "ingress"
CLUSTER_RESOURCES_NETWORK_POLICY = "network-policy"
CLUSTER_RESOURCES_RESOURCE_QUOTA = "resource-quota"
CLUSTER_RESOURCES_STORAGE_CLASS = "storage-classes"
CLUSTER_RESOURCES_CUSTOM_RESOURCE_DEFINITIONS = "custom-resource-definitions"
CLUSTER_RESOURCES_CUSTOM_RESOURCES = "custom-resources"
CLUSTER_RESOURCES_IMAGE_PULL_SECRETS = "image-pull-secrets" // nolint:gosec
CLUSTER_RESOURCES_NODES = "nodes"
CLUSTER_RESOURCES_GROUPS = "groups"
CLUSTER_RESOURCES_RESOURCES = "resources"
CLUSTER_RESOURCES_LIMITRANGES = "limitranges"
CLUSTER_RESOURCES_EVENTS = "events"
CLUSTER_RESOURCES_PVS = "pvs"
CLUSTER_RESOURCES_PVCS = "pvcs"
CLUSTER_RESOURCES_ROLES = "roles"
CLUSTER_RESOURCES_ROLE_BINDINGS = "rolebindings"
CLUSTER_RESOURCES_CLUSTER_ROLES = "clusterroles"
CLUSTER_RESOURCES_CLUSTER_ROLE_BINDINGS = "clusterrolebindings"
CLUSTER_RESOURCES_PRIORITY_CLASS = "priorityclasses"
CLUSTER_RESOURCES_ENDPOINTS = "endpoints"
CLUSTER_RESOURCES_ENDPOINTSLICES = "endpointslices"
CLUSTER_RESOURCES_SERVICE_ACCOUNTS = "serviceaccounts"
CLUSTER_RESOURCES_LEASES = "leases"
CLUSTER_RESOURCES_VOLUME_ATTACHMENTS = "volumeattachments"
CLUSTER_RESOURCES_CONFIGMAPS = "configmaps"
CLUSTER_RESOURCES_REPLICATED_LICENSE = "license.json"
CLUSTER_RESOURCES_CERTIFICATE_SIGNING_REQUESTS = "certificatesigningrequests"
CLUSTER_RESOURCES_INGRESS_CLASS = "ingress-classes"
CLUSTER_RESOURCES_VALIDATING_WEBHOOK_CONFIGURATIONS = "validating-webhook-configurations"
CLUSTER_RESOURCES_MUTATING_WEBHOOK_CONFIGURATIONS = "mutating-webhook-configurations"
// SelfSubjectRulesReview evaluation responses
SELFSUBJECTRULESREVIEW_ERROR_AUTHORIZATION_WEBHOOK_UNSUPPORTED = "webhook authorizer does not support user rule resolution"
+27
View File
@@ -0,0 +1,27 @@
package k8sutil
import (
"net/url"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/streaming/pkg/httpstream"
)
// NewFallbackExecutor creates an executor that tries WebSocket first and falls
// back to SPDY if the server does not support it. Use this in place of
// remotecommand.NewSPDYExecutor everywhere.
func NewFallbackExecutor(config *restclient.Config, u *url.URL) (remotecommand.Executor, error) {
// WebSocket upgrade requires GET per RFC 6455; SPDY uses POST.
wsExec, err := remotecommand.NewWebSocketExecutor(config, "GET", u.String())
if err != nil {
return nil, err
}
spdyExec, err := remotecommand.NewSPDYExecutor(config, "POST", u)
if err != nil {
return nil, err
}
return remotecommand.NewFallbackExecutor(wsExec, spdyExec, func(err error) bool {
return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)
})
}
+19
View File
@@ -0,0 +1,19 @@
package k8sutil
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
restclient "k8s.io/client-go/rest"
)
func TestNewFallbackExecutor(t *testing.T) {
config := &restclient.Config{Host: "http://localhost:8080"}
u, err := url.Parse("http://localhost:8080/api/v1/namespaces/default/pods/foo/exec")
require.NoError(t, err)
exec, err := NewFallbackExecutor(config, u)
require.NoError(t, err)
require.NotNil(t, exec)
}
-72
View File
@@ -1,72 +0,0 @@
package k8sutil
import (
"bytes"
"fmt"
"net/http"
"net/url"
"strings"
"time"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
func PortForward(config *restclient.Config, localPort int, remotePort int, namespace string, podName string) (chan struct{}, error) {
roundTripper, upgrader, err := spdy.RoundTripperFor(config)
if err != nil {
return nil, err
}
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, podName)
hostIP := strings.TrimLeft(config.Host, "htps:/")
serverURL := url.URL{Scheme: "http", Path: path, Host: hostIP}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL)
stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1)
out, errOut := new(bytes.Buffer), new(bytes.Buffer)
forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%d:%d", localPort, remotePort)}, stopChan, readyChan, out, errOut)
if err != nil {
return nil, err
}
go func() {
for range readyChan { // Kubernetes will close this channel when it has something to tell us.
}
if errOut.String() != "" {
panic(errOut.String())
} else if out.String() != "" {
// fmt.Println(out.String())
}
}()
go func() error {
if err = forwarder.ForwardPorts(); err != nil { // Locks until stopChan is closed.
panic(err)
}
return nil
}()
// Block until the new service is responding, limited to (math) seconds
quickClient := &http.Client{
Timeout: time.Millisecond * 200,
}
start := time.Now()
for {
response, err := quickClient.Get(fmt.Sprintf("http://localhost:%d", localPort))
if err == nil && response.StatusCode == http.StatusOK {
break
}
if time.Now().Sub(start) > time.Duration(time.Second*5) {
return nil, err
}
time.Sleep(time.Millisecond * 100)
}
return stopChan, nil
}
+25
View File
@@ -245,6 +245,8 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
// move Copy Collectors if any to the end of the execution list
allCollectors = collect.EnsureCopyLast(allCollectors)
var skippedCollectors []collect.SkippedCollector
for i, collector := range allCollectors {
_, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, collector.Title())
span.SetAttributes(attribute.String("type", reflect.TypeOf(collector).String()))
@@ -254,6 +256,13 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
klog.V(1).Infof("excluding %q collector", collector.Title())
span.SetAttributes(attribute.Bool(constants.EXCLUDED, true))
span.End()
skippedCollectors = append(skippedCollectors, collect.SkippedCollector{
Collector: collector.Title(),
Reason: "excluded",
Timestamp: time.Now().Format(time.RFC3339),
})
continue
}
@@ -270,6 +279,19 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
}
span.SetStatus(codes.Error, "skipping collector, insufficient RBAC permissions")
span.End()
rbacErrors := collector.GetRBACErrors()
errorMessages := make([]string, 0, len(rbacErrors))
for _, e := range rbacErrors {
errorMessages = append(errorMessages, e.Error())
}
skippedCollectors = append(skippedCollectors, collect.SkippedCollector{
Collector: collector.Title(),
Reason: "insufficient RBAC permissions",
Errors: errorMessages,
Timestamp: time.Now().Format(time.RFC3339),
})
continue
}
}
@@ -320,6 +342,9 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
span.End()
}
// Write skipped collectors manifest so users can see what was missed
collect.WriteSkippedCollectors(skippedCollectors, allCollectedData, opts.BundlePath)
// The values of map entries will contain the collected data in bytes if the data was not stored to disk
collectResult.AllCollectedData = allCollectedData
+30 -5
View File
@@ -19,6 +19,7 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/convert"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/redact"
"github.com/replicatedhq/troubleshoot/pkg/version"
"go.opentelemetry.io/otel"
@@ -39,9 +40,8 @@ import (
)
const (
selectorLabelKey = "ds-selector-label"
selectorLabelValue = "remote-host-collector"
defaultTimeout = 30
selectorLabelKey = "ds-selector-label"
defaultTimeout = 30
)
func runHostCollectors(ctx context.Context, hostCollectors []*troubleshootv1beta2.HostCollect, additionalRedactors *troubleshootv1beta2.Redactor, bundlePath string, opts SupportBundleCreateOpts) (collect.CollectorResult, error) {
@@ -107,7 +107,7 @@ func runCollectors(ctx context.Context, collectors []*troubleshootv1beta2.Collec
allCollectorsMap := make(map[reflect.Type][]collect.Collector)
collectorTypeOrder := make([]reflect.Type, 0) // Preserve order of collector types
allCollectedData := make(map[string][]byte)
allCollectedData := map[string][]byte{}
for _, desiredCollector := range collectSpecs {
if collectorInterface, ok := collect.GetCollector(desiredCollector, bundlePath, opts.Namespace, opts.KubernetesRestConfig, k8sClient, opts.SinceTime); ok {
@@ -155,6 +155,8 @@ func runCollectors(ctx context.Context, collectors []*troubleshootv1beta2.Collec
// move Copy Collectors if any to the end of the execution list
allCollectors = collect.EnsureCopyLast(allCollectors)
var skippedCollectors []collect.SkippedCollector
for _, collector := range allCollectors {
_, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, collector.Title())
span.SetAttributes(attribute.String("type", reflect.TypeOf(collector).String()))
@@ -165,6 +167,13 @@ func runCollectors(ctx context.Context, collectors []*troubleshootv1beta2.Collec
opts.CollectorProgressCallback(opts.ProgressChan, msg)
span.SetAttributes(attribute.Bool(constants.EXCLUDED, true))
span.End()
skippedCollectors = append(skippedCollectors, collect.SkippedCollector{
Collector: collector.Title(),
Reason: "excluded",
Timestamp: time.Now().Format(time.RFC3339),
})
continue
}
@@ -175,6 +184,19 @@ func runCollectors(ctx context.Context, collectors []*troubleshootv1beta2.Collec
opts.CollectorProgressCallback(opts.ProgressChan, msg)
span.SetStatus(codes.Error, "skipping collector, insufficient RBAC permissions")
span.End()
rbacErrors := collector.GetRBACErrors()
errorMessages := make([]string, 0, len(rbacErrors))
for _, e := range rbacErrors {
errorMessages = append(errorMessages, e.Error())
}
skippedCollectors = append(skippedCollectors, collect.SkippedCollector{
Collector: collector.Title(),
Reason: "insufficient RBAC permissions",
Errors: errorMessages,
Timestamp: time.Now().Format(time.RFC3339),
})
continue
}
}
@@ -207,6 +229,9 @@ func runCollectors(ctx context.Context, collectors []*troubleshootv1beta2.Collec
span.End()
}
// Write skipped collectors manifest to the bundle so users can see what was missed
collect.WriteSkippedCollectors(skippedCollectors, allCollectedData, bundlePath)
collectResult := allCollectedData
globalRedactors := []*troubleshootv1beta2.Redact{}
@@ -343,7 +368,7 @@ func getExecOutputs(
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
exec, err := k8sutil.NewFallbackExecutor(clientConfig, req.URL())
if err != nil {
return nil, nil, err
}
+29 -21
View File
@@ -34,6 +34,11 @@ func ExtractLicenseFromBundle(bundlePath string) (string, string, error) {
tarReader := tar.NewReader(gzReader)
// Collect results from both sources in a single pass; license.json takes priority
// regardless of its position in the tar, since configmaps often appear earlier.
var licenseJSONID, licenseJSONSlug string
var configmapID, configmapSlug string
for {
header, err := tarReader.Next()
if err == io.EOF {
@@ -43,50 +48,47 @@ func ExtractLicenseFromBundle(bundlePath string) (string, string, error) {
return "", "", errors.Wrap(err, "failed to read tar header")
}
// First priority: check for the new license.json file
if strings.Contains(header.Name, "cluster-resources/license.json") && header.Typeflag == tar.TypeReg {
if header.Typeflag != tar.TypeReg {
continue
}
// First priority: cluster-resources/license.json
if strings.Contains(header.Name, "cluster-resources/license.json") {
content := make([]byte, header.Size)
if _, err := io.ReadFull(tarReader, content); err != nil {
continue
}
// Parse the license.json file
var licenseData struct {
LicenseID string `json:"licenseID"`
AppSlug string `json:"appSlug"`
}
if err := json.Unmarshal(content, &licenseData); err == nil {
if licenseData.LicenseID != "" && licenseData.AppSlug != "" {
return licenseData.LicenseID, licenseData.AppSlug, nil
licenseJSONID = licenseData.LicenseID
licenseJSONSlug = licenseData.AppSlug
}
}
continue
}
// Fallback: process files in cluster-resources/configmaps/
// Fallback: cluster-resources/configmaps/
if configmapID != "" {
continue // already have a configmap result
}
if !strings.Contains(header.Name, "cluster-resources/configmaps/") {
continue
}
// Skip directories
if header.Typeflag != tar.TypeReg {
continue
}
// Process .yaml, .yml, and .json files
if !strings.HasSuffix(header.Name, ".yaml") &&
!strings.HasSuffix(header.Name, ".yml") &&
!strings.HasSuffix(header.Name, ".json") {
continue
}
// Read the file content
content := make([]byte, header.Size)
if _, err := io.ReadFull(tarReader, content); err != nil {
continue // Skip files we can't read
continue
}
// Try to extract license from this configmap
var license string
if strings.HasSuffix(header.Name, ".json") {
license = extractLicenseFromJSON(content)
@@ -95,15 +97,21 @@ func ExtractLicenseFromBundle(bundlePath string) (string, string, error) {
}
if license != "" {
// Extract app slug from filename
filename := filepath.Base(header.Name)
appSlug := strings.TrimSuffix(filename, ".json")
appSlug = strings.TrimSuffix(appSlug, ".yaml")
appSlug = strings.TrimSuffix(appSlug, ".yml")
return license, appSlug, nil
slug := strings.TrimSuffix(filename, ".json")
slug = strings.TrimSuffix(slug, ".yaml")
slug = strings.TrimSuffix(slug, ".yml")
configmapID = license
configmapSlug = slug
}
}
if licenseJSONID != "" {
return licenseJSONID, licenseJSONSlug, nil
}
if configmapID != "" {
return configmapID, configmapSlug, nil
}
return "", "", nil // No license found
}
+139
View File
@@ -0,0 +1,139 @@
package supportbundle
import (
"archive/tar"
"compress/gzip"
"os"
"path/filepath"
"testing"
)
// makeBundleTarGz creates a temporary .tar.gz file containing the given entries.
// Each entry is a (path, content) pair. Returns the file path; caller must remove it.
func makeBundleTarGz(t *testing.T, entries []struct{ name, content string }) string {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "bundle-*.tar.gz")
if err != nil {
t.Fatal(err)
}
defer f.Close()
gw := gzip.NewWriter(f)
tw := tar.NewWriter(gw)
for _, e := range entries {
hdr := &tar.Header{
Name: e.name,
Typeflag: tar.TypeReg,
Size: int64(len(e.content)),
Mode: 0644,
}
if err := tw.WriteHeader(hdr); err != nil {
t.Fatal(err)
}
if _, err := tw.Write([]byte(e.content)); err != nil {
t.Fatal(err)
}
}
if err := tw.Close(); err != nil {
t.Fatal(err)
}
if err := gw.Close(); err != nil {
t.Fatal(err)
}
return f.Name()
}
func TestExtractLicenseFromBundle_PrefersLicenseJSONOverConfigmap(t *testing.T) {
// The configmap appears first in the tar but license.json should win.
configmapContent := `{
"kind": "ConfigMapList",
"apiVersion": "v1",
"items": [{
"data": {
"license": "configmapLicenseIDAAAAAAAAAAAA"
}
}]
}`
licenseJSONContent := `{"licenseID":"correctLicenseIDAAAAAAAAAAAA","appSlug":"my-app"}`
bundlePath := makeBundleTarGz(t, []struct{ name, content string }{
// configmap comes first in the tar — this is the bug trigger
{"bundle/cluster-resources/configmaps/kotsadm.json", configmapContent},
{"bundle/cluster-resources/license.json", licenseJSONContent},
})
licenseID, appSlug, err := ExtractLicenseFromBundle(bundlePath)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if licenseID != "correctLicenseIDAAAAAAAAAAAA" {
t.Errorf("licenseID = %q, want %q", licenseID, "correctLicenseIDAAAAAAAAAAAA")
}
if appSlug != "my-app" {
t.Errorf("appSlug = %q, want %q", appSlug, "my-app")
}
}
func TestExtractLicenseFromBundle_FallsBackToConfigmap(t *testing.T) {
// No license.json — should fall back to configmap scan.
configmapContent := `{
"kind": "ConfigMapList",
"apiVersion": "v1",
"items": [{
"data": {
"licenseID": "fallbackLicenseIDAAAAAAAAAA"
}
}]
}`
bundlePath := makeBundleTarGz(t, []struct{ name, content string }{
{"bundle/cluster-resources/configmaps/my-app.json", configmapContent},
})
licenseID, appSlug, err := ExtractLicenseFromBundle(bundlePath)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if licenseID != "fallbackLicenseIDAAAAAAAAAA" {
t.Errorf("licenseID = %q, want %q", licenseID, "fallbackLicenseIDAAAAAAAAAA")
}
if appSlug != "my-app" {
t.Errorf("appSlug = %q, want %q", appSlug, "my-app")
}
}
func TestExtractLicenseFromBundle_ReturnsEmptyWhenNotFound(t *testing.T) {
bundlePath := makeBundleTarGz(t, []struct{ name, content string }{
{"bundle/cluster-resources/configmaps/some.json", `{"kind":"ConfigMapList"}`},
})
licenseID, appSlug, err := ExtractLicenseFromBundle(bundlePath)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if licenseID != "" || appSlug != "" {
t.Errorf("expected empty results, got licenseID=%q appSlug=%q", licenseID, appSlug)
}
}
func TestExtractLicenseFromBundle_RealBundle(t *testing.T) {
// Validates against the actual support bundle that triggered the bug.
// The bundle has license.json at tar entry #853 but kotsadm configmap at #94.
bundlePath := filepath.Join("..", "..", "support-bundle-2026-04-10T17_13_13.tar.gz")
if _, err := os.Stat(bundlePath); os.IsNotExist(err) {
t.Skip("real bundle not present")
}
licenseID, appSlug, err := ExtractLicenseFromBundle(bundlePath)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if licenseID != "36G95wTeTQoX7UYcm2QvhssCIkH" {
t.Errorf("licenseID = %q, want %q", licenseID, "36G95wTeTQoX7UYcm2QvhssCIkH")
}
if appSlug != "embedded-cluster-smoke-test-staging-app" {
t.Errorf("appSlug = %q, want %q", appSlug, "embedded-cluster-smoke-test-staging-app")
}
}
@@ -1836,6 +1836,9 @@
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"ignoreIfNoFiles": {
"type": "boolean"
},
"filters": {
"type": "object",
"properties": {
@@ -1836,6 +1836,9 @@
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"ignoreIfNoFiles": {
"type": "boolean"
},
"filters": {
"type": "object",
"properties": {
@@ -1882,6 +1882,9 @@
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"ignoreIfNoFiles": {
"type": "boolean"
},
"filters": {
"type": "object",
"properties": {
@@ -3,17 +3,31 @@ package e2e
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"testing"
"golang.org/x/exp/slices"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/e2e-framework/pkg/envconf"
"sigs.k8s.io/e2e-framework/pkg/features"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClusterResources(t *testing.T) {
const (
mutatingWebhookName = "e2e-mutating-webhook-config"
validatingWebhookName = "e2e-validating-webhook-config"
)
tests := []struct {
paths []string
expectType string
@@ -31,6 +45,8 @@ func TestClusterResources(t *testing.T) {
"namespaces.json",
"clusterrolebindings.json",
"storage-classes.json",
"mutating-webhook-configurations.json",
"validating-webhook-configurations.json",
},
expectType: "file",
},
@@ -64,6 +80,17 @@ func TestClusterResources(t *testing.T) {
}
feature := features.New("Cluster Resources Test").
Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
client := clusterClientset(t, ctx)
_, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, newMutatingWebhookConfiguration(mutatingWebhookName), metav1.CreateOptions{})
require.NoError(t, err)
_, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, newValidatingWebhookConfiguration(validatingWebhookName), metav1.CreateOptions{})
require.NoError(t, err)
return ctx
}).
Assess("check support bundle catch cluster resources", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
var out bytes.Buffer
supportBundleName := "cluster-resources"
@@ -105,7 +132,133 @@ func TestClusterResources(t *testing.T) {
}
}
assertWebhookConfigurationCollected(t, tarPath, supportBundleName, "mutating-webhook-configurations.json", mutatingWebhookName)
assertWebhookConfigurationCollected(t, tarPath, supportBundleName, "validating-webhook-configurations.json", validatingWebhookName)
return ctx
}).Feature()
}).
Teardown(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
client := clusterClientset(t, ctx)
err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(ctx, mutatingWebhookName, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
t.Fatal(err)
}
err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, validatingWebhookName, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
t.Fatal(err)
}
return ctx
}).
Feature()
testenv.Test(t, feature)
}
func clusterClientset(t *testing.T, ctx context.Context) *kubernetes.Clientset {
t.Helper()
cluster := getClusterFromContext(t, ctx, ClusterName)
restConfig, err := clientcmd.BuildConfigFromFlags("", cluster.GetKubeconfig())
require.NoError(t, err)
client, err := kubernetes.NewForConfig(restConfig)
require.NoError(t, err)
return client
}
func newMutatingWebhookConfiguration(name string) *admissionregistrationv1.MutatingWebhookConfiguration {
sideEffects := admissionregistrationv1.SideEffectClassNone
failurePolicy := admissionregistrationv1.Ignore
url := "https://example.com/mutate"
return &admissionregistrationv1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1.MutatingWebhook{
{
Name: fmt.Sprintf("%s.example.com", name),
AdmissionReviewVersions: []string{"v1"},
SideEffects: &sideEffects,
FailurePolicy: &failurePolicy,
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: &url,
},
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"configmaps"},
},
},
},
},
},
}
}
func newValidatingWebhookConfiguration(name string) *admissionregistrationv1.ValidatingWebhookConfiguration {
sideEffects := admissionregistrationv1.SideEffectClassNone
failurePolicy := admissionregistrationv1.Ignore
url := "https://example.com/validate"
return &admissionregistrationv1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Webhooks: []admissionregistrationv1.ValidatingWebhook{
{
Name: fmt.Sprintf("%s.example.com", name),
AdmissionReviewVersions: []string{"v1"},
SideEffects: &sideEffects,
FailurePolicy: &failurePolicy,
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: &url,
},
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"secrets"},
},
},
},
},
},
}
}
func assertWebhookConfigurationCollected(t *testing.T, tarPath string, supportBundleName string, fileName string, expectedName string) {
t.Helper()
type webhookList struct {
Items []struct {
Metadata metav1.ObjectMeta `json:"metadata"`
} `json:"items"`
}
targetFile := fmt.Sprintf("%s/cluster-resources/%s", supportBundleName, fileName)
payload, err := readFileFromTar(tarPath, targetFile)
require.NoError(t, err)
var configs webhookList
err = json.Unmarshal(payload, &configs)
require.NoError(t, err)
found := false
for _, item := range configs.Items {
if item.Metadata.Name == expectedName {
found = true
break
}
}
assert.True(t, found, "expected webhook configuration %q in %s", expectedName, fileName)
}
@@ -0,0 +1,101 @@
package e2e
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/e2e-framework/pkg/envconf"
"sigs.k8s.io/e2e-framework/pkg/features"
)
// TestNodeResourcesNoFiles verifies the behavior of the nodeResources
// analyzer when cluster-resources/nodes.json is not present in the
// bundle. The spec excludes the clusterResources collector so nodes.json
// is never written, then runs `support-bundle analyze` against the
// generated bundle.
//
// Expected outcomes:
// - "warn-default": warn outcome (default behavior)
// - "warn-explicit-false": warn outcome (ignoreIfNoFiles: false)
// - "ignored": no result (ignoreIfNoFiles: true)
func TestNodeResourcesNoFiles(t *testing.T) {
feature := features.New("Node Resources No Files").
Assess("warns per analyzer when nodes.json is missing and respects ignoreIfNoFiles", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
supportBundleName := "node-resources-no-files-test"
tarPath := fmt.Sprintf("%s.tar.gz", supportBundleName)
specPath := "spec/nodeResourcesNoFiles.yaml"
var collectOut bytes.Buffer
collectCmd := exec.CommandContext(ctx, sbBinary(), specPath,
"--interactive=false",
fmt.Sprintf("-o=%s", supportBundleName),
)
collectCmd.Stdout = &collectOut
collectCmd.Stderr = &collectOut
require.NoErrorf(t, collectCmd.Run(), "support-bundle collect failed: %s", collectOut.String())
defer func() {
if err := os.Remove(tarPath); err != nil {
t.Logf("Error removing %s: %v", tarPath, err)
}
}()
// Sanity check: nodes.json should NOT have been collected.
_, err := readFileFromTar(tarPath, fmt.Sprintf("%s/cluster-resources/nodes.json", supportBundleName))
require.Error(t, err, "nodes.json should not exist in the bundle")
var analyzeOut bytes.Buffer
var analyzeErr bytes.Buffer
analyzeCmd := exec.CommandContext(ctx, sbBinary(), "analyze",
"--bundle", tarPath,
"--output", "json",
specPath,
)
analyzeCmd.Stdout = &analyzeOut
analyzeCmd.Stderr = &analyzeErr
require.NoErrorf(t, analyzeCmd.Run(), "support-bundle analyze failed: %s", analyzeErr.String())
type analyzeResult struct {
IsPass bool `json:"IsPass"`
IsFail bool `json:"IsFail"`
IsWarn bool `json:"IsWarn"`
Title string `json:"Title"`
Message string `json:"Message"`
}
var results []analyzeResult
require.NoError(t, json.Unmarshal(analyzeOut.Bytes(), &results), "analyzer JSON output: %s", analyzeOut.String())
byTitle := map[string]analyzeResult{}
for _, r := range results {
byTitle[r.Title] = r
}
// Two warns, no result for the suppressed entry.
assert.Len(t, results, 2, "expected exactly two analyzer results, got %d: %s", len(results), analyzeOut.String())
for _, title := range []string{"warn-default", "warn-explicit-false"} {
r, ok := byTitle[title]
if !assert.Truef(t, ok, "expected an analyzer result with title %q", title) {
continue
}
assert.Truef(t, r.IsWarn, "%q: expected IsWarn=true", title)
assert.Falsef(t, r.IsFail, "%q: expected IsFail=false", title)
assert.Falsef(t, r.IsPass, "%q: expected IsPass=false", title)
assert.Containsf(t, r.Message, "No node resources were collected", "%q: unexpected message %q", title, r.Message)
}
_, ignored := byTitle["ignored"]
assert.Falsef(t, ignored, "analyzer with ignoreIfNoFiles: true should have produced no result")
return ctx
}).Feature()
testenv.Test(t, feature)
}
@@ -0,0 +1,72 @@
package e2e
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/e2e-framework/pkg/envconf"
"sigs.k8s.io/e2e-framework/pkg/features"
)
func TestSkippedCollectors(t *testing.T) {
feature := features.New("Skipped Collectors").
Assess("bundle contains skipped-collectors.json for excluded collectors", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
var out bytes.Buffer
supportBundleName := "skipped-collectors-test"
tarPath := fmt.Sprintf("%s.tar.gz", supportBundleName)
cmd := exec.CommandContext(ctx, sbBinary(), "spec/skippedCollectors.yaml",
"--interactive=false",
fmt.Sprintf("-o=%s", supportBundleName),
)
cmd.Stdout = &out
cmd.Stderr = &out
err := cmd.Run()
if err != nil {
t.Fatalf("support-bundle command failed: %v\nOutput: %s", err, out.String())
}
defer func() {
err := os.Remove(tarPath)
if err != nil {
t.Fatal("Error removing file:", err)
}
}()
// Read skipped-collectors.json from the bundle
skippedJSON, err := readFileFromTar(tarPath, fmt.Sprintf("%s/skipped-collectors.json", supportBundleName))
require.NoError(t, err, "skipped-collectors.json should exist in the bundle")
var skipped []struct {
Collector string `json:"collector"`
Reason string `json:"reason"`
Errors []string `json:"errors"`
Timestamp string `json:"timestamp"`
}
err = json.Unmarshal(skippedJSON, &skipped)
require.NoError(t, err)
// Both excluded collectors should be recorded
assert.Len(t, skipped, 2)
collectors := map[string]string{}
for _, s := range skipped {
collectors[s.Collector] = s.Reason
assert.NotEmpty(t, s.Timestamp, "timestamp should be set")
}
assert.Equal(t, "excluded", collectors["cluster-resources"])
assert.Equal(t, "excluded", collectors["cluster-info"])
return ctx
}).Feature()
testenv.Test(t, feature)
}
@@ -0,0 +1,23 @@
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: linux-arch-smoke-local-host-collectors
spec:
collectors:
- clusterResources: {}
hostCollectors:
- cpu: {}
- memory: {}
- run:
collectorName: uname
command: uname
args: ["-m"]
analyzers:
- nodeResources:
checkName: Linux arch smoke node count
outcomes:
- pass:
when: "= 1"
message: This cluster has exactly 1 node
- fail:
message: Expected exactly 1 node in the cluster
@@ -0,0 +1,37 @@
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: node-resources-no-files-test
spec:
collectors:
- clusterResources:
exclude: true
- clusterInfo:
exclude: true
analyzers:
- nodeResources:
checkName: warn-default
outcomes:
- fail:
when: "count() < 3"
message: This application requires at least 3 nodes
- pass:
message: This cluster has enough nodes
- nodeResources:
checkName: warn-explicit-false
ignoreIfNoFiles: false
outcomes:
- fail:
when: "min(memoryCapacity) < 16Gi"
message: All nodes must have at least 16Gi of memory
- pass:
message: All nodes have at least 16Gi of memory
- nodeResources:
checkName: ignored
ignoreIfNoFiles: true
outcomes:
- fail:
when: "count() < 3"
message: This application requires at least 3 nodes
- pass:
message: This cluster has enough nodes
@@ -0,0 +1,10 @@
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: skipped-collectors-test
spec:
collectors:
- clusterResources:
exclude: true
- clusterInfo:
exclude: true