mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a2b0e0f30 | ||
|
|
1013f2a160 | ||
|
|
e73e1375cc | ||
|
|
9278d50252 | ||
|
|
e084c0a46a | ||
|
|
f7a941c220 | ||
|
|
8920a9d09a | ||
|
|
a677646b17 | ||
|
|
c84ea20b88 | ||
|
|
409a02a125 | ||
|
|
78af9208cf | ||
|
|
fb0983480c | ||
|
|
1aa74db81d | ||
|
|
8f7cf26cc9 | ||
|
|
a6d3e3e423 | ||
|
|
6ea8cbcb09 | ||
|
|
bf1dfa0318 | ||
|
|
45ee61aa4d | ||
|
|
e3458ad297 | ||
|
|
cb32216df8 | ||
|
|
2b82454ef8 | ||
|
|
e05962bcf3 | ||
|
|
0f747f55dc | ||
|
|
1c234e432c | ||
|
|
d325804359 | ||
|
|
2d664b8736 | ||
|
|
8762bd1515 | ||
|
|
a2a424b201 | ||
|
|
3f069125a2 | ||
|
|
37292e6ba1 |
@@ -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)"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func checkAndSetChroot(newroot string) error {
|
||||
return errors.New("chroot is only implimented in linux/darwin")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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: {}
|
||||
@@ -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-----
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
-51
@@ -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,
|
||||
}
|
||||
}
|
||||
-39
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,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
Reference in New Issue
Block a user