Compare commits

..
Author SHA1 Message Date
Noah Campbell 2a2b0e0f30 reverted regression tests to how they were before
This was turning into something worthy of its own PR so I put it back to how it was before to work on next/separately
2025-10-14 11:30:05 -05:00
Noah Campbell 1013f2a160 does not recalculate affected e2e tests 2025-10-14 10:24:27 -05:00
Noah Campbell e73e1375cc uses unique namespaces for different preflight regression tests 2025-10-14 10:14:33 -05:00
Noah Campbell 9278d50252 use kubeconfig to properly set up preflight regression tests 2025-10-14 10:00:35 -05:00
Noah Campbell e084c0a46a regression tests run in matrix 2025-10-14 09:53:58 -05:00
Noah Campbell f7a941c220 regression tests run separate from relevant test detection 2025-10-14 08:11:07 -05:00
Noah Campbell 8920a9d09a Update Makefile 2025-10-13 14:35:25 -05:00
Noah Campbell a677646b17 reduced redundant building 2025-10-13 14:31:14 -05:00
Noah Campbell c84ea20b88 e2e test make target build the binaries first 2025-10-13 14:19:27 -05:00
Noah Campbell 409a02a125 Update affected-tests.yml 2025-10-13 14:02:20 -05:00
Noah Campbell 78af9208cf runs e2e preflight and support bundle tests as matrix 2025-10-13 13:53:09 -05:00
Noah Campbell fb0983480c Update affected-tests.yml 2025-10-13 13:40:06 -05:00
Noah Campbell 1aa74db81d Update affected-tests.yml 2025-10-13 13:24:35 -05:00
Noah Campbell 8f7cf26cc9 Update affected-tests.yml 2025-10-13 13:13:19 -05:00
Noah Campbell a6d3e3e423 Update affected-tests.yml 2025-10-13 13:02:45 -05:00
Noah Campbell 6ea8cbcb09 creates clusters for tests via replicated actions 2025-10-13 13:00:59 -05:00
Noah Campbell bf1dfa0318 removed references to nonexistent tests 2025-10-13 12:45:59 -05:00
Noah Campbell 45ee61aa4d creates cluster for tests and removed non testable files from being flagged 2025-10-13 12:01:58 -05:00
Noah Campbell e3458ad297 can run unit tests via makefile 2025-10-13 11:29:58 -05:00
Noah Campbell cb32216df8 runs all tests when test change is detected 2025-10-13 10:54:59 -05:00
Noah Campbell 2b82454ef8 Update affected-tests.yml 2025-10-13 10:18:43 -05:00
Noah Campbell e05962bcf3 affected test workflow runs on PR 2025-10-13 10:11:46 -05:00
Noah Campbell 0f747f55dc removed main requirement on affected tests 2025-10-13 10:07:26 -05:00
Noah Campbell 1c234e432c shows affected test summary 2025-10-13 10:00:49 -05:00
Noah Campbell d325804359 readded build binary action for tests 2025-10-13 09:52:47 -05:00
Noah Campbell 2d664b8736 separated build and test actions for relevant tests 2025-10-13 09:44:39 -05:00
Noah Campbell 8762bd1515 changed tests always are flagged as affected tests 2025-10-11 11:31:43 -07:00
Noah Campbell a2a424b201 added test and makes new relevant tests run tests 2025-10-11 11:17:26 -07:00
Noah Campbell 3f069125a2 added flag to specify files changed for testing 2025-10-11 10:49:54 -07:00
Noah Campbell 37292e6ba1 finds and runs relevant code change tests 2025-10-10 07:50:07 -07:00
145 changed files with 2368 additions and 10944 deletions
+39
View File
@@ -0,0 +1,39 @@
name: 'Setup Go Environment'
description: 'Setup Go with caching and common environment variables'
inputs:
go-version-file:
description: 'Path to go.mod file'
required: false
default: 'go.mod'
outputs:
go-version:
description: 'The Go version that was installed'
value: ${{ steps.setup-go.outputs.go-version }}
cache-hit:
description: 'Whether the Go cache was hit'
value: ${{ steps.setup-go.outputs.cache-hit }}
runs:
using: 'composite'
steps:
- name: Setup Go
id: setup-go
uses: actions/setup-go@v5
with:
go-version-file: ${{ inputs.go-version-file }}
cache: true
- name: Set Go environment variables
shell: bash
run: |
echo "GOMAXPROCS=2" >> $GITHUB_ENV
echo "GOCACHE=$(go env GOCACHE)" >> $GITHUB_ENV
echo "GOMODCACHE=$(go env GOMODCACHE)" >> $GITHUB_ENV
- name: Print Go environment
shell: bash
run: |
echo "Go version: $(go version)"
echo "GOOS: $(go env GOOS)"
echo "GOARCH: $(go env GOARCH)"
echo "Cache directory: $(go env GOCACHE)"
echo "Module cache: $(go env GOMODCACHE)"
+192
View File
@@ -0,0 +1,192 @@
name: Affected Go Tests
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
permissions:
contents: read
jobs:
test-affected:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
unit_has_changes: ${{ steps.affected.outputs.has_changes }}
e2e_has_changes: ${{ steps.affected_e2e.outputs.has_changes }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Go Mod Download
run: go mod download
- name: Compute base ref
id: pr-info
run: |
echo "BASE_REF=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
echo "Base ref: origin/${{ github.base_ref }}"
# 2) Detect relevant unit packages and e2e tests
- name: Compute affected packages
id: affected
run: |
set -euo pipefail
echo "Base: ${{ steps.pr-info.outputs.BASE_REF }}"
# Generate affected package list to a file for reuse in subsequent steps
go run ./scripts/affected-packages.go -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected.txt
echo "Affected packages:" || true
if [ -s /tmp/affected.txt ]; then
cat /tmp/affected.txt
else
echo "(none)"
fi
# Expose whether we have any packages to test
if [ -s /tmp/affected.txt ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi
- name: Compute affected e2e tests
id: affected_e2e
run: |
set -euo pipefail
go run ./scripts/affected-packages.go -mode=suites -base "${{ steps.pr-info.outputs.BASE_REF }}" > /tmp/affected-e2e.txt
awk -F: '$1=="preflight"{print $2}' /tmp/affected-e2e.txt > /tmp/preflight-tests.txt
awk -F: '$1=="support-bundle"{print $2}' /tmp/affected-e2e.txt > /tmp/support-tests.txt
if [ -s /tmp/preflight-tests.txt ] || [ -s /tmp/support-tests.txt ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish affected summary
if: always()
run: |
{
echo "### Affected unit packages";
if [ -s /tmp/affected.txt ]; then
sed 's/^/- /' /tmp/affected.txt;
else
echo "- (none)";
fi;
echo;
echo "### Affected e2e tests";
if [ -s /tmp/affected-e2e.txt ]; then
sed 's/^/- /' /tmp/affected-e2e.txt;
else
echo "- (none)";
fi;
} | tee -a "$GITHUB_STEP_SUMMARY"
- name: Upload affected unit packages
uses: actions/upload-artifact@v4
with:
name: affected-unit
path: /tmp/affected.txt
if-no-files-found: warn
- name: Upload affected e2e artifacts
uses: actions/upload-artifact@v4
with:
name: affected-e2e
path: |
/tmp/affected-e2e.txt
/tmp/preflight-tests.txt
/tmp/support-tests.txt
if-no-files-found: warn
- name: No affected packages — skip tests
if: steps.affected.outputs.has_changes != 'true'
run: echo "No Go packages affected by this PR; skipping tests."
e2e-affected:
needs: test-affected
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
suite: [unit, preflight, support-bundle]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Go Mod Download
run: go mod download
- name: Download affected unit packages
uses: actions/download-artifact@v4
with:
name: affected-unit
path: /tmp
- name: Download affected e2e artifacts
uses: actions/download-artifact@v4
with:
name: affected-e2e
path: /tmp
- name: Run unit tests (filtered)
if: matrix.suite == 'unit' && needs.test-affected.outputs.unit_has_changes == 'true'
run: |
set -euo pipefail
if grep -qx "./..." /tmp/affected.txt; then
echo "Module files changed; running all unit tests"
make test
else
echo "Running unit tests for affected packages"
pkgs=$(tr '\n' ' ' < /tmp/affected.txt)
PACKAGES="$pkgs" make test-packages
fi
- name: Run e2e (filtered) - ${{ matrix.suite }}
if: matrix.suite != 'unit' && needs.test-affected.outputs.e2e_has_changes == 'true'
run: |
set -euo pipefail
docker rm -f kind-cluster-control-plane 2>/dev/null || true
if [ "${{ matrix.suite }}" = "preflight" ]; then
file=/tmp/preflight-tests.txt
path=./test/e2e/preflight
else
file=/tmp/support-tests.txt
path=./test/e2e/support-bundle
fi
if [ -s "$file" ]; then
regex="$(grep -v '^$' "$file" | tr '\n' '|' | sed 's/|$//')"
if [ -n "$regex" ]; then
if [ "${{ matrix.suite }}" = "preflight" ]; then
E2EPATHS="$path" RUN="^(${regex})$" make preflight-e2e-go-test
else
E2EPATHS="$path" RUN="^(${regex})$" make support-bundle-e2e-go-test
fi
else
echo "No valid ${{ matrix.suite }} tests matched after filtering"
fi
else
echo "No ${{ matrix.suite }} e2e changes"
fi
+22 -30
View File
@@ -30,16 +30,17 @@ jobs:
tidy-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- run: make tidy-diff
test-integration:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
@@ -54,27 +55,28 @@ jobs:
compile-preflight:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- run: make generate preflight
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: preflight
path: bin/preflight
validate-preflight-e2e:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: compile-preflight
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: replicatedhq/action-k3s@main
id: k3s
with:
version: v1.31.2-k3s1
- name: Download preflight binary
uses: actions/download-artifact@v8
uses: actions/download-artifact@v5
with:
name: preflight
path: bin/
@@ -84,40 +86,28 @@ jobs:
compile-supportbundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- run: make generate support-bundle
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: support-bundle
path: bin/support-bundle
compile-collect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- run: make generate collect
- uses: actions/upload-artifact@v7
with:
name: collect
path: bin/collect
validate-supportbundle-e2e:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: compile-supportbundle
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: replicatedhq/action-k3s@main
id: k3s
with:
version: v1.31.2-k3s1
- name: Download support bundle binary
uses: actions/download-artifact@v8
uses: actions/download-artifact@v5
with:
name: support-bundle
path: bin/
@@ -126,18 +116,19 @@ jobs:
# Additional e2e tests for support bundle that run in Go, these create a Kind cluster
validate-supportbundle-e2e-go:
runs-on: ubuntu-latest
needs: [compile-supportbundle, compile-collect]
steps:
- uses: actions/checkout@v6
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: compile-supportbundle
steps:
- uses: actions/checkout@v5
- name: Download support bundle binary
uses: actions/download-artifact@v8
uses: actions/download-artifact@v5
with:
name: support-bundle
path: bin/
- run: chmod +x bin/support-bundle
- name: Download preflight binary
uses: actions/download-artifact@v8
uses: actions/download-artifact@v5
with:
name: preflight
path: bin/
@@ -146,6 +137,7 @@ jobs:
# summary jobs, these jobs will only run if all the other jobs have succeeded
validate-pr-tests:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs:
- tidy-check
@@ -160,10 +152,10 @@ jobs:
# this job will validate that the validation did not fail and that all pr-tests succeed
# it is used for the github branch protection rule
validate-success:
if: ${{ always() && github.event_name == 'push' }}
runs-on: ubuntu-latest
needs:
- validate-pr-tests
if: always()
steps:
# https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context
# if the validate-pr-tests job was not successful, this job will fail
@@ -1,4 +1,4 @@
name: build-test
name: build
on:
pull_request:
@@ -21,7 +21,7 @@ jobs:
support-bundle: ${{ steps.filter.outputs.support-bundle }}
examples: ${{ steps.filter.outputs.examples }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
@@ -44,10 +44,21 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-go
- name: Cache Go build and modules
uses: actions/cache@v4
with:
go-version-file: 'go.mod'
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Go mod download
run: go mod download
- name: Check go mod tidy
run: |
@@ -67,24 +78,7 @@ jobs:
make vet
# Unit and integration tests
test:
if: needs.changes.outputs.go-files == 'true'
needs: [changes, lint]
runs-on: ubuntu-latest
timeout-minutes: 20
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
- name: Run tests
run: make test-integration
# (moved to push-full-tests.yml)
# Build binaries
build:
@@ -93,75 +87,48 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-go
- name: Cache Go build and modules
uses: actions/cache@v4
with:
go-version-file: 'go.mod'
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Go mod download
run: go mod download
- run: make build
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: binaries
path: bin/
retention-days: 1
# E2E tests
e2e:
if: needs.changes.outputs.go-files == 'true' || github.event_name == 'push'
needs: [changes, build]
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: preflight
target: preflight-e2e-test
needs-k3s: true
- name: support-bundle-shell
target: support-bundle-e2e-test
needs-k3s: true
- name: support-bundle-go
target: support-bundle-e2e-go-test
needs-k3s: false
steps:
- uses: actions/checkout@v6
- name: Setup K3s
if: matrix.needs-k3s
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- uses: actions/download-artifact@v8
with:
name: binaries
path: bin/
- run: chmod +x bin/*
- run: make ${{ matrix.target }}
# (moved to push-full-tests.yml)
# Success summary
success:
if: always()
needs: [lint, test, build, e2e]
needs: [lint, build]
runs-on: ubuntu-latest
steps:
- name: Check results
run: |
# Check if any required jobs failed
if [[ "${{ needs.lint.result }}" == "failure" ]] || \
[[ "${{ needs.test.result }}" == "failure" ]] || \
[[ "${{ needs.build.result }}" == "failure" ]] || \
[[ "${{ needs.e2e.result }}" == "failure" ]]; then
[[ "${{ needs.build.result }}" == "failure" ]]; then
echo "::error::Some jobs failed or were cancelled"
exit 1
fi
# Check if any required jobs were cancelled
if [[ "${{ needs.lint.result }}" == "cancelled" ]] || \
[[ "${{ needs.test.result }}" == "cancelled" ]] || \
[[ "${{ needs.build.result }}" == "cancelled" ]] || \
[[ "${{ needs.e2e.result }}" == "cancelled" ]]; then
[[ "${{ needs.build.result }}" == "cancelled" ]]; then
echo "::error::Some jobs failed or were cancelled"
exit 1
fi
+77
View File
@@ -0,0 +1,77 @@
name: push-full-tests
on:
push:
branches: [main]
jobs:
unit-integration:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-go
- name: Setup K3s
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- name: Run tests
run: make test-integration
build-binaries:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-go
- name: Cache Go build and modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Go mod download
run: go mod download
- name: Build binaries
run: make build
- uses: actions/upload-artifact@v4
with:
name: binaries
path: bin/
retention-days: 1
e2e:
needs: [unit-integration, build-binaries]
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: preflight
target: preflight-e2e-test
needs-k3s: true
- name: support-bundle-shell
target: support-bundle-e2e-test
needs-k3s: true
- name: support-bundle-go
target: support-bundle-e2e-go-test
needs-k3s: false
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v4
with:
name: binaries
path: bin/
- run: chmod +x bin/*
- name: Setup K3s
if: matrix.needs-k3s
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- run: make ${{ matrix.target }}
+134 -339
View File
@@ -2,7 +2,7 @@ name: Regression Test Suite
on:
push:
branches: [main, v1beta3]
branches: [main]
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
@@ -13,22 +13,44 @@ on:
default: false
jobs:
# Build binaries once (shared by all test jobs)
build-binaries:
if: github.actor != 'dependabot[bot]'
regression-test:
runs-on: ubuntu-22.04
timeout-minutes: 5
timeout-minutes: 25
steps:
# 1. SETUP
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 0 # Fetch all history for git describe to work
- name: Create output directory
run: mkdir -p test/output
- name: Create k3s cluster
id: create-cluster
uses: replicatedhq/compatibility-actions/create-cluster@v1
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
kubernetes-distribution: k3s
cluster-name: regression-${{ github.run_id }}-${{ github.run_attempt }}
ttl: 25m
timeout-minutes: 5
- name: Configure kubeconfig
run: |
echo "${{ steps.create-cluster.outputs.cluster-kubeconfig }}" > $GITHUB_WORKSPACE/kubeconfig.yaml
echo "KUBECONFIG=$GITHUB_WORKSPACE/kubeconfig.yaml" >> $GITHUB_ENV
- name: Verify cluster access
run: kubectl get nodes -o wide
- name: Setup Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
go-version-file: go.mod
cache: true
cache-dependency-path: go.sum
- name: Build binaries
run: |
@@ -37,83 +59,84 @@ jobs:
./bin/preflight version
./bin/support-bundle version
- name: Upload binaries
uses: actions/upload-artifact@v7
with:
name: binaries-${{ github.run_id }}
path: |
bin/preflight
bin/support-bundle
retention-days: 1
# Preflight v1beta3 test (parallel job 1)
test-preflight-v1beta3:
needs: [build-binaries]
if: github.actor != 'dependabot[bot]'
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Create output directory
run: mkdir -p test/output
- name: Create k3s cluster
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- name: Verify cluster access
run: kubectl get nodes -o wide
- name: Wait for all pods to be ready
run: |
echo "Waiting for all pods to be running..."
kubectl get pods --all-namespaces
kubectl wait --for=condition=Ready pods --all --all-namespaces --timeout=300s || true
kubectl get pods --all-namespaces
- name: Download binaries
uses: actions/download-artifact@v8
with:
name: binaries-${{ github.run_id }}
path: bin/
- name: Make binaries executable
run: |
chmod +x bin/preflight bin/support-bundle
./bin/preflight version
- name: Setup Python for comparison
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Python dependencies
run: pip install pyyaml deepdiff
run: |
pip install pyyaml deepdiff
- name: Run preflight v1beta3
# 2. EXECUTE SPECS (in parallel)
- name: Run all specs in parallel
continue-on-error: true
run: |
echo "Running preflight v1beta3..."
./bin/preflight \
examples/preflight/complex-v1beta3.yaml \
--values examples/preflight/values-complex-full.yaml \
--interactive=false \
--format=json \
--auto-update=false \
--output=test/output/preflight-results-v1beta3.json 2>&1 | tee test/output/v1beta3.log || true
echo "Running all 3 specs in parallel..."
BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1)
if [ -n "$BUNDLE" ]; then
mv "$BUNDLE" test/output/preflight-v1beta3-bundle.tar.gz
echo "✓ v1beta3 bundle saved"
fi
# Run v1beta3 in background
(
echo "Starting preflight v1beta3..."
./bin/preflight \
examples/preflight/complex-v1beta3.yaml \
--values examples/preflight/values-complex-full.yaml \
--interactive=false \
--format=json \
--output=test/output/preflight-results-v1beta3.json 2>&1 | tee test/output/v1beta3.log || true
BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1)
if [ -n "$BUNDLE" ]; then
mv "$BUNDLE" test/output/preflight-v1beta3-bundle.tar.gz
echo "✓ v1beta3 bundle saved"
fi
) &
PID_V1BETA3=$!
# Run v1beta2 in background
(
echo "Starting preflight v1beta2..."
./bin/preflight \
examples/preflight/all-analyzers-v1beta2.yaml \
--interactive=false \
--format=json \
--output=test/output/preflight-results-v1beta2.json 2>&1 | tee test/output/v1beta2.log || true
BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1)
if [ -n "$BUNDLE" ]; then
mv "$BUNDLE" test/output/preflight-v1beta2-bundle.tar.gz
echo "✓ v1beta2 bundle saved"
fi
) &
PID_V1BETA2=$!
# Run support bundle in background
(
echo "Starting support bundle..."
./bin/support-bundle \
examples/collect/host/all-kubernetes-collectors.yaml \
--interactive=false \
--output=test/output/supportbundle.tar.gz 2>&1 | tee test/output/supportbundle.log || true
if [ -f test/output/supportbundle.tar.gz ]; then
echo "✓ Support bundle saved"
fi
) &
PID_SUPPORTBUNDLE=$!
# Wait for all to complete
echo "Waiting for all specs to complete..."
wait $PID_V1BETA3
wait $PID_V1BETA2
wait $PID_SUPPORTBUNDLE
echo "All specs completed!"
# Verify bundles exist
ls -lh test/output/*.tar.gz || echo "Warning: Some bundles may be missing"
# 3. COMPARE BUNDLES
- name: Compare preflight v1beta3 bundle
id: compare
id: compare-v1beta3
continue-on-error: true
run: |
echo "Comparing v1beta3 preflight bundle against baseline..."
@@ -130,93 +153,8 @@ jobs:
--report test/output/diff-report-v1beta3.json \
--spec-type preflight
- name: Upload test artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: test-results-v1beta3-${{ github.run_id }}-${{ github.run_attempt }}
path: |
test/output/preflight-v1beta3-bundle.tar.gz
test/output/preflight-results-v1beta3.json
test/output/diff-report-v1beta3.json
test/output/v1beta3.log
retention-days: 30
- name: Set job outcome
if: ${{ !cancelled() }}
run: |
if [ "${{ steps.compare.outcome }}" == "failure" ] && [ "${{ steps.compare.outputs.baseline_missing }}" != "true" ]; then
echo "comparison_failed=true" >> $GITHUB_OUTPUT
exit 1
fi
# Preflight v1beta2 test (parallel job 2)
test-preflight-v1beta2:
needs: [build-binaries]
if: github.actor != 'dependabot[bot]'
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Create output directory
run: mkdir -p test/output
- name: Create k3s cluster
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- name: Verify cluster access
run: kubectl get nodes -o wide
- name: Wait for all pods to be ready
run: |
echo "Waiting for all pods to be running..."
kubectl get pods --all-namespaces
kubectl wait --for=condition=Ready pods --all --all-namespaces --timeout=300s || true
kubectl get pods --all-namespaces
- name: Download binaries
uses: actions/download-artifact@v8
with:
name: binaries-${{ github.run_id }}
path: bin/
- name: Make binaries executable
run: |
chmod +x bin/preflight bin/support-bundle
./bin/preflight version
- name: Setup Python for comparison
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install Python dependencies
run: pip install pyyaml deepdiff
- name: Run preflight v1beta2
continue-on-error: true
run: |
echo "Running preflight v1beta2..."
./bin/preflight \
examples/preflight/all-analyzers-v1beta2.yaml \
--interactive=false \
--format=json \
--auto-update=false \
--output=test/output/preflight-results-v1beta2.json 2>&1 | tee test/output/v1beta2.log || true
BUNDLE=$(ls -t preflightbundle-*.tar.gz 2>/dev/null | head -1)
if [ -n "$BUNDLE" ]; then
mv "$BUNDLE" test/output/preflight-v1beta2-bundle.tar.gz
echo "✓ v1beta2 bundle saved"
fi
- name: Compare preflight v1beta2 bundle
id: compare
id: compare-v1beta2
continue-on-error: true
run: |
echo "Comparing v1beta2 preflight bundle against baseline..."
@@ -233,90 +171,8 @@ jobs:
--report test/output/diff-report-v1beta2.json \
--spec-type preflight
- name: Upload test artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: test-results-v1beta2-${{ github.run_id }}-${{ github.run_attempt }}
path: |
test/output/preflight-v1beta2-bundle.tar.gz
test/output/preflight-results-v1beta2.json
test/output/diff-report-v1beta2.json
test/output/v1beta2.log
retention-days: 30
- name: Set job outcome
if: ${{ !cancelled() }}
run: |
if [ "${{ steps.compare.outcome }}" == "failure" ] && [ "${{ steps.compare.outputs.baseline_missing }}" != "true" ]; then
echo "comparison_failed=true" >> $GITHUB_OUTPUT
exit 1
fi
# Support bundle test (parallel job 3)
test-supportbundle:
needs: [build-binaries]
if: github.actor != 'dependabot[bot]'
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Create output directory
run: mkdir -p test/output
- name: Create k3s cluster
uses: replicatedhq/action-k3s@main
with:
version: v1.31.2-k3s1
- name: Verify cluster access
run: kubectl get nodes -o wide
- name: Wait for all pods to be ready
run: |
echo "Waiting for all pods to be running..."
kubectl get pods --all-namespaces
kubectl wait --for=condition=Ready pods --all --all-namespaces --timeout=300s || true
kubectl get pods --all-namespaces
- name: Download binaries
uses: actions/download-artifact@v8
with:
name: binaries-${{ github.run_id }}
path: bin/
- name: Make binaries executable
run: |
chmod +x bin/preflight bin/support-bundle
./bin/support-bundle version
- name: Setup Python for comparison
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install Python dependencies
run: pip install pyyaml deepdiff
- name: Run support bundle
continue-on-error: true
run: |
echo "Running support bundle..."
./bin/support-bundle \
examples/collect/host/all-kubernetes-collectors.yaml \
--interactive=false \
--auto-update=false \
--output=test/output/supportbundle.tar.gz 2>&1 | tee test/output/supportbundle.log || true
if [ -f test/output/supportbundle.tar.gz ]; then
echo "✓ Support bundle saved"
fi
- name: Compare support bundle
id: compare
id: compare-supportbundle
continue-on-error: true
run: |
echo "Comparing support bundle against baseline..."
@@ -333,116 +189,45 @@ jobs:
--report test/output/diff-report-supportbundle.json \
--spec-type supportbundle
# 4. REPORT RESULTS
- name: Generate summary report
if: always()
run: |
python3 scripts/generate_summary.py \
--reports test/output/diff-report-*.json \
--output-file $GITHUB_STEP_SUMMARY \
--output-console
- name: Upload test artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-supportbundle-${{ github.run_id }}-${{ github.run_attempt }}
name: regression-test-results-${{ github.run_id }}-${{ github.run_attempt }}
path: |
test/output/supportbundle.tar.gz
test/output/diff-report-supportbundle.json
test/output/supportbundle.log
test/output/*.tar.gz
test/output/*.json
retention-days: 30
- name: Set job outcome
if: ${{ !cancelled() }}
run: |
if [ "${{ steps.compare.outcome }}" == "failure" ] && [ "${{ steps.compare.outputs.baseline_missing }}" != "true" ]; then
echo "comparison_failed=true" >> $GITHUB_OUTPUT
exit 1
fi
# Report results (runs after all tests complete)
report-results:
needs: [test-preflight-v1beta3, test-preflight-v1beta2, test-supportbundle]
if: ${{ !cancelled() && github.actor != 'dependabot[bot]' }}
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install Python dependencies
run: pip install pyyaml deepdiff
- name: Download all test artifacts
uses: actions/download-artifact@v8
with:
path: test/output
pattern: test-results-*
- name: Reorganize artifacts
run: |
# Move artifacts from nested directories to test/output
# Use shopt to handle glob patterns that don't match
shopt -s nullglob
for dir in test/output/test-results-*; do
if [ -d "$dir" ]; then
find "$dir" -type f -exec mv {} test/output/ \;
fi
done
# Clean up empty directories
for dir in test/output/test-results-*; do
if [ -d "$dir" ]; then
find "$dir" -type d -empty -delete || true
fi
done
shopt -u nullglob
- name: Generate summary report
run: |
# Handle case where no reports exist
shopt -s nullglob
REPORTS=(test/output/diff-report-*.json)
shopt -u nullglob
if [ ${#REPORTS[@]} -eq 0 ]; then
echo "⚠ No comparison reports found - test jobs may have failed before generating reports"
echo "## Summary Report" >> $GITHUB_STEP_SUMMARY
echo "No comparison reports available. Check individual test job results." >> $GITHUB_STEP_SUMMARY
else
python3 scripts/generate_summary.py \
--reports test/output/diff-report-*.json \
--output-file $GITHUB_STEP_SUMMARY \
--output-console
fi
- name: Check for regressions
if: always()
run: |
echo "Checking comparison results..."
# Check if any comparisons failed
FAILURES=0
if [ "${{ needs.test-preflight-v1beta3.result }}" == "failure" ] || [ "${{ needs.test-preflight-v1beta3.result }}" == "skipped" ]; then
if [ "${{ needs.test-preflight-v1beta3.result }}" == "skipped" ]; then
echo "❌ v1beta3 test was skipped (likely due to build failure)"
else
echo "❌ v1beta3 comparison failed"
fi
if [ "${{ steps.compare-v1beta3.outcome }}" == "failure" ] && [ "${{ steps.compare-v1beta3.outputs.baseline_missing }}" != "true" ]; then
echo "❌ v1beta3 comparison failed"
FAILURES=$((FAILURES + 1))
fi
if [ "${{ needs.test-preflight-v1beta2.result }}" == "failure" ] || [ "${{ needs.test-preflight-v1beta2.result }}" == "skipped" ]; then
if [ "${{ needs.test-preflight-v1beta2.result }}" == "skipped" ]; then
echo "❌ v1beta2 test was skipped (likely due to build failure)"
else
echo "❌ v1beta2 comparison failed"
fi
if [ "${{ steps.compare-v1beta2.outcome }}" == "failure" ] && [ "${{ steps.compare-v1beta2.outputs.baseline_missing }}" != "true" ]; then
echo "❌ v1beta2 comparison failed"
FAILURES=$((FAILURES + 1))
fi
if [ "${{ needs.test-supportbundle.result }}" == "failure" ] || [ "${{ needs.test-supportbundle.result }}" == "skipped" ]; then
if [ "${{ needs.test-supportbundle.result }}" == "skipped" ]; then
echo "❌ Support bundle test was skipped (likely due to build failure)"
else
echo "❌ Support bundle comparison failed"
fi
if [ "${{ steps.compare-supportbundle.outcome }}" == "failure" ] && [ "${{ steps.compare-supportbundle.outputs.baseline_missing }}" != "true" ]; then
echo "❌ Support bundle comparison failed"
FAILURES=$((FAILURES + 1))
fi
@@ -455,8 +240,9 @@ jobs:
echo "✅ All comparisons passed or skipped (no baseline)"
fi
# 5. UPDATE BASELINES (optional, manual trigger only)
- name: Update baselines
if: ${{ !cancelled() && github.event.inputs.update_baselines == 'true' && github.event_name == 'workflow_dispatch' }}
if: github.event.inputs.update_baselines == 'true' && github.event_name == 'workflow_dispatch'
run: |
echo "Updating baselines with current bundles..."
@@ -484,7 +270,7 @@ jobs:
{
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"git_sha": "${{ github.sha }}",
"k8s_version": "v1.31.2-k3s1",
"k8s_version": "v1.28.3",
"workflow_run": "${{ github.run_id }}"
}
EOF
@@ -495,3 +281,12 @@ jobs:
git add test/baselines/
git commit -m "chore: update regression test baselines from run ${{ github.run_id }}"
git push
# 6. CLEANUP
- name: Remove cluster
if: always()
uses: replicatedhq/compatibility-actions/remove-cluster@v1
continue-on-error: true
with:
api-token: ${{ secrets.REPLICATED_API_TOKEN }}
cluster-id: ${{ steps.create-cluster.outputs.cluster-id }}
+4 -4
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: troubleshoot_release
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -28,7 +28,7 @@ jobs:
go-version-file: 'go.mod'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
uses: goreleaser/goreleaser-action@v6
with:
version: "v2.12.3"
args: release --clean --config deploy/.goreleaser.yaml
@@ -37,12 +37,12 @@ jobs:
- name: Update new preflight version in krew-index
if: ${{ !contains(github.ref_name, '-') }}
uses: rajatjindal/krew-release-bot@v0.0.51
uses: rajatjindal/krew-release-bot@v0.0.47
with:
krew_template_file: deploy/krew/preflight.yaml
- name: Update new support-bundle version in krew-index
if: ${{ !contains(github.ref_name, '-') }}
uses: rajatjindal/krew-release-bot@v0.0.51
uses: rajatjindal/krew-release-bot@v0.0.47
with:
krew_template_file: deploy/krew/support-bundle.yaml
-24
View File
@@ -1,24 +0,0 @@
name: Upgrade Go Version
on:
# Run manually when needed
workflow_dispatch:
# Run weekly on Mondays at 8am UTC
schedule:
- cron: "0 8 * * MON"
jobs:
upgrade-go:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Check for Go updates
uses: StefMa/Upgrade-Go-Action@v1
with:
base-branch: 'main'
gh-token: ${{ secrets.TROUBLESHOOT_GH_PAT }}
+4 -9
View File
@@ -50,14 +50,9 @@ sbom/
!testdata/supportbundle/*.tar.gz
!test/baselines/**/baseline.tar.gz
# Ignore built binaries (use / prefix to avoid catching source files)
/troubleshoot
/troubleshoot-test
# Ignore built binaries
troubleshoot
troubleshoot-test
cmd/troubleshoot/troubleshoot
cmd/*/troubleshoot
/support-bundle
/.worktrees/
# IDEs
## IntelliJ / GoLand
/troubleshoot.iml
support-bundle
+25 -7
View File
@@ -37,7 +37,7 @@ endef
BUILDTAGS = "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp"
BUILDFLAGS = -tags ${BUILDTAGS} -installsuffix netgo
BUILDPATHS = ./pkg/... ./cmd/... ./internal/...
E2EPATHS = ./test/e2e/...
E2EPATHS ?= ./test/e2e/...
TESTFLAGS ?= -v -coverprofile cover.out
.DEFAULT_GOAL := all
@@ -49,12 +49,23 @@ ffi: fmt vet
.PHONY: test
test: generate fmt vet
if [ -n $(RUN) ]; then \
go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS} -run $(RUN); \
if [ -n "$(RUN)" ]; then \
go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS} -run "$(RUN)"; \
else \
go test ${BUILDFLAGS} ${BUILDPATHS} ${TESTFLAGS}; \
fi
# Run unit tests only for a provided list of packages.
# Usage: make test-packages PACKAGES="pkg/a pkg/b cmd/foo"
.PHONY: test-packages
test-packages:
@if [ -z "$(PACKAGES)" ]; then \
echo "No PACKAGES provided; nothing to test."; \
exit 0; \
fi
@echo "Running unit tests for packages: $(PACKAGES)"
go test ${BUILDFLAGS} $(PACKAGES) ${TESTFLAGS}
# Go tests that require a K8s instance
# TODOLATER: merge with test, so we get unified coverage reports? it'll add 21~sec to the test job though...
.PHONY: test-integration
@@ -73,10 +84,18 @@ run-examples:
support-bundle-e2e-test:
./test/validate-support-bundle-e2e.sh
.PHONY: preflight-e2e-go-test
preflight-e2e-go-test: bin/preflight
if [ -n "$(RUN)" ]; then \
go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \
else \
go test ${BUILDFLAGS} ${E2EPATHS} -v; \
fi
.PHONY: support-bundle-e2e-go-test
support-bundle-e2e-go-test:
if [ -n $(RUN) ]; then \
go test ${BUILDFLAGS} ${E2EPATHS} -v -run $(RUN); \
support-bundle-e2e-go-test: bin/support-bundle
if [ -n "$(RUN)" ]; then \
go test ${BUILDFLAGS} ${E2EPATHS} -v -run "$(RUN)"; \
else \
go test ${BUILDFLAGS} ${E2EPATHS} -v; \
fi
@@ -158,7 +177,6 @@ generate: controller-gen client-gen
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
--input troubleshoot/v1beta1 \
--input troubleshoot/v1beta2 \
--input troubleshoot/v1beta3 \
--go-header-file ./hack/boilerplate.go.txt
cp -r troubleshootclientset pkg/client
rm -rf troubleshootclientset
-21
View File
@@ -1,21 +0,0 @@
package cli
import (
"errors"
"syscall"
"github.com/replicatedhq/troubleshoot/internal/util"
)
func checkAndSetChroot(newroot string) error {
if newroot == "" {
return nil
}
if !util.IsRunningAsRoot() {
return errors.New("Can only chroot when run as root")
}
if err := syscall.Chroot(newroot); err != nil {
return err
}
return nil
}
-21
View File
@@ -1,21 +0,0 @@
package cli
import (
"errors"
"syscall"
"github.com/replicatedhq/troubleshoot/internal/util"
)
func checkAndSetChroot(newroot string) error {
if newroot == "" {
return nil
}
if !util.IsRunningAsRoot() {
return errors.New("Can only chroot when run as root")
}
if err := syscall.Chroot(newroot); err != nil {
return err
}
return nil
}
-9
View File
@@ -1,9 +0,0 @@
package cli
import (
"errors"
)
func checkAndSetChroot(newroot string) error {
return errors.New("chroot is only implimented in linux/darwin")
}
-90
View File
@@ -1,90 +0,0 @@
package cli
import (
"os"
"strings"
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
)
func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "collect [url]",
Args: cobra.MinimumNArgs(1),
Short: "Run a collector",
Long: `Run a collector and output the results.`,
SilenceUsage: true,
PreRun: func(cmd *cobra.Command, args []string) {
v := viper.GetViper()
v.BindPFlags(cmd.Flags())
logger.SetupLogger(v)
if err := util.StartProfiling(); err != nil {
klog.Errorf("Failed to start profiling: %v", err)
}
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
if err := checkAndSetChroot(v.GetString("chroot")); err != nil {
return err
}
return runCollect(v, args[0])
},
PostRun: func(cmd *cobra.Command, args []string) {
if err := util.StopProfiling(); err != nil {
klog.Errorf("Failed to stop profiling: %v", err)
}
},
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(util.VersionCmd())
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
cmd.Flags().String("format", "json", "output format, one of json or raw.")
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
cmd.Flags().String("collector-pull-policy", "", "the pull policy of the collector image")
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
cmd.Flags().Bool("debug", false, "enable debug logging")
cmd.Flags().String("chroot", "", "Chroot to path")
// hidden in favor of the `insecure-skip-tls-verify` flag
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
cmd.Flags().MarkHidden("allow-insecure-connections")
viper.BindPFlags(cmd.Flags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
k8sutil.AddFlags(cmd.Flags())
// Initialize klog flags
logger.InitKlogFlags(cmd)
// CPU and memory profiling flags
util.AddProfilingFlags(cmd)
return cmd
}
func InitAndExecute() {
if err := RootCmd().Execute(); err != nil {
os.Exit(1)
}
}
func initConfig() {
viper.SetEnvPrefix("TROUBLESHOOT")
viper.AutomaticEnv()
}
-189
View File
@@ -1,189 +0,0 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strings"
"time"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/specs"
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
"github.com/spf13/viper"
"k8s.io/apimachinery/pkg/labels"
)
const (
defaultTimeout = 30 * time.Second
)
func runCollect(v *viper.Viper, arg string) error {
go func() {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
<-signalChan
os.Exit(0)
}()
var collectorContent []byte
var err error
if strings.HasPrefix(arg, "secret/") {
// format secret/namespace-name/secret-name
pathParts := strings.Split(arg, "/")
if len(pathParts) != 3 {
return errors.Errorf("path %s must have 3 components", arg)
}
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "collect-spec")
if err != nil {
return errors.Wrap(err, "failed to get spec from secret")
}
collectorContent = spec
} else if arg == "-" {
b, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
collectorContent = b
} else if _, err = os.Stat(arg); err == nil {
b, err := os.ReadFile(arg)
if err != nil {
return err
}
collectorContent = b
} else {
if !util.IsURL(arg) {
return fmt.Errorf("%s is not a URL and was not found", arg)
}
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Replicated_Collect/v1beta2")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
collectorContent = body
}
collectorContent, err = docrewrite.ConvertToV1Beta2(collectorContent)
if err != nil {
return errors.Wrap(err, "failed to convert to v1beta2")
}
multidocs := strings.Split(string(collectorContent), "\n---\n")
decode := scheme.Codecs.UniversalDeserializer().Decode
redactors, err := supportbundle.GetRedactorsFromURIs(v.GetStringSlice("redactors"))
if err != nil {
return errors.Wrap(err, "failed to get redactors")
}
additionalRedactors := &troubleshootv1beta2.Redactor{
Spec: troubleshootv1beta2.RedactorSpec{
Redactors: redactors,
},
}
for i, additionalDoc := range multidocs {
if i == 0 {
continue
}
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
if err != nil {
return errors.Wrap(err, "failed to convert to v1beta2")
}
obj, _, err := decode(additionalDoc, nil, nil)
if err != nil {
return errors.Wrapf(err, "failed to parse additional doc %d", i)
}
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
if !ok {
continue
}
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
}
// make sure we don't block any senders
progressCh := make(chan interface{})
defer close(progressCh)
go func() {
for range progressCh {
}
}()
restConfig, err := k8sutil.GetRESTConfig()
if err != nil {
return errors.Wrap(err, "failed to convert kube flags to rest config")
}
labelSelector, err := labels.Parse(v.GetString("selector"))
if err != nil {
return errors.Wrap(err, "unable to parse selector")
}
namespace := v.GetString("namespace")
if namespace == "" {
namespace = "default"
}
timeout := v.GetDuration("request-timeout")
if timeout == 0 {
timeout = defaultTimeout
}
createOpts := collect.CollectorRunOpts{
CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
KubernetesRestConfig: restConfig,
Image: v.GetString("collector-image"),
PullPolicy: v.GetString("collector-pullpolicy"),
LabelSelector: labelSelector.String(),
Namespace: namespace,
Timeout: timeout,
ProgressChan: progressCh,
}
// we only support HostCollector or RemoteCollector kinds.
hostCollector, err := collect.ParseHostCollectorFromDoc([]byte(multidocs[0]))
if err == nil {
results, err := collect.CollectHost(hostCollector, additionalRedactors, createOpts)
if err != nil {
return errors.Wrap(err, "failed to collect from host")
}
return showHostStdoutResults(v.GetString("format"), hostCollector.Name, results)
}
remoteCollector, err := collect.ParseRemoteCollectorFromDoc([]byte(multidocs[0]))
if err == nil {
results, err := collect.CollectRemote(remoteCollector, additionalRedactors, createOpts)
if err != nil {
return errors.Wrap(err, "failed to collect from remote host(s)")
}
return showRemoteStdoutResults(v.GetString("format"), remoteCollector.Name, results)
}
return errors.New("failed to parse hostCollector or remoteCollector")
}
-103
View File
@@ -1,103 +0,0 @@
package cli
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
const (
// FormatJSON is intended for CLI output.
FormatJSON = "json"
// FormatRaw is intended for consumption by a remote collector. Output is a
// string of quoted JSON.
FormatRaw = "raw"
)
func showHostStdoutResults(format string, collectName string, results *collect.HostCollectResult) error {
switch format {
case FormatJSON:
return showHostStdoutResultsJSON(collectName, results.AllCollectedData)
case FormatRaw:
return showHostStdoutResultsRaw(collectName, results.AllCollectedData)
default:
return errors.Errorf("unknown output format: %q", format)
}
}
func showRemoteStdoutResults(format string, collectName string, results *collect.RemoteCollectResult) error {
switch format {
case FormatJSON:
return showRemoteStdoutResultsJSON(collectName, results.AllCollectedData)
case FormatRaw:
return errors.Errorf("raw format not supported for remote collectors")
default:
return errors.Errorf("unknown output format: %q", format)
}
}
func showHostStdoutResultsJSON(collectName string, results map[string][]byte) error {
output := make(map[string]interface{})
for file, collectorResult := range results {
var collectedItems map[string]interface{}
if err := json.Unmarshal([]byte(collectorResult), &collectedItems); err != nil {
return errors.Wrap(err, "failed to marshal collector results")
}
output[file] = collectedItems
}
formatted, err := json.MarshalIndent(output, "", " ")
if err != nil {
return errors.Wrap(err, "failed to convert output to json")
}
fmt.Print(string(formatted))
return nil
}
// showHostStdoutResultsRaw outputs the collector output as a string of quoted json.
func showHostStdoutResultsRaw(collectName string, results map[string][]byte) error {
strData := map[string]string{}
for k, v := range results {
strData[k] = string(v)
}
formatted, err := json.MarshalIndent(strData, "", " ")
if err != nil {
return errors.Wrap(err, "failed to convert output to json")
}
fmt.Print(string(formatted))
return nil
}
func showRemoteStdoutResultsJSON(collectName string, results map[string][]byte) error {
type CollectorResult map[string]interface{}
type NodeResult map[string]CollectorResult
var output = make(map[string]NodeResult)
for node, result := range results {
var nodeResult map[string]string
if err := json.Unmarshal(result, &nodeResult); err != nil {
return errors.Wrap(err, "failed to marshal node results")
}
nr := make(NodeResult)
for file, collectorResult := range nodeResult {
var collectedItems map[string]interface{}
if err := json.Unmarshal([]byte(collectorResult), &collectedItems); err != nil {
return errors.Wrap(err, "failed to marshal collector results")
}
nr[file] = collectedItems
}
output[node] = nr
}
formatted, err := json.MarshalIndent(output, "", " ")
if err != nil {
return errors.Wrap(err, "failed to convert output to json")
}
fmt.Print(string(formatted))
return nil
}
-10
View File
@@ -1,10 +0,0 @@
package main
import (
"github.com/replicatedhq/troubleshoot/cmd/collect/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
func main() {
cli.InitAndExecute()
}
+20 -1
View File
@@ -88,7 +88,7 @@ func extractDocs(templateFiles []string, valuesFiles []string, setValues []strin
if err != nil {
return errors.Wrapf(err, "failed to load values file %s", valuesFile)
}
values = preflight.MergeMaps(values, fileValues)
values = mergeMaps(values, fileValues)
}
// Normalize maps for Helm set merging
@@ -331,6 +331,25 @@ func setNestedValue(m map[string]interface{}, keys []string, value interface{})
}
}
func mergeMaps(base, overlay map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range base {
result[k] = v
}
for k, v := range overlay {
if baseVal, exists := result[k]; exists {
if baseMap, ok := baseVal.(map[string]interface{}); ok {
if overlayMap, ok := v.(map[string]interface{}); ok {
result[k] = mergeMaps(baseMap, overlayMap)
continue
}
}
}
result[k] = v
}
return result
}
func renderTemplate(templateContent string, values map[string]interface{}) (string, error) {
tmpl := template.New("preflight").Funcs(sprig.FuncMap())
tmpl, err := tmpl.Parse(templateContent)
-100
View File
@@ -1,100 +0,0 @@
package cli
import (
"fmt"
"os"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/lint"
"github.com/replicatedhq/troubleshoot/pkg/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func LintCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "lint [spec-files...]",
Args: cobra.MinimumNArgs(1),
Short: "Lint v1beta2/v1beta3 preflight specs for syntax and structural errors",
Long: `Lint v1beta2/v1beta3 preflight specs for syntax and structural errors.
This command validates v1beta2/v1beta3 preflight specs and checks for:
- YAML syntax errors
- Missing required fields (apiVersion, kind, metadata, spec)
- Invalid template syntax ({{ .Values.* }})
- Missing analyzers or collectors
- Common structural issues
- Missing docStrings (warning)
Examples:
# Lint a single spec file
preflight lint my-preflight.yaml
# Lint multiple spec files
preflight lint spec1.yaml spec2.yaml spec3.yaml
# Lint with automatic fixes
preflight lint --fix my-preflight.yaml
# Lint and output as JSON for CI/CD integration
preflight lint --format json my-preflight.yaml
Notes:
- v1beta2 does not support templating; template syntax in v1beta2 files will be flagged as errors.
- v1beta3 supports templating and is linted with template-awareness.
Exit codes:
0 - No errors found
2 - Validation errors found`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
opts := lint.LintOptions{
FilePaths: args,
Fix: v.GetBool("fix"),
Format: v.GetString("format"),
ValuesFiles: v.GetStringSlice("values"),
SetValues: v.GetStringSlice("set"),
}
return runLint(opts)
},
}
cmd.Flags().Bool("fix", false, "Automatically fix issues where possible")
cmd.Flags().String("format", "text", "Output format: text or json")
cmd.Flags().StringSlice("values", []string{}, "Path to YAML files with template values (required for v1beta3 specs)")
cmd.Flags().StringSlice("set", []string{}, "Set template values via command line (e.g., --set key=value)")
return cmd
}
func runLint(opts lint.LintOptions) error {
// Validate file paths exist
for _, filePath := range opts.FilePaths {
if _, err := os.Stat(filePath); err != nil {
return errors.Wrapf(err, "file not found: %s", filePath)
}
}
// Run linting
results, err := lint.LintFiles(opts)
if err != nil {
return errors.Wrap(err, "failed to lint files")
}
// Format and print results
output := lint.FormatResults(results, opts.Format)
fmt.Print(output)
// Return appropriate exit code
if lint.HasErrors(results) {
return types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, nil)
}
return nil
}
-1
View File
@@ -89,7 +89,6 @@ that a cluster meets the requirements to run an application.`,
cmd.AddCommand(TemplateCmd())
cmd.AddCommand(DocsCmd())
cmd.AddCommand(ConvertCmd())
cmd.AddCommand(LintCmd())
preflight.AddFlags(cmd.PersistentFlags())
-100
View File
@@ -1,100 +0,0 @@
package cli
import (
"fmt"
"os"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/lint"
"github.com/replicatedhq/troubleshoot/pkg/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func LintCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "lint [spec-files...]",
Args: cobra.MinimumNArgs(1),
Short: "Lint v1beta2/v1beta3 troubleshoot specs for syntax and structural errors",
Long: `Lint v1beta2/v1beta3 troubleshoot specs (both preflight and support-bundle) for syntax and structural errors.
This command validates v1beta2/v1beta3 troubleshoot specs and checks for:
- YAML syntax errors
- Missing required fields (apiVersion, kind, metadata, spec)
- Invalid template syntax ({{ .Values.* }})
- Missing collectors or hostCollectors
- Common structural issues
- Missing docStrings (warning)
Examples:
# Lint a single spec file
support-bundle lint my-spec.yaml
# Lint multiple spec files
support-bundle lint spec1.yaml spec2.yaml spec3.yaml
# Lint with automatic fixes
support-bundle lint --fix my-spec.yaml
# Lint and output as JSON for CI/CD integration
support-bundle lint --format json my-spec.yaml
Notes:
- v1beta2 does not support templating; template syntax in v1beta2 files will be flagged as errors.
- v1beta3 supports templating and is linted with template-awareness.
Exit codes:
0 - No errors found
2 - Validation errors found`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
opts := lint.LintOptions{
FilePaths: args,
Fix: v.GetBool("fix"),
Format: v.GetString("format"),
ValuesFiles: v.GetStringSlice("values"),
SetValues: v.GetStringSlice("set"),
}
return runLint(opts)
},
}
cmd.Flags().Bool("fix", false, "Automatically fix issues where possible")
cmd.Flags().String("format", "text", "Output format: text or json")
cmd.Flags().StringSlice("values", []string{}, "Path to YAML files with template values (required for v1beta3 specs)")
cmd.Flags().StringSlice("set", []string{}, "Set template values via command line (e.g., --set key=value)")
return cmd
}
func runLint(opts lint.LintOptions) error {
// Validate file paths exist
for _, filePath := range opts.FilePaths {
if _, err := os.Stat(filePath); err != nil {
return errors.Wrapf(err, "file not found: %s", filePath)
}
}
// Run linting
results, err := lint.LintFiles(opts)
if err != nil {
return errors.Wrap(err, "failed to lint files")
}
// Format and print results
output := lint.FormatResults(results, opts.Format)
fmt.Print(output)
// Return appropriate exit code
if lint.HasErrors(results) {
return types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, nil)
}
return nil
}
+1 -23
View File
@@ -5,14 +5,10 @@ import (
"os"
"strings"
"errors"
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
"github.com/replicatedhq/troubleshoot/internal/traces"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/replicatedhq/troubleshoot/pkg/types"
"github.com/replicatedhq/troubleshoot/pkg/updater"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -112,7 +108,6 @@ If no arguments are provided, specs are automatically loaded from the cluster by
cmd.AddCommand(Diff())
cmd.AddCommand(Schedule())
cmd.AddCommand(UploadCmd())
cmd.AddCommand(LintCmd())
cmd.AddCommand(util.VersionCmd())
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
@@ -132,18 +127,10 @@ If no arguments are provided, specs are automatically loaded from the cluster by
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. Do not load by default unless no specs are provided in the cli args")
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().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")
cmd.Flags().StringSlice("metadata", []string{}, "user-provided metadata key=value pairs to include in the bundle (can be specified multiple times)")
// Upload flags
cmd.Flags().Bool("auto-upload", false, "automatically upload resulting bundle to replicated.app")
cmd.Flags().String("license-id", "", "license ID for authentication when uploading (auto-detected from bundle if not provided)")
cmd.Flags().String("app-slug", "", "application slug when uploading (auto-detected from bundle if not provided)")
cmd.Flags().String("upload-domain", "", "custom domain for upload (default: replicated.app)")
// Auto-discovery flags
cmd.Flags().Bool("auto", false, "enable auto-discovery of foundational collectors. When used with YAML specs, adds foundational collectors to YAML collectors. When used alone, collects only foundational data")
@@ -174,16 +161,7 @@ If no arguments are provided, specs are automatically loaded from the cluster by
}
func InitAndExecute() {
cmd := RootCmd()
if err := cmd.Execute(); err != nil {
var exitErr types.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitStatus() != constants.EXIT_CODE_FAIL && exitErr.ExitStatus() != constants.EXIT_CODE_WARN {
cmd.PrintErrln("Error:", err.Error())
}
os.Exit(exitErr.ExitStatus())
}
cmd.PrintErrln("Error:", err.Error())
if err := RootCmd().Execute(); err != nil {
os.Exit(1)
}
}
+12 -55
View File
@@ -200,23 +200,17 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
}()
}
userMetadata, err := parseMetadataFlag(v.GetStringSlice("metadata"))
if err != nil {
return errors.Wrap(err, "invalid metadata flag")
}
createOpts := supportbundle.SupportBundleCreateOpts{
CollectorProgressCallback: collectorCB,
CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
RemoteHostCollectTimeoutSeconds: v.GetInt("remote-host-collect-timeout"),
KubernetesRestConfig: restConfig,
Namespace: v.GetString("namespace"),
ProgressChan: progressChan,
SinceTime: sinceTime,
OutputPath: v.GetString("output"),
Redact: v.GetBool("redact"),
FromCLI: true,
RunHostCollectorsInPod: mainBundle.Spec.RunHostCollectorsInPod,
CollectorProgressCallback: collectorCB,
CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
KubernetesRestConfig: restConfig,
Namespace: v.GetString("namespace"),
ProgressChan: progressChan,
SinceTime: sinceTime,
OutputPath: v.GetString("output"),
Redact: v.GetBool("redact"),
FromCLI: true,
RunHostCollectorsInPod: mainBundle.Spec.RunHostCollectorsInPod,
// Phase 4: Tokenization options
Tokenize: v.GetBool("tokenize"),
@@ -226,7 +220,6 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
VerifyTokenization: v.GetBool("verify-tokenization"),
BundleID: v.GetString("bundle-id"),
TokenizationStats: v.GetBool("tokenization-stats"),
UserMetadata: userMetadata,
}
nonInteractiveOutput := analysisOutput{}
@@ -249,26 +242,6 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
}
}
// Attempt auto-upload before any early returns
if v.GetBool("auto-upload") && !response.FileUploaded {
licenseID := v.GetString("license-id")
appSlug := v.GetString("app-slug")
uploadDomain := v.GetString("upload-domain")
targetDomain := uploadDomain
if targetDomain == "" {
targetDomain = "replicated.app"
}
fmt.Fprintf(os.Stderr, "Auto-uploading bundle to %s...\n", targetDomain)
if err := supportbundle.UploadBundleAutoDetect(response.ArchivePath, licenseID, appSlug, uploadDomain); err != nil {
fmt.Fprintf(os.Stderr, "Auto-upload failed: %v\n", err)
fmt.Fprintf(os.Stderr, "You can manually upload the bundle using: support-bundle upload %s\n", response.ArchivePath)
} else {
response.FileUploaded = true
}
}
if !response.FileUploaded {
if appName := mainBundle.Labels["applicationName"]; appName != "" {
f := `A support bundle for %s has been created in this directory
@@ -296,12 +269,11 @@ the %s Admin Console to begin analysis.`
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
}
if response.FileUploaded {
fmt.Printf("A support bundle has been created and uploaded to replicated.app for analysis.\n")
fmt.Printf("A support bundle has been created and uploaded to your cluster for analysis. Please visit the Troubleshoot page to continue.\n")
fmt.Printf("A copy of this support bundle was written to the current directory, named %q\n", response.ArchivePath)
} else {
fmt.Printf("A support bundle has been created in the current directory named %q\n", response.ArchivePath)
}
return nil
}
@@ -525,7 +497,7 @@ func (a *analysisOutput) FormattedAnalysisOutput() (outputJson string, err error
formatted, err := json.MarshalIndent(o, "", " ")
if err != nil {
return "", fmt.Errorf("\r * Failed to format analysis: %v", err)
return "", fmt.Errorf("\r * Failed to format analysis: %v\n", err)
}
return string(formatted), nil
}
@@ -632,18 +604,3 @@ func VerifyTokenizationSetup(v *viper.Viper) error {
return nil
}
func parseMetadataFlag(values []string) (map[string]string, error) {
if len(values) == 0 {
return nil, nil
}
metadata := make(map[string]string, len(values))
for _, v := range values {
k, val, ok := strings.Cut(v, "=")
if !ok {
return nil, fmt.Errorf("invalid metadata format %q, expected key=value", v)
}
metadata[k] = val
}
return metadata, nil
}
-79
View File
@@ -13,7 +13,6 @@ import (
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/httputil"
"github.com/replicatedhq/troubleshoot/pkg/loader"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
testclient "k8s.io/client-go/kubernetes/fake"
@@ -437,81 +436,3 @@ func Test_loadInvalidURISpec(t *testing.T) {
assert.Len(t, sb.Spec.Collectors, 3) // default + clusterInfo + clusterResources
assert.NotNil(t, sb.Spec.Collectors[0].ConfigMap) // come from the original spec
}
func TestCollectTimeoutFlag(t *testing.T) {
const defaultCollectTimeout = 30
// Parse flags and bind to viper without running the full command (avoids k8s connection).
// This verifies the flag is defined and viper receives the correct value.
bindFlagsFromArgs := func(t *testing.T, args []string) {
t.Helper()
cmd := RootCmd()
require.NoError(t, cmd.Flags().Parse(args))
if cmd.PersistentPreRun != nil {
cmd.PersistentPreRun(cmd, nil)
}
}
t.Run("default value when flag not set", func(t *testing.T) {
bindFlagsFromArgs(t, []string{})
actualTimeout := viper.GetInt("remote-host-collect-timeout")
assert.Equal(t, defaultCollectTimeout, actualTimeout, "remote-host-collect-timeout should default to 30 seconds")
})
t.Run("custom value when flag set", func(t *testing.T) {
bindFlagsFromArgs(t, []string{"--remote-host-collect-timeout=90"})
actualTimeout := viper.GetInt("remote-host-collect-timeout")
assert.Equal(t, 90, actualTimeout, "remote-host-collect-timeout should be 90 when --remote-host-collect-timeout=90 is passed")
})
}
func TestParseMetadataFlag(t *testing.T) {
tests := []struct {
name string
values []string
want map[string]string
wantErr bool
}{
{
name: "nil input",
values: nil,
want: nil,
},
{
name: "empty input",
values: []string{},
want: nil,
},
{
name: "single pair",
values: []string{"env=staging"},
want: map[string]string{"env": "staging"},
},
{
name: "multiple pairs",
values: []string{"env=staging", "version=1.2.3"},
want: map[string]string{"env": "staging", "version": "1.2.3"},
},
{
name: "value contains equals",
values: []string{"config=key=value"},
want: map[string]string{"config": "key=value"},
},
{
name: "missing equals",
values: []string{"noequals"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseMetadataFlag(tt.values)
if tt.wantErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
+2 -7
View File
@@ -26,10 +26,7 @@ Examples:
support-bundle upload bundle.tar.gz --license-id YOUR_LICENSE_ID
# Specify both license and app
support-bundle upload bundle.tar.gz --license-id YOUR_LICENSE_ID --app-slug my-app
# Upload to a custom domain (e.g., development environment)
support-bundle upload bundle.tar.gz --upload-domain replicated-app-dev.example.com`,
support-bundle upload bundle.tar.gz --license-id YOUR_LICENSE_ID --app-slug my-app`,
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
bundlePath := args[0]
@@ -42,10 +39,9 @@ Examples:
// Get upload parameters
licenseID := v.GetString("license-id")
appSlug := v.GetString("app-slug")
uploadDomain := v.GetString("upload-domain")
// Use auto-detection for uploads
if err := supportbundle.UploadBundleAutoDetect(bundlePath, licenseID, appSlug, uploadDomain); err != nil {
if err := supportbundle.UploadBundleAutoDetect(bundlePath, licenseID, appSlug); err != nil {
return errors.Wrap(err, "upload failed")
}
@@ -55,7 +51,6 @@ Examples:
cmd.Flags().String("license-id", "", "license ID for authentication (auto-detected from bundle if not provided)")
cmd.Flags().String("app-slug", "", "application slug (auto-detected from bundle if not provided)")
cmd.Flags().String("upload-domain", "", "custom domain for upload (default: replicated.app)")
return cmd
}
+4 -27
View File
@@ -8,7 +8,7 @@ builds:
- id: preflight
main: ./cmd/preflight/main.go
env: [CGO_ENABLED=0]
goos: [linux, darwin, windows]
goos: [linux, darwin]
goarch: [amd64, arm, arm64]
ignore:
- goos: windows
@@ -31,7 +31,7 @@ builds:
- id: support-bundle
main: ./cmd/troubleshoot/main.go
env: [CGO_ENABLED=0]
goos: [linux, darwin, windows]
goos: [linux, darwin]
goarch: [amd64, arm, arm64]
ignore:
- goos: windows
@@ -51,29 +51,6 @@ builds:
- -installsuffix=netgo
binary: support-bundle
- id: collect
main: ./cmd/collect/main.go
env: [CGO_ENABLED=0]
goos: [linux, darwin, windows]
goarch: [amd64, arm, arm64]
ignore:
- goos: windows
goarch: arm
ldflags:
- -s -w
- -X github.com/replicatedhq/troubleshoot/pkg/version.version={{ .Version }}
- -X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{ .Commit }}
- -X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{ .Date }}
- -extldflags "-static"
flags:
- -tags=netgo
- -tags=containers_image_ostree_stub
- -tags=exclude_graphdriver_devicemapper
- -tags=exclude_graphdriver_btrfs
- -tags=containers_image_openpgp
- -installsuffix=netgo
binary: collect
archives:
- id: preflight
ids: [preflight]
@@ -158,7 +135,7 @@ dockers:
ids:
- support-bundle
- preflight
- collect
skip_push: true
- dockerfile: ./deploy/Dockerfile.troubleshoot
image_templates:
- "replicated/preflight:latest"
@@ -168,7 +145,7 @@ dockers:
ids:
- support-bundle
- preflight
- collect
skip_push: true
universal_binaries:
- id: preflight-universal
-1
View File
@@ -7,7 +7,6 @@ RUN apt-get -qq update \
COPY support-bundle /troubleshoot/support-bundle
COPY preflight /troubleshoot/preflight
COPY collect /troubleshoot/collect
ENV PATH="/troubleshoot:${PATH}"
@@ -1,59 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: test-v1beta3-secretref
spec:
collectors:
# Test 1: PostgreSQL with URI from secret
- postgres:
collectorName: postgres-with-secret
uri:
valueFrom:
secretKeyRef:
name: test-database-credentials
key: postgres-uri
# This will fail to connect (fake server) but that's OK -
# we're testing secret resolution, not actual DB connectivity
# Test 2: PostgreSQL with TLS certs from secret
- postgres:
collectorName: postgres-with-tls
uri:
value: "postgresql://testuser:testpass@localhost:5432/testdb"
tls:
cacert:
valueFrom:
secretKeyRef:
name: test-database-credentials
key: ca.crt
clientCert:
valueFrom:
secretKeyRef:
name: test-database-credentials
key: client.crt
clientKey:
valueFrom:
secretKeyRef:
name: test-database-credentials
key: client.key
# Test 3: MySQL with URI from secret
- mysql:
collectorName: mysql-with-secret
uri:
valueFrom:
secretKeyRef:
name: test-database-credentials
key: mysql-uri
# Test 4: Redis with URI from secret
- redis:
collectorName: redis-with-secret
uri:
valueFrom:
secretKeyRef:
name: test-database-credentials
key: redis-uri
# Test 5: Literal value (no secret) for comparison
- clusterInfo: {}
-39
View File
@@ -1,39 +0,0 @@
---
# Secret containing database credentials
apiVersion: v1
kind: Secret
metadata:
name: test-database-credentials
namespace: default
type: Opaque
stringData:
# PostgreSQL connection URI
postgres-uri: "postgresql://testuser:supersecret@postgres.example.com:5432/testdb?sslmode=require"
# MySQL connection URI
mysql-uri: "mysql://testuser:supersecret@mysql.example.com:3306/testdb"
# Redis connection URI
redis-uri: "redis://:supersecret@redis.example.com:6379"
# TLS certificates (example data)
ca.crt: |
-----BEGIN CERTIFICATE-----
MIICpDCCAYwCCQDU+pQ3ZUD30jANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjQwMTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7
VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1+fWIcPm15A8IgC0qC1J3xGhE=
-----END CERTIFICATE-----
client.crt: |
-----BEGIN CERTIFICATE-----
MIICpDCCAYwCCQDU+pQ3ZUD30jANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjQwMTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjAUMRIwEAYD
VQQDDA5jbGllbnQtY2VydA==
-----END CERTIFICATE-----
client.key: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj
MzEfYyjiWA4R4/M2bS1+fWIcPm15A8IgC0qC1J3xGhE=
-----END PRIVATE KEY-----
+1 -1
View File
@@ -92,7 +92,7 @@ spec:
- nodeResources:
checkName: Must have 1 node with 2Gi (available) memory and at least 2 cores (on a single node)
filters:
memoryAllocatable: 2Gi
allocatableMemory: 2Gi
cpuCapacity: "2"
outcomes:
- pass:
+3 -3
View File
@@ -28,7 +28,7 @@ spec:
- nodeResources:
checkName: Must have 1 node with 16 GB (available) memory and 10 cores (on a single node)
filters:
memoryAllocatable: 16Gi
allocatableMemory: 16Gi
cpuCapacity: "10"
outcomes:
- fail:
@@ -39,7 +39,7 @@ spec:
- nodeResources:
checkName: Must have 1 node with 16 GB (available) memory and 4 cores of amd64 arch (on a single node)
filters:
memoryAllocatable: 16Gi
allocatableMemory: 16Gi
cpuArchitecture: amd64
cpuCapacity: "4"
outcomes:
@@ -54,7 +54,7 @@ spec:
selector:
matchLabel:
node-role.kubernetes.io/master: ""
memoryAllocatable: 16Gi
allocatableMemory: 16Gi
cpuArchitecture: amd64
cpuCapacity: "6"
outcomes:
+22 -22
View File
@@ -1,6 +1,6 @@
module helm-template
go 1.25.5
go 1.24.6
// Always use the local version of troubleshoot so as to build using
// the latest version of the library. This will ensure the example
@@ -9,17 +9,17 @@ 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.19.0
sigs.k8s.io/yaml v1.6.0
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
@@ -28,6 +28,7 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -45,29 +46,28 @@ require (
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v2 v2.4.2 // 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
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/protobuf v1.36.6 // 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/api v0.34.1 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/apimachinery v0.34.1 // indirect
k8s.io/client-go v0.34.1 // indirect
k8s.io/klog/v2 v2.130.1 // 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/json v0.0.0-20250730193827-2d320260d730 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/controller-runtime v0.22.1 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)
+83 -56
View File
@@ -2,16 +2,16 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -36,6 +36,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -43,8 +45,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
@@ -53,6 +55,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -71,17 +75,17 @@ 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/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/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/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
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/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -98,63 +102,86 @@ 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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
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=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
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.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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=
helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k=
helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
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/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg=
sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/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=
+1 -1
View File
@@ -77,7 +77,7 @@ spec:
- nodeResources:
checkName: Must have 1 node with 2Gi (available) memory and at least 2 cores (on a single node)
filters:
memoryAllocatable: 2Gi
allocatableMemory: 2Gi
cpuCapacity: "2"
outcomes:
- pass:
-236
View File
@@ -1,236 +0,0 @@
# v1beta3 Support Bundle Examples
This directory contains example Support Bundle specs using the v1beta3 API, which introduces `StringOrValueFrom` support for securely referencing Kubernetes Secrets and ConfigMaps in collector fields.
## Features
### StringOrValueFrom Pattern
The v1beta3 API introduces a Kubernetes-native pattern for referencing sensitive values:
```yaml
uri:
valueFrom:
secretKeyRef:
name: my-secret
key: connection-uri
```
or
```yaml
uri: "postgresql://localhost:5432/db" # Literal value
```
### Supported Collectors
Currently, v1beta3 supports `StringOrValueFrom` for:
- **Database collectors**: `postgres`, `mysql`, `redis`, `mssql`
- `uri` field - Connection strings from secrets
- `tls` fields - CA cert, client cert, and client key from secrets
## Examples
### 1. postgres-with-secret.yaml
Basic PostgreSQL collector with connection URI from a secret.
**Use case**: Securely store database credentials without hardcoding them in the spec.
```bash
kubectl apply -f postgres-with-secret.yaml
```
### 2. postgres-with-tls.yaml
PostgreSQL with TLS configuration from secrets.
**Use case**: Secure database connections with mutual TLS, storing certificates in secrets.
```bash
kubectl apply -f postgres-with-tls.yaml
```
### 3. multiple-databases.yaml
Multiple database collectors (PostgreSQL, MySQL, Redis, MSSQL) with various configurations.
**Use case**: Collect diagnostics from multiple databases in your application stack.
```bash
kubectl apply -f multiple-databases.yaml
```
### 4. cross-namespace-secrets.yaml
Accessing secrets from different namespaces.
**Use case**: Centralized credential management in a shared namespace.
```bash
kubectl apply -f cross-namespace-secrets.yaml
```
**RBAC Requirements**: The support bundle service account needs `get` permission on secrets in the referenced namespaces.
### 5. optional-secrets.yaml
Using the `optional` field for graceful degradation.
**Use case**: Collect diagnostics even when some credentials are unavailable (e.g., optional secondary databases).
```bash
kubectl apply -f optional-secrets.yaml
```
### 6. configmap-example.yaml
Using ConfigMaps for non-sensitive configuration.
**Use case**: Store non-sensitive connection strings (e.g., development databases) in ConfigMaps.
```bash
kubectl apply -f configmap-example.yaml
```
## Key Concepts
### Secret vs ConfigMap
- **Secrets**: Use for sensitive data (passwords, tokens, certificates)
- **ConfigMaps**: Use for non-sensitive configuration (development endpoints, feature flags)
### Optional Field
```yaml
uri:
valueFrom:
secretKeyRef:
name: my-secret
key: uri
optional: true # Returns empty string if secret/key doesn't exist
```
- `optional: false` (default): Collection fails if secret is missing
- `optional: true`: Returns empty string if secret/key is missing
### Cross-Namespace Access
```yaml
uri:
valueFrom:
secretKeyRef:
name: shared-secret
key: uri
namespace: other-namespace # Access secrets in different namespaces
```
If `namespace` is not specified, uses the support bundle's namespace.
### Backward Compatibility
v1beta3 maintains backward compatibility with v1beta2 TLS configuration:
```yaml
tls:
secret: # v1beta2 style
name: tls-secret
namespace: default
```
## RBAC Configuration
Support bundles need appropriate RBAC permissions to read secrets:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: troubleshoot-secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["postgres-connection", "redis-creds"] # Restrict to specific secrets
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: troubleshoot-secret-reader-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: troubleshoot-secret-reader
subjects:
- kind: ServiceAccount
name: troubleshoot
namespace: default
```
## Migration from v1beta2
### Before (v1beta2):
```yaml
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
spec:
collectors:
- postgres:
uri: "postgresql://user:password@host:5432/db" # Hardcoded
```
### After (v1beta3):
```yaml
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
spec:
collectors:
- postgres:
uri:
valueFrom:
secretKeyRef:
name: postgres-connection
key: connection-uri
```
## Limitations
1. **No value composition**: Cannot combine multiple secrets into a single value
```yaml
# NOT SUPPORTED
uri: "postgresql://$(USERNAME):$(PASSWORD)@host:5432/db"
```
Store the complete connection string in a single secret key.
2. **Collector scope**: Only database collectors support `StringOrValueFrom` initially
- Future versions will extend to HTTP, Data, and other collectors
3. **No templating**: The entire field value comes from one source
## Security Best Practices
1. **Use resourceNames in RBAC**: Restrict access to specific secrets
2. **Separate secrets**: Don't reuse secrets across applications
3. **Rotate credentials**: Update secrets regularly
4. **Audit access**: Monitor secret access logs
5. **Redact output**: Ensure connection strings are redacted in bundle output
## Troubleshooting
### Error: "failed to get secret default/my-secret"
- **Cause**: Secret doesn't exist or RBAC denied access
- **Solution**: Verify secret exists: `kubectl get secret my-secret`
- **Solution**: Check RBAC: `kubectl auth can-i get secret/my-secret`
### Error: "key 'uri' not found in secret"
- **Cause**: Secret exists but doesn't contain the specified key
- **Solution**: Check secret keys: `kubectl get secret my-secret -o jsonpath='{.data}'`
### Error: "cannot specify both 'value' and 'valueFrom'"
- **Cause**: Both literal value and secret reference provided
- **Solution**: Use only one: either `value: "string"` or `valueFrom: {...}`
## Additional Resources
- [Troubleshoot Documentation](https://troubleshoot.sh)
- [v1beta3 API Reference](https://troubleshoot.sh/docs/v1beta3/)
- [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/)
- [RBAC Authorization](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
@@ -1,57 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: configmap-example
spec:
collectors:
# Database URI from ConfigMap (non-sensitive connection string)
- postgres:
collectorName: dev-database
uri:
valueFrom:
configMapKeyRef:
name: database-config
key: dev-connection-uri
# Redis URI from ConfigMap
- redis:
collectorName: dev-redis
uri:
valueFrom:
configMapKeyRef:
name: cache-config
key: redis-uri
# Mixed: URI from ConfigMap, password from Secret
# Note: This shows the limitation - you can't compose values from multiple sources
# The full connection string must be in one place
- mysql:
collectorName: staging-mysql
uri:
valueFrom:
secretKeyRef:
name: mysql-secret
key: complete-connection-string
---
apiVersion: v1
kind: ConfigMap
metadata:
name: database-config
data:
dev-connection-uri: "postgresql://devuser@dev-postgres.default.svc:5432/devdb"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cache-config
data:
redis-uri: "redis://dev-redis.default.svc:6379/0"
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
stringData:
# Complete connection string with password included
complete-connection-string: "mysql://staging:stagingpass@staging-mysql:3306/stagingdb"
@@ -1,45 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: cross-namespace-example
spec:
collectors:
# Database in one namespace, secret in another
- postgres:
collectorName: shared-database
uri:
valueFrom:
secretKeyRef:
name: shared-postgres-connection
key: uri
namespace: shared-services # Secret is in a different namespace
# Redis accessing centralized credentials
- redis:
collectorName: shared-cache
uri:
valueFrom:
secretKeyRef:
name: shared-redis-creds
key: uri
namespace: platform-credentials
---
# This secret would be in the 'shared-services' namespace
apiVersion: v1
kind: Secret
metadata:
name: shared-postgres-connection
namespace: shared-services
type: Opaque
stringData:
uri: "postgresql://shared:password@shared-postgres.shared-services.svc:5432/shared_db"
---
# This secret would be in the 'platform-credentials' namespace
apiVersion: v1
kind: Secret
metadata:
name: shared-redis-creds
namespace: platform-credentials
type: Opaque
stringData:
uri: "redis://shared-redis.shared-services.svc:6379/0"
@@ -1,78 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: multi-database-support-bundle
spec:
collectors:
# PostgreSQL with secret reference
- postgres:
collectorName: primary-db
uri:
valueFrom:
secretKeyRef:
name: postgres-primary
key: connection-uri
# PostgreSQL replica with secret reference
- postgres:
collectorName: replica-db
uri:
valueFrom:
secretKeyRef:
name: postgres-replica
key: connection-uri
# Redis cache with secret reference
- redis:
collectorName: cache
uri:
valueFrom:
secretKeyRef:
name: redis-creds
key: uri
# MySQL with literal value (for development/testing)
- mysql:
collectorName: local-mysql
uri: "mysql://root:password@localhost:3306/testdb"
# MSSQL with secret reference
- mssql:
collectorName: legacy-db
uri:
valueFrom:
secretKeyRef:
name: mssql-connection
key: dsn
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-primary
type: Opaque
stringData:
connection-uri: "postgresql://app:secret123@postgres-primary.default.svc:5432/appdb"
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-replica
type: Opaque
stringData:
connection-uri: "postgresql://app:secret123@postgres-replica.default.svc:5432/appdb"
---
apiVersion: v1
kind: Secret
metadata:
name: redis-creds
type: Opaque
stringData:
uri: "redis://:cachesecret@redis.default.svc:6379/0"
---
apiVersion: v1
kind: Secret
metadata:
name: mssql-connection
type: Opaque
stringData:
dsn: "sqlserver://sa:Str0ngP@ssw0rd@mssql.default.svc:1433?database=legacy"
@@ -1,58 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: optional-secrets-example
spec:
collectors:
# Required database - collection will fail if secret doesn't exist
- postgres:
collectorName: required-db
uri:
valueFrom:
secretKeyRef:
name: required-postgres
key: uri
optional: false # Default behavior - secret must exist
# Optional database - collection continues if secret doesn't exist
- postgres:
collectorName: optional-db
uri:
valueFrom:
secretKeyRef:
name: optional-postgres
key: uri
optional: true # Gracefully degrades if secret is missing
# Mixed required and optional TLS
- postgres:
collectorName: partially-optional
uri: "postgresql://localhost:5432/db"
tls:
cacert:
valueFrom:
secretKeyRef:
name: tls-certs
key: ca.crt
optional: false # CA cert is required
clientCert:
valueFrom:
secretKeyRef:
name: tls-certs
key: client.crt
optional: true # Client cert is optional (cert-only TLS)
clientKey:
valueFrom:
secretKeyRef:
name: tls-certs
key: client.key
optional: true # Client key is optional
---
apiVersion: v1
kind: Secret
metadata:
name: required-postgres
type: Opaque
stringData:
uri: "postgresql://user:pass@required-postgres:5432/db"
# Note: optional-postgres secret intentionally not created
@@ -1,21 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: postgres-support-bundle
spec:
collectors:
- postgres:
collectorName: main-database
uri:
valueFrom:
secretKeyRef:
name: postgres-connection
key: connection-uri
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-connection
type: Opaque
stringData:
connection-uri: "postgresql://myuser:mypassword@postgres.default.svc:5432/mydb?sslmode=require"
@@ -1,47 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: postgres-tls-support-bundle
spec:
collectors:
- postgres:
collectorName: secure-database
uri:
valueFrom:
secretKeyRef:
name: postgres-connection
key: connection-uri
tls:
cacert:
valueFrom:
secretKeyRef:
name: postgres-tls
key: ca.crt
clientCert:
valueFrom:
secretKeyRef:
name: postgres-tls
key: tls.crt
clientKey:
valueFrom:
secretKeyRef:
name: postgres-tls
key: tls.key
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-connection
type: Opaque
stringData:
connection-uri: "postgresql://myuser:mypassword@postgres.default.svc:5432/mydb?sslmode=verify-full"
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-tls
type: kubernetes.io/tls
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi4uLgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi4uLgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi4uLgotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t
@@ -1,33 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
name: helm-builtins-example
labels:
release: {{ .Release.Name }}
spec:
analyzers:
- docString: |
Title: Example using Helm builtin objects
Requirement: Demonstrates .Values, .Release, .Chart, etc.
Supported Helm builtin objects:
- .Values.* - User-provided values
- .Release.Name - Release name (default: "preflight")
- .Release.Namespace - Release namespace (default: "default")
- .Release.IsInstall - Whether this is an install (true)
- .Release.IsUpgrade - Whether this is an upgrade (false)
- .Release.Revision - Release revision (1)
- .Chart.Name - Chart name
- .Chart.Version - Chart version
- .Capabilities.KubeVersion - Kubernetes version capabilities
clusterVersion:
checkName: Kubernetes version check in {{ .Release.Namespace }}
outcomes:
- fail:
when: '< {{ .Values.minVersion | default "1.19.0" }}'
message: |
Release {{ .Release.Name }} requires Kubernetes {{ .Values.minVersion | default "1.19.0" }} or later.
Chart: {{ .Chart.Name }}
- pass:
when: '>= {{ .Values.minVersion | default "1.19.0" }}'
message: Kubernetes version is supported for release {{ .Release.Name }}
@@ -1,19 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: invalid-collectors
spec:
collectors:
# Unknown collector type
- notACollector: {}
# Known collector but missing required fields (e.g., ceph requires namespace)
- ceph: {}
# Field exists but wrong type (should be a list)
hostCollectors: "not-a-list"
analyzers:
# Unknown analyzer type
- notAnAnalyzer: {}
# Known analyzer missing required 'outcomes'
- cephStatus:
namespace: default
@@ -1,8 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata
name: invalid-yaml
spec:
analyzers:
- clusterVersion:
checkName: Kubernetes version
@@ -1,11 +0,0 @@
kind: Preflight
metadata:
name: missing-apiversion
spec:
analyzers:
- clusterVersion:
checkName: Kubernetes version
outcomes:
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
@@ -1,10 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
spec:
analyzers:
- clusterVersion:
checkName: Kubernetes version
outcomes:
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
@@ -1,7 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
name: no-analyzers
spec:
collectors:
- clusterInfo: {}
@@ -1,18 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
name: simple-no-template
spec:
analyzers:
- docString: |
Title: Kubernetes Version Check
Requirement: Kubernetes 1.19.0 or later
clusterVersion:
checkName: Kubernetes version
outcomes:
- fail:
when: '< 1.19.0'
message: Kubernetes version must be at least 1.19.0
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
@@ -1,12 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: no-collectors
spec:
analyzers:
- clusterVersion:
checkName: Kubernetes version
outcomes:
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
@@ -1,15 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: SupportBundle
metadata:
name: valid-support-bundle
spec:
collectors:
- clusterInfo: {}
- clusterResources: {}
analyzers:
- clusterVersion:
checkName: Kubernetes version
outcomes:
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
@@ -1,15 +0,0 @@
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
name: valid-preflight
spec:
analyzers:
- docString: |
Title: Test Analyzer
Requirement: Test requirement
clusterVersion:
checkName: Kubernetes version
outcomes:
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
@@ -1,2 +0,0 @@
# Empty values file for v1beta3 specs without templates
{}
@@ -1 +0,0 @@
minVersion: "1.19.0"
@@ -1,12 +0,0 @@
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: wrong-version
spec:
analyzers:
- clusterVersion:
checkName: Kubernetes version
outcomes:
- pass:
when: '>= 1.19.0'
message: Kubernetes version is supported
+126 -116
View File
@@ -1,6 +1,6 @@
module github.com/replicatedhq/troubleshoot
go 1.26.1
go 1.24.6
require (
github.com/Masterminds/sprig/v3 v3.3.0
@@ -8,124 +8,124 @@ require (
github.com/apparentlymart/go-cidr v1.1.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/cilium/ebpf v0.19.0
github.com/containerd/cgroups/v3 v3.0.5
github.com/containers/image/v5 v5.36.2
github.com/distribution/distribution/v3 v3.0.0
github.com/fatih/color v1.18.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/godbus/dbus/v5 v5.1.0
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.2
github.com/hashicorp/go-multierror v1.1.1
github.com/jackc/pgx/v5 v5.8.0
github.com/jackc/pgx/v5 v5.7.6
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/microsoft/go-mssqldb v1.9.8
github.com/miekg/dns v1.1.72
github.com/microsoft/go-mssqldb v1.9.3
github.com/miekg/dns v1.1.68
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/spf13/cobra v1.10.2
github.com/shirou/gopsutil/v4 v4.25.9
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/tj/go-spin v1.1.0
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
github.com/vmware-tanzu/velero v1.17.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
golang.org/x/mod v0.33.0
golang.org/x/sync v0.20.0
golang.org/x/mod v0.28.0
golang.org/x/sync v0.17.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/klog/v2 v2.140.0
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.23.3
k8s.io/api v0.34.1
k8s.io/apiextensions-apiserver v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
k8s.io/cli-runtime v0.34.1
k8s.io/client-go v0.34.1
k8s.io/klog/v2 v2.130.1
k8s.io/kubernetes v1.34.1
oras.land/oras-go v1.2.6
sigs.k8s.io/controller-runtime v0.22.2
sigs.k8s.io/e2e-framework v0.6.0
)
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth v0.16.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/compute/metadata v0.7.0 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cyphar.com/go-pathrs v0.2.1 // 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
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.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 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.15 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.68 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20 // indirect
github.com/aws/smithy-go v1.22.3 // 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-20250326154945-ae57f3c0d45f // 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/containerd/typeurl/v2 v2.2.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.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/ebitengine/purego v0.10.0 // indirect
github.com/ebitengine/purego v0.9.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/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // 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.0.5 // 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/go-containerregistry v0.20.3 // 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.6 // 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.65 // 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,7 +134,7 @@ 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/mistifyio/go-zfs/v3 v3.0.1 // 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
@@ -143,59 +143,66 @@ require (
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
github.com/rubenv/sql-migrate v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/sylabs/sif/v2 v2.22.0 // indirect
github.com/sylabs/sif/v2 v2.21.1 // 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/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
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.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.yaml.in/yaml/v2 v2.4.3 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // 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
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
golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
k8s.io/component-base v0.34.1 // indirect
k8s.io/kubectl v0.34.0 // indirect
oras.land/oras-go/v2 v2.6.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.0 // 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 v0.121.1 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.13.0 // 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
github.com/cespare/xxhash/v2 v2.3.0 // indirect
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/containerd/containerd v1.7.28 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.1 // indirect
github.com/containers/storage v1.59.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.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/cli v28.3.2+incompatible // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -205,19 +212,21 @@ require (
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // 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/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // 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.7.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/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.9.0 // indirect
@@ -225,6 +234,7 @@ require (
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/locker v1.0.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/term v0.5.2 // indirect
@@ -234,45 +244,45 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
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/opencontainers/runtime-spec v1.2.1
github.com/opencontainers/selinux v1.12.0 // 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_golang v1.22.0 // 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.65.0 // indirect
github.com/prometheus/procfs v0.15.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
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/vbatts/tar-split v0.12.1 // 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
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/net v0.44.0
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.36.0
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0
golang.org/x/time v0.12.0 // indirect
google.golang.org/api v0.241.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // 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.19.0
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/kubelet v0.34.1
k8s.io/metrics v0.34.1
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
periph.io/x/host/v3 v3.8.5
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/api v0.20.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/yaml v1.6.0
+425 -278
View File
File diff suppressed because it is too large Load Diff
+3 -9
View File
@@ -191,9 +191,7 @@ func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []st
// load URL spec first to remove URI key from the spec
urlSpec, err := loader.LoadSpecs(ctx, loader.LoadOptions{
RawSpec: rawURLSpec,
Client: client,
Namespace: vp.GetString("namespace"),
RawSpec: rawURLSpec,
})
if err != nil {
fmt.Println(color.YellowString("failed to load spec from URI %q: %v\n", v, err))
@@ -211,9 +209,7 @@ func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []st
}
kinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{
RawSpecs: rawSpecs,
Client: client,
Namespace: vp.GetString("namespace"),
RawSpecs: rawSpecs,
})
if err != nil {
return nil, err
@@ -367,9 +363,7 @@ func LoadFromCluster(ctx context.Context, client kubernetes.Interface, selectors
// Load troubleshoot specs from the raw specs
return loader.LoadSpecs(ctx, loader.LoadOptions{
RawSpecs: rawSpecs,
Client: client,
Namespace: ns,
RawSpecs: rawSpecs,
})
}
-2
View File
@@ -194,8 +194,6 @@ func GetAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
return &AnalyzeClusterVersion{analyzer: analyzer.ClusterVersion}
case analyzer.StorageClass != nil:
return &AnalyzeStorageClass{analyzer: analyzer.StorageClass}
case analyzer.IngressClass != nil:
return &AnalyzeIngressClass{analyzer: analyzer.IngressClass}
case analyzer.CustomResourceDefinition != nil:
return &AnalyzeCustomResourceDefinition{analyzer: analyzer.CustomResourceDefinition}
case analyzer.Ingress != nil:
-87
View File
@@ -1,87 +0,0 @@
package analyzer
import (
"encoding/json"
"fmt"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/constants"
networkingv1 "k8s.io/api/networking/v1"
)
type AnalyzeIngressClass struct {
analyzer *troubleshootv1beta2.IngressClass
}
func (a *AnalyzeIngressClass) Title() string {
title := a.analyzer.CheckName
if title == "" {
if a.analyzer.IngressClassName != "" {
title = fmt.Sprintf("Ingress class %s", a.analyzer.IngressClassName)
} else {
title = "Default Ingress Class"
}
}
return title
}
func (a *AnalyzeIngressClass) IsExcluded() (bool, error) {
return isExcluded(a.analyzer.Exclude)
}
func (a *AnalyzeIngressClass) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
result, err := a.analyzeIngressClass(a.analyzer, getFile)
if err != nil {
return nil, err
}
result.Strict = a.analyzer.Strict.BoolOrDefaultFalse()
return []*AnalyzeResult{result}, nil
}
func (a *AnalyzeIngressClass) analyzeIngressClass(analyzer *troubleshootv1beta2.IngressClass, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
ingressClassesData, err := getCollectedFileContents(fmt.Sprintf("%s/%s.json", constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_INGRESS_CLASS))
if err != nil {
return nil, err
}
var ingressClasses networkingv1.IngressClassList
if err := json.Unmarshal(ingressClassesData, &ingressClasses); err != nil {
return nil, err
}
result := AnalyzeResult{
Title: a.Title(),
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
}
for _, ingressClass := range ingressClasses.Items {
val := ingressClass.Annotations["ingressclass.kubernetes.io/is-default-class"]
if (ingressClass.Name == analyzer.IngressClassName) || (analyzer.IngressClassName == "" && val == "true") {
result.IsPass = true
for _, outcome := range analyzer.Outcomes {
if outcome.Pass != nil {
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
}
}
if analyzer.IngressClassName == "" && result.Message == "" {
result.Message = "Default Ingress Class found"
}
return &result, nil
}
}
result.IsFail = true
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
}
}
if analyzer.IngressClassName == "" && result.Message == "" {
result.Message = "No Default Ingress Class found"
}
return &result, nil
}
-202
View File
@@ -1,202 +0,0 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestAnalyzeIngressClass(t *testing.T) {
tests := []struct {
name string
analyzer *troubleshootv1beta2.IngressClass
ingressList *networkingv1.IngressClassList
expectResult AnalyzeResult
}{
{
name: "named ingress class found",
analyzer: &troubleshootv1beta2.IngressClass{
IngressClassName: "nginx",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "nginx ingress class found",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "nginx ingress class not found",
},
},
},
},
ingressList: &networkingv1.IngressClassList{
Items: []networkingv1.IngressClass{
{ObjectMeta: metav1.ObjectMeta{Name: "nginx"}},
},
},
expectResult: AnalyzeResult{
IsPass: true,
Title: "Ingress class nginx",
Message: "nginx ingress class found",
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
},
},
{
name: "named ingress class not found",
analyzer: &troubleshootv1beta2.IngressClass{
IngressClassName: "nginx",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "nginx ingress class found",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "nginx ingress class not found",
},
},
},
},
ingressList: &networkingv1.IngressClassList{
Items: []networkingv1.IngressClass{
{ObjectMeta: metav1.ObjectMeta{Name: "traefik"}},
},
},
expectResult: AnalyzeResult{
IsFail: true,
Title: "Ingress class nginx",
Message: "nginx ingress class not found",
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
},
},
{
name: "default ingress class found",
analyzer: &troubleshootv1beta2.IngressClass{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "default ingress class exists",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "no default ingress class",
},
},
},
},
ingressList: &networkingv1.IngressClassList{
Items: []networkingv1.IngressClass{
{
ObjectMeta: metav1.ObjectMeta{
Name: "nginx",
Annotations: map[string]string{
"ingressclass.kubernetes.io/is-default-class": "true",
},
},
},
},
},
expectResult: AnalyzeResult{
IsPass: true,
Title: "Default Ingress Class",
Message: "default ingress class exists",
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
},
},
{
name: "default ingress class not found",
analyzer: &troubleshootv1beta2.IngressClass{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "default ingress class exists",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "no default ingress class",
},
},
},
},
ingressList: &networkingv1.IngressClassList{
Items: []networkingv1.IngressClass{
{ObjectMeta: metav1.ObjectMeta{Name: "nginx"}},
},
},
expectResult: AnalyzeResult{
IsFail: true,
Title: "Default Ingress Class",
Message: "no default ingress class",
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
},
},
{
name: "default ingress class not found with default message",
analyzer: &troubleshootv1beta2.IngressClass{
Outcomes: []*troubleshootv1beta2.Outcome{},
},
ingressList: &networkingv1.IngressClassList{},
expectResult: AnalyzeResult{
IsFail: true,
Title: "Default Ingress Class",
Message: "No Default Ingress Class found",
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
},
},
{
name: "default ingress class found with default message",
analyzer: &troubleshootv1beta2.IngressClass{
Outcomes: []*troubleshootv1beta2.Outcome{},
},
ingressList: &networkingv1.IngressClassList{
Items: []networkingv1.IngressClass{
{
ObjectMeta: metav1.ObjectMeta{
Name: "nginx",
Annotations: map[string]string{
"ingressclass.kubernetes.io/is-default-class": "true",
},
},
},
},
},
expectResult: AnalyzeResult{
IsPass: true,
Title: "Default Ingress Class",
Message: "Default Ingress Class found",
IconKey: "kubernetes_ingress_class",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/ingress-class.svg?w=12&h=12",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := json.Marshal(tt.ingressList)
require.NoError(t, err)
getFile := func(_ string) ([]byte, error) {
return b, nil
}
a := AnalyzeIngressClass{analyzer: tt.analyzer}
result, err := a.analyzeIngressClass(tt.analyzer, getFile)
require.NoError(t, err)
assert.Equal(t, tt.expectResult, *result)
})
}
}
+2 -2
View File
@@ -12,11 +12,11 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/kubernetes/pkg/util/taints"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
)
type AnalyzeNodeResources struct {
@@ -453,7 +453,7 @@ func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta2.NodeResou
}
if filters.Taint != nil {
return k8sutil.TaintExists(node.Spec.Taints, filters.Taint), nil
return taints.TaintExists(node.Spec.Taints, filters.Taint), nil
}
if filters.CPUArchitecture != "" {
+1 -8
View File
@@ -146,7 +146,7 @@ func (h *OllamaHelper) downloadAndInstallWindows() error {
return errors.Wrap(err, "failed to create temporary file")
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close() // Ensures file is closed in error paths
defer tmpFile.Close()
// Download installer
resp, err := http.Get(h.downloadURL)
@@ -165,13 +165,6 @@ func (h *OllamaHelper) downloadAndInstallWindows() error {
return errors.Wrap(err, "failed to write installer")
}
// Close the file before executing it (required on Windows)
// Note: This will be called twice (here and via defer), but that's safe
// The defer ensures cleanup on error paths, this ensures closure before execution
if err := tmpFile.Close(); err != nil {
return errors.Wrap(err, "failed to close installer file")
}
// Run installer
klog.Info("Running Ollama installer...")
cmd := exec.Command(tmpFile.Name())
@@ -18,12 +18,6 @@ type StorageClass struct {
StorageClassName string `json:"storageClassName,omitempty" yaml:"storageClassName,omitempty"`
}
type IngressClass struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
IngressClassName string `json:"ingressClassName,omitempty" yaml:"ingressClassName,omitempty"`
}
type CustomResourceDefinition struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
@@ -282,7 +276,6 @@ type PVCRef struct {
type Analyze struct {
ClusterVersion *ClusterVersion `json:"clusterVersion,omitempty" yaml:"clusterVersion,omitempty"`
StorageClass *StorageClass `json:"storageClass,omitempty" yaml:"storageClass,omitempty"`
IngressClass *IngressClass `json:"ingressClass,omitempty" yaml:"ingressClass,omitempty"`
CustomResourceDefinition *CustomResourceDefinition `json:"customResourceDefinition,omitempty" yaml:"customResourceDefinition,omitempty"`
Ingress *Ingress `json:"ingress,omitempty" yaml:"ingress,omitempty"`
Secret *AnalyzeSecret `json:"secret,omitempty" yaml:"secret,omitempty"`
@@ -318,43 +318,37 @@ type Etcd struct {
Image string `json:"image" yaml:"image"`
}
type SupportBundleMetadata struct {
CollectorMeta `json:",inline" yaml:",inline"`
Namespace string `json:"namespace" yaml:"namespace"`
}
type Collect struct {
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty"`
CustomMetrics *CustomMetrics `json:"customMetrics,omitempty" yaml:"customMetrics,omitempty"`
ConfigMap *ConfigMap `json:"configMap,omitempty" yaml:"configMap,omitempty"`
Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"`
Run *Run `json:"run,omitempty" yaml:"run,omitempty"`
RunPod *RunPod `json:"runPod,omitempty" yaml:"runPod,omitempty"`
RunDaemonSet *RunDaemonSet `json:"runDaemonSet,omitempty" yaml:"runDaemonSet,omitempty"`
Exec *Exec `json:"exec,omitempty" yaml:"exec,omitempty"`
Data *Data `json:"data,omitempty" yaml:"data,omitempty"`
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
CopyFromHost *CopyFromHost `json:"copyFromHost,omitempty" yaml:"copyFromHost,omitempty"`
HTTP *HTTP `json:"http,omitempty" yaml:"http,omitempty"`
Postgres *Database `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mssql *Database `json:"mssql,omitempty" yaml:"mssql,omitempty"`
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
Longhorn *Longhorn `json:"longhorn,omitempty" yaml:"longhorn,omitempty"`
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
Sysctl *Sysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
Certificates *Certificates `json:"certificates,omitempty" yaml:"certificates,omitempty"`
Helm *Helm `json:"helm,omitempty" yaml:"helm,omitempty"`
Goldpinger *Goldpinger `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"`
Sonobuoy *Sonobuoy `json:"sonobuoy,omitempty" yaml:"sonobuoy,omitempty"`
NodeMetrics *NodeMetrics `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"`
DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
Etcd *Etcd `json:"etcd,omitempty" yaml:"etcd,omitempty"`
SupportBundleMetadata *SupportBundleMetadata `json:"supportBundleMetadata,omitempty" yaml:"supportBundleMetadata,omitempty"`
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty"`
CustomMetrics *CustomMetrics `json:"customMetrics,omitempty" yaml:"customMetrics,omitempty"`
ConfigMap *ConfigMap `json:"configMap,omitempty" yaml:"configMap,omitempty"`
Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"`
Run *Run `json:"run,omitempty" yaml:"run,omitempty"`
RunPod *RunPod `json:"runPod,omitempty" yaml:"runPod,omitempty"`
RunDaemonSet *RunDaemonSet `json:"runDaemonSet,omitempty" yaml:"runDaemonSet,omitempty"`
Exec *Exec `json:"exec,omitempty" yaml:"exec,omitempty"`
Data *Data `json:"data,omitempty" yaml:"data,omitempty"`
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
CopyFromHost *CopyFromHost `json:"copyFromHost,omitempty" yaml:"copyFromHost,omitempty"`
HTTP *HTTP `json:"http,omitempty" yaml:"http,omitempty"`
Postgres *Database `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mssql *Database `json:"mssql,omitempty" yaml:"mssql,omitempty"`
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
Collectd *Collectd `json:"collectd,omitempty" yaml:"collectd,omitempty"`
Ceph *Ceph `json:"ceph,omitempty" yaml:"ceph,omitempty"`
Longhorn *Longhorn `json:"longhorn,omitempty" yaml:"longhorn,omitempty"`
RegistryImages *RegistryImages `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
Sysctl *Sysctl `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
Certificates *Certificates `json:"certificates,omitempty" yaml:"certificates,omitempty"`
Helm *Helm `json:"helm,omitempty" yaml:"helm,omitempty"`
Goldpinger *Goldpinger `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"`
Sonobuoy *Sonobuoy `json:"sonobuoy,omitempty" yaml:"sonobuoy,omitempty"`
NodeMetrics *NodeMetrics `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"`
DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
Etcd *Etcd `json:"etcd,omitempty" yaml:"etcd,omitempty"`
}
func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSubjectAccessReviewSpec {
@@ -574,19 +568,6 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
})
} else if c.Sysctl != nil {
// TODO
} else if c.SupportBundleMetadata != nil {
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: pickNamespaceOrDefault(c.SupportBundleMetadata.Namespace, overrideNS),
Verb: "get",
Group: "",
Version: "",
Resource: "secrets",
Subresource: "",
Name: "replicated-support-metadata",
},
NonResourceAttributes: nil,
})
}
return result
@@ -690,10 +671,6 @@ func (c *Collect) GetName() string {
collector = "certificates"
name = c.Certificates.CollectorName
}
if c.SupportBundleMetadata != nil {
collector = "support-bundle-metadata"
name = c.SupportBundleMetadata.CollectorName
}
if collector == "" {
return "<none>"
@@ -65,11 +65,6 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(StorageClass)
(*in).DeepCopyInto(*out)
}
if in.IngressClass != nil {
in, out := &in.IngressClass, &out.IngressClass
*out = new(IngressClass)
(*in).DeepCopyInto(*out)
}
if in.CustomResourceDefinition != nil {
in, out := &in.CustomResourceDefinition, &out.CustomResourceDefinition
*out = new(CustomResourceDefinition)
@@ -985,11 +980,6 @@ func (in *Collect) DeepCopyInto(out *Collect) {
*out = new(Etcd)
(*in).DeepCopyInto(*out)
}
if in.SupportBundleMetadata != nil {
in, out := &in.SupportBundleMetadata, &out.SupportBundleMetadata
*out = new(SupportBundleMetadata)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
@@ -3049,33 +3039,6 @@ func (in *Ingress) DeepCopy() *Ingress {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IngressClass) DeepCopyInto(out *IngressClass) {
*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 IngressClass.
func (in *IngressClass) DeepCopy() *IngressClass {
if in == nil {
return nil
}
out := new(IngressClass)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JobStatus) DeepCopyInto(out *JobStatus) {
*out = *in
@@ -4948,22 +4911,6 @@ func (in *SupportBundleList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleMetadata) DeepCopyInto(out *SupportBundleMetadata) {
*out = *in
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportBundleMetadata.
func (in *SupportBundleMetadata) DeepCopy() *SupportBundleMetadata {
if in == nil {
return nil
}
out := new(SupportBundleMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleSpec) DeepCopyInto(out *SupportBundleSpec) {
*out = *in
@@ -1,78 +0,0 @@
package v1beta3
import (
"github.com/replicatedhq/troubleshoot/pkg/multitype"
)
// CollectorMeta contains metadata for collectors
type CollectorMeta struct {
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
// +optional
Exclude *multitype.BoolOrString `json:"exclude,omitempty" yaml:"exclude,omitempty"`
}
// Database represents database collectors (PostgreSQL, MySQL, Redis, MSSQL)
// In v1beta3, URI and TLS fields support valueFrom references
type Database struct {
CollectorMeta `json:",inline" yaml:",inline"`
// URI can be a literal value or reference to a Secret/ConfigMap
URI StringOrValueFrom `json:"uri" yaml:"uri"`
// Parameters for the database connection
Parameters []string `json:"parameters,omitempty"`
// TLS configuration with support for valueFrom references
TLS *TLSParams `json:"tls,omitempty" yaml:"tls,omitempty"`
}
// TLSParams contains TLS configuration
// In v1beta3, certificate fields support valueFrom references
type TLSParams struct {
// SkipVerify disables TLS verification
SkipVerify bool `json:"skipVerify,omitempty" yaml:"skipVerify,omitempty"`
// Secret references a Kubernetes Secret containing TLS materials (v1beta2 compatibility)
Secret *TLSSecret `json:"secret,omitempty" yaml:"secret,omitempty"`
// CACert can be a literal value or reference to a Secret/ConfigMap
CACert StringOrValueFrom `json:"cacert,omitempty" yaml:"cacert,omitempty"`
// ClientCert can be a literal value or reference to a Secret/ConfigMap
ClientCert StringOrValueFrom `json:"clientCert,omitempty" yaml:"clientCert,omitempty"`
// ClientKey can be a literal value or reference to a Secret/ConfigMap
ClientKey StringOrValueFrom `json:"clientKey,omitempty" yaml:"clientKey,omitempty"`
}
// TLSSecret references a Kubernetes Secret containing TLS materials
// Maintained for backward compatibility
type TLSSecret struct {
Name string `json:"name" yaml:"name"`
Namespace string `json:"namespace" yaml:"namespace"`
}
// Temporary placeholder types for minimal v1beta3 implementation
// These will be properly defined as we expand v1beta3 support
type AfterCollection struct {
CollectorMeta `json:",inline" yaml:",inline"`
// TODO: Add fields as needed
}
type Analyze struct {
// TODO: Add fields as needed
}
type HostAnalyze struct {
// TODO: Add fields as needed
}
type HostCollect struct {
// TODO: Add fields as needed
}
// Collect contains all collector definitions
// For phase 1, we're focusing on Database collectors with StringOrValueFrom support
type Collect struct {
// Database collectors with v1beta3 StringOrValueFrom support
Postgres *Database `json:"postgres,omitempty" yaml:"postgres,omitempty"`
Mssql *Database `json:"mssql,omitempty" yaml:"mssql,omitempty"`
Mysql *Database `json:"mysql,omitempty" yaml:"mysql,omitempty"`
Redis *Database `json:"redis,omitempty" yaml:"redis,omitempty"`
// TODO: Add remaining collector types as we expand v1beta3 support
// For now, these are placeholders to make the types compile
}
-162
View File
@@ -1,162 +0,0 @@
package v1beta3
import (
"context"
"fmt"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/client-go/kubernetes"
)
// ConvertToV1Beta2WithResolution converts a v1beta3 SupportBundleSpec to v1beta2
// by resolving all StringOrValueFrom fields to their actual values
func ConvertToV1Beta2WithResolution(
ctx context.Context,
v3spec *SupportBundleSpec,
client kubernetes.Interface,
defaultNamespace string,
) (*troubleshootv1beta2.SupportBundleSpec, error) {
v2spec := &troubleshootv1beta2.SupportBundleSpec{
Uri: v3spec.Uri,
RunHostCollectorsInPod: v3spec.RunHostCollectorsInPod,
}
// Convert collectors
if v3spec.Collectors != nil {
v2collectors := make([]*troubleshootv1beta2.Collect, 0, len(v3spec.Collectors))
for _, v3collector := range v3spec.Collectors {
v2collector, err := convertCollector(ctx, v3collector, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to convert collector: %w", err)
}
v2collectors = append(v2collectors, v2collector)
}
v2spec.Collectors = v2collectors
}
// TODO: Convert AfterCollection, HostCollectors, Analyzers, HostAnalyzers when v1beta3 support is expanded
return v2spec, nil
}
// convertCollector converts a v1beta3 Collect to v1beta2 Collect
func convertCollector(
ctx context.Context,
v3collector *Collect,
client kubernetes.Interface,
defaultNamespace string,
) (*troubleshootv1beta2.Collect, error) {
v2collector := &troubleshootv1beta2.Collect{}
// Convert database collectors
if v3collector.Postgres != nil {
db, err := convertDatabase(ctx, v3collector.Postgres, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to convert postgres collector: %w", err)
}
v2collector.Postgres = db
}
if v3collector.Mysql != nil {
db, err := convertDatabase(ctx, v3collector.Mysql, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to convert mysql collector: %w", err)
}
v2collector.Mysql = db
}
if v3collector.Mssql != nil {
db, err := convertDatabase(ctx, v3collector.Mssql, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to convert mssql collector: %w", err)
}
v2collector.Mssql = db
}
if v3collector.Redis != nil {
db, err := convertDatabase(ctx, v3collector.Redis, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to convert redis collector: %w", err)
}
v2collector.Redis = db
}
// TODO: Add conversion for other collector types as v1beta3 support expands
return v2collector, nil
}
// convertDatabase converts a v1beta3 Database to v1beta2 Database
func convertDatabase(
ctx context.Context,
v3db *Database,
client kubernetes.Interface,
defaultNamespace string,
) (*troubleshootv1beta2.Database, error) {
// Resolve URI
uri, err := ResolveStringOrValueFrom(ctx, v3db.URI, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to resolve database URI: %w", err)
}
v2db := &troubleshootv1beta2.Database{
CollectorMeta: troubleshootv1beta2.CollectorMeta{
CollectorName: v3db.CollectorName,
Exclude: v3db.Exclude,
},
URI: uri,
Parameters: v3db.Parameters,
}
// Convert TLS params if present
if v3db.TLS != nil {
tlsParams, err := convertTLSParams(ctx, v3db.TLS, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to convert TLS params: %w", err)
}
v2db.TLS = tlsParams
}
return v2db, nil
}
// convertTLSParams converts v1beta3 TLSParams to v1beta2 TLSParams
func convertTLSParams(
ctx context.Context,
v3tls *TLSParams,
client kubernetes.Interface,
defaultNamespace string,
) (*troubleshootv1beta2.TLSParams, error) {
v2tls := &troubleshootv1beta2.TLSParams{
SkipVerify: v3tls.SkipVerify,
}
// Preserve v1beta2 Secret reference if present (backward compatibility)
if v3tls.Secret != nil {
v2tls.Secret = &troubleshootv1beta2.TLSSecret{
Name: v3tls.Secret.Name,
Namespace: v3tls.Secret.Namespace,
}
}
// Resolve v1beta3 StringOrValueFrom fields
caCert, err := ResolveStringOrValueFrom(ctx, v3tls.CACert, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to resolve CA cert: %w", err)
}
v2tls.CACert = caCert
clientCert, err := ResolveStringOrValueFrom(ctx, v3tls.ClientCert, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to resolve client cert: %w", err)
}
v2tls.ClientCert = clientCert
clientKey, err := ResolveStringOrValueFrom(ctx, v3tls.ClientKey, client, defaultNamespace)
if err != nil {
return nil, fmt.Errorf("failed to resolve client key: %w", err)
}
v2tls.ClientKey = clientKey
return v2tls, nil
}
@@ -1,386 +0,0 @@
package v1beta3
import (
"context"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestConvertToV1Beta2WithResolution_PostgresWithLiteralValue(t *testing.T) {
client := fake.NewSimpleClientset()
uri := "postgresql://user:pass@localhost:5432/db"
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
URI: StringOrValueFrom{
Value: &uri,
},
},
},
},
}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
require.Len(t, v2spec.Collectors, 1)
require.NotNil(t, v2spec.Collectors[0].Postgres)
assert.Equal(t, "postgresql://user:pass@localhost:5432/db", v2spec.Collectors[0].Postgres.URI)
}
func TestConvertToV1Beta2WithResolution_PostgresWithSecretRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "db-secret",
Namespace: "default",
},
Data: map[string][]byte{
"uri": []byte("postgresql://user:secret-pass@db.example.com:5432/mydb"),
},
}
client := fake.NewSimpleClientset(secret)
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
URI: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "db-secret",
Key: "uri",
},
},
},
},
},
},
}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
require.Len(t, v2spec.Collectors, 1)
require.NotNil(t, v2spec.Collectors[0].Postgres)
assert.Equal(t, "postgresql://user:secret-pass@db.example.com:5432/mydb", v2spec.Collectors[0].Postgres.URI)
}
func TestConvertToV1Beta2WithResolution_PostgresWithTLS(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "tls-secret",
Namespace: "default",
},
Data: map[string][]byte{
"ca.crt": []byte("-----BEGIN CERTIFICATE-----\nCA_CERT_DATA\n-----END CERTIFICATE-----"),
"client.crt": []byte("-----BEGIN CERTIFICATE-----\nCLIENT_CERT_DATA\n-----END CERTIFICATE-----"),
"client.key": []byte("-----BEGIN PRIVATE KEY-----\nCLIENT_KEY_DATA\n-----END PRIVATE KEY-----"),
},
}
client := fake.NewSimpleClientset(secret)
uri := "postgresql://user:pass@localhost:5432/db"
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
URI: StringOrValueFrom{
Value: &uri,
},
TLS: &TLSParams{
CACert: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "tls-secret",
Key: "ca.crt",
},
},
},
ClientCert: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "tls-secret",
Key: "client.crt",
},
},
},
ClientKey: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "tls-secret",
Key: "client.key",
},
},
},
},
},
},
},
}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
require.Len(t, v2spec.Collectors, 1)
require.NotNil(t, v2spec.Collectors[0].Postgres)
require.NotNil(t, v2spec.Collectors[0].Postgres.TLS)
assert.Equal(t, "-----BEGIN CERTIFICATE-----\nCA_CERT_DATA\n-----END CERTIFICATE-----", v2spec.Collectors[0].Postgres.TLS.CACert)
assert.Equal(t, "-----BEGIN CERTIFICATE-----\nCLIENT_CERT_DATA\n-----END CERTIFICATE-----", v2spec.Collectors[0].Postgres.TLS.ClientCert)
assert.Equal(t, "-----BEGIN PRIVATE KEY-----\nCLIENT_KEY_DATA\n-----END PRIVATE KEY-----", v2spec.Collectors[0].Postgres.TLS.ClientKey)
}
func TestConvertToV1Beta2WithResolution_MultipleDatabases(t *testing.T) {
pgSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "postgres-secret",
Namespace: "default",
},
Data: map[string][]byte{
"uri": []byte("postgresql://user:pass@pg.example.com:5432/db"),
},
}
mysqlSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "mysql-secret",
Namespace: "default",
},
Data: map[string][]byte{
"uri": []byte("mysql://user:pass@mysql.example.com:3306/db"),
},
}
redisSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "redis-secret",
Namespace: "default",
},
Data: map[string][]byte{
"uri": []byte("redis://redis.example.com:6379"),
},
}
client := fake.NewSimpleClientset(pgSecret, mysqlSecret, redisSecret)
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
URI: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "postgres-secret",
Key: "uri",
},
},
},
},
},
{
Mysql: &Database{
URI: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "mysql-secret",
Key: "uri",
},
},
},
},
},
{
Redis: &Database{
URI: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "redis-secret",
Key: "uri",
},
},
},
},
},
},
}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
require.Len(t, v2spec.Collectors, 3)
require.NotNil(t, v2spec.Collectors[0].Postgres)
assert.Equal(t, "postgresql://user:pass@pg.example.com:5432/db", v2spec.Collectors[0].Postgres.URI)
require.NotNil(t, v2spec.Collectors[1].Mysql)
assert.Equal(t, "mysql://user:pass@mysql.example.com:3306/db", v2spec.Collectors[1].Mysql.URI)
require.NotNil(t, v2spec.Collectors[2].Redis)
assert.Equal(t, "redis://redis.example.com:6379", v2spec.Collectors[2].Redis.URI)
}
func TestConvertToV1Beta2WithResolution_SecretNotFound(t *testing.T) {
client := fake.NewSimpleClientset()
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
URI: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "nonexistent-secret",
Key: "uri",
},
},
},
},
},
},
}
_, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to convert collector")
assert.Contains(t, err.Error(), "failed to resolve database URI")
}
func TestConvertToV1Beta2WithResolution_PreservesCollectorMeta(t *testing.T) {
client := fake.NewSimpleClientset()
uri := "postgresql://user:pass@localhost:5432/db"
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
CollectorMeta: CollectorMeta{
CollectorName: "my-postgres-collector",
},
URI: StringOrValueFrom{
Value: &uri,
},
Parameters: []string{"sslmode=require"},
},
},
},
}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
require.Len(t, v2spec.Collectors, 1)
require.NotNil(t, v2spec.Collectors[0].Postgres)
assert.Equal(t, "my-postgres-collector", v2spec.Collectors[0].Postgres.CollectorName)
assert.Equal(t, []string{"sslmode=require"}, v2spec.Collectors[0].Postgres.Parameters)
}
func TestConvertToV1Beta2WithResolution_TLSBackwardCompatibility(t *testing.T) {
client := fake.NewSimpleClientset()
uri := "postgresql://user:pass@localhost:5432/db"
v3spec := &SupportBundleSpec{
Collectors: []*Collect{
{
Postgres: &Database{
URI: StringOrValueFrom{
Value: &uri,
},
TLS: &TLSParams{
SkipVerify: true,
Secret: &TLSSecret{
Name: "old-tls-secret",
Namespace: "default",
},
},
},
},
},
}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
require.Len(t, v2spec.Collectors, 1)
require.NotNil(t, v2spec.Collectors[0].Postgres)
require.NotNil(t, v2spec.Collectors[0].Postgres.TLS)
assert.True(t, v2spec.Collectors[0].Postgres.TLS.SkipVerify)
require.NotNil(t, v2spec.Collectors[0].Postgres.TLS.Secret)
assert.Equal(t, "old-tls-secret", v2spec.Collectors[0].Postgres.TLS.Secret.Name)
assert.Equal(t, "default", v2spec.Collectors[0].Postgres.TLS.Secret.Namespace)
}
func TestConvertToV1Beta2WithResolution_EmptySpec(t *testing.T) {
client := fake.NewSimpleClientset()
v3spec := &SupportBundleSpec{}
v2spec, err := ConvertToV1Beta2WithResolution(context.Background(), v3spec, client, "default")
require.NoError(t, err)
require.NotNil(t, v2spec)
assert.Nil(t, v2spec.Collectors)
}
func TestConvertDatabase_AllDatabaseTypes(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "db-secret",
Namespace: "default",
},
Data: map[string][]byte{
"uri": []byte("test-uri"),
},
}
client := fake.NewSimpleClientset(secret)
v3db := &Database{
URI: StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "db-secret",
Key: "uri",
},
},
},
}
// Test that the same database struct works for all DB types
ctx := context.Background()
pgDB, err := convertDatabase(ctx, v3db, client, "default")
require.NoError(t, err)
assert.Equal(t, "test-uri", pgDB.URI)
mysqlDB, err := convertDatabase(ctx, v3db, client, "default")
require.NoError(t, err)
assert.Equal(t, "test-uri", mysqlDB.URI)
mssqlDB, err := convertDatabase(ctx, v3db, client, "default")
require.NoError(t, err)
assert.Equal(t, "test-uri", mssqlDB.URI)
redisDB, err := convertDatabase(ctx, v3db, client, "default")
require.NoError(t, err)
assert.Equal(t, "test-uri", redisDB.URI)
}
// Helper function to convert v2spec back to ensure type compatibility
func ensureV2SpecCompatibility(v2spec *troubleshootv1beta2.SupportBundleSpec) {
// This function just exists to ensure the types are compatible
// If this compiles, we know the conversion produces valid v1beta2 types
_ = v2spec.Uri
_ = v2spec.Collectors
_ = v2spec.Analyzers
}
-6
View File
@@ -1,6 +0,0 @@
// +k8s:deepcopy-gen=package
// +k8s:defaulter-gen=TypeMeta
// +groupName=troubleshoot.sh
// Package v1beta3 is the v1beta3 version of the API.
package v1beta3
-46
View File
@@ -1,46 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// NOTE: Boilerplate only. Ignore this file.
// Package v1beta3 contains API Schema definitions for the troubleshoot v1beta3 API group
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package,register
// +k8s:conversion-gen=github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot
// +k8s:defaulter-gen=TypeMeta
// +groupName=troubleshoot.sh
package v1beta3
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "troubleshoot.sh", Version: "v1beta3"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
// AddToScheme is required by pkg/client/...
AddToScheme = SchemeBuilder.AddToScheme
)
// Resource is required by pkg/client/listers/...
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
-117
View File
@@ -1,117 +0,0 @@
package v1beta3
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// ResolveStringOrValueFrom resolves a StringOrValueFrom to its actual string value
// by fetching from Secrets or ConfigMaps as needed.
//
// Parameters:
// - ctx: Context for the resolution operation
// - sov: The StringOrValueFrom to resolve
// - client: Kubernetes client for fetching Secrets/ConfigMaps
// - defaultNamespace: Namespace to use when not specified in the reference
//
// Returns:
// - The resolved string value
// - An error if resolution fails (unless Optional is true)
func ResolveStringOrValueFrom(
ctx context.Context,
sov StringOrValueFrom,
client kubernetes.Interface,
defaultNamespace string,
) (string, error) {
// If Value is directly specified, use it
if sov.Value != nil {
return *sov.Value, nil
}
// If ValueFrom is not specified, return empty string
if sov.ValueFrom == nil {
return "", nil
}
// Resolve from SecretKeyRef
if sov.ValueFrom.SecretKeyRef != nil {
return resolveSecretKeyRef(ctx, sov.ValueFrom.SecretKeyRef, client, defaultNamespace)
}
// Resolve from ConfigMapKeyRef
if sov.ValueFrom.ConfigMapKeyRef != nil {
return resolveConfigMapKeyRef(ctx, sov.ValueFrom.ConfigMapKeyRef, client, defaultNamespace)
}
return "", nil
}
// resolveSecretKeyRef fetches a value from a Kubernetes Secret
func resolveSecretKeyRef(
ctx context.Context,
ref *SecretKeyRef,
client kubernetes.Interface,
defaultNamespace string,
) (string, error) {
namespace := ref.Namespace
if namespace == "" {
namespace = defaultNamespace
}
secret, err := client.CoreV1().Secrets(namespace).Get(ctx, ref.Name, metav1.GetOptions{})
if err != nil {
if isOptional(ref.Optional) {
return "", nil
}
return "", fmt.Errorf("failed to get secret %s/%s: %w", namespace, ref.Name, err)
}
value, ok := secret.Data[ref.Key]
if !ok {
if isOptional(ref.Optional) {
return "", nil
}
return "", fmt.Errorf("key %q not found in secret %s/%s", ref.Key, namespace, ref.Name)
}
return string(value), nil
}
// resolveConfigMapKeyRef fetches a value from a Kubernetes ConfigMap
func resolveConfigMapKeyRef(
ctx context.Context,
ref *ConfigMapKeyRef,
client kubernetes.Interface,
defaultNamespace string,
) (string, error) {
namespace := ref.Namespace
if namespace == "" {
namespace = defaultNamespace
}
configMap, err := client.CoreV1().ConfigMaps(namespace).Get(ctx, ref.Name, metav1.GetOptions{})
if err != nil {
if isOptional(ref.Optional) {
return "", nil
}
return "", fmt.Errorf("failed to get configmap %s/%s: %w", namespace, ref.Name, err)
}
value, ok := configMap.Data[ref.Key]
if !ok {
if isOptional(ref.Optional) {
return "", nil
}
return "", fmt.Errorf("key %q not found in configmap %s/%s", ref.Key, namespace, ref.Name)
}
return value, nil
}
// isOptional checks if the optional flag is set to true
func isOptional(optional *bool) bool {
return optional != nil && *optional
}
@@ -1,333 +0,0 @@
package v1beta3
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestResolveStringOrValueFrom_LiteralValue(t *testing.T) {
client := fake.NewSimpleClientset()
value := "literal-value"
sov := StringOrValueFrom{
Value: &value,
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "literal-value", result)
}
func TestResolveStringOrValueFrom_EmptyValue(t *testing.T) {
client := fake.NewSimpleClientset()
sov := StringOrValueFrom{}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_SecretKeyRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Namespace: "default",
},
Data: map[string][]byte{
"password": []byte("super-secret-password"),
},
}
client := fake.NewSimpleClientset(secret)
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "test-secret",
Key: "password",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "super-secret-password", result)
}
func TestResolveStringOrValueFrom_SecretKeyRef_WithNamespace(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Namespace: "custom-namespace",
},
Data: map[string][]byte{
"password": []byte("secret-from-custom-ns"),
},
}
client := fake.NewSimpleClientset(secret)
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "test-secret",
Key: "password",
Namespace: "custom-namespace",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "secret-from-custom-ns", result)
}
func TestResolveStringOrValueFrom_SecretKeyRef_NotFound(t *testing.T) {
client := fake.NewSimpleClientset()
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "nonexistent-secret",
Key: "password",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to get secret")
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_SecretKeyRef_KeyNotFound(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Namespace: "default",
},
Data: map[string][]byte{
"password": []byte("secret-value"),
},
}
client := fake.NewSimpleClientset(secret)
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "test-secret",
Key: "nonexistent-key",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.Error(t, err)
assert.Contains(t, err.Error(), "key \"nonexistent-key\" not found")
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_SecretKeyRef_Optional(t *testing.T) {
client := fake.NewSimpleClientset()
optional := true
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "nonexistent-secret",
Key: "password",
Optional: &optional,
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_SecretKeyRef_OptionalKeyNotFound(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-secret",
Namespace: "default",
},
Data: map[string][]byte{
"password": []byte("secret-value"),
},
}
client := fake.NewSimpleClientset(secret)
optional := true
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
SecretKeyRef: &SecretKeyRef{
Name: "test-secret",
Key: "nonexistent-key",
Optional: &optional,
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_ConfigMapKeyRef(t *testing.T) {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "test-configmap",
Namespace: "default",
},
Data: map[string]string{
"config-key": "config-value",
},
}
client := fake.NewSimpleClientset(configMap)
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
ConfigMapKeyRef: &ConfigMapKeyRef{
Name: "test-configmap",
Key: "config-key",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "config-value", result)
}
func TestResolveStringOrValueFrom_ConfigMapKeyRef_WithNamespace(t *testing.T) {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "test-configmap",
Namespace: "custom-namespace",
},
Data: map[string]string{
"config-key": "config-from-custom-ns",
},
}
client := fake.NewSimpleClientset(configMap)
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
ConfigMapKeyRef: &ConfigMapKeyRef{
Name: "test-configmap",
Key: "config-key",
Namespace: "custom-namespace",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "config-from-custom-ns", result)
}
func TestResolveStringOrValueFrom_ConfigMapKeyRef_NotFound(t *testing.T) {
client := fake.NewSimpleClientset()
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
ConfigMapKeyRef: &ConfigMapKeyRef{
Name: "nonexistent-configmap",
Key: "config-key",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to get configmap")
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_ConfigMapKeyRef_KeyNotFound(t *testing.T) {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "test-configmap",
Namespace: "default",
},
Data: map[string]string{
"config-key": "config-value",
},
}
client := fake.NewSimpleClientset(configMap)
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
ConfigMapKeyRef: &ConfigMapKeyRef{
Name: "test-configmap",
Key: "nonexistent-key",
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.Error(t, err)
assert.Contains(t, err.Error(), "key \"nonexistent-key\" not found")
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_ConfigMapKeyRef_Optional(t *testing.T) {
client := fake.NewSimpleClientset()
optional := true
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
ConfigMapKeyRef: &ConfigMapKeyRef{
Name: "nonexistent-configmap",
Key: "config-key",
Optional: &optional,
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "", result)
}
func TestResolveStringOrValueFrom_ConfigMapKeyRef_OptionalKeyNotFound(t *testing.T) {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "test-configmap",
Namespace: "default",
},
Data: map[string]string{
"config-key": "config-value",
},
}
client := fake.NewSimpleClientset(configMap)
optional := true
sov := StringOrValueFrom{
ValueFrom: &ValueFromSource{
ConfigMapKeyRef: &ConfigMapKeyRef{
Name: "test-configmap",
Key: "nonexistent-key",
Optional: &optional,
},
},
}
result, err := ResolveStringOrValueFrom(context.Background(), sov, client, "default")
require.NoError(t, err)
assert.Equal(t, "", result)
}
@@ -1,64 +0,0 @@
package v1beta3
// StringOrValueFrom represents a string value that can either be specified
// directly or sourced from a Kubernetes Secret or ConfigMap
type StringOrValueFrom struct {
// Value is a literal string value
// +optional
Value *string `json:"value,omitempty" yaml:"value,omitempty"`
// ValueFrom is a reference to a value in a Secret or ConfigMap
// +optional
ValueFrom *ValueFromSource `json:"valueFrom,omitempty" yaml:"valueFrom,omitempty"`
}
// ValueFromSource represents the source of a value from a Secret or ConfigMap
type ValueFromSource struct {
// SecretKeyRef references a key in a Secret
// +optional
SecretKeyRef *SecretKeyRef `json:"secretKeyRef,omitempty" yaml:"secretKeyRef,omitempty"`
// ConfigMapKeyRef references a key in a ConfigMap
// +optional
ConfigMapKeyRef *ConfigMapKeyRef `json:"configMapKeyRef,omitempty" yaml:"configMapKeyRef,omitempty"`
}
// SecretKeyRef references a specific key in a Kubernetes Secret
type SecretKeyRef struct {
// Name is the name of the Secret
Name string `json:"name" yaml:"name"`
// Key is the key within the Secret to read
Key string `json:"key" yaml:"key"`
// Namespace is the namespace of the Secret
// If not specified, defaults to the namespace where the SupportBundle is running
// +optional
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
// Optional specifies whether the Secret must exist
// If true and the Secret or key doesn't exist, resolves to empty string
// If false (default) and the Secret or key doesn't exist, resolution fails
// +optional
Optional *bool `json:"optional,omitempty" yaml:"optional,omitempty"`
}
// ConfigMapKeyRef references a specific key in a Kubernetes ConfigMap
type ConfigMapKeyRef struct {
// Name is the name of the ConfigMap
Name string `json:"name" yaml:"name"`
// Key is the key within the ConfigMap to read
Key string `json:"key" yaml:"key"`
// Namespace is the namespace of the ConfigMap
// If not specified, defaults to the namespace where the SupportBundle is running
// +optional
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
// Optional specifies whether the ConfigMap must exist
// If true and the ConfigMap or key doesn't exist, resolves to empty string
// If false (default) and the ConfigMap or key doesn't exist, resolution fails
// +optional
Optional *bool `json:"optional,omitempty" yaml:"optional,omitempty"`
}
@@ -1,65 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta3
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// SupportBundleSpec defines the desired state of SupportBundle
type SupportBundleSpec struct {
AfterCollection []*AfterCollection `json:"afterCollection,omitempty" yaml:"afterCollection,omitempty"`
Collectors []*Collect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
HostCollectors []*HostCollect `json:"hostCollectors,omitempty" yaml:"hostCollectors,omitempty"`
Analyzers []*Analyze `json:"analyzers,omitempty" yaml:"analyzers,omitempty"`
HostAnalyzers []*HostAnalyze `json:"hostAnalyzers,omitempty" yaml:"hostAnalyzers,omitempty"`
// URI optionally defines a location which is the source of this spec to allow updating of the spec at runtime
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
RunHostCollectorsInPod bool `json:"runHostCollectorsInPod,omitempty" yaml:"runHostCollectorsInPod,omitempty"`
}
// SupportBundleStatus defines the observed state of SupportBundle
type SupportBundleStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SupportBundle is the Schema for the SupportBundles API
// +k8s:openapi-gen=true
type SupportBundle struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec SupportBundleSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
Status SupportBundleStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SupportBundleList contains a list of SupportBundle
type SupportBundleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []SupportBundle `json:"items"`
}
func init() {
SchemeBuilder.Register(&SupportBundle{}, &SupportBundleList{})
}
@@ -1,441 +0,0 @@
//go:build !ignore_autogenerated
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1beta3
import (
"github.com/replicatedhq/troubleshoot/pkg/multitype"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AfterCollection) DeepCopyInto(out *AfterCollection) {
*out = *in
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterCollection.
func (in *AfterCollection) DeepCopy() *AfterCollection {
if in == nil {
return nil
}
out := new(AfterCollection)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
func (in *Analyze) DeepCopy() *Analyze {
if in == nil {
return nil
}
out := new(Analyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Collect) DeepCopyInto(out *Collect) {
*out = *in
if in.Postgres != nil {
in, out := &in.Postgres, &out.Postgres
*out = new(Database)
(*in).DeepCopyInto(*out)
}
if in.Mssql != nil {
in, out := &in.Mssql, &out.Mssql
*out = new(Database)
(*in).DeepCopyInto(*out)
}
if in.Mysql != nil {
in, out := &in.Mysql, &out.Mysql
*out = new(Database)
(*in).DeepCopyInto(*out)
}
if in.Redis != nil {
in, out := &in.Redis, &out.Redis
*out = new(Database)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
func (in *Collect) DeepCopy() *Collect {
if in == nil {
return nil
}
out := new(Collect)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorMeta) DeepCopyInto(out *CollectorMeta) {
*out = *in
if in.Exclude != nil {
in, out := &in.Exclude, &out.Exclude
*out = new(multitype.BoolOrString)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorMeta.
func (in *CollectorMeta) DeepCopy() *CollectorMeta {
if in == nil {
return nil
}
out := new(CollectorMeta)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigMapKeyRef) DeepCopyInto(out *ConfigMapKeyRef) {
*out = *in
if in.Optional != nil {
in, out := &in.Optional, &out.Optional
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeyRef.
func (in *ConfigMapKeyRef) DeepCopy() *ConfigMapKeyRef {
if in == nil {
return nil
}
out := new(ConfigMapKeyRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Database) DeepCopyInto(out *Database) {
*out = *in
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
in.URI.DeepCopyInto(&out.URI)
if in.Parameters != nil {
in, out := &in.Parameters, &out.Parameters
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSParams)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Database.
func (in *Database) DeepCopy() *Database {
if in == nil {
return nil
}
out := new(Database)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
func (in *HostAnalyze) DeepCopy() *HostAnalyze {
if in == nil {
return nil
}
out := new(HostAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostCollect) DeepCopyInto(out *HostCollect) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
func (in *HostCollect) DeepCopy() *HostCollect {
if in == nil {
return nil
}
out := new(HostCollect)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef) {
*out = *in
if in.Optional != nil {
in, out := &in.Optional, &out.Optional
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeyRef.
func (in *SecretKeyRef) DeepCopy() *SecretKeyRef {
if in == nil {
return nil
}
out := new(SecretKeyRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StringOrValueFrom) DeepCopyInto(out *StringOrValueFrom) {
*out = *in
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = new(string)
**out = **in
}
if in.ValueFrom != nil {
in, out := &in.ValueFrom, &out.ValueFrom
*out = new(ValueFromSource)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringOrValueFrom.
func (in *StringOrValueFrom) DeepCopy() *StringOrValueFrom {
if in == nil {
return nil
}
out := new(StringOrValueFrom)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundle) DeepCopyInto(out *SupportBundle) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportBundle.
func (in *SupportBundle) DeepCopy() *SupportBundle {
if in == nil {
return nil
}
out := new(SupportBundle)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SupportBundle) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleList) DeepCopyInto(out *SupportBundleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]SupportBundle, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportBundleList.
func (in *SupportBundleList) DeepCopy() *SupportBundleList {
if in == nil {
return nil
}
out := new(SupportBundleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SupportBundleList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleSpec) DeepCopyInto(out *SupportBundleSpec) {
*out = *in
if in.AfterCollection != nil {
in, out := &in.AfterCollection, &out.AfterCollection
*out = make([]*AfterCollection, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(AfterCollection)
(*in).DeepCopyInto(*out)
}
}
}
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
if in.HostCollectors != nil {
in, out := &in.HostCollectors, &out.HostCollectors
*out = make([]*HostCollect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(HostCollect)
**out = **in
}
}
}
if in.Analyzers != nil {
in, out := &in.Analyzers, &out.Analyzers
*out = make([]*Analyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Analyze)
**out = **in
}
}
}
if in.HostAnalyzers != nil {
in, out := &in.HostAnalyzers, &out.HostAnalyzers
*out = make([]*HostAnalyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(HostAnalyze)
**out = **in
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportBundleSpec.
func (in *SupportBundleSpec) DeepCopy() *SupportBundleSpec {
if in == nil {
return nil
}
out := new(SupportBundleSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundleStatus) DeepCopyInto(out *SupportBundleStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportBundleStatus.
func (in *SupportBundleStatus) DeepCopy() *SupportBundleStatus {
if in == nil {
return nil
}
out := new(SupportBundleStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSParams) DeepCopyInto(out *TLSParams) {
*out = *in
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(TLSSecret)
**out = **in
}
in.CACert.DeepCopyInto(&out.CACert)
in.ClientCert.DeepCopyInto(&out.ClientCert)
in.ClientKey.DeepCopyInto(&out.ClientKey)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSParams.
func (in *TLSParams) DeepCopy() *TLSParams {
if in == nil {
return nil
}
out := new(TLSParams)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSSecret) DeepCopyInto(out *TLSSecret) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSecret.
func (in *TLSSecret) DeepCopy() *TLSSecret {
if in == nil {
return nil
}
out := new(TLSSecret)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValueFromSource) DeepCopyInto(out *ValueFromSource) {
*out = *in
if in.SecretKeyRef != nil {
in, out := &in.SecretKeyRef, &out.SecretKeyRef
*out = new(SecretKeyRef)
(*in).DeepCopyInto(*out)
}
if in.ConfigMapKeyRef != nil {
in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef
*out = new(ConfigMapKeyRef)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValueFromSource.
func (in *ValueFromSource) DeepCopy() *ValueFromSource {
if in == nil {
return nil
}
out := new(ValueFromSource)
in.DeepCopyInto(out)
return out
}
@@ -23,7 +23,6 @@ import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"
@@ -33,7 +32,6 @@ type Interface interface {
Discovery() discovery.DiscoveryInterface
TroubleshootV1beta1() troubleshootv1beta1.TroubleshootV1beta1Interface
TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta2Interface
TroubleshootV1beta3() troubleshootv1beta3.TroubleshootV1beta3Interface
}
// Clientset contains the clients for groups.
@@ -41,7 +39,6 @@ type Clientset struct {
*discovery.DiscoveryClient
troubleshootV1beta1 *troubleshootv1beta1.TroubleshootV1beta1Client
troubleshootV1beta2 *troubleshootv1beta2.TroubleshootV1beta2Client
troubleshootV1beta3 *troubleshootv1beta3.TroubleshootV1beta3Client
}
// TroubleshootV1beta1 retrieves the TroubleshootV1beta1Client
@@ -54,11 +51,6 @@ func (c *Clientset) TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta
return c.troubleshootV1beta2
}
// TroubleshootV1beta3 retrieves the TroubleshootV1beta3Client
func (c *Clientset) TroubleshootV1beta3() troubleshootv1beta3.TroubleshootV1beta3Interface {
return c.troubleshootV1beta3
}
// Discovery retrieves the DiscoveryClient
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
if c == nil {
@@ -111,10 +103,6 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
if err != nil {
return nil, err
}
cs.troubleshootV1beta3, err = troubleshootv1beta3.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
@@ -138,7 +126,6 @@ func New(c rest.Interface) *Clientset {
var cs Clientset
cs.troubleshootV1beta1 = troubleshootv1beta1.New(c)
cs.troubleshootV1beta2 = troubleshootv1beta2.New(c)
cs.troubleshootV1beta3 = troubleshootv1beta3.New(c)
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
return &cs
@@ -23,8 +23,6 @@ import (
faketroubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1/fake"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2"
faketroubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2/fake"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
faketroubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
@@ -100,8 +98,3 @@ func (c *Clientset) TroubleshootV1beta1() troubleshootv1beta1.TroubleshootV1beta
func (c *Clientset) TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta2Interface {
return &faketroubleshootv1beta2.FakeTroubleshootV1beta2{Fake: &c.Fake}
}
// TroubleshootV1beta3 retrieves the TroubleshootV1beta3Client
func (c *Clientset) TroubleshootV1beta3() troubleshootv1beta3.TroubleshootV1beta3Interface {
return &faketroubleshootv1beta3.FakeTroubleshootV1beta3{Fake: &c.Fake}
}
@@ -20,7 +20,6 @@ package fake
import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -34,7 +33,6 @@ var codecs = serializer.NewCodecFactory(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
troubleshootv1beta1.AddToScheme,
troubleshootv1beta2.AddToScheme,
troubleshootv1beta3.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
@@ -20,7 +20,6 @@ package scheme
import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -34,7 +33,6 @@ var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
troubleshootv1beta1.AddToScheme,
troubleshootv1beta2.AddToScheme,
troubleshootv1beta3.AddToScheme,
}
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
@@ -1,19 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
// This package has the automatically generated typed clients.
package v1beta3
@@ -1,19 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
// Package fake has the automatically generated clients.
package fake
@@ -1,51 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
gentype "k8s.io/client-go/gentype"
)
// fakeSupportBundles implements SupportBundleInterface
type fakeSupportBundles struct {
*gentype.FakeClientWithList[*v1beta3.SupportBundle, *v1beta3.SupportBundleList]
Fake *FakeTroubleshootV1beta3
}
func newFakeSupportBundles(fake *FakeTroubleshootV1beta3, namespace string) troubleshootv1beta3.SupportBundleInterface {
return &fakeSupportBundles{
gentype.NewFakeClientWithList[*v1beta3.SupportBundle, *v1beta3.SupportBundleList](
fake.Fake,
namespace,
v1beta3.SchemeGroupVersion.WithResource("supportbundles"),
v1beta3.SchemeGroupVersion.WithKind("SupportBundle"),
func() *v1beta3.SupportBundle { return &v1beta3.SupportBundle{} },
func() *v1beta3.SupportBundleList { return &v1beta3.SupportBundleList{} },
func(dst, src *v1beta3.SupportBundleList) { dst.ListMeta = src.ListMeta },
func(list *v1beta3.SupportBundleList) []*v1beta3.SupportBundle {
return gentype.ToPointerSlice(list.Items)
},
func(list *v1beta3.SupportBundleList, items []*v1beta3.SupportBundle) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
}
@@ -1,39 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1beta3 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta3"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeTroubleshootV1beta3 struct {
*testing.Fake
}
func (c *FakeTroubleshootV1beta3) SupportBundles(namespace string) v1beta3.SupportBundleInterface {
return newFakeSupportBundles(c, namespace)
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeTroubleshootV1beta3) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
@@ -1,20 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
type SupportBundleExpansion interface{}
@@ -1,69 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
import (
context "context"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
scheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
gentype "k8s.io/client-go/gentype"
)
// SupportBundlesGetter has a method to return a SupportBundleInterface.
// A group's client should implement this interface.
type SupportBundlesGetter interface {
SupportBundles(namespace string) SupportBundleInterface
}
// SupportBundleInterface has methods to work with SupportBundle resources.
type SupportBundleInterface interface {
Create(ctx context.Context, supportBundle *troubleshootv1beta3.SupportBundle, opts v1.CreateOptions) (*troubleshootv1beta3.SupportBundle, error)
Update(ctx context.Context, supportBundle *troubleshootv1beta3.SupportBundle, opts v1.UpdateOptions) (*troubleshootv1beta3.SupportBundle, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, supportBundle *troubleshootv1beta3.SupportBundle, opts v1.UpdateOptions) (*troubleshootv1beta3.SupportBundle, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*troubleshootv1beta3.SupportBundle, error)
List(ctx context.Context, opts v1.ListOptions) (*troubleshootv1beta3.SupportBundleList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *troubleshootv1beta3.SupportBundle, err error)
SupportBundleExpansion
}
// supportBundles implements SupportBundleInterface
type supportBundles struct {
*gentype.ClientWithList[*troubleshootv1beta3.SupportBundle, *troubleshootv1beta3.SupportBundleList]
}
// newSupportBundles returns a SupportBundles
func newSupportBundles(c *TroubleshootV1beta3Client, namespace string) *supportBundles {
return &supportBundles{
gentype.NewClientWithList[*troubleshootv1beta3.SupportBundle, *troubleshootv1beta3.SupportBundleList](
"supportbundles",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *troubleshootv1beta3.SupportBundle { return &troubleshootv1beta3.SupportBundle{} },
func() *troubleshootv1beta3.SupportBundleList { return &troubleshootv1beta3.SupportBundleList{} },
),
}
}
@@ -1,100 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1beta3
import (
http "net/http"
troubleshootv1beta3 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta3"
scheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
rest "k8s.io/client-go/rest"
)
type TroubleshootV1beta3Interface interface {
RESTClient() rest.Interface
SupportBundlesGetter
}
// TroubleshootV1beta3Client is used to interact with features provided by the troubleshoot.sh group.
type TroubleshootV1beta3Client struct {
restClient rest.Interface
}
func (c *TroubleshootV1beta3Client) SupportBundles(namespace string) SupportBundleInterface {
return newSupportBundles(c, namespace)
}
// NewForConfig creates a new TroubleshootV1beta3Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(c *rest.Config) (*TroubleshootV1beta3Client, error) {
config := *c
setConfigDefaults(&config)
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&config, httpClient)
}
// NewForConfigAndClient creates a new TroubleshootV1beta3Client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*TroubleshootV1beta3Client, error) {
config := *c
setConfigDefaults(&config)
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &TroubleshootV1beta3Client{client}, nil
}
// NewForConfigOrDie creates a new TroubleshootV1beta3Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *TroubleshootV1beta3Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
}
return client
}
// New creates a new TroubleshootV1beta3Client for the given RESTClient.
func New(c rest.Interface) *TroubleshootV1beta3Client {
return &TroubleshootV1beta3Client{c}
}
func setConfigDefaults(config *rest.Config) {
gv := troubleshootv1beta3.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *TroubleshootV1beta3Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
return c.restClient
}
+3 -145
View File
@@ -232,7 +232,7 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
// replicasets
replicasets, replicasetsErrors := replicasets(ctx, client, namespaceNames)
for k, v := range replicasets {
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_REPLICASETS, k), bytes.NewBuffer(v))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_STATEFULSETS), k), bytes.NewBuffer(v))
}
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_REPLICASETS)), marshalErrors(replicasetsErrors))
@@ -276,11 +276,6 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS)), bytes.NewBuffer(storageClasses))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS)), marshalErrors(storageErrors))
// ingress classes
ingressClasses, ingressClassErrors := ingressClasses(ctx, client)
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_INGRESS_CLASS)), bytes.NewBuffer(ingressClasses))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_INGRESS_CLASS)), marshalErrors(ingressClassErrors))
// priority classes
priorityClasses, priorityErrors := priorityClasses(ctx, client)
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PRIORITY_CLASS)), bytes.NewBuffer(priorityClasses))
@@ -375,9 +370,9 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
// endpointslices
endpointslices, endpointslicesErrors := endpointslices(ctx, client, namespaceNames)
for k, v := range endpointslices {
_ = output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_ENDPOINTSLICES, k), bytes.NewBuffer(v))
_ = output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_ENDPOINTSICES, k), bytes.NewBuffer(v))
}
_ = output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_ENDPOINTSLICES)), marshalErrors(endpointslicesErrors))
_ = output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_ENDPOINTSICES)), marshalErrors(endpointslicesErrors))
// Service Accounts
servicesAccounts, servicesAccountsErrors := serviceAccounts(ctx, client, namespaceNames)
@@ -398,11 +393,6 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_VOLUME_ATTACHMENTS)), bytes.NewBuffer(volumeAttachments))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_VOLUME_ATTACHMENTS)), marshalErrors(volumeAttachmentsErrors))
// Certificate Signing Requests
csrs, csrsErrors := certificateSigningRequests(ctx, client)
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_CERTIFICATE_SIGNING_REQUESTS)), bytes.NewBuffer(csrs))
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, fmt.Sprintf("%s-errors.json", constants.CLUSTER_RESOURCES_CERTIFICATE_SIGNING_REQUESTS)), marshalErrors(csrsErrors))
// ConfigMaps
configMaps, configMapsErrors := configMaps(ctx, client, namespaceNames)
for k, v := range configMaps {
@@ -410,13 +400,6 @@ 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))
// Replicated License
licenseData, licenseErr := replicatedLicense(ctx, client, namespaceNames)
if licenseErr == nil {
output.SaveResult(c.BundlePath, path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_REPLICATED_LICENSE), bytes.NewBuffer(licenseData))
}
return output, nil
}
@@ -1122,40 +1105,6 @@ func storageClassesV1beta(ctx context.Context, client *kubernetes.Clientset) ([]
return b, nil
}
func ingressClasses(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
ok, err := discovery.HasResource(client, "networking.k8s.io/v1", "IngressClass")
if err != nil {
return nil, []string{err.Error()}
}
if !ok {
return nil, nil
}
ingressClasses, err := client.NetworkingV1().IngressClasses().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
gvk, err := apiutil.GVKForObject(ingressClasses, scheme.Scheme)
if err == nil {
ingressClasses.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range ingressClasses.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
ingressClasses.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(ingressClasses, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
func priorityClasses(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
ok, err := discovery.HasResource(client, "scheduling.k8s.io/v1", "PriorityClass")
if err != nil {
@@ -2173,32 +2122,6 @@ func volumeAttachments(ctx context.Context, client kubernetes.Interface) ([]byte
return b, nil
}
func certificateSigningRequests(ctx context.Context, client kubernetes.Interface) ([]byte, []string) {
csrs, err := client.CertificatesV1().CertificateSigningRequests().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
gvk, err := apiutil.GVKForObject(csrs, scheme.Scheme)
if err == nil {
csrs.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range csrs.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
csrs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(csrs, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
func configMaps(ctx context.Context, client kubernetes.Interface, namespaces []string) (map[string][]byte, map[string]string) {
configmapByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
@@ -2253,68 +2176,3 @@ func storeCustomResource(name string, objects any, m map[string][]byte) error {
m[fmt.Sprintf("%s.yaml", name)] = y
return nil
}
// replicatedLicense searches for the replicated secret across namespaces,
// extracts the config.yaml field, and extracts the licenseID and appSlug.
// Note: secret.Data already contains decoded bytes; no base64 decoding is required.
func replicatedLicense(ctx context.Context, client *kubernetes.Clientset, namespaces []string) ([]byte, error) {
// Structure to parse the config.yaml content
type ConfigYAML struct {
License string `yaml:"license"` // This is a YAML string containing the License object
}
type LicenseSpec struct {
LicenseID string `yaml:"licenseID"`
AppSlug string `yaml:"appSlug"`
}
type License struct {
Spec LicenseSpec `yaml:"spec"`
}
// Search through all namespaces for the replicated secret
for _, namespace := range namespaces {
secret, err := client.CoreV1().Secrets(namespace).Get(ctx, "replicated", metav1.GetOptions{})
if err != nil {
// Secret not found in this namespace, continue to next
continue
}
// Extract the config.yaml field from the secret data
configYAMLBase64, exists := secret.Data["config.yaml"]
if !exists {
continue
}
configYAMLBytes := configYAMLBase64
// Parse the YAML to extract the license field
var config ConfigYAML
if err := yaml.Unmarshal(configYAMLBytes, &config); err != nil {
// Malformed config in this namespace; try the next namespace
continue
}
// Parse the license field (which is a YAML string) to extract licenseID and appSlug
var license License
if err := yaml.Unmarshal([]byte(config.License), &license); err != nil {
// Malformed license in this namespace; try the next namespace
continue
}
// Return both licenseID and appSlug as JSON
licenseData := map[string]string{
"licenseID": license.Spec.LicenseID,
"appSlug": license.Spec.AppSlug,
}
licenseJSON, err := json.Marshal(licenseData)
if err != nil {
return nil, fmt.Errorf("failed to marshal license data: %w", err)
}
return licenseJSON, nil
}
// No replicated secret with a parsable license found in any namespace
return nil, fmt.Errorf("replicated secret with parsable license not found in any namespace")
}
-79
View File
@@ -3,7 +3,6 @@ package collect
import (
"context"
"encoding/json"
"fmt"
"os"
"reflect"
"testing"
@@ -12,7 +11,6 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
certificatesv1 "k8s.io/api/certificates/v1"
v1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
@@ -20,13 +18,11 @@ import (
storagev1 "k8s.io/api/storage/v1"
apixfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
fakediscovery "k8s.io/client-go/discovery/fake"
testdynamicclient "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes"
testclient "k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
"sigs.k8s.io/yaml"
)
@@ -701,78 +697,3 @@ func createTestPodDisruptionBudgetsV1beta1(client kubernetes.Interface, pdbNames
}
return nil
}
func Test_CertificateSigningRequests(t *testing.T) {
tests := []struct {
name string
csrNames []string
}{
{
name: "single certificate signing request",
csrNames: []string{"test-csr"},
},
{
name: "multiple certificate signing requests",
csrNames: []string{"test-csr-1", "test-csr-2", "test-csr-3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := testclient.NewSimpleClientset()
ctx := context.Background()
err := createTestCertificateSigningRequests(client, tt.csrNames)
assert.NoError(t, err)
csrs, csrErrors := certificateSigningRequests(ctx, client)
assert.Empty(t, csrErrors)
assert.NotEmpty(t, csrs)
var csrList certificatesv1.CertificateSigningRequestList
err = json.Unmarshal(csrs, &csrList)
assert.NoError(t, err)
assert.Equal(t, len(tt.csrNames), len(csrList.Items))
for _, csr := range csrList.Items {
assert.Contains(t, tt.csrNames, csr.ObjectMeta.Name)
}
})
}
}
func Test_CertificateSigningRequests_PermissionDenied(t *testing.T) {
client := testclient.NewSimpleClientset()
ctx := context.Background()
// Add a reactor to simulate permission denied error
client.PrependReactor("list", "certificatesigningrequests", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("certificatesigningrequests.certificates.k8s.io is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"certificatesigningrequests\" in API group \"certificates.k8s.io\" at the cluster scope")
})
csrs, csrErrors := certificateSigningRequests(ctx, client)
// Verify fail-safe behavior: returns nil data + error string (not panic)
assert.Nil(t, csrs)
assert.NotEmpty(t, csrErrors)
assert.Len(t, csrErrors, 1)
// Verify the error is captured as a string
assert.IsType(t, "", csrErrors[0])
assert.Contains(t, csrErrors[0], "forbidden")
}
func createTestCertificateSigningRequests(client kubernetes.Interface, csrNames []string) error {
for _, csrName := range csrNames {
_, err := client.CertificatesV1().CertificateSigningRequests().Create(context.Background(), &certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: csrName,
},
Spec: certificatesv1.CertificateSigningRequestSpec{
Request: []byte("-----BEGIN CERTIFICATE REQUEST-----\ntest\n-----END CERTIFICATE REQUEST-----"),
SignerName: "kubernetes.io/kube-apiserver-client",
Usages: []certificatesv1.KeyUsage{certificatesv1.UsageClientAuth},
},
}, metav1.CreateOptions{})
if err != nil {
return err
}
}
return nil
}
+1 -94
View File
@@ -15,7 +15,7 @@ func Test_ensureClusterResourcesFirst(t *testing.T) {
list []*troubleshootv1beta2.Collect
}{
{
name: "Reorg OK - clusterResources moved to front",
name: "Reorg OK",
want: []*troubleshootv1beta2.Collect{
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
@@ -33,99 +33,6 @@ func Test_ensureClusterResourcesFirst(t *testing.T) {
},
},
},
{
name: "Already first - no change",
want: []*troubleshootv1beta2.Collect{
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
{
Data: &troubleshootv1beta2.Data{},
},
{
Secret: &troubleshootv1beta2.Secret{},
},
},
list: []*troubleshootv1beta2.Collect{
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
{
Data: &troubleshootv1beta2.Data{},
},
{
Secret: &troubleshootv1beta2.Secret{},
},
},
},
{
name: "Multiple clusterResources - all moved to front",
want: []*troubleshootv1beta2.Collect{
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
{
Data: &troubleshootv1beta2.Data{},
},
{
Secret: &troubleshootv1beta2.Secret{},
},
},
list: []*troubleshootv1beta2.Collect{
{
Data: &troubleshootv1beta2.Data{},
},
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
{
Secret: &troubleshootv1beta2.Secret{},
},
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
},
},
{
name: "No clusterResources - no change",
want: []*troubleshootv1beta2.Collect{
{
Data: &troubleshootv1beta2.Data{},
},
{
Secret: &troubleshootv1beta2.Secret{},
},
},
list: []*troubleshootv1beta2.Collect{
{
Data: &troubleshootv1beta2.Data{},
},
{
Secret: &troubleshootv1beta2.Secret{},
},
},
},
{
name: "Empty list - no change",
want: []*troubleshootv1beta2.Collect{},
list: []*troubleshootv1beta2.Collect{},
},
{
name: "Only clusterResources - no change",
want: []*troubleshootv1beta2.Collect{
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
},
list: []*troubleshootv1beta2.Collect{
{
ClusterResources: &troubleshootv1beta2.ClusterResources{},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
-5
View File
@@ -128,8 +128,6 @@ func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, nam
return &CollectDNS{collector.DNS, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Etcd != nil:
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
default:
return nil, false
}
@@ -225,9 +223,6 @@ func getCollectorName(c interface{}) string {
collector = "dns"
case *CollectEtcd:
collector = "etcd"
case *CollectSupportBundleMetadata:
collector = "support-bundle-metadata"
name = v.Collector.CollectorName
default:
collector = "<none>"
}
+14 -7
View File
@@ -44,7 +44,8 @@ pwd=somethinggoeshere;`,
want: map[string]string{
"data/datacollectorname": ` 123
another***HIDDEN***here
pwd=***HIDDEN***;`,
pwd=***HIDDEN***;
`,
},
},
{
@@ -77,7 +78,8 @@ pwd=somethinggoeshere;`,
want: map[string]string{
"data/datacollectorname": `abc 123
another***HIDDEN***here
pwd=***HIDDEN***;`,
pwd=***HIDDEN***;
`,
},
},
{
@@ -110,7 +112,8 @@ pwd=somethinggoeshere;`,
want: map[string]string{
"data/datacollectorname": `abc 123
another line here
pwd=***HIDDEN***;`,
pwd=***HIDDEN***;
`,
},
},
{
@@ -146,7 +149,8 @@ pwd=somethinggoeshere;`,
want: map[string]string{
"data/datacollectorname": `abc 123
another***HIDDEN***here
pwd=***HIDDEN***;`,
pwd=***HIDDEN***;
`,
},
},
{
@@ -182,7 +186,8 @@ pwd=somethinggoeshere;`,
want: map[string]string{
"data/data/collectorname": `***HIDDEN*** ***HIDDEN***
***HIDDEN*** line here
pwd=***HIDDEN***;`,
pwd=***HIDDEN***;
`,
},
},
{
@@ -208,7 +213,8 @@ another line here`,
},
want: map[string]string{
"data/datacollectorname": `abc 123
another line here`,
another line here
`,
},
},
{
@@ -243,7 +249,8 @@ abc`,
abc
123
xyz123
abc`,
abc
`,
},
},
{
+4 -4
View File
@@ -9,15 +9,15 @@ import (
"strings"
"time"
imagedocker "github.com/containers/image/v5/docker"
dockerref "github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/distribution/distribution/v3/registry/api/errcode"
registryv2 "github.com/distribution/distribution/v3/registry/api/v2"
"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"
+1 -1
View File
@@ -8,9 +8,9 @@ import (
"testing"
"time"
"github.com/containers/image/v5/transports/alltransports"
"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"
)
-54
View File
@@ -1,54 +0,0 @@
package collect
import (
"bytes"
"context"
"encoding/json"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type CollectSupportBundleMetadata struct {
Collector *troubleshootv1beta2.SupportBundleMetadata
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectSupportBundleMetadata) Title() string {
return getCollectorName(c)
}
func (c *CollectSupportBundleMetadata) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectSupportBundleMetadata) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
output := NewResult()
const secretName = "replicated-support-metadata"
secret, err := c.Client.CoreV1().Secrets(c.Collector.Namespace).Get(c.Context, secretName, metav1.GetOptions{})
if err != nil {
return output, errors.Wrapf(err, "failed to get secret %s/%s", c.Collector.Namespace, secretName)
}
metadata := make(map[string]string)
for k, v := range secret.Data {
metadata[k] = string(v)
}
b, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return output, errors.Wrap(err, "failed to marshal metadata")
}
output.SaveResult(c.BundlePath, "metadata/cluster.json", bytes.NewBuffer(b))
return output, nil
}
@@ -1,94 +0,0 @@
package collect
import (
"context"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testclient "k8s.io/client-go/kubernetes/fake"
)
func TestCollectSupportBundleMetadata(t *testing.T) {
tests := []struct {
name string
collector *troubleshootv1beta2.SupportBundleMetadata
mockSecrets []corev1.Secret
want CollectorResult
wantErr bool
}{
{
name: "reads all data fields from secret",
collector: &troubleshootv1beta2.SupportBundleMetadata{
Namespace: "test-ns",
},
mockSecrets: []corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replicated-support-metadata",
Namespace: "test-ns",
},
Data: map[string][]byte{
"mykey": []byte("myvalue"),
"myversion": []byte("1.0.0-example"),
"numCrashes": []byte("57"),
},
},
},
want: CollectorResult{
"metadata/cluster.json": mustJSONMarshalIndent(t, map[string]string{
"mykey": "myvalue",
"myversion": "1.0.0-example",
"numCrashes": "57",
}),
},
},
{
name: "empty data map",
collector: &troubleshootv1beta2.SupportBundleMetadata{
Namespace: "test-ns",
},
mockSecrets: []corev1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replicated-support-metadata",
Namespace: "test-ns",
},
Data: map[string][]byte{},
},
},
want: CollectorResult{
"metadata/cluster.json": mustJSONMarshalIndent(t, map[string]string{}),
},
},
{
name: "secret not found returns error",
collector: &troubleshootv1beta2.SupportBundleMetadata{
Namespace: "test-ns",
},
mockSecrets: []corev1.Secret{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
client := testclient.NewSimpleClientset()
for _, secret := range tt.mockSecrets {
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
require.NoError(t, err)
}
c := &CollectSupportBundleMetadata{tt.collector, "", "", nil, client, ctx, nil}
got, err := c.Collect(nil)
if tt.wantErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
+1 -13
View File
@@ -80,19 +80,7 @@ func DeterministicIDForCollector(collector *troubleshootv1beta2.Collect) string
}
func selectorToString(selector []string) string {
result := strings.Replace(strings.Join(selector, "-"), "=", "-", -1)
// Sanitize characters that are invalid in Windows filenames: < > : " / \ | ? *
// Replace them with underscores to ensure cross-platform compatibility
result = strings.ReplaceAll(result, "*", "all")
result = strings.ReplaceAll(result, "?", "_")
result = strings.ReplaceAll(result, ":", "_")
result = strings.ReplaceAll(result, "<", "_")
result = strings.ReplaceAll(result, ">", "_")
result = strings.ReplaceAll(result, "|", "_")
result = strings.ReplaceAll(result, "\"", "_")
result = strings.ReplaceAll(result, "/", "_")
result = strings.ReplaceAll(result, "\\", "_")
return result
return strings.Replace(strings.Join(selector, "-"), "=", "-", -1)
}
func pathToString(path string) string {
+38 -41
View File
@@ -23,47 +23,44 @@ 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_ENDPOINTSICES = "endpointslices"
CLUSTER_RESOURCES_SERVICE_ACCOUNTS = "serviceaccounts"
CLUSTER_RESOURCES_LEASES = "leases"
CLUSTER_RESOURCES_VOLUME_ATTACHMENTS = "volumeattachments"
CLUSTER_RESOURCES_CONFIGMAPS = "configmaps"
// SelfSubjectRulesReview evaluation responses
SELFSUBJECTRULESREVIEW_ERROR_AUTHORIZATION_WEBHOOK_UNSUPPORTED = "webhook authorizer does not support user rule resolution"
+2 -7
View File
@@ -6,15 +6,11 @@ import (
// HasResource takes an api version and a kind of a resource and checks if the resource
// is supported by the k8s api server.
// This function handles partial results from ServerGroupsAndResources(): "The returned group and resource lists might be non-nil with partial
// results even in the case of non-nil error."
func HasResource(dc discovery.DiscoveryInterface, apiVersion, kind string) (bool, error) {
_, apiLists, err := dc.ServerGroupsAndResources()
if apiLists == nil {
if err != nil {
return false, err
}
// Compare the resource api version and kind and find the resource.
for _, apiList := range apiLists {
if apiList.GroupVersion == apiVersion {
@@ -25,6 +21,5 @@ func HasResource(dc discovery.DiscoveryInterface, apiVersion, kind string) (bool
}
}
}
return false, err
return false, nil
}
-232
View File
@@ -1,12 +1,9 @@
package discovery
import (
"errors"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
fakediscovery "k8s.io/client-go/discovery/fake"
fakeclientset "k8s.io/client-go/kubernetes/fake"
)
@@ -81,232 +78,3 @@ func TestHasResource(t *testing.T) {
})
}
}
// TestHasResourceWithPartialDiscoveryFailure verifies that HasResource correctly handles
// partial discovery failures where ServerGroupsAndResources() returns both an error AND
// partial results (non-nil apiLists). This simulates real Kubernetes behavior when some
// API groups fail to load but others succeed.
func TestHasResourceWithPartialDiscoveryFailure(t *testing.T) {
testKind := "Foo"
testKindGroupVersion := "v1"
testcases := []struct {
name string
apiResourceList []*metav1.APIResourceList
discoveryError error
wantResult bool
wantError bool
description string
}{
{
name: "resource found in partial results with discovery error",
apiResourceList: []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Kind: "Foo",
},
{
Kind: "Bar",
},
},
},
},
discoveryError: &discovery.ErrGroupDiscoveryFailed{
Groups: map[schema.GroupVersion]error{
{Group: "apps", Version: "v1"}: errors.New("failed to retrieve apps/v1"),
},
},
wantResult: true,
wantError: false,
description: "Should return (true, nil) when resource exists in partial results despite discovery error",
},
{
name: "resource not found in partial results with discovery error",
apiResourceList: []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Kind: "Bar",
},
{
Kind: "Baz",
},
},
},
},
discoveryError: &discovery.ErrGroupDiscoveryFailed{
Groups: map[schema.GroupVersion]error{
{Group: "apps", Version: "v1"}: errors.New("failed to retrieve apps/v1"),
},
},
wantResult: false,
wantError: true,
description: "Should return (false, error) when resource not in partial results and discovery error exists",
},
{
name: "nil api resource list with discovery error",
apiResourceList: nil,
discoveryError: &discovery.ErrGroupDiscoveryFailed{
Groups: map[schema.GroupVersion]error{
{Group: "apps", Version: "v1"}: errors.New("failed to retrieve apps/v1"),
},
},
wantResult: false,
wantError: true,
description: "Should return (false, error) when apiLists is nil and discovery error exists",
},
{
name: "empty api resource list with discovery error",
apiResourceList: []*metav1.APIResourceList{},
discoveryError: &discovery.ErrGroupDiscoveryFailed{
Groups: map[schema.GroupVersion]error{
{Group: "apps", Version: "v1"}: errors.New("failed to retrieve apps/v1"),
},
},
wantResult: false,
wantError: true,
description: "Should return (false, error) when apiLists is empty and discovery error exists",
},
{
name: "multiple groups with partial results and discovery error",
apiResourceList: []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Kind: "Pod",
},
{
Kind: "Service",
},
},
},
{
GroupVersion: "v2",
APIResources: []metav1.APIResource{
{
Kind: "Foo",
},
},
},
},
discoveryError: &discovery.ErrGroupDiscoveryFailed{
Groups: map[schema.GroupVersion]error{
{Group: "apps", Version: "v1"}: errors.New("failed to retrieve apps/v1"),
{Group: "batch", Version: "v1beta1"}: errors.New("failed to retrieve batch/v1beta1"),
},
},
wantResult: false,
wantError: true,
description: "Should return (false, error) when resource not found across multiple partial groups with discovery error",
},
{
name: "resource found with different version in partial results with discovery error",
apiResourceList: []*metav1.APIResourceList{
{
GroupVersion: "v2",
APIResources: []metav1.APIResource{
{
Kind: "Foo",
},
},
},
},
discoveryError: &discovery.ErrGroupDiscoveryFailed{
Groups: map[schema.GroupVersion]error{
{Group: "apps", Version: "v1"}: errors.New("failed to retrieve apps/v1"),
},
},
wantResult: false,
wantError: true,
description: "Should return (false, error) when resource exists with different version in partial results",
},
{
name: "generic error with partial results containing resource",
apiResourceList: []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Kind: "Foo",
},
},
},
},
discoveryError: errors.New("connection timeout"),
wantResult: true,
wantError: false,
description: "Should return (true, nil) when resource exists in partial results even with generic error",
},
{
name: "generic error without resource in partial results",
apiResourceList: []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Kind: "Bar",
},
},
},
},
discoveryError: errors.New("connection timeout"),
wantResult: false,
wantError: true,
description: "Should return (false, error) when resource not in partial results with generic error",
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
client := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := client.Discovery().(*fakediscovery.FakeDiscovery)
if !ok {
t.Fatalf("could not convert Discovery() to *FakeDiscovery")
}
// Configure the fake discovery to return both resources and error
fakeDiscovery.Resources = tc.apiResourceList
// Create a mock discovery interface that returns both error and partial results
mockDiscovery := &mockDiscoveryWithPartialFailure{
FakeDiscovery: fakeDiscovery,
errorToReturn: tc.discoveryError,
}
exists, err := HasResource(mockDiscovery, testKindGroupVersion, testKind)
// Verify error expectation
if tc.wantError && err == nil {
t.Errorf("%s: expected error but got nil", tc.description)
}
if !tc.wantError && err != nil {
t.Errorf("%s: expected no error but got: %v", tc.description, err)
}
// Verify result expectation
if exists != tc.wantResult {
t.Errorf("%s: unexpected result for HasResource:\n\t(WANT) %t\n\t(GOT) %t", tc.description, tc.wantResult, exists)
}
})
}
}
// mockDiscoveryWithPartialFailure wraps FakeDiscovery to simulate partial discovery failures
// where ServerGroupsAndResources() returns both an error AND partial results.
type mockDiscoveryWithPartialFailure struct {
*fakediscovery.FakeDiscovery
errorToReturn error
}
// ServerGroupsAndResources simulates the Kubernetes API behavior where partial results
// can be returned even when an error occurs. This happens when some API groups fail to
// load but others succeed.
func (m *mockDiscoveryWithPartialFailure) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
groups, resources, _ := m.FakeDiscovery.ServerGroupsAndResources()
return groups, resources, m.errorToReturn
}
-25
View File
@@ -25,26 +25,6 @@ const (
PodStatusReasonInitCrashLoopBackOff PodStatusReason = "Init:CrashLoopBackOff"
)
// isNativeSidecar checks if an init container is a native sidecar.
// Native sidecars are init containers with restartPolicy: Always (Kubernetes 1.28+).
// They run continuously alongside main containers, unlike traditional init containers
// which must complete before main containers start.
func isNativeSidecar(pod *corev1.Pod, initContainerIndex int) bool {
// Bounds check - ensure the index is valid
if initContainerIndex >= len(pod.Spec.InitContainers) {
return false
}
initContainer := pod.Spec.InitContainers[initContainerIndex]
// Check if RestartPolicy is set to Always
if initContainer.RestartPolicy != nil && *initContainer.RestartPolicy == corev1.ContainerRestartPolicyAlways {
return true
}
return false
}
// reference: https://github.com/kubernetes/kubernetes/blob/e8fcd0de98d50f4019561a6b7a0287f5c059267a/pkg/printers/internalversion/printers.go#L741
func GetPodStatusReason(pod *corev1.Pod) (string, string) {
reason := string(pod.Status.Phase)
@@ -75,11 +55,6 @@ func GetPodStatusReason(pod *corev1.Pod) (string, string) {
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
reason = "Init:" + container.State.Waiting.Reason
initializing = true
case isNativeSidecar(pod, i) && container.State.Running != nil:
// Native sidecar running - this is expected, not stuck initializing.
// Native sidecars (init containers with restartPolicy: Always) are designed
// to run continuously, so a Running state means successful initialization.
continue
default:
reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
initializing = true

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