mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c6af55e7c | ||
|
|
a4ce199005 | ||
|
|
48c45d7b0a | ||
|
|
2fdd21fca1 | ||
|
|
572cb8e31d | ||
|
|
21948fe959 | ||
|
|
b56b98742e | ||
|
|
b105fa62c7 | ||
|
|
558a8f5110 | ||
|
|
070fd9bab4 | ||
|
|
e89c858bd2 | ||
|
|
483467d8b9 | ||
|
|
27903af638 | ||
|
|
6eb09ee9d5 | ||
|
|
845fdcc8c8 | ||
|
|
a3c453d1d6 | ||
|
|
35d5ad17af | ||
|
|
daeab2dc20 | ||
|
|
5a611b3a6a | ||
|
|
fc72539585 | ||
|
|
69743729a3 | ||
|
|
6d1b1eba49 | ||
|
|
db137b1c33 | ||
|
|
a6915eac63 | ||
|
|
95ef65b08d | ||
|
|
c096e5d075 | ||
|
|
e3903add9f | ||
|
|
ad7d52f7e5 | ||
|
|
670a510a2d | ||
|
|
cb1e39cb61 | ||
|
|
a697d59040 | ||
|
|
b4fd76a5e1 | ||
|
|
11e06c7b37 | ||
|
|
af3e5ab501 | ||
|
|
3ded294ae8 |
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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}}
|
||||
@@ -0,0 +1,3 @@
|
||||
exclude:
|
||||
- ./examples/**
|
||||
- ./bin/**
|
||||
@@ -0,0 +1,3 @@
|
||||
# AGENTS.md
|
||||
|
||||
ALWAYS read and follow the instructions in CLAUDE.md before starting any work in this repository.
|
||||
@@ -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).
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, ®istryInfo); 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))
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
@@ -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,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
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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()}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 ®istryAuthConfig{
|
||||
username: c.hostCollector.Username,
|
||||
password: c.hostCollector.Password,
|
||||
}
|
||||
}
|
||||
// No credentials: rely on ambient auth (~/.docker/config.json)
|
||||
return nil
|
||||
}
|
||||
@@ -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: ®istryAuthConfig{
|
||||
username: "user",
|
||||
password: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "returns auth with username only",
|
||||
username: "user",
|
||||
password: "",
|
||||
expected: ®istryAuthConfig{
|
||||
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)
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user