mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d83d8ebfc6 | ||
|
|
47656a8e6f | ||
|
|
60263caf78 | ||
|
|
87cedca468 | ||
|
|
16237e4459 | ||
|
|
a57171a918 | ||
|
|
01d5804977 | ||
|
|
22cc02cd29 | ||
|
|
1444c01725 | ||
|
|
0020c1129e | ||
|
|
1788376472 | ||
|
|
04e656a0a5 | ||
|
|
790c8d4bde | ||
|
|
4e999d6bfb | ||
|
|
403b1ceccf | ||
|
|
8173759e52 | ||
|
|
f5f02f5a80 | ||
|
|
e882f44ae9 | ||
|
|
d79ff950ca | ||
|
|
191ebdb598 | ||
|
|
edfa01c5c4 | ||
|
|
18ec6adc12 | ||
|
|
15b908f9e0 | ||
|
|
a9c5baca14 | ||
|
|
5778d4656a | ||
|
|
905c9771bc | ||
|
|
ce155270c8 | ||
|
|
91da8f13bb | ||
|
|
1a8aff1d79 | ||
|
|
ece20d3c2c | ||
|
|
80e5fac07c | ||
|
|
51c07b42c3 | ||
|
|
bd7bae6302 | ||
|
|
9d41460d33 | ||
|
|
fb0f81d076 | ||
|
|
78bbea18ac | ||
|
|
224d55b076 | ||
|
|
db967d2896 | ||
|
|
088f0321e7 | ||
|
|
aeaac7a70f | ||
|
|
6b368f2221 | ||
|
|
cb5db1733a | ||
|
|
c2f72ecd0c | ||
|
|
f3bad5f409 | ||
|
|
f438467003 | ||
|
|
a374b0a3ab | ||
|
|
6aaba59ebd | ||
|
|
f18b5d754e | ||
|
|
2ede46ed58 | ||
|
|
07d1747d81 | ||
|
|
db871e6889 | ||
|
|
dc4403811b | ||
|
|
6f7acec7b3 | ||
|
|
123d17ab4a | ||
|
|
867c70619c | ||
|
|
c9830de736 | ||
|
|
ca5ff88964 | ||
|
|
e6aff48f1b | ||
|
|
76c52d2b93 | ||
|
|
bece801ba8 | ||
|
|
72701fa4d2 | ||
|
|
7eae0fe687 | ||
|
|
6f839b389d | ||
|
|
ebe98ff2f1 | ||
|
|
59624e88fd | ||
|
|
15446996f9 | ||
|
|
c4237b6c40 | ||
|
|
d0685ea81a | ||
|
|
742e92f1ee | ||
|
|
553d709043 | ||
|
|
29435b1c15 | ||
|
|
a9b889e033 | ||
|
|
4a84e26f21 | ||
|
|
2eb3aa9883 | ||
|
|
ffd357c1e8 | ||
|
|
09287f624f | ||
|
|
cd7f2dc74d | ||
|
|
8132936e3e | ||
|
|
b49d1050c9 | ||
|
|
27f867bd4e | ||
|
|
f4d2cfb749 | ||
|
|
8620e001d7 | ||
|
|
bb9a2931a1 | ||
|
|
7c6788c0c5 | ||
|
|
e24ca642aa | ||
|
|
7dc9d6dfe4 | ||
|
|
118b2edc0a | ||
|
|
84d51481da | ||
|
|
fad7a26156 | ||
|
|
772d867093 | ||
|
|
5daf6d6c89 | ||
|
|
8b491b5702 | ||
|
|
18efea6616 | ||
|
|
48fc8b2e84 | ||
|
|
e2adfa3774 | ||
|
|
41134ca621 | ||
|
|
05a852d68c | ||
|
|
90d04b1f7d | ||
|
|
de0a2c631c | ||
|
|
2def517d39 | ||
|
|
fe6b1c7448 | ||
|
|
710366c8e5 | ||
|
|
96529106c7 | ||
|
|
39b371991e | ||
|
|
d4c1e9cc04 | ||
|
|
70a0024a76 | ||
|
|
62984cba40 | ||
|
|
1447e18c56 | ||
|
|
37a5cce8ce | ||
|
|
80de5e3d12 | ||
|
|
c1eddca1e5 | ||
|
|
19a04dc24e | ||
|
|
5d51207f4d | ||
|
|
8d727ea31f | ||
|
|
b46f89af7f | ||
|
|
5ad108fab6 | ||
|
|
361e12e691 | ||
|
|
e542f4fd0a | ||
|
|
e4363a1e50 | ||
|
|
b5996d06ba | ||
|
|
b1b6299ed5 | ||
|
|
7681cc249c | ||
|
|
cad5e8179c | ||
|
|
4c77bcb7cc | ||
|
|
534931600b | ||
|
|
acaa2edd30 | ||
|
|
a86b5c4441 | ||
|
|
bd275dfbab | ||
|
|
7093fa0865 | ||
|
|
3012b870bd | ||
|
|
f44a3d3986 | ||
|
|
53113c0170 | ||
|
|
b0907d7139 | ||
|
|
fef12be8f1 | ||
|
|
e5e26eea14 | ||
|
|
d4623d9404 | ||
|
|
bc4856869e | ||
|
|
f6373f3e36 | ||
|
|
7038da85b1 | ||
|
|
a7bb9ea31e | ||
|
|
08c3fcf3df | ||
|
|
1d2c1191e2 | ||
|
|
66e070a07d | ||
|
|
7528c57e83 |
+5
-1
@@ -14,4 +14,8 @@
|
||||
## RULES
|
||||
|
||||
* @replicatedhq/troubleshoot
|
||||
*.md @replicatedhq/cre
|
||||
*.md @replicatedhq/cre
|
||||
go.mod
|
||||
go.sum
|
||||
/examples/sdk/helm-template/go.mod
|
||||
/examples/sdk/helm-template/go.sum
|
||||
|
||||
+21
-3
@@ -10,10 +10,28 @@ updates:
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "go"
|
||||
- "type::chore"
|
||||
- "type::security"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
groups:
|
||||
security:
|
||||
update-types:
|
||||
- "patch"
|
||||
- "minor"
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/examples/sdk/helm-template" # Location of package manifests
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "go"
|
||||
- "type::security"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
security:
|
||||
update-types:
|
||||
- "patch"
|
||||
- "minor"
|
||||
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
# Workflow files stored in the
|
||||
@@ -22,6 +40,6 @@ updates:
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
- "type::chore"
|
||||
- "type::security"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Automated PRs Manager
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */6 * * *" # every 6 hours
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
list-prs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
prs: ${{ steps.list-prs.outputs.prs }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.REPLICATED_GH_PAT }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: List PRs
|
||||
id: list-prs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# list prs that are less than 24h old and exclude prs from forks
|
||||
|
||||
dependabot_prs=$(
|
||||
gh pr list \
|
||||
--author 'dependabot[bot]' \
|
||||
--json url,createdAt,headRefName,headRepository,headRepositoryOwner \
|
||||
-q '.[] | select((.createdAt | fromdateiso8601 > now - 24*60*60) and .headRepositoryOwner.login == "replicatedhq" and .headRepository.name == "troubleshoot")'
|
||||
)
|
||||
|
||||
prs=$(echo "$dependabot_prs" | jq -sc '. | unique')
|
||||
echo "prs=$prs" >> "$GITHUB_OUTPUT"
|
||||
|
||||
process-prs:
|
||||
needs: list-prs
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.list-prs.outputs.prs != '[]'
|
||||
strategy:
|
||||
matrix:
|
||||
pr: ${{ fromJson(needs.list-prs.outputs.prs) }}
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.REPLICATED_GH_PAT }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ matrix.pr.headRefName }}
|
||||
|
||||
- name: Process PR
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "Ensuring required labels..."
|
||||
gh pr edit "${{ matrix.pr.url }}" --add-label "type::security"
|
||||
|
||||
echo "Checking status of tests..."
|
||||
run_id=$(gh run list --branch "${{ matrix.pr.headRefName }}" --workflow build-test-deploy --limit 1 --json databaseId -q '.[0].databaseId')
|
||||
|
||||
# If there are still pending jobs, skip.
|
||||
|
||||
num_of_pending_jobs=$(gh run view "$run_id" --json jobs -q '.jobs[] | select(.conclusion == "") | .name' | wc -l)
|
||||
if [ "$num_of_pending_jobs" -gt 0 ]; then
|
||||
echo "There are still pending jobs. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If all checks passed, approve and merge.
|
||||
if gh run view "$run_id" --json jobs -q '.jobs[] | select(.name == "validate-success") | .conclusion' | grep -q "success"; then
|
||||
if gh pr checks "${{ matrix.pr.url }}"; then
|
||||
echo "All tests passed. Approving and merging."
|
||||
echo -e "LGTM :thumbsup: \n\nThis PR was automatically approved and merged by the [automated-prs-manager](https://github.com/replicatedhq/troubleshoot/blob/main/.github/workflows/automated-prs-manager.yaml) GitHub action" > body.txt
|
||||
gh pr review --approve "${{ matrix.pr.url }}" --body-file body.txt
|
||||
sleep 10
|
||||
gh pr merge --auto --squash "${{ matrix.pr.url }}"
|
||||
exit 0
|
||||
else
|
||||
echo "Some checks did not pass. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# If more than half of the jobs are successful, re-run the failed jobs.
|
||||
|
||||
num_of_jobs=$(gh run view "$run_id" --json jobs -q '.jobs[].name ' | wc -l)
|
||||
num_of_successful_jobs=$(gh run view "$run_id" --json jobs -q '.jobs[] | select(.conclusion == "success") | .name' | wc -l)
|
||||
|
||||
if [ "$num_of_successful_jobs" -gt $((num_of_jobs / 2)) ]; then
|
||||
echo "More than half of the jobs are successful. Re-running failed jobs."
|
||||
gh run rerun "$run_id" --failed
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Less than half of the jobs are successful. Skipping."
|
||||
@@ -1,3 +1,5 @@
|
||||
name: build-test-deploy
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
@@ -28,9 +30,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
@@ -45,9 +47,9 @@ jobs:
|
||||
test-integration:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
@@ -59,16 +61,16 @@ jobs:
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
version: v1.24.1-k3s1
|
||||
|
||||
- run: make test-integration
|
||||
|
||||
ensure-schemas-are-generated:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
@@ -82,9 +84,9 @@ jobs:
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
@@ -92,7 +94,7 @@ jobs:
|
||||
shell: bash
|
||||
- uses: actions/checkout@v4
|
||||
- run: make generate preflight
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/preflight
|
||||
@@ -106,7 +108,7 @@ jobs:
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
@@ -155,7 +157,7 @@ jobs:
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
@@ -171,9 +173,9 @@ jobs:
|
||||
compile-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
@@ -181,7 +183,7 @@ jobs:
|
||||
shell: bash
|
||||
- uses: actions/checkout@v4
|
||||
- run: make generate support-bundle
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/support-bundle
|
||||
@@ -196,7 +198,7 @@ jobs:
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download support-bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
@@ -215,7 +217,7 @@ jobs:
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download support bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
@@ -226,9 +228,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
@@ -236,7 +238,7 @@ jobs:
|
||||
shell: bash
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download support bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
@@ -246,9 +248,9 @@ jobs:
|
||||
compile-collect:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
@@ -256,7 +258,7 @@ jobs:
|
||||
shell: bash
|
||||
- uses: actions/checkout@v4
|
||||
- run: make generate collect
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: collect
|
||||
path: bin/collect
|
||||
@@ -279,12 +281,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: build --rm-dist --snapshot --config deploy/.goreleaser.yaml --single-target
|
||||
@@ -305,14 +307,14 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: azure/docker-login@v1
|
||||
- uses: azure/docker-login@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.22"
|
||||
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
@@ -333,7 +335,7 @@ jobs:
|
||||
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: "v1.x" # Binary version to install
|
||||
args: release --rm-dist --config deploy/.goreleaser.yaml
|
||||
@@ -348,3 +350,40 @@ jobs:
|
||||
uses: rajatjindal/krew-release-bot@v0.0.46
|
||||
with:
|
||||
krew_template_file: deploy/krew/support-bundle.yaml
|
||||
|
||||
|
||||
# summary jobs, these jobs will only run if all the other jobs have succeeded
|
||||
validate-pr-tests:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- test-integration
|
||||
- run-examples
|
||||
- compile-collect
|
||||
- validate-preflight
|
||||
- validate-preflight-e2e
|
||||
- validate-supportbundle
|
||||
- validate-supportbundle-e2e
|
||||
- validate-supportbundle-e2e-go-test
|
||||
- ensure-schemas-are-generated
|
||||
steps:
|
||||
- run: echo "All PR tests passed"
|
||||
|
||||
|
||||
# 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:
|
||||
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
|
||||
- name: fail if validate-pr-tests job was not successful
|
||||
if: needs.validate-pr-tests.result != 'success'
|
||||
run: exit 1
|
||||
# if the validate-pr-tests job was successful, this job will succeed
|
||||
- name: succeed if validate-pr-tests job succeeded
|
||||
if: needs.validate-pr-tests.result == 'success'
|
||||
run: echo "Validation succeeded"
|
||||
@@ -22,6 +22,6 @@ jobs:
|
||||
severity: 'HIGH,CRITICAL'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
@@ -16,7 +16,9 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21
|
||||
|
||||
- name: Install Go deps
|
||||
run: go mod download
|
||||
@@ -30,7 +32,7 @@ jobs:
|
||||
run: trivy fs --scanners license --skip-dirs ".github" . | tee license-report.txt
|
||||
|
||||
- name: Upload license report artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: license-report
|
||||
path: license-report.txt
|
||||
|
||||
@@ -10,3 +10,4 @@ linters:
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gosec
|
||||
- govet
|
||||
|
||||
+4
-4
@@ -23,12 +23,12 @@ To get started we recommend:
|
||||
|
||||
1. Go (v1.20 or later)
|
||||
2. A Kubernetes cluster (we recommend <https://k3d.io/>. This requires Docker v20.10.5 or later)
|
||||
3. Fork and clone the repo to $GOPATH/src/github.com/replicatedhq/
|
||||
4. Run `make support-bundle preflight` to generate binaries
|
||||
5. Run `make run-troubleshoot` to generate a support bundle with the `sample-troubleshoot.yaml` in the root of the repo
|
||||
3. Fork and clone repo
|
||||
4. Run `make clean build` to generate binaries
|
||||
5. Run `make run-support-bundle` to generate a support bundle with the `sample-troubleshoot.yaml` in the root of the repo
|
||||
|
||||
> Note: recent versions of Go support easy cross-compilation. For example, to cross-compile a Linux binary from MacOS:
|
||||
> `GOOS=linux GOARCH=amd64 make support-bundle preflight`
|
||||
> `GOOS=linux GOARCH=amd64 make clean build`
|
||||
|
||||
6. Install [golangci-lint] linter and run `make lint` to execute additional code linters.
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ BUILDPATHS = ./pkg/... ./cmd/... ./internal/...
|
||||
E2EPATHS = ./test/e2e/...
|
||||
TESTFLAGS ?= -v -coverprofile cover.out
|
||||
|
||||
.DEFAULT: all
|
||||
all: test build
|
||||
.DEFAULT_GOAL := all
|
||||
all: clean build test
|
||||
|
||||
.PHONY: ffi
|
||||
ffi: fmt vet
|
||||
@@ -80,30 +80,44 @@ support-bundle-e2e-go-test:
|
||||
go test ${BUILDFLAGS} ${E2EPATHS} -v; \
|
||||
fi
|
||||
|
||||
# Build all binaries in parallel ( -j )
|
||||
build:
|
||||
@echo "Build cli binaries"
|
||||
$(MAKE) -j support-bundle preflight analyze collect
|
||||
rebuild: clean build
|
||||
|
||||
mod-tidy:
|
||||
# Build all binaries in parallel ( -j )
|
||||
build: tidy
|
||||
@echo "Build cli binaries"
|
||||
$(MAKE) -j bin/support-bundle bin/preflight bin/analyze bin/collect
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -f bin/analyze
|
||||
@rm -f bin/support-bundle
|
||||
@rm -f bin/collect
|
||||
@rm -f bin/preflight
|
||||
@rm -f bin/troubleshoot.h
|
||||
@rm -f bin/troubleshoot.so
|
||||
@rm -f bin/schemagen
|
||||
@rm -f bin/docsgen
|
||||
|
||||
.PHONY: tidy
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle:
|
||||
bin/support-bundle:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: preflight
|
||||
preflight:
|
||||
bin/preflight:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
|
||||
.PHONY: analyze
|
||||
analyze:
|
||||
bin/analyze:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.PHONY: collect
|
||||
collect:
|
||||
bin/collect:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
|
||||
|
||||
build-linux: tidy
|
||||
@echo "Build cli binaries for Linux"
|
||||
GOOS=linux GOARCH=amd64 $(MAKE) -j bin/support-bundle bin/preflight bin/analyze bin/collect
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
go fmt ${BUILDPATHS}
|
||||
@@ -138,27 +152,27 @@ check-schemas: generate schemas
|
||||
fi
|
||||
|
||||
.PHONY: schemas
|
||||
schemas: fmt vet openapischema
|
||||
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
|
||||
schemas: fmt vet openapischema bin/schemagen
|
||||
./bin/schemagen --output-dir ./schemas
|
||||
|
||||
bin/schemagen:
|
||||
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
|
||||
|
||||
.PHONY: docs
|
||||
docs: fmt vet
|
||||
go build ${LDFLAGS} -o bin/docsgen github.com/replicatedhq/troubleshoot/cmd/docsgen
|
||||
docs: fmt vet bin/docsgen
|
||||
./bin/docsgen
|
||||
|
||||
bin/docsgen:
|
||||
go build ${LDFLAGS} -o bin/docsgen github.com/replicatedhq/troubleshoot/cmd/docsgen
|
||||
|
||||
controller-gen:
|
||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.11.2
|
||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
|
||||
.PHONY: client-gen
|
||||
client-gen:
|
||||
ifeq (, $(shell which client-gen 2>/dev/null))
|
||||
go install k8s.io/code-generator/cmd/client-gen@v0.28.2
|
||||
CLIENT_GEN=$(shell go env GOPATH)/bin/client-gen
|
||||
else
|
||||
go install k8s.io/code-generator/cmd/client-gen@v0.28.8
|
||||
CLIENT_GEN=$(shell which client-gen)
|
||||
endif
|
||||
|
||||
.PHONY: release
|
||||
release: export GITHUB_TOKEN = $(shell echo ${GITHUB_TOKEN_TROUBLESHOOT})
|
||||
@@ -180,15 +194,15 @@ local-release:
|
||||
docker push localhost:32000/preflight:alpha
|
||||
|
||||
.PHONY: run-preflight
|
||||
run-preflight: preflight
|
||||
run-preflight: bin/preflight
|
||||
./bin/preflight ./examples/preflight/sample-preflight.yaml
|
||||
|
||||
.PHONY: run-troubleshoot
|
||||
run-troubleshoot: support-bundle
|
||||
.PHONY: run-support-bundle
|
||||
run-support-bundle: bin/support-bundle
|
||||
./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
|
||||
|
||||
.PHONY: run-analyze
|
||||
run-analyze: analyze
|
||||
run-analyze: bin/analyze
|
||||
./bin/analyze --analyzers ./examples/support-bundle/sample-analyzers.yaml ./support-bundle.tar.gz
|
||||
|
||||
.PHONY: init-sbom
|
||||
@@ -212,20 +226,6 @@ sbom: sbom/assets/troubleshoot-sbom.tgz
|
||||
cosign sign-blob -key cosign.key sbom/assets/troubleshoot-sbom.tgz > sbom/assets/troubleshoot-sbom.tgz.sig
|
||||
cosign public-key -key cosign.key -outfile sbom/assets/key.pub
|
||||
|
||||
longhorn:
|
||||
git clone https://github.com/longhorn/longhorn-manager.git
|
||||
cd longhorn-manager && git checkout v1.2.2 && cd ..
|
||||
rm -rf pkg/longhorn
|
||||
mv longhorn-manager/k8s/pkg pkg/longhorn
|
||||
mv longhorn-manager/types pkg/longhorn/types
|
||||
mv longhorn-manager/util pkg/longhorn/util
|
||||
rm -rf pkg/longhorn/util/daemon
|
||||
rm -rf pkg/longhorn/util/server
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/k8s\/pkg/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn/g"
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/types/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/types/g"
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/util/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/util/g"
|
||||
rm -rf longhorn-manager
|
||||
|
||||
.PHONY: scan
|
||||
scan:
|
||||
trivy fs \
|
||||
@@ -236,11 +236,11 @@ scan:
|
||||
./
|
||||
|
||||
.PHONY: lint
|
||||
lint: fmt vet
|
||||
lint:
|
||||
golangci-lint run --new -c .golangci.yaml ${BUILDPATHS}
|
||||
|
||||
.PHONY: lint-and-fix
|
||||
lint-and-fix: fmt vet
|
||||
lint-and-fix:
|
||||
golangci-lint run --new --fix -c .golangci.yaml ${BUILDPATHS}
|
||||
|
||||
.PHONY: watch
|
||||
@@ -256,3 +256,35 @@ watchrsync: npm-install
|
||||
npm-install:
|
||||
npm --version 2>&1 >/dev/null || ( echo "npm not installed; install npm to set up watchrsync" && exit 1 )
|
||||
npm list gaze-run-interrupt || npm install install --no-save gaze-run-interrupt@~2.0.0
|
||||
|
||||
|
||||
######## Lagacy make targets ###########
|
||||
# Deprecated: These can be removed
|
||||
.PHONY: support-bundle
|
||||
support-bundle: clean bin/support-bundle
|
||||
|
||||
.PHONY: preflight
|
||||
preflight: clean bin/preflight
|
||||
|
||||
.PHONY: analyze
|
||||
analyze: clean bin/analyze
|
||||
|
||||
.PHONY: collect
|
||||
collect: clean bin/collect
|
||||
|
||||
.PHONY: run-troubleshoot
|
||||
run-troubleshoot: run-support-bundle
|
||||
|
||||
longhorn:
|
||||
git clone https://github.com/longhorn/longhorn-manager.git
|
||||
cd longhorn-manager && git checkout v1.2.2 && cd ..
|
||||
rm -rf pkg/longhorn
|
||||
mv longhorn-manager/k8s/pkg pkg/longhorn
|
||||
mv longhorn-manager/types pkg/longhorn/types
|
||||
mv longhorn-manager/util pkg/longhorn/util
|
||||
rm -rf pkg/longhorn/util/daemon
|
||||
rm -rf pkg/longhorn/util/server
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/k8s\/pkg/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn/g"
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/types/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/types/g"
|
||||
find pkg/longhorn -type f | xargs sed -i "s/github.com\/longhorn\/longhorn-manager\/util/github.com\/replicatedhq\/troubleshoot\/pkg\/longhorn\/util/g"
|
||||
rm -rf longhorn-manager
|
||||
|
||||
@@ -27,7 +27,7 @@ func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
} else {
|
||||
if !util.IsURL(specPath) {
|
||||
// TODO: Better error message when we do not have a file/url etc
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
|
||||
return fmt.Errorf("%s is not a URL and was not found", specPath)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
|
||||
@@ -58,7 +58,7 @@ func runCollect(v *viper.Viper, arg string) error {
|
||||
collectorContent = b
|
||||
} else {
|
||||
if !util.IsURL(arg) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
return fmt.Errorf("%s is not a URL and was not found", arg)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
|
||||
@@ -71,6 +71,7 @@ that a cluster meets the requirements to run an application.`,
|
||||
// Dry run flag should be in cmd.PersistentFlags() flags made available to all subcommands
|
||||
// Adding here to avoid that
|
||||
cmd.Flags().Bool("dry-run", false, "print the preflight spec without running preflight checks")
|
||||
cmd.Flags().Bool("no-uri", false, "When this flag is used, Preflight does not attempt to retrieve the spec referenced by the uri: field`")
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ func downloadAnalyzerSpec(specPath string) (string, error) {
|
||||
specContent = string(b)
|
||||
} else {
|
||||
if !util.IsURL(specPath) {
|
||||
return "", fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
|
||||
return "", fmt.Errorf("%s is not a URL and was not found", specPath)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
|
||||
+78
-37
@@ -106,6 +106,22 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
if interactive {
|
||||
c := color.New()
|
||||
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
|
||||
}
|
||||
|
||||
if interactive {
|
||||
if len(mainBundle.Spec.HostCollectors) > 0 && !util.IsRunningAsRoot() {
|
||||
fmt.Print(cursor.Show())
|
||||
if util.PromptYesNo(util.HOST_COLLECTORS_RUN_AS_ROOT_PROMPT) {
|
||||
fmt.Println("Exiting...")
|
||||
return nil
|
||||
}
|
||||
fmt.Print(cursor.Hide())
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
collectorCB := func(c chan interface{}, msg string) { c <- msg }
|
||||
progressChan := make(chan interface{})
|
||||
@@ -174,11 +190,6 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
|
||||
|
||||
nonInteractiveOutput := analysisOutput{}
|
||||
|
||||
if interactive {
|
||||
c := color.New()
|
||||
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
|
||||
}
|
||||
|
||||
response, err := supportbundle.CollectSupportBundleFromSpec(&mainBundle.Spec, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run collect and analyze process")
|
||||
@@ -232,24 +243,45 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSupportBundleSpecsFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) (*loader.TroubleshootKinds, error) {
|
||||
remoteRawSpecs := []string{}
|
||||
// loadSupportBundleSpecsFromURIs loads support bundle specs from URIs
|
||||
func loadSupportBundleSpecsFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) error {
|
||||
moreKinds := loader.NewTroubleshootKinds()
|
||||
|
||||
// iterate through original kinds and replace any support bundle spec with provided uri spec
|
||||
for _, s := range kinds.SupportBundlesV1Beta2 {
|
||||
if s.Spec.Uri != "" && util.IsURL(s.Spec.Uri) {
|
||||
// We are using LoadSupportBundleSpec function here since it handles prompting
|
||||
// users to accept insecure connections
|
||||
// There is an opportunity to refactor this code in favour of the Loader APIs
|
||||
rawSpec, err := supportbundle.LoadSupportBundleSpec(s.Spec.Uri)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to load support bundle from URI %q", s.Spec.Uri)
|
||||
}
|
||||
remoteRawSpecs = append(remoteRawSpecs, string(rawSpec))
|
||||
if s.Spec.Uri == "" || !util.IsURL(s.Spec.Uri) {
|
||||
moreKinds.SupportBundlesV1Beta2 = append(moreKinds.SupportBundlesV1Beta2, s)
|
||||
continue
|
||||
}
|
||||
|
||||
// We are using LoadSupportBundleSpec function here since it handles prompting
|
||||
// users to accept insecure connections
|
||||
// There is an opportunity to refactor this code in favour of the Loader APIs
|
||||
// TODO: Pass ctx to LoadSupportBundleSpec
|
||||
rawSpec, err := supportbundle.LoadSupportBundleSpec(s.Spec.Uri)
|
||||
if err != nil {
|
||||
// add back original spec
|
||||
moreKinds.SupportBundlesV1Beta2 = append(moreKinds.SupportBundlesV1Beta2, s)
|
||||
// In the event a spec can't be loaded, we'll just skip it and print a warning
|
||||
klog.Warningf("unable to load support bundle from URI: %q: %v", s.Spec.Uri, err)
|
||||
continue
|
||||
}
|
||||
k, err := loader.LoadSpecs(ctx, loader.LoadOptions{RawSpec: string(rawSpec)})
|
||||
if err != nil {
|
||||
// add back original spec
|
||||
moreKinds.SupportBundlesV1Beta2 = append(moreKinds.SupportBundlesV1Beta2, s)
|
||||
klog.Warningf("unable to load spec: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// finally append the uri spec
|
||||
moreKinds.SupportBundlesV1Beta2 = append(moreKinds.SupportBundlesV1Beta2, k.SupportBundlesV1Beta2...)
|
||||
|
||||
}
|
||||
|
||||
return loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
RawSpecs: remoteRawSpecs,
|
||||
})
|
||||
kinds.SupportBundlesV1Beta2 = moreKinds.SupportBundlesV1Beta2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface) (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta2.Redactor, error) {
|
||||
@@ -261,12 +293,12 @@ func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface)
|
||||
}
|
||||
|
||||
// Load additional specs from support bundle URIs
|
||||
// only when no-uri flag is not set and no URLs are provided in the args
|
||||
if !viper.GetBool("no-uri") {
|
||||
moreKinds, err := loadSupportBundleSpecsFromURIs(ctx, kinds)
|
||||
err := loadSupportBundleSpecsFromURIs(ctx, kinds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
klog.Warningf("unable to load support bundles from URIs: %v", err)
|
||||
}
|
||||
kinds.Add(moreKinds)
|
||||
}
|
||||
|
||||
// Check if we have any collectors to run in the troubleshoot specs
|
||||
@@ -295,25 +327,28 @@ func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface)
|
||||
}
|
||||
|
||||
for _, c := range kinds.CollectorsV1Beta2 {
|
||||
mainBundle.Spec.Collectors = append(mainBundle.Spec.Collectors, c.Spec.Collectors...)
|
||||
mainBundle.Spec.Collectors = util.Append(mainBundle.Spec.Collectors, c.Spec.Collectors)
|
||||
}
|
||||
|
||||
for _, hc := range kinds.HostCollectorsV1Beta2 {
|
||||
mainBundle.Spec.HostCollectors = append(mainBundle.Spec.HostCollectors, hc.Spec.Collectors...)
|
||||
mainBundle.Spec.HostCollectors = util.Append(mainBundle.Spec.HostCollectors, hc.Spec.Collectors)
|
||||
}
|
||||
|
||||
// Ensure cluster info and cluster resources collectors are in the merged spec
|
||||
// We need to add them here so when we --dry-run, these collectors are included.
|
||||
// supportbundle.runCollectors duplicates this bit. We'll need to refactor it out later
|
||||
// when its clearer what other code depends on this logic e.g KOTS
|
||||
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
|
||||
mainBundle.Spec.Collectors,
|
||||
troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}},
|
||||
)
|
||||
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
|
||||
mainBundle.Spec.Collectors,
|
||||
troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}},
|
||||
)
|
||||
if !(len(mainBundle.Spec.HostCollectors) > 0 && len(mainBundle.Spec.Collectors) == 0) {
|
||||
// Always add default collectors unless we only have host collectors
|
||||
// We need to add them here so when we --dry-run, these collectors
|
||||
// are included. supportbundle.runCollectors duplicates this bit.
|
||||
// We'll need to refactor it out later when its clearer what other
|
||||
// code depends on this logic e.g KOTS
|
||||
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
|
||||
mainBundle.Spec.Collectors,
|
||||
troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}},
|
||||
)
|
||||
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
|
||||
mainBundle.Spec.Collectors,
|
||||
troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}},
|
||||
)
|
||||
}
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -325,9 +360,15 @@ func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface)
|
||||
},
|
||||
}
|
||||
for _, r := range kinds.RedactorsV1Beta2 {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, r.Spec.Redactors...)
|
||||
additionalRedactors.Spec.Redactors = util.Append(additionalRedactors.Spec.Redactors, r.Spec.Redactors)
|
||||
}
|
||||
|
||||
// dedupe specs
|
||||
mainBundle.Spec.Collectors = util.Dedup(mainBundle.Spec.Collectors)
|
||||
mainBundle.Spec.Analyzers = util.Dedup(mainBundle.Spec.Analyzers)
|
||||
mainBundle.Spec.HostCollectors = util.Dedup(mainBundle.Spec.HostCollectors)
|
||||
mainBundle.Spec.HostAnalyzers = util.Dedup(mainBundle.Spec.HostAnalyzers)
|
||||
|
||||
return mainBundle, additionalRedactors, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,37 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/internal/testutils"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/loader"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func templSpec() string {
|
||||
return `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-1
|
||||
spec:
|
||||
uri: $MY_URI
|
||||
collectors:
|
||||
- configMap:
|
||||
name: kube-root-ca.crt
|
||||
namespace: default
|
||||
`
|
||||
}
|
||||
|
||||
func Test_loadSupportBundleSpecsFromURIs(t *testing.T) {
|
||||
// Run a webserver to serve the spec
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -25,27 +47,373 @@ spec:
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
orig := `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-1
|
||||
spec:
|
||||
uri: ` + srv.URL + `
|
||||
collectors:
|
||||
- configMap:
|
||||
name: kube-root-ca.crt
|
||||
namespace: default
|
||||
`
|
||||
orig := strings.ReplaceAll(templSpec(), "$MY_URI", srv.URL)
|
||||
|
||||
ctx := context.Background()
|
||||
kinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{RawSpec: orig})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, kinds)
|
||||
|
||||
moreKinds, err := loadSupportBundleSpecsFromURIs(ctx, kinds)
|
||||
assert.Len(t, kinds.SupportBundlesV1Beta2, 1)
|
||||
assert.NotNil(t, kinds.SupportBundlesV1Beta2[0].Spec.Collectors[0].ConfigMap)
|
||||
err = loadSupportBundleSpecsFromURIs(ctx, kinds)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, moreKinds.SupportBundlesV1Beta2, 1)
|
||||
assert.NotNil(t, moreKinds.SupportBundlesV1Beta2[0].Spec.Collectors[0].ClusterInfo)
|
||||
require.Len(t, kinds.SupportBundlesV1Beta2, 1)
|
||||
assert.NotNil(t, kinds.SupportBundlesV1Beta2[0].Spec.Collectors[0].ClusterInfo)
|
||||
}
|
||||
|
||||
func Test_loadMultipleSupportBundleSpecsWithNoURIs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
specs := []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-1
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo:{}`), testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-2
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`)}
|
||||
|
||||
sb, _, err := loadSpecs(ctx, specs, client)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sb.Spec.Collectors, 2)
|
||||
}
|
||||
|
||||
func Test_loadMultipleSupportBundleSpecsWithURIs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
|
||||
specFile := testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-file
|
||||
spec:
|
||||
collectors:
|
||||
- logs:
|
||||
name: podlogs/kotsadm
|
||||
selector:
|
||||
- app=kotsadm
|
||||
`)
|
||||
|
||||
// Run a webserver to serve the spec
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-uri
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
orig := strings.ReplaceAll(templSpec(), "$MY_URI", srv.URL)
|
||||
specUri := testutils.ServeFromFilePath(t, orig)
|
||||
specs := []string{specFile, specUri}
|
||||
|
||||
sb, _, err := loadSpecs(ctx, specs, client)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, sb.Spec.Collectors[0].Logs)
|
||||
assert.Nil(t, sb.Spec.Collectors[1].ConfigMap) // original spec gone
|
||||
assert.NotNil(t, sb.Spec.Collectors[1].ClusterInfo) // new spec from URI
|
||||
}
|
||||
|
||||
func Test_loadSupportBundleSpecsFromURIs_TimeoutError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(2 * time.Second) // this will cause a timeout
|
||||
}))
|
||||
defer srv.Close()
|
||||
ctx := context.Background()
|
||||
|
||||
kinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
RawSpec: strings.ReplaceAll(templSpec(), "$MY_URI", srv.URL),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set the timeout on the http client to 10ms
|
||||
// supportbundle.LoadSupportBundleSpec does not yet use the context
|
||||
before := httputil.GetHttpClient().Timeout
|
||||
httputil.GetHttpClient().Timeout = 10 * time.Millisecond
|
||||
defer func() {
|
||||
// Reinstate the original timeout. Its a global var so we need to reset it
|
||||
httputil.GetHttpClient().Timeout = before
|
||||
}()
|
||||
|
||||
beforeJSON, err := json.Marshal(kinds)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = loadSupportBundleSpecsFromURIs(ctx, kinds)
|
||||
require.NoError(t, err)
|
||||
|
||||
afterJSON, err := json.Marshal(kinds)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.JSONEq(t, string(beforeJSON), string(afterJSON))
|
||||
}
|
||||
|
||||
func Test_loadSupportBundleSpecs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected troubleshootv1beta2.SupportBundleSpec
|
||||
}{
|
||||
{
|
||||
name: "empty collectors array in spec, default collectors added",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
spec:
|
||||
collectors: []
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no collectors defined in spec, default collectors added",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
spec:
|
||||
analyzers: []
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
},
|
||||
Analyzers: []*troubleshootv1beta2.Analyze{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "collectors present but defaults missing, default collectors added",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
spec:
|
||||
collectors:
|
||||
- logs: {}
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
Logs: &troubleshootv1beta2.Logs{},
|
||||
},
|
||||
{
|
||||
ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty support bundle spec adds default collectors",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sb spec with host collectors does not add default collectors",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
spec:
|
||||
hostCollectors:
|
||||
- cpu: {}
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
HostCollectors: []*troubleshootv1beta2.HostCollect{
|
||||
{
|
||||
CPU: &troubleshootv1beta2.CPU{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "host collector spec with collectors does not add default collectors",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostCollector
|
||||
spec:
|
||||
collectors:
|
||||
- cpu: {}
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
HostCollectors: []*troubleshootv1beta2.HostCollect{
|
||||
{
|
||||
CPU: &troubleshootv1beta2.CPU{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sb spec with host and in-cluster collectors adds default collectors",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
spec:
|
||||
hostCollectors:
|
||||
- cpu: {}
|
||||
collectors:
|
||||
- logs: {}
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
HostCollectors: []*troubleshootv1beta2.HostCollect{
|
||||
{
|
||||
CPU: &troubleshootv1beta2.CPU{},
|
||||
},
|
||||
},
|
||||
Collectors: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
Logs: &troubleshootv1beta2.Logs{},
|
||||
},
|
||||
{
|
||||
ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sb spec with host collectors and empty in-cluster collectors does not default collectors",
|
||||
args: []string{testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
spec:
|
||||
hostCollectors:
|
||||
- cpu: {}
|
||||
collectors: []
|
||||
`)},
|
||||
expected: troubleshootv1beta2.SupportBundleSpec{
|
||||
HostCollectors: []*troubleshootv1beta2.HostCollect{
|
||||
{
|
||||
CPU: &troubleshootv1beta2.CPU{},
|
||||
},
|
||||
},
|
||||
Collectors: []*troubleshootv1beta2.Collect{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
|
||||
sb, _, err := loadSpecs(ctx, test.args, client)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, sb.Spec)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_loadDuplicatedBundleSpecs(t *testing.T) {
|
||||
spec := testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb
|
||||
spec:
|
||||
collectors:
|
||||
- helm: {}
|
||||
analyzers:
|
||||
- clusterVersion: {}
|
||||
hostCollectors:
|
||||
- cpu: {}
|
||||
hostAnalyzers:
|
||||
- cpu: {}
|
||||
`)
|
||||
args := []string{spec, spec}
|
||||
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
sb, _, err := loadSpecs(ctx, args, client)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, sb.Spec.Collectors, 1+2) // default clusterInfo + clusterResources
|
||||
assert.Len(t, sb.Spec.Analyzers, 1)
|
||||
assert.Len(t, sb.Spec.HostCollectors, 1)
|
||||
assert.Len(t, sb.Spec.HostAnalyzers, 1)
|
||||
}
|
||||
|
||||
func Test_loadSpecsFromURL(t *testing.T) {
|
||||
// Run a webserver to serve the URI spec
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-2
|
||||
spec:
|
||||
collectors:
|
||||
- logs:
|
||||
name: podlogs/kotsadm
|
||||
selector:
|
||||
- app=kotsadm`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
// update URI spec with the server URL
|
||||
orig := strings.ReplaceAll(templSpec(), "$MY_URI", srv.URL)
|
||||
|
||||
// now create a webserver to serve the spec with URI
|
||||
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(orig))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
fileSpec := testutils.ServeFromFilePath(t, `
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb
|
||||
spec:
|
||||
collectors:
|
||||
- helm: {}`)
|
||||
|
||||
// test and ensure that URI spec is not loaded
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset()
|
||||
sb, _, err := loadSpecs(ctx, []string{fileSpec, srv.URL}, client)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, sb.Spec.Collectors, 2+2) // default + clusterInfo + clusterResources
|
||||
assert.NotNil(t, sb.Spec.Collectors[0].Helm) // come from the file spec
|
||||
assert.NotNil(t, sb.Spec.Collectors[1].ConfigMap) // come from the original spec
|
||||
assert.Nil(t, sb.Spec.Collectors[1].Logs) // come from the URI spec
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: analyzers.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: Analyzer is the Schema for the analyzers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: collectors.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: Collector is the Schema for the collectors API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -164,10 +168,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
@@ -188,10 +192,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
@@ -210,10 +214,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: preflights.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: Preflight is the Schema for the preflights API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -836,10 +840,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
@@ -860,10 +864,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
@@ -882,10 +886,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: redactors.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: Redactor is the Schema for the redaction API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: supportbundles.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: SupportBundle is the Schema for the SupportBundles API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -867,10 +871,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
@@ -891,10 +895,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
@@ -913,10 +917,10 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: A Duration represents the elapsed time
|
||||
between two instants as an int64 nanosecond count.
|
||||
The representation limits the largest representable
|
||||
duration to approximately 290 years.
|
||||
description: |-
|
||||
A Duration represents the elapsed time between two instants
|
||||
as an int64 nanosecond count. The representation limits the
|
||||
largest representable duration to approximately 290 years.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: analyzers.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: Analyzer is the Schema for the analyzers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -559,6 +563,165 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
event:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
kind:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
reason:
|
||||
type: string
|
||||
regex:
|
||||
type: string
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
- reason
|
||||
type: object
|
||||
goldpinger:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
filePath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- collectorName
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
imagePullSecret:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -931,6 +1094,66 @@ spec:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
nodeMetrics:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
filters:
|
||||
properties:
|
||||
pvc:
|
||||
properties:
|
||||
nameRegex:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
nodeResources:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -1489,6 +1712,19 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
velero:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
strict:
|
||||
type: BoolString
|
||||
type: object
|
||||
weaveReport:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -2112,6 +2348,117 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
jsonCompare:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
jsonPath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
path:
|
||||
type: string
|
||||
strict:
|
||||
type: BoolString
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
kernelConfigs:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
selectedConfigs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
- selectedConfigs
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -2616,6 +2963,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: AnalyzerStatus defines the observed state of Analyzer
|
||||
|
||||
+12024
-3954
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: hostcollectors.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: HostCollector is the Schema for the collectors API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -584,6 +588,117 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
jsonCompare:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
jsonPath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
path:
|
||||
type: string
|
||||
strict:
|
||||
type: BoolString
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
kernelConfigs:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
selectedConfigs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
- selectedConfigs
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -1125,6 +1240,15 @@ spec:
|
||||
required:
|
||||
- paths
|
||||
type: object
|
||||
cgroups:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
mountPoint:
|
||||
type: string
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
collectorName:
|
||||
@@ -1155,9 +1279,9 @@ spec:
|
||||
- path
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
description: FilesystemPerformance benchmarks sequential write
|
||||
latency on a single file. The optional background IOPS feature
|
||||
attempts to mimic real-world conditions by running read and
|
||||
description: |-
|
||||
FilesystemPerformance benchmarks sequential write latency on a single file.
|
||||
The optional background IOPS feature attempts to mimic real-world conditions by running read and
|
||||
write workloads prior to and during benchmark execution.
|
||||
properties:
|
||||
backgroundIOPSWarmupSeconds:
|
||||
@@ -1165,34 +1289,33 @@ spec:
|
||||
write workloads prior to starting the benchmarks.
|
||||
type: integer
|
||||
backgroundReadIOPS:
|
||||
description: The target read IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background read jobs.
|
||||
description: |-
|
||||
The target read IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background read jobs.
|
||||
type: integer
|
||||
backgroundReadIOPSJobs:
|
||||
description: Number of threads to use for background read
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgrounReadIOPS.
|
||||
description: |-
|
||||
Number of threads to use for background read IOPS. This should be set high enough to reach
|
||||
the target specified in BackgrounReadIOPS.
|
||||
type: integer
|
||||
backgroundWriteIOPS:
|
||||
description: The target write IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background write jobs.
|
||||
description: |-
|
||||
The target write IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background write jobs.
|
||||
type: integer
|
||||
backgroundWriteIOPSJobs:
|
||||
description: 'Number of threads to use for background write
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgroundWriteIOPS. Example: If BackgroundWriteIOPS
|
||||
is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at
|
||||
least 2.'
|
||||
description: |-
|
||||
Number of threads to use for background write IOPS. This should be set high enough to reach
|
||||
the target specified in BackgroundWriteIOPS.
|
||||
Example: If BackgroundWriteIOPS is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at least 2.
|
||||
type: integer
|
||||
collectorName:
|
||||
type: string
|
||||
datasync:
|
||||
description: Whether to call datasync on the file after
|
||||
each write. Skipped if Sync is also true. Does not apply
|
||||
to background IOPS task.
|
||||
description: |-
|
||||
Whether to call datasync on the file after each write. Skipped if Sync is also true. Does not
|
||||
apply to background IOPS task.
|
||||
type: boolean
|
||||
directory:
|
||||
description: The directory where the benchmark will create
|
||||
@@ -1204,16 +1327,14 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileSize:
|
||||
description: The size of the file used in the benchmark.
|
||||
The number of IO operations for the benchmark will be
|
||||
FileSize / OperationSizeBytes. Accepts valid Kubernetes
|
||||
resource units such as Mi.
|
||||
description: |-
|
||||
The size of the file used in the benchmark. The number of IO operations for the benchmark
|
||||
will be FileSize / OperationSizeBytes. Accepts valid Kubernetes resource units such as Mi.
|
||||
type: string
|
||||
operationSize:
|
||||
description: The size of each write operation performed
|
||||
while benchmarking. This does not apply to the background
|
||||
IOPS feature if enabled, since those must be fixed at
|
||||
4096.
|
||||
description: |-
|
||||
The size of each write operation performed while benchmarking. This does not apply to the
|
||||
background IOPS feature if enabled, since those must be fixed at 4096.
|
||||
format: int64
|
||||
type: integer
|
||||
sync:
|
||||
@@ -1261,9 +1382,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1281,9 +1402,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1301,9 +1422,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1337,6 +1458,42 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
journald:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
dmesg:
|
||||
type: boolean
|
||||
exclude:
|
||||
type: BoolString
|
||||
lines:
|
||||
type: integer
|
||||
output:
|
||||
type: string
|
||||
reverse:
|
||||
type: boolean
|
||||
since:
|
||||
type: string
|
||||
system:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: string
|
||||
units:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
until:
|
||||
type: string
|
||||
utc:
|
||||
type: boolean
|
||||
type: object
|
||||
kernelConfigs:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
collectorName:
|
||||
@@ -1368,8 +1525,26 @@ spec:
|
||||
type: string
|
||||
command:
|
||||
type: string
|
||||
env:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
ignoreParentEnvs:
|
||||
type: boolean
|
||||
inheritEnvs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
input:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
outputDir:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- args
|
||||
- command
|
||||
@@ -1543,6 +1718,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: HostCollectorStatus defines the observed state of HostCollector
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: hostpreflights.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: HostPreflight is the Schema for the hostpreflights API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -584,6 +588,117 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
jsonCompare:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
jsonPath:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
path:
|
||||
type: string
|
||||
strict:
|
||||
type: BoolString
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
kernelConfigs:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
selectedConfigs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
strict:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
- selectedConfigs
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
annotations:
|
||||
@@ -1125,6 +1240,15 @@ spec:
|
||||
required:
|
||||
- paths
|
||||
type: object
|
||||
cgroups:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
mountPoint:
|
||||
type: string
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
collectorName:
|
||||
@@ -1155,9 +1279,9 @@ spec:
|
||||
- path
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
description: FilesystemPerformance benchmarks sequential write
|
||||
latency on a single file. The optional background IOPS feature
|
||||
attempts to mimic real-world conditions by running read and
|
||||
description: |-
|
||||
FilesystemPerformance benchmarks sequential write latency on a single file.
|
||||
The optional background IOPS feature attempts to mimic real-world conditions by running read and
|
||||
write workloads prior to and during benchmark execution.
|
||||
properties:
|
||||
backgroundIOPSWarmupSeconds:
|
||||
@@ -1165,34 +1289,33 @@ spec:
|
||||
write workloads prior to starting the benchmarks.
|
||||
type: integer
|
||||
backgroundReadIOPS:
|
||||
description: The target read IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background read jobs.
|
||||
description: |-
|
||||
The target read IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background read jobs.
|
||||
type: integer
|
||||
backgroundReadIOPSJobs:
|
||||
description: Number of threads to use for background read
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgrounReadIOPS.
|
||||
description: |-
|
||||
Number of threads to use for background read IOPS. This should be set high enough to reach
|
||||
the target specified in BackgrounReadIOPS.
|
||||
type: integer
|
||||
backgroundWriteIOPS:
|
||||
description: The target write IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background write jobs.
|
||||
description: |-
|
||||
The target write IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background write jobs.
|
||||
type: integer
|
||||
backgroundWriteIOPSJobs:
|
||||
description: 'Number of threads to use for background write
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgroundWriteIOPS. Example: If BackgroundWriteIOPS
|
||||
is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at
|
||||
least 2.'
|
||||
description: |-
|
||||
Number of threads to use for background write IOPS. This should be set high enough to reach
|
||||
the target specified in BackgroundWriteIOPS.
|
||||
Example: If BackgroundWriteIOPS is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at least 2.
|
||||
type: integer
|
||||
collectorName:
|
||||
type: string
|
||||
datasync:
|
||||
description: Whether to call datasync on the file after
|
||||
each write. Skipped if Sync is also true. Does not apply
|
||||
to background IOPS task.
|
||||
description: |-
|
||||
Whether to call datasync on the file after each write. Skipped if Sync is also true. Does not
|
||||
apply to background IOPS task.
|
||||
type: boolean
|
||||
directory:
|
||||
description: The directory where the benchmark will create
|
||||
@@ -1204,16 +1327,14 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileSize:
|
||||
description: The size of the file used in the benchmark.
|
||||
The number of IO operations for the benchmark will be
|
||||
FileSize / OperationSizeBytes. Accepts valid Kubernetes
|
||||
resource units such as Mi.
|
||||
description: |-
|
||||
The size of the file used in the benchmark. The number of IO operations for the benchmark
|
||||
will be FileSize / OperationSizeBytes. Accepts valid Kubernetes resource units such as Mi.
|
||||
type: string
|
||||
operationSize:
|
||||
description: The size of each write operation performed
|
||||
while benchmarking. This does not apply to the background
|
||||
IOPS feature if enabled, since those must be fixed at
|
||||
4096.
|
||||
description: |-
|
||||
The size of each write operation performed while benchmarking. This does not apply to the
|
||||
background IOPS feature if enabled, since those must be fixed at 4096.
|
||||
format: int64
|
||||
type: integer
|
||||
sync:
|
||||
@@ -1261,9 +1382,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1281,9 +1402,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1301,9 +1422,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1337,6 +1458,42 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
journald:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
dmesg:
|
||||
type: boolean
|
||||
exclude:
|
||||
type: BoolString
|
||||
lines:
|
||||
type: integer
|
||||
output:
|
||||
type: string
|
||||
reverse:
|
||||
type: boolean
|
||||
since:
|
||||
type: string
|
||||
system:
|
||||
type: boolean
|
||||
timeout:
|
||||
type: string
|
||||
units:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
until:
|
||||
type: string
|
||||
utc:
|
||||
type: boolean
|
||||
type: object
|
||||
kernelConfigs:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
collectorName:
|
||||
@@ -1368,8 +1525,26 @@ spec:
|
||||
type: string
|
||||
command:
|
||||
type: string
|
||||
env:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: BoolString
|
||||
ignoreParentEnvs:
|
||||
type: boolean
|
||||
inheritEnvs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
input:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
outputDir:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- args
|
||||
- command
|
||||
@@ -1586,44 +1761,43 @@ spec:
|
||||
- path
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
description: RemoteFilesystemPerformance benchmarks sequential
|
||||
write latency on a single file. The optional background IOPS
|
||||
feature attempts to mimic real-world conditions by running
|
||||
read and write workloads prior to and during benchmark execution.
|
||||
description: |-
|
||||
RemoteFilesystemPerformance benchmarks sequential write latency on a single file.
|
||||
The optional background IOPS feature attempts to mimic real-world conditions by running read and
|
||||
write workloads prior to and during benchmark execution.
|
||||
properties:
|
||||
backgroundIOPSWarmupSeconds:
|
||||
description: How long to run the background IOPS read and
|
||||
write workloads prior to starting the benchmarks.
|
||||
type: integer
|
||||
backgroundReadIOPS:
|
||||
description: The target read IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background read jobs.
|
||||
description: |-
|
||||
The target read IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background read jobs.
|
||||
type: integer
|
||||
backgroundReadIOPSJobs:
|
||||
description: Number of threads to use for background read
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgrounReadIOPS.
|
||||
description: |-
|
||||
Number of threads to use for background read IOPS. This should be set high enough to reach
|
||||
the target specified in BackgrounReadIOPS.
|
||||
type: integer
|
||||
backgroundWriteIOPS:
|
||||
description: The target write IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background write jobs.
|
||||
description: |-
|
||||
The target write IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background write jobs.
|
||||
type: integer
|
||||
backgroundWriteIOPSJobs:
|
||||
description: 'Number of threads to use for background write
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgroundWriteIOPS. Example: If BackgroundWriteIOPS
|
||||
is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at
|
||||
least 2.'
|
||||
description: |-
|
||||
Number of threads to use for background write IOPS. This should be set high enough to reach
|
||||
the target specified in BackgroundWriteIOPS.
|
||||
Example: If BackgroundWriteIOPS is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at least 2.
|
||||
type: integer
|
||||
collectorName:
|
||||
type: string
|
||||
datasync:
|
||||
description: Whether to call datasync on the file after
|
||||
each write. Skipped if Sync is also true. Does not apply
|
||||
to background IOPS task.
|
||||
description: |-
|
||||
Whether to call datasync on the file after each write. Skipped if Sync is also true. Does not
|
||||
apply to background IOPS task.
|
||||
type: boolean
|
||||
directory:
|
||||
description: The directory where the benchmark will create
|
||||
@@ -1635,16 +1809,14 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileSize:
|
||||
description: The size of the file used in the benchmark.
|
||||
The number of IO operations for the benchmark will be
|
||||
FileSize / OperationSizeBytes. Accepts valid Kubernetes
|
||||
resource units such as Mi.
|
||||
description: |-
|
||||
The size of the file used in the benchmark. The number of IO operations for the benchmark
|
||||
will be FileSize / OperationSizeBytes. Accepts valid Kubernetes resource units such as Mi.
|
||||
type: string
|
||||
operationSize:
|
||||
description: The size of each write operation performed
|
||||
while benchmarking. This does not apply to the background
|
||||
IOPS feature if enabled, since those must be fixed at
|
||||
4096.
|
||||
description: |-
|
||||
The size of each write operation performed while benchmarking. This does not apply to the
|
||||
background IOPS feature if enabled, since those must be fixed at 4096.
|
||||
format: int64
|
||||
type: integer
|
||||
sync:
|
||||
@@ -1685,9 +1857,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1705,9 +1877,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1725,9 +1897,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -1860,6 +2032,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: HostPreflightStatus defines the observed state of HostPreflight
|
||||
|
||||
+12292
-3993
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: redactors.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: Redactor is the Schema for the redaction API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -71,6 +75,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: RedactorStatus defines the observed state of Redactor
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.2
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: remotecollectors.troubleshoot.sh
|
||||
spec:
|
||||
group: troubleshoot.sh
|
||||
@@ -21,14 +20,19 @@ spec:
|
||||
description: RemoteCollector is the Schema for the remote collectors API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -109,44 +113,43 @@ spec:
|
||||
- path
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
description: RemoteFilesystemPerformance benchmarks sequential
|
||||
write latency on a single file. The optional background IOPS
|
||||
feature attempts to mimic real-world conditions by running
|
||||
read and write workloads prior to and during benchmark execution.
|
||||
description: |-
|
||||
RemoteFilesystemPerformance benchmarks sequential write latency on a single file.
|
||||
The optional background IOPS feature attempts to mimic real-world conditions by running read and
|
||||
write workloads prior to and during benchmark execution.
|
||||
properties:
|
||||
backgroundIOPSWarmupSeconds:
|
||||
description: How long to run the background IOPS read and
|
||||
write workloads prior to starting the benchmarks.
|
||||
type: integer
|
||||
backgroundReadIOPS:
|
||||
description: The target read IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background read jobs.
|
||||
description: |-
|
||||
The target read IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background read jobs.
|
||||
type: integer
|
||||
backgroundReadIOPSJobs:
|
||||
description: Number of threads to use for background read
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgrounReadIOPS.
|
||||
description: |-
|
||||
Number of threads to use for background read IOPS. This should be set high enough to reach
|
||||
the target specified in BackgrounReadIOPS.
|
||||
type: integer
|
||||
backgroundWriteIOPS:
|
||||
description: The target write IOPS to run while benchmarking.
|
||||
This is a limit and there is no guarantee it will be reached.
|
||||
This is the total IOPS for all background write jobs.
|
||||
description: |-
|
||||
The target write IOPS to run while benchmarking. This is a limit and there is no guarantee
|
||||
it will be reached. This is the total IOPS for all background write jobs.
|
||||
type: integer
|
||||
backgroundWriteIOPSJobs:
|
||||
description: 'Number of threads to use for background write
|
||||
IOPS. This should be set high enough to reach the target
|
||||
specified in BackgroundWriteIOPS. Example: If BackgroundWriteIOPS
|
||||
is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at
|
||||
least 2.'
|
||||
description: |-
|
||||
Number of threads to use for background write IOPS. This should be set high enough to reach
|
||||
the target specified in BackgroundWriteIOPS.
|
||||
Example: If BackgroundWriteIOPS is 100 and write latency is 10ms then a single job would
|
||||
barely be able to reach 100 IOPS so this should be at least 2.
|
||||
type: integer
|
||||
collectorName:
|
||||
type: string
|
||||
datasync:
|
||||
description: Whether to call datasync on the file after
|
||||
each write. Skipped if Sync is also true. Does not apply
|
||||
to background IOPS task.
|
||||
description: |-
|
||||
Whether to call datasync on the file after each write. Skipped if Sync is also true. Does not
|
||||
apply to background IOPS task.
|
||||
type: boolean
|
||||
directory:
|
||||
description: The directory where the benchmark will create
|
||||
@@ -158,16 +161,14 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileSize:
|
||||
description: The size of the file used in the benchmark.
|
||||
The number of IO operations for the benchmark will be
|
||||
FileSize / OperationSizeBytes. Accepts valid Kubernetes
|
||||
resource units such as Mi.
|
||||
description: |-
|
||||
The size of the file used in the benchmark. The number of IO operations for the benchmark
|
||||
will be FileSize / OperationSizeBytes. Accepts valid Kubernetes resource units such as Mi.
|
||||
type: string
|
||||
operationSize:
|
||||
description: The size of each write operation performed
|
||||
while benchmarking. This does not apply to the background
|
||||
IOPS feature if enabled, since those must be fixed at
|
||||
4096.
|
||||
description: |-
|
||||
The size of each write operation performed while benchmarking. This does not apply to the
|
||||
background IOPS feature if enabled, since those must be fixed at 4096.
|
||||
format: int64
|
||||
type: integer
|
||||
sync:
|
||||
@@ -208,9 +209,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -228,9 +229,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -248,9 +249,9 @@ spec:
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for a server's
|
||||
response. Its a duration e.g 15s, 2h30m. Missing value
|
||||
or empty string or means no timeout.
|
||||
description: |-
|
||||
Timeout is the time to wait for a server's response. Its a duration e.g 15s, 2h30m.
|
||||
Missing value or empty string or means no timeout.
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
@@ -387,6 +388,8 @@ spec:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: CollectorStatus defines the observed state of Collector
|
||||
|
||||
+12463
-3992
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@ An initially unintended benefit of using the Aggregation Layer is that any HostC
|
||||
* [microk8s implementation](https://github.com/canonical/microk8s/blob/master/build-scripts/patches/0000-Kubelite-integration.patch) - bundles slightly modified binaries
|
||||
* [k0s uses upstream binaries statically compiled](https://docs.k0sproject.io/v1.23.8+k0s.0/architecture/) - bundles statically compiled binaries that self extract and uses a process monitor to run them
|
||||
|
||||
2. Can you in fact push metadat like "Status" into an api-server or do we have to write directly to etcd?
|
||||
2. Can you in fact push metadata like "Status" into an api-server or do we have to write directly to etcd?
|
||||
|
||||
* If we can't push to the api-server is just writing the information directly into etcd something we can do and have a reasonable expectation of compatibility?
|
||||
|
||||
|
||||
+1
-1
@@ -54,4 +54,4 @@ preflight [url] [flags]
|
||||
* [preflight oci-fetch](preflight_oci-fetch.md) - Fetch a preflight from an OCI registry and print it to standard out
|
||||
* [preflight version](preflight_version.md) - Print the current version and exit
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -9,7 +9,8 @@ preflight oci-fetch [URI] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for oci-fetch
|
||||
-h, --help help for oci-fetch
|
||||
-v, --v Level number for the log level verbosity
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
@@ -33,4 +34,4 @@ preflight oci-fetch [URI] [flags]
|
||||
|
||||
* [preflight](preflight.md) - Run and retrieve preflight checks in a cluster
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -37,4 +37,4 @@ preflight version [flags]
|
||||
|
||||
* [preflight](preflight.md) - Run and retrieve preflight checks in a cluster
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -27,6 +27,7 @@ support-bundle [urls...] [flags]
|
||||
--cpuprofile string File path to write cpu profiling data
|
||||
--debug enable debug logging. This is equivalent to --v=0
|
||||
--disable-compression If true, opt-out of response compression for all requests to the server
|
||||
--dry-run print support bundle spec without collecting anything
|
||||
-h, --help help for support-bundle
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--interactive enable/disable interactive mode (default true)
|
||||
@@ -34,7 +35,7 @@ support-bundle [urls...] [flags]
|
||||
--load-cluster-specs enable/disable loading additional troubleshoot specs found within the cluster. required when no specs are provided on the command line
|
||||
--memprofile string File path to write memory profiling data
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
--no-uri When this flag is used, Troubleshoot does not attempt to retrieve the bundle referenced by the uri: field in the spec.`
|
||||
--no-uri When this flag is used, Troubleshoot does not attempt to retrieve the spec referenced by the uri: field`
|
||||
-o, --output string specify the output file path for the support bundle
|
||||
--redact enable/disable default redactions (default true)
|
||||
--redactors strings names of the additional redactors to use
|
||||
@@ -55,4 +56,4 @@ support-bundle [urls...] [flags]
|
||||
* [support-bundle redact](support-bundle_redact.md) - Redact information from a generated support bundle archive
|
||||
* [support-bundle version](support-bundle_version.md) - Print the current version and exit
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -30,4 +30,4 @@ support-bundle analyze [url] [flags]
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -39,4 +39,4 @@ support-bundle redact [urls...] [flags]
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -27,4 +27,4 @@ support-bundle version [flags]
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Aug-2023
|
||||
###### Auto generated by spf13/cobra on 25-Jan-2024
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostCollector
|
||||
metadata:
|
||||
name: run-host-cmd-and-save-output
|
||||
spec:
|
||||
collectors:
|
||||
- run:
|
||||
collectorName: "my-custom-run"
|
||||
command: "sh"
|
||||
# this is for demonstration purpose only -- you probably don't want to drop your input to the bundle!
|
||||
args:
|
||||
- "-c"
|
||||
- "cat $TS_INPUT_DIR/dummy.yaml > $TS_OUTPUT_DIR/dummy_content.yaml"
|
||||
outputDir: "myCommandOutputs"
|
||||
env:
|
||||
- AWS_REGION=us-west-1
|
||||
# if ignoreParentEnvs is true, it will not inherit envs from parent process.
|
||||
# values specified in inheritEnv will not be used either
|
||||
# ignoreParentEnvs: true
|
||||
inheritEnvs:
|
||||
- USER
|
||||
input:
|
||||
dummy.conf: |-
|
||||
[hello]
|
||||
hello = 1
|
||||
|
||||
[bye]
|
||||
bye = 2
|
||||
dummy.yaml: |-
|
||||
username: postgres
|
||||
password: <my-pass>
|
||||
dbHost: <hostname>
|
||||
map:
|
||||
key: value
|
||||
list:
|
||||
- val1
|
||||
- val2
|
||||
@@ -72,6 +72,12 @@ spec:
|
||||
- pass:
|
||||
when: "== k3s"
|
||||
message: K3S is a supported distribution
|
||||
- pass:
|
||||
when: "== oke"
|
||||
message: OKE is a supported distribution
|
||||
- pass:
|
||||
when: "== kind"
|
||||
message: Kind is a supported distribution
|
||||
- warn:
|
||||
message: Unable to determine the distribution of Kubernetes
|
||||
- nodeResources:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module helm-template
|
||||
|
||||
go 1.20
|
||||
go 1.22.3
|
||||
|
||||
// Always use the local version of troubleshoot so as to build using
|
||||
// the latest version of the library. This will ensure the example
|
||||
@@ -9,29 +9,28 @@ replace github.com/replicatedhq/troubleshoot v0.0.0 => ../../../
|
||||
|
||||
require (
|
||||
github.com/replicatedhq/troubleshoot v0.0.0
|
||||
helm.sh/helm/v3 v3.12.3
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
helm.sh/helm/v3 v3.15.3
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
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.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -44,30 +43,29 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.12.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/oauth2 v0.20.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/term v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.28.2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.28.1 // indirect
|
||||
k8s.io/apimachinery v0.28.2 // indirect
|
||||
k8s.io/client-go v0.28.2 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.16.2 // indirect
|
||||
k8s.io/api v0.30.2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.30.2 // indirect
|
||||
k8s.io/apimachinery v0.30.2 // indirect
|
||||
k8s.io/client-go v0.30.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
sigs.k8s.io/controller-runtime v0.18.4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
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.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
@@ -7,46 +7,47 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE=
|
||||
github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/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/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
@@ -59,10 +60,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
@@ -80,31 +79,31 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/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 v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
|
||||
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
|
||||
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -120,23 +119,22 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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=
|
||||
@@ -149,38 +147,34 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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=
|
||||
@@ -191,30 +185,29 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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.12.3 h1:5y1+Sbty12t48T/t/CGNYUIME5BJ0WKfmW/sobYqkFg=
|
||||
helm.sh/helm/v3 v3.12.3/go.mod h1:KPKQiX9IP5HX7o5YnnhViMnNuKiL/lJBVQ47GHe1R0k=
|
||||
k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=
|
||||
k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=
|
||||
k8s.io/apiextensions-apiserver v0.28.1 h1:l2ThkBRjrWpw4f24uq0Da2HaEgqJZ7pcgiEUTKSmQZw=
|
||||
k8s.io/apiextensions-apiserver v0.28.1/go.mod h1:sVvrI+P4vxh2YBBcm8n2ThjNyzU4BQGilCQ/JAY5kGs=
|
||||
k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
|
||||
k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
|
||||
k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=
|
||||
k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQVuIPU=
|
||||
sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU=
|
||||
helm.sh/helm/v3 v3.15.3 h1:HcZDaVFe9uHa6hpsR54mJjYyRy4uz/pc6csg27nxFOc=
|
||||
helm.sh/helm/v3 v3.15.3/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ=
|
||||
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
|
||||
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
|
||||
k8s.io/apiextensions-apiserver v0.30.2 h1:l7Eue2t6QiLHErfn2vwK4KgF4NeDgjQkCXtEbOocKIE=
|
||||
k8s.io/apiextensions-apiserver v0.30.2/go.mod h1:lsJFLYyK40iguuinsb3nt+Sj6CmodSI4ACDLep1rgjw=
|
||||
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
|
||||
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
|
||||
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
|
||||
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
|
||||
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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw=
|
||||
sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
||||
@@ -1,60 +1,68 @@
|
||||
module github.com/replicatedhq/troubleshoot
|
||||
|
||||
go 1.20
|
||||
go 1.22.4
|
||||
|
||||
toolchain go1.22.5
|
||||
|
||||
require (
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
|
||||
github.com/apparentlymart/go-cidr v1.1.0
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
github.com/containers/image/v5 v5.25.0
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/go-logr/logr v1.2.4
|
||||
github.com/cilium/ebpf v0.16.0
|
||||
github.com/containerd/cgroups/v3 v3.0.3
|
||||
github.com/containers/image/v5 v5.32.1
|
||||
github.com/distribution/distribution/v3 v3.0.0-beta.1
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/go-redis/redis/v7 v7.4.1
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/hashicorp/go-getter v1.7.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/hashicorp/go-getter v1.7.5
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jackc/pgx/v5 v5.4.3
|
||||
github.com/jackc/pgx/v5 v5.6.0
|
||||
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.19
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microsoft/go-mssqldb v1.6.0
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
|
||||
github.com/microsoft/go-mssqldb v1.7.2
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
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/v3 v3.23.9
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.17.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tj/go-spin v1.1.0
|
||||
go.opentelemetry.io/otel v1.18.0
|
||||
go.opentelemetry.io/otel/sdk v1.18.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/sync v0.3.0
|
||||
github.com/vmware-tanzu/velero v1.14.0
|
||||
go.opentelemetry.io/otel v1.28.0
|
||||
go.opentelemetry.io/otel/sdk v1.28.0
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/mod v0.20.0
|
||||
golang.org/x/sync v0.8.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.28.2
|
||||
k8s.io/apiextensions-apiserver v0.28.1
|
||||
k8s.io/apimachinery v0.28.2
|
||||
k8s.io/apiserver v0.28.1
|
||||
k8s.io/cli-runtime v0.28.2
|
||||
k8s.io/client-go v0.28.2
|
||||
k8s.io/klog/v2 v2.100.1
|
||||
oras.land/oras-go v1.2.3
|
||||
sigs.k8s.io/controller-runtime v0.16.2
|
||||
sigs.k8s.io/e2e-framework v0.3.0
|
||||
k8s.io/api v0.30.3
|
||||
k8s.io/apiextensions-apiserver v0.30.3
|
||||
k8s.io/apimachinery v0.30.3
|
||||
k8s.io/apiserver v0.30.3
|
||||
k8s.io/cli-runtime v0.30.3
|
||||
k8s.io/client-go v0.30.3
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
oras.land/oras-go v1.2.6
|
||||
sigs.k8s.io/controller-runtime v0.18.5
|
||||
sigs.k8s.io/e2e-framework v0.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
@@ -62,18 +70,25 @@ require (
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.0.5 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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.6.8 // indirect
|
||||
github.com/google/go-containerregistry v0.20.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
@@ -83,173 +98,168 @@ 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/v3 v3.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/onsi/ginkgo v1.14.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/user v0.2.0 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/rubenv/sql-migrate v1.3.1 // indirect
|
||||
github.com/rubenv/sql-migrate v1.5.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.11.1 // indirect
|
||||
github.com/sylabs/sif/v2 v2.18.0 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
github.com/vladimirvivien/gexe v0.2.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.18.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.18.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
k8s.io/component-base v0.28.1 // indirect
|
||||
k8s.io/kubectl v0.28.1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
k8s.io/component-base v0.30.3 // indirect
|
||||
k8s.io/kubectl v0.30.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.7 // indirect
|
||||
cloud.google.com/go/compute v1.23.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.1 // indirect
|
||||
cloud.google.com/go/storage v1.30.1 // indirect
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/iam v1.1.7 // indirect
|
||||
cloud.google.com/go/storage v1.40.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.10.0-rc.7 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.12.5 // indirect
|
||||
github.com/andybalholm/brotli v1.0.1 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.198 // indirect
|
||||
github.com/aws/aws-sdk-go v1.48.10 // 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.2.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/containerd v1.7.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/containerd/containerd v1.7.20 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
||||
github.com/containers/ocicrypt v1.1.7 // indirect
|
||||
github.com/containers/storage v1.46.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/containers/ocicrypt v1.2.0 // indirect
|
||||
github.com/containers/storage v1.55.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/docker v23.0.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/cli v27.1.1+incompatible // indirect
|
||||
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // 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/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
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.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-intervals v0.0.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6-0.20220930104621-17e8dac29df8 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
|
||||
github.com/nwaples/rardecode v1.1.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.0
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.52.3 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/vbatts/tar-split v0.11.5 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.12.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.143.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/grpc v1.58.2 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sys v0.24.0
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/api v0.172.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
helm.sh/helm/v3 v3.12.3
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/metrics v0.28.2
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
|
||||
helm.sh/helm/v3 v3.15.3
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/kubelet v0.30.3
|
||||
k8s.io/metrics v0.30.3
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||
periph.io/x/host/v3 v3.8.2
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
replace (
|
||||
|
||||
+109
-10
@@ -78,6 +78,7 @@ func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []st
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
var kindsFromURL *loader.TroubleshootKinds
|
||||
rawSpecs := []string{}
|
||||
|
||||
for _, v := range args {
|
||||
@@ -167,25 +168,42 @@ func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []st
|
||||
rawSpecs = append(rawSpecs, content...)
|
||||
} else {
|
||||
if !util.IsURL(v) {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, fmt.Errorf("%s is not a URL and was not found (err %s)", v, err))
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, fmt.Errorf("%s is not a URL and was not found", v))
|
||||
}
|
||||
|
||||
// Download preflight specs
|
||||
rawSpec, err := downloadFromHttpURL(ctx, v, map[string]string{"User-Agent": "Replicated_Preflight/v1beta2"})
|
||||
parsedURL, err := url.ParseRequestURI(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
rawSpecs = append(rawSpecs, rawSpec)
|
||||
|
||||
// Download support bundle specs
|
||||
rawSpec, err = downloadFromHttpURL(ctx, v, map[string]string{
|
||||
"User-Agent": "Replicated_Troubleshoot/v1beta1",
|
||||
"Bundle-Upload-Host": fmt.Sprintf("%s://%s", u.Scheme, u.Host),
|
||||
var specFromURL string
|
||||
if parsedURL.Host == "kots.io" {
|
||||
// To download specs from kots.io, we need to set the User-Agent header
|
||||
specFromURL, err = downloadFromHttpURL(ctx, v, map[string]string{
|
||||
"User-Agent": "Replicated_Troubleshoot/v1beta1",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
specFromURL, err = downloadFromHttpURL(ctx, v, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// load URL spec first to remove URI key from the spec
|
||||
kindsFromURL, err = loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
RawSpec: specFromURL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawSpecs = append(rawSpecs, rawSpec)
|
||||
// remove URI key from the spec if any
|
||||
for i := range kindsFromURL.SupportBundlesV1Beta2 {
|
||||
kindsFromURL.SupportBundlesV1Beta2[i].Spec.Uri = ""
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +214,9 @@ func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kindsFromURL != nil {
|
||||
kinds.Add(kindsFromURL)
|
||||
}
|
||||
|
||||
if vp.GetBool("load-cluster-specs") {
|
||||
clusterKinds, err := LoadFromCluster(ctx, client, vp.GetStringSlice("selector"), vp.GetString("namespace"))
|
||||
@@ -340,3 +361,81 @@ func LoadFromCluster(ctx context.Context, client kubernetes.Interface, selectors
|
||||
RawSpecs: rawSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
// LoadAdditionalSpecFromURIs loads additional specs from the URIs provided in the kinds.
|
||||
// This function will modify kinds in place.
|
||||
func LoadAdditionalSpecFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) {
|
||||
|
||||
obj := reflect.ValueOf(*kinds)
|
||||
|
||||
// iterate over all fields of the TroubleshootKinds
|
||||
// e.g. SupportBundlesV1Beta2, PreflightsV1Beta2, etc.
|
||||
for i := 0; i < obj.NumField(); i++ {
|
||||
field := obj.Field(i)
|
||||
if field.Kind() != reflect.Slice {
|
||||
continue
|
||||
}
|
||||
|
||||
// look at each spec in the slice
|
||||
// e.g. each spec in []PreflightsV1Beta2
|
||||
for count := 0; count < field.Len(); count++ {
|
||||
currentSpec := field.Index(count)
|
||||
specName := currentSpec.Type().Name()
|
||||
|
||||
// check if .Spec.Uri exists
|
||||
specField := currentSpec.FieldByName("Spec")
|
||||
if !specField.IsValid() {
|
||||
continue
|
||||
}
|
||||
uriField := specField.FieldByName("Uri")
|
||||
if uriField.Kind() != reflect.String {
|
||||
continue
|
||||
}
|
||||
|
||||
// download spec from URI
|
||||
uri := uriField.String()
|
||||
if uri == "" {
|
||||
continue
|
||||
}
|
||||
rawSpec, err := downloadFromHttpURL(ctx, uri, nil)
|
||||
if err != nil {
|
||||
klog.Warningf("failed to download spec from URI %q: %v", uri, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// load spec from raw spec
|
||||
uriKinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{RawSpec: string(rawSpec)})
|
||||
if err != nil {
|
||||
klog.Warningf("failed to load spec from URI %q: %v", uri, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// replace original spec with the loaded spec from URI
|
||||
newSpec := getFirstSpecOf(uriKinds, specName)
|
||||
if !newSpec.IsValid() {
|
||||
klog.Warningf("failed to read spec of type %s in URI %s", specName, uri)
|
||||
continue
|
||||
}
|
||||
currentSpec.Set(newSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dynamically get spec from kinds of given name
|
||||
// return first element of the spec slice
|
||||
func getFirstSpecOf(kinds *loader.TroubleshootKinds, name string) reflect.Value {
|
||||
obj := reflect.ValueOf(*kinds)
|
||||
for i := 0; i < obj.NumField(); i++ {
|
||||
field := obj.Field(i)
|
||||
if field.Kind() != reflect.Slice {
|
||||
continue
|
||||
}
|
||||
if field.Len() > 0 {
|
||||
if field.Index(0).Type().Name() == name {
|
||||
// return first element
|
||||
return field.Index(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package specs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -10,6 +12,7 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/internal/testutils"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/loader"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -201,3 +204,59 @@ spec:
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromURI(t *testing.T) {
|
||||
m := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostCollector
|
||||
metadata:
|
||||
name: cpu
|
||||
spec:
|
||||
collectors:
|
||||
- cpu: {}
|
||||
`))
|
||||
}))
|
||||
defer m.Close()
|
||||
|
||||
client := testclient.NewSimpleClientset()
|
||||
specs, err := LoadFromCLIArgs(context.Background(), client, []string{m.URL}, viper.New())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, specs.HostCollectorsV1Beta2, 1)
|
||||
}
|
||||
|
||||
func TestLoadAdditionalSpecFromURIs(t *testing.T) {
|
||||
m := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: preflight-2
|
||||
spec:
|
||||
collectors:
|
||||
- ceph: {}
|
||||
`))
|
||||
}))
|
||||
defer m.Close()
|
||||
kinds := loader.NewTroubleshootKinds()
|
||||
kinds.PreflightsV1Beta2 = []troubleshootv1beta2.Preflight{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "preflight-1",
|
||||
},
|
||||
Spec: troubleshootv1beta2.PreflightSpec{
|
||||
Uri: m.URL,
|
||||
Collectors: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
DNS: &troubleshootv1beta2.DNS{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
LoadAdditionalSpecFromURIs(context.Background(), kinds)
|
||||
require.Len(t, kinds.PreflightsV1Beta2, 1)
|
||||
require.Len(t, kinds.PreflightsV1Beta2[0].Spec.Collectors, 1)
|
||||
require.NotNil(t, kinds.PreflightsV1Beta2[0].Spec.Collectors[0].Ceph)
|
||||
}
|
||||
|
||||
@@ -15,16 +15,25 @@ import (
|
||||
|
||||
func GetTestFixture(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
p := path
|
||||
if !filepath.IsAbs(path) {
|
||||
p = filepath.Join("../../testdata", path)
|
||||
}
|
||||
b, err := os.ReadFile(p)
|
||||
|
||||
b, err := os.ReadFile(TestFixtureFilePath(t, path))
|
||||
require.NoError(t, err)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// FileDir returns the directory of the current source file.
|
||||
func TestFixtureFilePath(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
p, err := filepath.Abs(filepath.Join(FileDir(), "../../testdata", path))
|
||||
require.NoError(t, err)
|
||||
return p
|
||||
} else {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// FileDir returns the directory of this source file
|
||||
func FileDir() string {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
return filepath.Dir(filename)
|
||||
@@ -32,9 +41,13 @@ func FileDir() string {
|
||||
|
||||
// Generates a temporary filename
|
||||
func TempFilename(prefix string) string {
|
||||
return filepath.Join(os.TempDir(), generateTempName(prefix))
|
||||
}
|
||||
|
||||
func generateTempName(prefix string) string {
|
||||
randBytes := make([]byte, 16)
|
||||
rand.Read(randBytes)
|
||||
return filepath.Join(os.TempDir(), fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(randBytes)))
|
||||
return fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(randBytes))
|
||||
}
|
||||
|
||||
func CreateTestFile(t *testing.T, path string) {
|
||||
@@ -69,3 +82,11 @@ func AsJSON(t *testing.T, v interface{}) string {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ServeFromFilePath(t *testing.T, data string) string {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(t.TempDir(), generateTempName("testfile"))
|
||||
CreateTestFileWithData(t, path, data)
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const HOST_COLLECTORS_RUN_AS_ROOT_PROMPT = "Some host collectors need to be run as root.\nDo you want to exit and rerun the command using sudo?"
|
||||
|
||||
func HomeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
@@ -52,3 +60,112 @@ func EstimateNumberOfLines(text string) int {
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Append appends elements in src to target.
|
||||
// We have this function because of how the
|
||||
// builtin append() function works. It treats
|
||||
// target nil slices the same as empty slices.
|
||||
func Append[T any](target []T, src []T) []T {
|
||||
// Do nothing only if src is nil
|
||||
if src == nil {
|
||||
return target
|
||||
}
|
||||
|
||||
// In case target is nil, we need to initialize it
|
||||
// since append() will not do it for us when len(src) == 0
|
||||
if target == nil {
|
||||
target = []T{}
|
||||
}
|
||||
return append(target, src...)
|
||||
}
|
||||
|
||||
// IsInCluster returns true if the code is running within a process
|
||||
// inside a kubernetes pod
|
||||
func IsInCluster() bool {
|
||||
// This is a best effort check, it's not guaranteed to be accurate
|
||||
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
|
||||
if len(host) == 0 || len(port) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// RenderTemplate renders a template and returns the result as a string
|
||||
func RenderTemplate(tpl string, data interface{}) (string, error) {
|
||||
// Create a new template and parse the letter into it
|
||||
t, err := template.New("data").Parse(tpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create a new buffer
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Execute the template and write the bytes to the buffer
|
||||
err = t.Execute(buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return the string representation of the buffer
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func IsRunningAsRoot() bool {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the user ID is 0 (root's UID)
|
||||
return currentUser.Uid == "0"
|
||||
}
|
||||
|
||||
func PromptYesNo(question string) bool {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
for {
|
||||
fmt.Printf("%s (yes/no): ", question)
|
||||
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
response = strings.TrimSpace(response)
|
||||
response = strings.ToLower(response)
|
||||
|
||||
if response == "yes" || response == "y" {
|
||||
return true
|
||||
} else if response == "no" || response == "n" {
|
||||
return false
|
||||
} else {
|
||||
fmt.Println("Please type 'yes' or 'no'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Dedup[T any](objs []T) []T {
|
||||
seen := make(map[string]bool)
|
||||
out := []T{}
|
||||
|
||||
if len(objs) == 0 {
|
||||
return objs
|
||||
}
|
||||
|
||||
for _, o := range objs {
|
||||
data, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
out = append(out, o)
|
||||
continue
|
||||
}
|
||||
key := string(data)
|
||||
if _, ok := seen[key]; !ok {
|
||||
out = append(out, o)
|
||||
seen[key] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -194,3 +194,111 @@ func Test_EstimateNumberOfLines(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
target []string
|
||||
src []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "empty target",
|
||||
target: []string{},
|
||||
src: []string{"a", "b", "c"},
|
||||
want: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "empty src",
|
||||
target: []string{"a", "b", "c"},
|
||||
src: []string{},
|
||||
want: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "non-empty target and src",
|
||||
target: []string{"a", "b", "c"},
|
||||
src: []string{"d", "e", "f"},
|
||||
want: []string{"a", "b", "c", "d", "e", "f"},
|
||||
},
|
||||
{
|
||||
name: "nil target",
|
||||
target: nil,
|
||||
src: []string{"a", "b", "c"},
|
||||
want: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "nil src",
|
||||
target: []string{"a", "b", "c"},
|
||||
src: nil,
|
||||
want: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "nil target and empty src",
|
||||
target: nil,
|
||||
src: []string{},
|
||||
want: []string{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Append(tt.target, tt.src)
|
||||
assert.Equal(t, tt.want, got, "Append() = %v, want %v", got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tpl string
|
||||
data interface{}
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty template and data",
|
||||
tpl: "",
|
||||
data: nil,
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty template with data",
|
||||
tpl: "",
|
||||
data: map[string]string{"Name": "World"},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty data with template with no keys",
|
||||
tpl: "Hello, World!",
|
||||
data: nil,
|
||||
want: "Hello, World!",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple template",
|
||||
tpl: "Hello, {{ .Name }}!",
|
||||
data: map[string]string{"Name": "World"},
|
||||
want: "Hello, World!",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "template with missing key",
|
||||
tpl: "Hello, {{ .Name }}!",
|
||||
data: map[string]string{"Name2": "World"},
|
||||
want: "Hello, <no value>!",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := RenderTemplate(tt.tpl, tt.data)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RenderTemplate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got, "RenderTemplate() = %v, want %v", got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/multitype"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/redact"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
@@ -226,6 +230,8 @@ func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
|
||||
return &AnalyzeRedis{analyzer: analyzer.Redis}
|
||||
case analyzer.CephStatus != nil:
|
||||
return &AnalyzeCephStatus{analyzer: analyzer.CephStatus}
|
||||
case analyzer.Velero != nil:
|
||||
return &AnalyzeVelero{analyzer: analyzer.Velero}
|
||||
case analyzer.Longhorn != nil:
|
||||
return &AnalyzeLonghorn{analyzer: analyzer.Longhorn}
|
||||
case analyzer.RegistryImages != nil:
|
||||
@@ -238,6 +244,14 @@ func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
|
||||
return &AnalyzeClusterResource{analyzer: analyzer.ClusterResource}
|
||||
case analyzer.Certificates != nil:
|
||||
return &AnalyzeCertificates{analyzer: analyzer.Certificates}
|
||||
case analyzer.Goldpinger != nil:
|
||||
return &AnalyzeGoldpinger{analyzer: analyzer.Goldpinger}
|
||||
case analyzer.Event != nil:
|
||||
return &AnalyzeEvent{analyzer: analyzer.Event}
|
||||
case analyzer.NodeMetrics != nil:
|
||||
return &AnalyzeNodeMetrics{analyzer: analyzer.NodeMetrics}
|
||||
case analyzer.HTTP != nil:
|
||||
return &AnalyzeHTTPAnalyze{analyzer: analyzer.HTTP}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -265,3 +279,18 @@ func DedupAnalyzers(allAnalyzers []*troubleshootv1beta2.Analyze) []*troubleshoot
|
||||
}
|
||||
return finalAnalyzers
|
||||
}
|
||||
|
||||
func stripRedactedLines(yaml []byte) []byte {
|
||||
buf := bytes.NewBuffer(yaml)
|
||||
scanner := bufio.NewScanner(buf)
|
||||
|
||||
out := []byte{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.ReplaceAll(scanner.Text(), redact.MASK_TEXT, "HIDDEN")
|
||||
out = append(out, []byte(line)...)
|
||||
out = append(out, '\n')
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func analyzeClusterVersion(analyzer *troubleshootv1beta2.ClusterVersion, getColl
|
||||
return nil, errors.Wrap(err, "failed to parse cluster_version.json")
|
||||
}
|
||||
|
||||
k8sVersion, err := semver.Make(strings.TrimLeft(collectorClusterVersion.String, "v"))
|
||||
k8sVersion, err := parseK8sVersionString(collectorClusterVersion.String)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse semver from cluster_version.json")
|
||||
}
|
||||
@@ -58,6 +58,16 @@ func title(checkName string) string {
|
||||
return checkName
|
||||
}
|
||||
|
||||
func parseK8sVersionString(version string) (semver.Version, error) {
|
||||
// Workaround for https://github.com/aws/containers-roadmap/issues/1404
|
||||
// for EKS, replace pre-release gitVersion string with the release version
|
||||
if strings.Contains(version, "-eks-") {
|
||||
version = strings.Split(version, "-")[0]
|
||||
}
|
||||
|
||||
return semver.Make(strings.TrimLeft(version, "v"))
|
||||
}
|
||||
|
||||
func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
for _, outcome := range outcomes {
|
||||
when := ""
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
@@ -103,3 +104,41 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseVersionString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rawVersion string
|
||||
want semver.Version
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid version",
|
||||
rawVersion: "1.17.0",
|
||||
want: semver.MustParse("1.17.0"),
|
||||
},
|
||||
{
|
||||
name: "valid version with v prefix",
|
||||
rawVersion: "v1.17.0",
|
||||
want: semver.MustParse("1.17.0"),
|
||||
},
|
||||
{
|
||||
name: "invalid version",
|
||||
rawVersion: "v1.17",
|
||||
want: semver.Version{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "EKS version",
|
||||
rawVersion: "v1.25.16-eks-8cb36c9",
|
||||
want: semver.MustParse("1.25.16"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseK8sVersionString(tt.rawVersion)
|
||||
assert.Equal(t, tt.wantErr, err != nil, "parseVersionString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got, "parseVersionString() = %v, want %v", got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ComparisonOperator int
|
||||
|
||||
const (
|
||||
Unknown ComparisonOperator = iota
|
||||
Equal
|
||||
NotEqual
|
||||
GreaterThan
|
||||
GreaterThanOrEqual
|
||||
LessThan
|
||||
LessThanOrEqual
|
||||
)
|
||||
|
||||
func ParseComparisonOperator(s string) (ComparisonOperator, error) {
|
||||
switch s {
|
||||
case "=", "==", "===":
|
||||
return Equal, nil
|
||||
case "!=", "!==":
|
||||
return NotEqual, nil
|
||||
case "<":
|
||||
return LessThan, nil
|
||||
case ">":
|
||||
return GreaterThan, nil
|
||||
case "<=":
|
||||
return LessThanOrEqual, nil
|
||||
case ">=":
|
||||
return GreaterThanOrEqual, nil
|
||||
}
|
||||
|
||||
return Unknown, fmt.Errorf("unknown operator: %s", s)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseComparisonOperator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want ComparisonOperator
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "equal",
|
||||
input: "=",
|
||||
want: Equal,
|
||||
},
|
||||
{
|
||||
name: "equal",
|
||||
input: "==",
|
||||
want: Equal,
|
||||
},
|
||||
{
|
||||
name: "equal",
|
||||
input: "===",
|
||||
want: Equal,
|
||||
},
|
||||
{
|
||||
name: "not equal",
|
||||
input: "!=",
|
||||
want: NotEqual,
|
||||
},
|
||||
{
|
||||
name: "not equal",
|
||||
input: "!==",
|
||||
want: NotEqual,
|
||||
},
|
||||
{
|
||||
name: "less than",
|
||||
input: "<",
|
||||
want: LessThan,
|
||||
},
|
||||
{
|
||||
name: "greater than",
|
||||
input: ">",
|
||||
want: GreaterThan,
|
||||
},
|
||||
{
|
||||
name: "less than or equal",
|
||||
input: "<=",
|
||||
want: LessThanOrEqual,
|
||||
},
|
||||
{
|
||||
name: "greater than or equal",
|
||||
input: ">=",
|
||||
want: GreaterThanOrEqual,
|
||||
},
|
||||
{
|
||||
name: "invalid operator 1",
|
||||
input: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid operator 2",
|
||||
input: "gibberish",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseComparisonOperator(tt.input)
|
||||
assert.Equal(t, tt.want, got, "ParseOperator() = %v, want %v", got, tt.want)
|
||||
assert.Equalf(t, tt.wantErr, err != nil, "ParseOperator() error = %v, wantErr %v", err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
+74
-27
@@ -13,38 +13,46 @@ import (
|
||||
)
|
||||
|
||||
type providers struct {
|
||||
microk8s bool
|
||||
dockerDesktop bool
|
||||
eks bool
|
||||
gke bool
|
||||
digitalOcean bool
|
||||
openShift bool
|
||||
tanzu bool
|
||||
kurl bool
|
||||
aks bool
|
||||
ibm bool
|
||||
minikube bool
|
||||
rke2 bool
|
||||
k3s bool
|
||||
microk8s bool
|
||||
dockerDesktop bool
|
||||
eks bool
|
||||
gke bool
|
||||
digitalOcean bool
|
||||
openShift bool
|
||||
tanzu bool
|
||||
kurl bool
|
||||
aks bool
|
||||
ibm bool
|
||||
minikube bool
|
||||
rke2 bool
|
||||
k3s bool
|
||||
oke bool
|
||||
kind bool
|
||||
k0s bool
|
||||
embeddedCluster bool
|
||||
}
|
||||
|
||||
type Provider int
|
||||
|
||||
const (
|
||||
unknown Provider = iota
|
||||
microk8s Provider = iota
|
||||
dockerDesktop Provider = iota
|
||||
eks Provider = iota
|
||||
gke Provider = iota
|
||||
digitalOcean Provider = iota
|
||||
openShift Provider = iota
|
||||
tanzu Provider = iota
|
||||
kurl Provider = iota
|
||||
aks Provider = iota
|
||||
ibm Provider = iota
|
||||
minikube Provider = iota
|
||||
rke2 Provider = iota
|
||||
k3s Provider = iota
|
||||
unknown Provider = iota
|
||||
microk8s Provider = iota
|
||||
dockerDesktop Provider = iota
|
||||
eks Provider = iota
|
||||
gke Provider = iota
|
||||
digitalOcean Provider = iota
|
||||
openShift Provider = iota
|
||||
tanzu Provider = iota
|
||||
kurl Provider = iota
|
||||
aks Provider = iota
|
||||
ibm Provider = iota
|
||||
minikube Provider = iota
|
||||
rke2 Provider = iota
|
||||
k3s Provider = iota
|
||||
oke Provider = iota
|
||||
kind Provider = iota
|
||||
k0s Provider = iota
|
||||
embeddedCluster Provider = iota
|
||||
)
|
||||
|
||||
type AnalyzeDistribution struct {
|
||||
@@ -125,6 +133,20 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
foundProviders.k3s = true
|
||||
stringProvider = "k3s"
|
||||
}
|
||||
if k == "oci.oraclecloud.com/fault-domain" {
|
||||
// Based on: https://docs.oracle.com/en-us/iaas/Content/ContEng/Reference/contengsupportedlabelsusecases.htm
|
||||
foundProviders.oke = true
|
||||
stringProvider = "oke"
|
||||
}
|
||||
if k == "node.k0sproject.io/role" {
|
||||
foundProviders.k0s = true
|
||||
stringProvider = "k0s"
|
||||
}
|
||||
|
||||
if k == "kots.io/embedded-cluster-role" {
|
||||
foundProviders.embeddedCluster = true
|
||||
stringProvider = "embedded-cluster"
|
||||
}
|
||||
}
|
||||
|
||||
for k := range node.ObjectMeta.Annotations {
|
||||
@@ -155,6 +177,10 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
foundProviders.ibm = true
|
||||
stringProvider = "ibm"
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "kind:") {
|
||||
foundProviders.kind = true
|
||||
stringProvider = "kind"
|
||||
}
|
||||
}
|
||||
|
||||
if foundMaster {
|
||||
@@ -165,6 +191,11 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
}
|
||||
}
|
||||
|
||||
// If k0s and embedded-cluster are both found, prefer embedded-cluster
|
||||
if foundProviders.k0s && foundProviders.embeddedCluster {
|
||||
stringProvider = "embedded-cluster"
|
||||
}
|
||||
|
||||
return foundProviders, stringProvider
|
||||
}
|
||||
|
||||
@@ -326,6 +357,14 @@ func compareDistributionConditionalToActual(conditional string, actual providers
|
||||
isMatch = actual.rke2
|
||||
case k3s:
|
||||
isMatch = actual.k3s
|
||||
case oke:
|
||||
isMatch = actual.oke
|
||||
case kind:
|
||||
isMatch = actual.kind
|
||||
case k0s:
|
||||
isMatch = actual.k0s
|
||||
case embeddedCluster:
|
||||
isMatch = actual.embeddedCluster
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
@@ -366,6 +405,14 @@ func mustNormalizeDistributionName(raw string) Provider {
|
||||
return rke2
|
||||
case "k3s":
|
||||
return k3s
|
||||
case "oke":
|
||||
return oke
|
||||
case "kind":
|
||||
return kind
|
||||
case "k0s":
|
||||
return k0s
|
||||
case "embeddedcluster":
|
||||
return embeddedCluster
|
||||
}
|
||||
|
||||
return unknown
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func Test_compareDistributionConditionalToActual(t *testing.T) {
|
||||
@@ -39,6 +41,30 @@ func Test_compareDistributionConditionalToActual(t *testing.T) {
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "== kind when kind is found",
|
||||
conditional: "== kind",
|
||||
input: providers{
|
||||
kind: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "== k0s when k0s is found",
|
||||
conditional: "== k0s",
|
||||
input: providers{
|
||||
k0s: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "== embedded-cluster when embedded-cluster is found",
|
||||
conditional: "== embedded-cluster",
|
||||
input: providers{
|
||||
embeddedCluster: true,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -73,6 +99,66 @@ func Test_mustNormalizeDistributionName(t *testing.T) {
|
||||
raw: "Docker-Desktop",
|
||||
expected: dockerDesktop,
|
||||
},
|
||||
{
|
||||
raw: "embedded-cluster",
|
||||
expected: embeddedCluster,
|
||||
},
|
||||
{
|
||||
raw: "k0s",
|
||||
expected: k0s,
|
||||
},
|
||||
{
|
||||
raw: "kind",
|
||||
expected: kind,
|
||||
},
|
||||
{
|
||||
raw: "k3s",
|
||||
expected: k3s,
|
||||
},
|
||||
{
|
||||
raw: "ibm",
|
||||
expected: ibm,
|
||||
},
|
||||
{
|
||||
raw: "ibmcloud",
|
||||
expected: ibm,
|
||||
},
|
||||
{
|
||||
raw: "ibm cloud",
|
||||
expected: ibm,
|
||||
},
|
||||
{
|
||||
raw: "gke",
|
||||
expected: gke,
|
||||
},
|
||||
{
|
||||
raw: "aks",
|
||||
expected: aks,
|
||||
},
|
||||
{
|
||||
raw: "eks",
|
||||
expected: eks,
|
||||
},
|
||||
{
|
||||
raw: "oke",
|
||||
expected: oke,
|
||||
},
|
||||
{
|
||||
raw: "rke2",
|
||||
expected: rke2,
|
||||
},
|
||||
{
|
||||
raw: "dockerdesktop",
|
||||
expected: dockerDesktop,
|
||||
},
|
||||
{
|
||||
raw: "docker desktop",
|
||||
expected: dockerDesktop,
|
||||
},
|
||||
{
|
||||
raw: "docker-desktop",
|
||||
expected: dockerDesktop,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -83,3 +169,48 @@ func Test_mustNormalizeDistributionName(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNodesForProviders(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodes []corev1.Node
|
||||
wantProviders providers
|
||||
wantProviderString string
|
||||
}{
|
||||
{
|
||||
name: "embedded-cluster",
|
||||
nodes: []corev1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "embedded-cluster",
|
||||
Labels: map[string]string{
|
||||
"beta.kubernetes.io/arch": "amd64",
|
||||
"beta.kubernetes.io/os": "linux",
|
||||
"kots.io/embedded-cluster-role": "total-1",
|
||||
"kots.io/embedded-cluster-role-0": "management",
|
||||
"kubernetes.io/arch": "amd64",
|
||||
"kubernetes.io/hostname": "evans-vm1",
|
||||
"kubernetes.io/os": "linux",
|
||||
"management": "true",
|
||||
"node-role.kubernetes.io/control-plane": "true",
|
||||
"node.k0sproject.io/role": "control-plane",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantProviders: providers{embeddedCluster: true, k0s: true},
|
||||
wantProviderString: "embedded-cluster",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
providers, stringProvider := ParseNodesForProviders(tt.nodes)
|
||||
assert.Equalf(t, tt.wantProviders, providers,
|
||||
"ParseNodesForProviders() gotProviders = %v, providers %v", providers, tt.wantProviders,
|
||||
)
|
||||
assert.Equalf(t, tt.wantProviderString, stringProvider,
|
||||
"ParseNodesForProviders() gotStringProvider = %v, stringProvider %v", stringProvider, tt.wantProviderString,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type AnalyzeEvent struct {
|
||||
analyzer *troubleshootv1beta2.EventAnalyze
|
||||
}
|
||||
|
||||
type eventFilter struct {
|
||||
kind string
|
||||
reason string
|
||||
msgRegex string
|
||||
}
|
||||
|
||||
func (a *AnalyzeEvent) Title() string {
|
||||
if a.analyzer.CheckName != "" {
|
||||
return a.analyzer.CheckName
|
||||
}
|
||||
if a.analyzer.CollectorName != "" {
|
||||
return a.analyzer.CollectorName
|
||||
}
|
||||
return "Event"
|
||||
}
|
||||
|
||||
func (a *AnalyzeEvent) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.analyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeEvent) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
// required check
|
||||
if a.analyzer.Reason == "" {
|
||||
return nil, errors.New("reason is required")
|
||||
}
|
||||
|
||||
// read collected events based on namespace
|
||||
namespace := getNamespace(a.analyzer.Namespace)
|
||||
fullPath := path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_EVENTS, namespace)
|
||||
fullPath = fmt.Sprintf("%s.json", fullPath)
|
||||
fileContent, err := getFile(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected events for namespace: %s", namespace)
|
||||
}
|
||||
|
||||
events, err := convertToEventList(fileContent)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected events")
|
||||
}
|
||||
|
||||
// filter if there's single event matched with the given criteria
|
||||
// match: Reason && Kind (optional) && MessageRegex (optional)
|
||||
// e.g. Reason: Unhealthy. Kind: Pod. Message: Readiness probe failed:...
|
||||
event := getEvent(events, eventFilter{
|
||||
kind: a.analyzer.Kind,
|
||||
reason: a.analyzer.Reason,
|
||||
msgRegex: a.analyzer.RegexPattern,
|
||||
})
|
||||
|
||||
return analyzeEventResult(event, a.analyzer.Outcomes, a.Title())
|
||||
|
||||
}
|
||||
|
||||
func getNamespace(namespace string) string {
|
||||
if namespace == "" {
|
||||
return corev1.NamespaceDefault
|
||||
}
|
||||
return namespace
|
||||
}
|
||||
|
||||
func convertToEventList(data []byte) (*corev1.EventList, error) {
|
||||
var eventList corev1.EventList
|
||||
err := json.Unmarshal(data, &eventList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert []byte to corev1.EventList: %w", err)
|
||||
}
|
||||
return &eventList, nil
|
||||
}
|
||||
|
||||
func getEvent(events *corev1.EventList, filter eventFilter) *corev1.Event {
|
||||
var (
|
||||
re *regexp.Regexp
|
||||
errParseRegex error
|
||||
)
|
||||
|
||||
if filter.msgRegex != "" {
|
||||
re, errParseRegex = regexp.Compile(filter.msgRegex)
|
||||
if errParseRegex != nil {
|
||||
klog.V(2).Infof("failed to read message regex: %v", errParseRegex)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, event := range events.Items {
|
||||
if !matchReason(event.Reason, filter.reason) {
|
||||
continue
|
||||
}
|
||||
if !matchKind(event.InvolvedObject.Kind, filter.kind) {
|
||||
continue
|
||||
}
|
||||
if re == nil || re.MatchString(event.Message) {
|
||||
klog.V(2).Infof("event matched: %v for reason: %s kind: %s messageRegex: %s ", event, filter.reason, filter.kind, filter.msgRegex)
|
||||
return &event
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchReason(actual, expected string) bool {
|
||||
// not possible to have empty reason
|
||||
if expected == "" {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(actual, expected)
|
||||
}
|
||||
|
||||
func matchKind(actual, expected string) bool {
|
||||
// kind is optional
|
||||
if expected == "" {
|
||||
return true
|
||||
}
|
||||
return strings.EqualFold(actual, expected)
|
||||
}
|
||||
|
||||
func analyzeEventResult(event *corev1.Event, outcomes []*troubleshootv1beta2.Outcome, checkName string) ([]*AnalyzeResult, error) {
|
||||
|
||||
results := []*AnalyzeResult{}
|
||||
|
||||
// for now, only support single outcome
|
||||
// we will return when there's a matched event
|
||||
willReturn := event != nil
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_event",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
}
|
||||
|
||||
for _, o := range outcomes {
|
||||
if o.Fail != nil {
|
||||
toReturn, err := strconv.ParseBool(o.Fail.When)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse when condition: %s", o.Fail.When)
|
||||
}
|
||||
if toReturn == willReturn {
|
||||
result.IsFail = true
|
||||
result.Message = decorateMessage(o.Fail.Message, event)
|
||||
result.URI = o.Fail.URI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if o.Warn != nil {
|
||||
toReturn, err := strconv.ParseBool(o.Warn.When)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse when condition: %s", o.Warn.When)
|
||||
}
|
||||
if toReturn == willReturn {
|
||||
result.IsWarn = true
|
||||
result.Message = decorateMessage(o.Warn.Message, event)
|
||||
result.URI = o.Warn.URI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if o.Pass != nil {
|
||||
toReturn, err := strconv.ParseBool(o.Pass.When)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse when condition: %s", o.Pass.When)
|
||||
}
|
||||
if toReturn == willReturn {
|
||||
result.IsPass = true
|
||||
result.Message = decorateMessage(o.Pass.Message, event)
|
||||
result.URI = o.Pass.URI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
results = append(results, result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func decorateMessage(message string, event *corev1.Event) string {
|
||||
if event == nil {
|
||||
return message
|
||||
}
|
||||
out := fmt.Sprintf("Event matched. Reason: %s Name: %s Message: %s", event.Reason, event.InvolvedObject.Name, event.Message)
|
||||
|
||||
tmpl := template.New("event")
|
||||
msgTmpl, err := tmpl.Parse(message)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("failed to parse message template: %v", err)
|
||||
return out
|
||||
}
|
||||
|
||||
var m bytes.Buffer
|
||||
err = msgTmpl.Execute(&m, event)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("failed to render message template: %v", err)
|
||||
return out
|
||||
}
|
||||
|
||||
return strings.TrimSpace(m.String())
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestAnalyzeEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer troubleshootv1beta2.EventAnalyze
|
||||
expectResult []AnalyzeResult
|
||||
files map[string][]byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "reason is required",
|
||||
analyzer: troubleshootv1beta2.EventAnalyze{
|
||||
CollectorName: "event-collector-0",
|
||||
Kind: "Pod",
|
||||
Namespace: "default",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: errors.New("reason is required"),
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/events/default.json": []byte(`
|
||||
{
|
||||
"kind": "EventList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"resourceVersion": "722"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"kind": "Event",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "nginx-rc",
|
||||
"namespace": "default",
|
||||
"creationTimestamp": "2022-01-01T00:00:00Z"
|
||||
},
|
||||
"involvedObject": {
|
||||
"kind": "Pod",
|
||||
"name": "nginx-rc-12345",
|
||||
"namespace": "default"
|
||||
},
|
||||
"reason": "OOMKilled",
|
||||
"message": "The container was killed due to an out-of-memory condition.",
|
||||
"type": "Warning"
|
||||
}
|
||||
]
|
||||
}
|
||||
`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail when OOMKilled event is present",
|
||||
analyzer: troubleshootv1beta2.EventAnalyze{
|
||||
CollectorName: "event-collector-1",
|
||||
Kind: "Pod",
|
||||
Namespace: "default",
|
||||
Reason: "OOMKilled",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "Detect OOMKilled event with {{ .InvolvedObject.Kind }}-{{ .InvolvedObject.Name }} with message {{ .Message }}",
|
||||
},
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "false",
|
||||
Message: "No OOMKilled event detected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
Title: "event-collector-1",
|
||||
IconKey: "kubernetes_event",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
IsFail: true,
|
||||
IsWarn: false,
|
||||
IsPass: false,
|
||||
Message: "Detect OOMKilled event with Pod-nginx-rc-12345 with message The container was killed due to an out-of-memory condition.",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/events/default.json": []byte(`
|
||||
{
|
||||
"kind": "EventList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"resourceVersion": "722"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"kind": "Event",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "nginx-rc",
|
||||
"namespace": "default",
|
||||
"creationTimestamp": "2022-01-01T00:00:00Z"
|
||||
},
|
||||
"involvedObject": {
|
||||
"kind": "Pod",
|
||||
"name": "nginx-rc-12345",
|
||||
"namespace": "default"
|
||||
},
|
||||
"reason": "OOMKilled",
|
||||
"message": "The container was killed due to an out-of-memory condition.",
|
||||
"type": "Warning"
|
||||
}
|
||||
]
|
||||
}
|
||||
`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass when no FailedMount event is present",
|
||||
analyzer: troubleshootv1beta2.EventAnalyze{
|
||||
CollectorName: "event-collector-2",
|
||||
Kind: "Pod",
|
||||
Namespace: "default",
|
||||
Reason: "FailedMount",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "false",
|
||||
Message: "No FailedMount event detected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
Title: "event-collector-2",
|
||||
IconKey: "kubernetes_event",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
IsPass: true,
|
||||
Message: "No FailedMount event detected",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/events/default.json": []byte(`
|
||||
{
|
||||
"kind": "EventList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"resourceVersion": "722"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"kind": "Event",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "nginx-rc-1",
|
||||
"namespace": "default",
|
||||
"creationTimestamp": "2022-01-01T00:00:00Z"
|
||||
},
|
||||
"involvedObject": {
|
||||
"kind": "Pod",
|
||||
"name": "nginx-rc-12345",
|
||||
"namespace": "default"
|
||||
},
|
||||
"reason": "Created",
|
||||
"message": "Created container",
|
||||
"type": "Normal"
|
||||
},
|
||||
{
|
||||
"kind": "Event",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "nginx-rc-2",
|
||||
"namespace": "default",
|
||||
"creationTimestamp": "2022-01-01T00:00:00Z"
|
||||
},
|
||||
"involvedObject": {
|
||||
"kind": "Pod",
|
||||
"name": "nginx-rc-67890",
|
||||
"namespace": "default"
|
||||
},
|
||||
"reason": "Started",
|
||||
"message": "Started container",
|
||||
"type": "Normal"
|
||||
}
|
||||
]
|
||||
}
|
||||
`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getFile := func(n string) ([]byte, error) {
|
||||
if b, ok := test.files[n]; ok {
|
||||
return b, nil
|
||||
}
|
||||
return nil, errors.New("file not found")
|
||||
}
|
||||
|
||||
findFiles := func(n string, _ []string) (map[string][]byte, error) {
|
||||
return nil, errors.New("method not implemented")
|
||||
}
|
||||
|
||||
a := &AnalyzeEvent{
|
||||
analyzer: &test.analyzer,
|
||||
}
|
||||
actual, err := a.Analyze(getFile, findFiles)
|
||||
if test.err != nil {
|
||||
req.EqualError(err, test.err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.NoError(err)
|
||||
unPointered := []AnalyzeResult{}
|
||||
for _, v := range actual {
|
||||
unPointered = append(unPointered, *v)
|
||||
}
|
||||
req.ElementsMatch(test.expectResult, unPointered)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeEventResult(t *testing.T) {
|
||||
event := &corev1.Event{
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "foo-pod",
|
||||
},
|
||||
Reason: "Unhealthy",
|
||||
Message: "foo-message",
|
||||
Type: "Warning",
|
||||
}
|
||||
|
||||
outcomes := []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "true",
|
||||
Message: "No unhealthy pods allowed",
|
||||
},
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "false",
|
||||
Message: "No unhealthy pod detected",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
checkName := "Test Event"
|
||||
|
||||
expectedResults := []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Event",
|
||||
IconKey: "kubernetes_event",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
IsFail: true,
|
||||
IsWarn: false,
|
||||
IsPass: false,
|
||||
Message: "No unhealthy pods allowed",
|
||||
},
|
||||
}
|
||||
|
||||
results, err := analyzeEventResult(event, outcomes, checkName)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(results) != len(expectedResults) {
|
||||
t.Fatalf("unexpected number of results, got %d, want %d", len(results), len(expectedResults))
|
||||
}
|
||||
|
||||
for i, result := range results {
|
||||
expectedResult := expectedResults[i]
|
||||
|
||||
if result.Title != expectedResult.Title {
|
||||
t.Errorf("unexpected title, got %s, want %s", result.Title, expectedResult.Title)
|
||||
}
|
||||
|
||||
if result.IsFail != expectedResult.IsFail {
|
||||
t.Errorf("unexpected IsFail value, got %v, want %v", result.IsFail, expectedResult.IsFail)
|
||||
}
|
||||
|
||||
if result.IsWarn != expectedResult.IsWarn {
|
||||
t.Errorf("unexpected IsWarn value, got %v, want %v", result.IsWarn, expectedResult.IsWarn)
|
||||
}
|
||||
|
||||
if result.IsPass != expectedResult.IsPass {
|
||||
t.Errorf("unexpected IsPass value, got %v, want %v", result.IsPass, expectedResult.IsPass)
|
||||
}
|
||||
|
||||
if result.Message != expectedResult.Message {
|
||||
t.Errorf("unexpected message, got %s, want %s", result.Message, expectedResult.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"kind": "ConfigMapList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"resourceVersion": "4825753"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"kind": "ConfigMap",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "kube-root-ca.crt",
|
||||
"namespace": "kube-public",
|
||||
"uid": "37a912b2-a666-480f-a1a8-05ab012432e4",
|
||||
"resourceVersion": "332",
|
||||
"creationTimestamp": "2023-05-29T23:33:07Z",
|
||||
"annotations": {
|
||||
"kubernetes.io/description": "Contains a CA bundle that can be used to verify the kube-apiserver when using internal endpoints such as the internal service IP or kubernetes.default.svc. No other usage is guaranteed across distributions of Kubernetes clusters."
|
||||
},
|
||||
"managedFields": [
|
||||
{
|
||||
"manager": "kube-controller-manager",
|
||||
"operation": "Update",
|
||||
"apiVersion": "v1",
|
||||
"time": "2023-05-29T23:33:07Z",
|
||||
"fieldsType": "FieldsV1",
|
||||
"fieldsV1": {
|
||||
"f:data": {
|
||||
".": {},
|
||||
"f:ca.crt": {}
|
||||
},
|
||||
"f:metadata": {
|
||||
"f:annotations": {
|
||||
".": {},
|
||||
"f:kubernetes.io/description": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"ca.crt": "-----BEGIN CERTIFICATE-----\nMIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p\na3ViZUNBMB4XDTIzMDUyODIzMzIzOVoXDTMzMDUyNjIzMzIzOVowFTETMBEGA1UE\nAxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOpU\nswd15tD+QCelgYlu5MRB260+Ke63gEDTkXIyAr+mR+TjW/v9TPPIP9iidvTUg+jq\nryrf1PwURVx3EUSZWtd9NpAEDT1ov/ggx16xxj2El7KLen0SXQutSF28zjCFXxYG\nMkPxXu+qYsn2mLJX5i1kaCZNffyToCJ0n2bxxx83rOS+fgz11JntAwcgC8V3Mtq6\nIv+2Xb9PSxPs38ef7r15j5KSTOrmWCR5texPFz/WU/YbZ3W42pj9T/EiuGwamfmI\ngevuwv7AxlfjKutp5UQEth5GhY6V4kJyVIUExN3ddEsTPLQD9zvdsP4DlkGpZmiR\n/Ip3rXY/ldxeeGZ0HUUCAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW\nMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\nBBS0XUXhCu/DOH8/0W5L0n4BOw9b6zANBgkqhkiG9w0BAQsFAAOCAQEAHrXOxKdy\n+DvPtvgfzzV3qqmIvTuuFyW0BdZdXV8yo1tZajkEO0B0HvLUyn8ljKgoK5YtCQcr\nzSj6QEewgP+JCBTsWCKzbOhMcDKw1pa6bSeLcQWwMxox+1Zcj7edMPlPcQ3SVLxZ\n6y7fD7BArTKRBKCr8Uwudwox5Vm0URWLRAvb+8jPv9BDuC/uMPJ4UrexL/Q2QQs8\nQkyWYeSk4mBCM3qAahQhYc0WSHbk+a/5iua/y+VUaa208CUbm5glBoAroHDk5eTN\nYstOSLUQAlzTdr4kCEVi+a3+NgmuvzYXWubAdy/PT860aFBJlmVNuDhy8V/bGpnS\nKxmG7B3yJmbmzg==\n-----END CERTIFICATE-----\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
)
|
||||
|
||||
type AnalyzeGoldpinger struct {
|
||||
analyzer *troubleshootv1beta2.GoldpingerAnalyze
|
||||
}
|
||||
|
||||
type checkAllOutput struct {
|
||||
Hosts []struct {
|
||||
HostIP string `json:"hostIP"`
|
||||
PodIP string `json:"podIP"`
|
||||
PodName string `json:"podName"`
|
||||
} `json:"hosts"`
|
||||
Responses map[string]struct {
|
||||
HostIP string `json:"hostIP"`
|
||||
PodIP string `json:"podIP"`
|
||||
OK bool `json:"OK"`
|
||||
Response struct {
|
||||
PodResults map[string]struct {
|
||||
HostIP string `json:"hostIP"`
|
||||
OK bool `json:"OK"`
|
||||
PingTime string `json:"pingTime"`
|
||||
PodIP string `json:"podIP"`
|
||||
Response struct {
|
||||
BootTime string `json:"boot_time"`
|
||||
} `json:"response"`
|
||||
Error string `json:"error"`
|
||||
ResponseTimeMS int `json:"response-time-ms"`
|
||||
StatusCode int `json:"status-code"`
|
||||
} `json:"podResults"`
|
||||
} `json:"response"`
|
||||
} `json:"responses"`
|
||||
}
|
||||
|
||||
func (a *AnalyzeGoldpinger) Title() string {
|
||||
title := a.analyzer.CheckName
|
||||
if title == "" {
|
||||
title = a.collectorName()
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
func (a *AnalyzeGoldpinger) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.analyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeGoldpinger) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
caoFilePath := constants.GP_CHECK_ALL_RESULTS_PATH
|
||||
// To allow analysing older support bundles, we can provide a custom file path
|
||||
if a.analyzer.FilePath != "" {
|
||||
caoFilePath = a.analyzer.FilePath
|
||||
}
|
||||
|
||||
collected, err := getFile(caoFilePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected file path: %q", caoFilePath)
|
||||
}
|
||||
|
||||
var cao checkAllOutput
|
||||
err = json.Unmarshal(collected, &cao)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal collected goldpinger output")
|
||||
}
|
||||
|
||||
return a.podPingsAnalysis(&cao), nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeGoldpinger) collectorName() string {
|
||||
if a.analyzer.CollectorName != "" {
|
||||
return a.analyzer.CollectorName
|
||||
}
|
||||
return "goldpinger"
|
||||
}
|
||||
|
||||
func (a *AnalyzeGoldpinger) podPingsAnalysis(cao *checkAllOutput) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
|
||||
for _, host := range cao.Hosts {
|
||||
// Check if the pod from a host has any ping errors from other pods
|
||||
targetPod := host.PodName
|
||||
pingsSucceeded := true
|
||||
for srcPod, resp := range cao.Responses {
|
||||
res := &AnalyzeResult{
|
||||
IconKey: "kubernetes",
|
||||
Strict: a.analyzer.Strict.BoolOrDefaultFalse(),
|
||||
}
|
||||
|
||||
// Get ping result for the pod
|
||||
podResult, ok := resp.Response.PodResults[targetPod]
|
||||
if !ok {
|
||||
// Pod not found in ping results from the source pod
|
||||
res.IsWarn = true
|
||||
res.Title = fmt.Sprintf("Missing ping results for %q pod", targetPod)
|
||||
res.Message = fmt.Sprintf("Ping result for %q pod from %q pod is missing", targetPod, srcPod)
|
||||
pingsSucceeded = false
|
||||
|
||||
results = append(results, res)
|
||||
continue
|
||||
}
|
||||
|
||||
if !podResult.OK {
|
||||
// Ping was not successful
|
||||
res.IsFail = true
|
||||
res.Title = fmt.Sprintf("Ping from %q pod to %q pod failed", srcPod, targetPod)
|
||||
res.Message = fmt.Sprintf("Ping error: %s", podResult.Error)
|
||||
pingsSucceeded = false
|
||||
|
||||
results = append(results, res)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If all pings succeeded, add a pass result
|
||||
if pingsSucceeded {
|
||||
results = append(results, &AnalyzeResult{
|
||||
IconKey: "kubernetes",
|
||||
Strict: a.analyzer.Strict.BoolOrDefaultFalse(),
|
||||
IsPass: true,
|
||||
Title: fmt.Sprintf("Pings to %q pod succeeded", targetPod),
|
||||
Message: fmt.Sprintf("Pings to %q pod from all other pods in the cluster succeeded", targetPod),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/internal/testutils"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnmarshallingCheckAllResults(t *testing.T) {
|
||||
s := testutils.GetTestFixture(t, "goldpinger/checkall-with-error.json")
|
||||
var res checkAllOutput
|
||||
err := json.Unmarshal([]byte(s), &res)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.Hosts, 3)
|
||||
assert.Equal(t, "goldpinger-tbdsb", res.Hosts[1].PodName)
|
||||
assert.Equal(t, "10.32.2.2", res.Responses["goldpinger-4hctt"].Response.PodResults["goldpinger-jj9mw"].PodIP)
|
||||
assert.Equal(t,
|
||||
`Get "http://10.32.0.9:80/ping": context deadline exceeded`,
|
||||
res.Responses["goldpinger-tbdsb"].Response.PodResults["goldpinger-4hctt"].Error,
|
||||
)
|
||||
}
|
||||
|
||||
func TestAnalyzeGoldpinger_podPingsAnalysis(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cao *checkAllOutput
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no ping errors",
|
||||
cao: caoFixture(t, "goldpinger/checkall-success.json"),
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Pings to \"goldpinger-kpz4g\" pod succeeded",
|
||||
Message: "Pings to \"goldpinger-kpz4g\" pod from all other pods in the cluster succeeded",
|
||||
IconKey: "kubernetes",
|
||||
IsPass: true,
|
||||
},
|
||||
{
|
||||
Title: "Pings to \"goldpinger-k6d2j\" pod succeeded",
|
||||
Message: "Pings to \"goldpinger-k6d2j\" pod from all other pods in the cluster succeeded",
|
||||
IconKey: "kubernetes",
|
||||
IsPass: true,
|
||||
},
|
||||
{
|
||||
Title: "Pings to \"goldpinger-5ck4d\" pod succeeded",
|
||||
Message: "Pings to \"goldpinger-5ck4d\" pod from all other pods in the cluster succeeded",
|
||||
IconKey: "kubernetes",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with some ping errors",
|
||||
cao: caoFixture(t, "goldpinger/checkall-with-error.json"),
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Ping from \"goldpinger-jj9mw\" pod to \"goldpinger-4hctt\" pod failed",
|
||||
Message: "Ping error: Get \"http://10.32.0.9:80/ping\": context deadline exceeded",
|
||||
IconKey: "kubernetes",
|
||||
IsFail: true,
|
||||
},
|
||||
{
|
||||
Title: "Ping from \"goldpinger-jj9mw\" pod to \"goldpinger-tbdsb\" pod failed",
|
||||
Message: "Ping error: Get \"http://10.32.1.2:80/ping\": context deadline exceeded",
|
||||
IconKey: "kubernetes",
|
||||
IsFail: true,
|
||||
},
|
||||
{
|
||||
Title: "Ping from \"goldpinger-4hctt\" pod to \"goldpinger-jj9mw\" pod failed",
|
||||
Message: "Ping error: Get \"http://10.32.2.2:80/ping\": context deadline exceeded",
|
||||
IconKey: "kubernetes",
|
||||
IsFail: true,
|
||||
},
|
||||
{
|
||||
Title: "Ping from \"goldpinger-4hctt\" pod to \"goldpinger-tbdsb\" pod failed",
|
||||
Message: "Ping error: Get \"http://10.32.1.2:80/ping\": context deadline exceeded",
|
||||
IconKey: "kubernetes",
|
||||
IsFail: true,
|
||||
},
|
||||
{
|
||||
Title: "Ping from \"goldpinger-tbdsb\" pod to \"goldpinger-4hctt\" pod failed",
|
||||
Message: "Ping error: Get \"http://10.32.0.9:80/ping\": context deadline exceeded",
|
||||
IconKey: "kubernetes",
|
||||
IsFail: true,
|
||||
},
|
||||
{
|
||||
Title: "Ping from \"goldpinger-tbdsb\" pod to \"goldpinger-jj9mw\" pod failed",
|
||||
Message: "Ping error: Get \"http://10.32.2.2:80/ping\": context deadline exceeded",
|
||||
IconKey: "kubernetes",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &AnalyzeGoldpinger{
|
||||
analyzer: &troubleshootv1beta2.GoldpingerAnalyze{},
|
||||
}
|
||||
|
||||
got := a.podPingsAnalysis(tt.cao)
|
||||
// Check existence of each want. Maps are not ordered, so we can't just compare
|
||||
for _, want := range tt.want {
|
||||
assert.Contains(t, got, want)
|
||||
}
|
||||
assert.Len(t, got, len(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func caoFixture(t *testing.T, path string) *checkAllOutput {
|
||||
t.Helper()
|
||||
|
||||
s := testutils.GetTestFixture(t, path)
|
||||
var res checkAllOutput
|
||||
err := json.Unmarshal([]byte(s), &res)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &res
|
||||
}
|
||||
@@ -52,6 +52,10 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
|
||||
return &AnalyzeHostOS{analyzer.HostOS}, true
|
||||
case analyzer.TextAnalyze != nil:
|
||||
return &AnalyzeHostTextAnalyze{analyzer.TextAnalyze}, true
|
||||
case analyzer.KernelConfigs != nil:
|
||||
return &AnalyzeHostKernelConfigs{analyzer.KernelConfigs}, true
|
||||
case analyzer.JsonCompare != nil:
|
||||
return &AnalyzeHostJsonCompare{analyzer.JsonCompare}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
+37
-32
@@ -38,7 +38,41 @@ func (a *AnalyzeHostHTTP) Analyze(
|
||||
if hostAnalyzer.CollectorName != "" {
|
||||
name = filepath.Join("host-collectors/http", hostAnalyzer.CollectorName+".json")
|
||||
}
|
||||
contents, err := getCollectedFileContents(name)
|
||||
|
||||
return analyzeHTTPResult(hostAnalyzer, name, getCollectedFileContents, a.Title())
|
||||
}
|
||||
|
||||
func compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) {
|
||||
if conditional == "error" {
|
||||
return result.Error != nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts))
|
||||
}
|
||||
|
||||
if parts[0] != "statusCode" {
|
||||
return false, errors.New(`Conditional must begin with keyword "statusCode"`)
|
||||
}
|
||||
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
|
||||
return false, errors.New(`Only supported operator is "=="`)
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if result.Response == nil {
|
||||
return false, err
|
||||
}
|
||||
return result.Response.Status == i, nil
|
||||
}
|
||||
|
||||
func analyzeHTTPResult(analyzer *troubleshootv1beta2.HTTPAnalyze, fileName string, getCollectedFileContents getCollectedFileContents, title string) ([]*AnalyzeResult, error) {
|
||||
contents, err := getCollectedFileContents(fileName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
@@ -49,10 +83,10 @@ func (a *AnalyzeHostHTTP) Analyze(
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
Title: title,
|
||||
}
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
@@ -122,32 +156,3 @@ func (a *AnalyzeHostHTTP) Analyze(
|
||||
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) {
|
||||
if conditional == "error" {
|
||||
return result.Error != nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts))
|
||||
}
|
||||
|
||||
if parts[0] != "statusCode" {
|
||||
return false, errors.New(`Conditional must begin with keyword "statusCode"`)
|
||||
}
|
||||
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
|
||||
return false, errors.New(`Only supported operator is "=="`)
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if result.Response == nil {
|
||||
return false, err
|
||||
}
|
||||
return result.Response.Status == i, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type AnalyzeHostJsonCompare struct {
|
||||
hostAnalyzer *troubleshootv1beta2.JsonCompare
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostJsonCompare) Title() string {
|
||||
return jsonCompareTitle(a.hostAnalyzer)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostJsonCompare) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostJsonCompare) Analyze(
|
||||
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
|
||||
) ([]*AnalyzeResult, error) {
|
||||
result, err := analyzeJsonCompare(a.hostAnalyzer, getCollectedFileContents, a.Title())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Strict = a.hostAnalyzer.Strict.BoolOrDefaultFalse()
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type AnalyzeHostKernelConfigs struct {
|
||||
hostAnalyzer *troubleshootv1beta2.KernelConfigsAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostKernelConfigs) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Kernel Configs")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostKernelConfigs) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostKernelConfigs) Analyze(
|
||||
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
|
||||
) ([]*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
contents, err := getCollectedFileContents(collect.HostKernelConfigsPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
kConfigs := collect.KConfigs{}
|
||||
if err := json.Unmarshal(contents, &kConfigs); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read kernel configs")
|
||||
}
|
||||
|
||||
var configsNotFound []string
|
||||
kConfigRegex := regexp.MustCompile("^(CONFIG_[A-Z0-9_]+)=([ymn]+)$")
|
||||
for _, config := range hostAnalyzer.SelectedConfigs {
|
||||
matches := kConfigRegex.FindStringSubmatch(config)
|
||||
// zero tolerance for invalid kernel config
|
||||
if matches == nil || len(matches) < 3 {
|
||||
return nil, errors.Errorf("invalid kernel config: %s", config)
|
||||
}
|
||||
|
||||
key := matches[1]
|
||||
values := matches[2] // values can contain multiple values in any order y, m, n
|
||||
|
||||
// check if the kernel config exists
|
||||
if _, ok := kConfigs[key]; !ok {
|
||||
configsNotFound = append(configsNotFound, config)
|
||||
continue
|
||||
}
|
||||
// check if the kernel config value matches
|
||||
if !strings.Contains(values, kConfigs[key]) {
|
||||
klog.V(2).Infof("collected kernel config %s=%s does not in expected values %s", key, kConfigs[key], values)
|
||||
configsNotFound = append(configsNotFound, config)
|
||||
}
|
||||
}
|
||||
|
||||
var results []*AnalyzeResult
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
Strict: hostAnalyzer.Strict.BoolOrDefaultFalse(),
|
||||
}
|
||||
|
||||
if outcome.Pass != nil && len(configsNotFound) == 0 {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
results = append(results, result)
|
||||
break
|
||||
}
|
||||
|
||||
if outcome.Fail != nil && len(configsNotFound) > 0 {
|
||||
result.IsFail = true
|
||||
result.Message = addMissingKernelConfigs(outcome.Fail.Message, configsNotFound)
|
||||
results = append(results, result)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func addMissingKernelConfigs(message string, missingConfigs []string) string {
|
||||
if message == "" && len(missingConfigs) == 0 {
|
||||
return message
|
||||
}
|
||||
return strings.ReplaceAll(message, "{{ .ConfigsNotFound }}", strings.Join(missingConfigs, ", "))
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAnalyzeKernelConfigs(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
selectedConfigs []string
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
results []*AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "all pass",
|
||||
selectedConfigs: []string{"CONFIG_CGROUP_FREEZER=y", "CONFIG_NETFILTER_XTABLES=m"},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "required kernel configs are available",
|
||||
},
|
||||
},
|
||||
},
|
||||
results: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Kernel Configs",
|
||||
IsPass: true,
|
||||
Message: "required kernel configs are available",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "has fail",
|
||||
selectedConfigs: []string{"CONFIG_UTS_NS=y"},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "missing kernel config(s): {{ .ConfigsNotFound }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
results: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Kernel Configs",
|
||||
IsFail: true,
|
||||
Message: "missing kernel config(s): CONFIG_UTS_NS=y",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "kernel config disabled",
|
||||
selectedConfigs: []string{"CONFIG_CGROUP_FREEZER=n"},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "missing kernel config(s): {{ .ConfigsNotFound }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
results: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Kernel Configs",
|
||||
IsFail: true,
|
||||
Message: "missing kernel config(s): CONFIG_CGROUP_FREEZER=n",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid kernel config",
|
||||
selectedConfigs: []string{"foobar=n"},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "select multiple kernel config values",
|
||||
selectedConfigs: []string{"CONFIG_BRIDGE=my"},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "required kernel configs are available",
|
||||
},
|
||||
},
|
||||
},
|
||||
results: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Kernel Configs",
|
||||
IsPass: true,
|
||||
Message: "required kernel configs are available",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
mockKernelFile := func(_ string) ([]byte, error) {
|
||||
return []byte(`{"CONFIG_CGROUP_FREEZER": "y", "CONFIG_NETFILTER_XTABLES": "m", "CONFIG_BRIDGE": "y"}`), nil
|
||||
}
|
||||
|
||||
analyzer := AnalyzeHostKernelConfigs{
|
||||
hostAnalyzer: &troubleshootv1beta2.KernelConfigsAnalyze{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "Kernel Configs",
|
||||
},
|
||||
SelectedConfigs: tt.selectedConfigs,
|
||||
Outcomes: tt.outcomes,
|
||||
},
|
||||
}
|
||||
|
||||
results, err := analyzer.Analyze(mockKernelFile, nil)
|
||||
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.results, results)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -86,19 +86,36 @@ func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv
|
||||
}
|
||||
|
||||
parts := strings.Split(when, " ")
|
||||
platform := parts[0]
|
||||
if len(parts) < 3 {
|
||||
return []*AnalyzeResult{&result}, errors.New("when condition must have at least 3 parts")
|
||||
}
|
||||
expectedVer := fixVersion(parts[2])
|
||||
toleratedVer, err := semver.ParseTolerant(expectedVer)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse tolerant: %s", expectedVer)
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse version: %s", expectedVer)
|
||||
}
|
||||
|
||||
when = fmt.Sprintf("%s %v", parts[1], toleratedVer)
|
||||
whenRange, err := semver.ParseRange(when)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse range: %s", when)
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to parse version range: %s", when)
|
||||
}
|
||||
|
||||
// Match the kernel version regardless of the platform
|
||||
// e.g "kernelVersion == 4.15"
|
||||
if parts[0] == "kernelVersion" {
|
||||
fixedKernelVer := fixVersion(osInfo.KernelVersion)
|
||||
toleratedKernelVer, err := semver.ParseTolerant(fixedKernelVer)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{}, errors.Wrapf(err, "failed to parse tolerant: %v", fixedKernelVer)
|
||||
}
|
||||
if whenRange(toleratedKernelVer) {
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Match the platform version and and kernel version passed in as
|
||||
// "<platform>-<kernelVersion>-kernel" e.g "centos-8.2-kernel == 8.2"
|
||||
platform := parts[0]
|
||||
kernelInfo := fmt.Sprintf("%s-%s-kernel", osInfo.Platform, osInfo.PlatformVersion)
|
||||
if len(strings.Split(platform, "-")) == 3 && strings.Split(platform, "-")[2] == "kernel" {
|
||||
if platform == kernelInfo {
|
||||
@@ -111,6 +128,8 @@ func analyzeOSVersionResult(osInfo collect.HostOSInfo, outcomes []*troubleshootv
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
}
|
||||
// Match the platform version
|
||||
// e.g "centos == 8.2"
|
||||
} else if platform == osInfo.Platform {
|
||||
fixedDistVer := fixVersion(osInfo.PlatformVersion)
|
||||
toleratedDistVer, err := semver.ParseTolerant(fixedDistVer)
|
||||
|
||||
@@ -319,6 +319,34 @@ func TestAnalyzeHostOS(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "test kernelVersion >= 6.4.9-abc",
|
||||
hostInfo: collect.HostOSInfo{
|
||||
Name: "my-host",
|
||||
KernelVersion: "6.5.0-1024-gcp",
|
||||
PlatformVersion: "22.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HostOSAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "kernelVersion >= 6.4.9-abc",
|
||||
Message: "supported kernel version >= 6.4.9-abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info",
|
||||
IsPass: true,
|
||||
Message: "supported kernel version >= 6.4.9-abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type AnalyzeHTTPAnalyze struct {
|
||||
analyzer *troubleshootv1beta2.HTTPAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHTTPAnalyze) Title() string {
|
||||
checkName := a.analyzer.CheckName
|
||||
if checkName == "" {
|
||||
checkName = a.analyzer.CollectorName
|
||||
}
|
||||
|
||||
return checkName
|
||||
}
|
||||
|
||||
func (a *AnalyzeHTTPAnalyze) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.analyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHTTPAnalyze) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
fileName := "result.json"
|
||||
if a.analyzer.CollectorName != "" {
|
||||
fileName = a.analyzer.CollectorName + ".json"
|
||||
}
|
||||
return analyzeHTTPResult(a.analyzer, fileName, getFile, a.Title())
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
util "github.com/replicatedhq/troubleshoot/internal/util"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
iutils "github.com/replicatedhq/troubleshoot/pkg/interfaceutils"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
@@ -20,9 +21,16 @@ type AnalyzeJsonCompare struct {
|
||||
}
|
||||
|
||||
func (a *AnalyzeJsonCompare) Title() string {
|
||||
title := a.analyzer.CheckName
|
||||
return jsonCompareTitle(a.analyzer)
|
||||
}
|
||||
|
||||
func jsonCompareTitle(analyser *troubleshootv1beta2.JsonCompare) string {
|
||||
title := analyser.CheckName
|
||||
if title == "" {
|
||||
title = a.analyzer.CollectorName
|
||||
title = analyser.CollectorName
|
||||
}
|
||||
if title == "" {
|
||||
title = "Json Compare"
|
||||
}
|
||||
|
||||
return title
|
||||
@@ -33,7 +41,7 @@ func (a *AnalyzeJsonCompare) IsExcluded() (bool, error) {
|
||||
}
|
||||
|
||||
func (a *AnalyzeJsonCompare) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
result, err := a.analyzeJsonCompare(a.analyzer, getFile)
|
||||
result, err := analyzeJsonCompare(a.analyzer, getFile, a.Title())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -41,7 +49,7 @@ func (a *AnalyzeJsonCompare) Analyze(getFile getCollectedFileContents, findFiles
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.JsonCompare, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeJsonCompare(analyzer *troubleshootv1beta2.JsonCompare, getCollectedFileContents func(string) ([]byte, error), title string) (*AnalyzeResult, error) {
|
||||
fullPath := filepath.Join(analyzer.CollectorName, analyzer.FileName)
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
if err != nil {
|
||||
@@ -54,6 +62,8 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js
|
||||
return nil, errors.Wrap(err, "failed to parse collected data as json")
|
||||
}
|
||||
|
||||
originalActual := actual
|
||||
|
||||
if analyzer.Path != "" {
|
||||
actual, err = iutils.GetAtPath(actual, analyzer.Path)
|
||||
if err != nil {
|
||||
@@ -94,7 +104,7 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
Title: title,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
}
|
||||
@@ -112,6 +122,11 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Fail.Message, err = util.RenderTemplate(outcome.Fail.Message, originalActual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to render template on outcome message")
|
||||
}
|
||||
|
||||
if when == equal {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
@@ -128,6 +143,11 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Warn.Message, err = util.RenderTemplate(outcome.Warn.Message, originalActual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to render template on outcome message")
|
||||
}
|
||||
|
||||
if when == equal {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
@@ -144,6 +164,11 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Pass.Message, err = util.RenderTemplate(outcome.Pass.Message, originalActual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to render template on outcome message")
|
||||
}
|
||||
|
||||
if when == equal {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
@@ -155,7 +180,7 @@ func (a *AnalyzeJsonCompare) analyzeJsonCompare(analyzer *troubleshootv1beta2.Js
|
||||
}
|
||||
|
||||
return &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
Title: title,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
IsFail: true,
|
||||
|
||||
@@ -756,6 +756,48 @@ func Test_jsonCompare(t *testing.T) {
|
||||
]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "basic comparison with outcome message templated",
|
||||
analyzer: troubleshootv1beta2.JsonCompare{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Status: {{ .morestuff.status }}, Info: {{ .morestuff.info }}",
|
||||
When: "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Status: {{ .morestuff.status }}, Info: {{ .morestuff.info }}",
|
||||
When: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "json-compare",
|
||||
FileName: "json-compare.json",
|
||||
Path: "morestuff.status",
|
||||
Value: `"ready"`,
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "json-compare",
|
||||
Message: "Status: notready, Info: morestuff is not ready",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
fileContents: []byte(`{
|
||||
"stuff": {
|
||||
"status": "ready",
|
||||
"info": "this stuff is ready"
|
||||
},
|
||||
"morestuff": {
|
||||
"status": "notready",
|
||||
"info": "morestuff is not ready"
|
||||
}
|
||||
}`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -766,11 +808,7 @@ func Test_jsonCompare(t *testing.T) {
|
||||
return test.fileContents, nil
|
||||
}
|
||||
|
||||
a := AnalyzeJsonCompare{
|
||||
analyzer: &test.analyzer,
|
||||
}
|
||||
|
||||
actual, err := a.analyzeJsonCompare(&test.analyzer, getCollectedFileContents)
|
||||
actual, err := analyzeJsonCompare(&test.analyzer, getCollectedFileContents, test.analyzer.CollectorName)
|
||||
if !test.isError {
|
||||
req.NoError(err)
|
||||
req.Equal(test.expectResult, *actual)
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/klog/v2"
|
||||
kubeletv1alpha1 "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
|
||||
)
|
||||
|
||||
type AnalyzeNodeMetrics struct {
|
||||
analyzer *troubleshootv1beta2.NodeMetricsAnalyze
|
||||
}
|
||||
|
||||
type nodeMetricsComparisonResults struct {
|
||||
PVC pvcTemplateData
|
||||
}
|
||||
|
||||
type pvcTemplateData struct {
|
||||
UsedPercentage float64
|
||||
ConcatenatedNames string
|
||||
Names []string
|
||||
}
|
||||
|
||||
type pvcUsageStats struct {
|
||||
PvcName string
|
||||
Used float64
|
||||
}
|
||||
|
||||
func (a *AnalyzeNodeMetrics) Title() string {
|
||||
title := a.analyzer.CheckName
|
||||
if title == "" {
|
||||
title = a.analyzer.CollectorName
|
||||
}
|
||||
if title == "" {
|
||||
title = "Node Metrics"
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
func (a *AnalyzeNodeMetrics) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.analyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeNodeMetrics) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
// Gather all collected node-metrics files
|
||||
collected, err := findFiles(filepath.Join("node-metrics", "*.json"), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected pods")
|
||||
}
|
||||
|
||||
// Unmarshal all collected node-metrics files
|
||||
summaries := []kubeletv1alpha1.Summary{}
|
||||
for _, fileContent := range collected {
|
||||
summary := kubeletv1alpha1.Summary{}
|
||||
if err := json.Unmarshal(fileContent, &summary); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal node metrics")
|
||||
}
|
||||
|
||||
summaries = append(summaries, summary)
|
||||
}
|
||||
|
||||
// Run through all outcomes to generate results
|
||||
result, err := a.compareCollectedMetricsWithOutcomes(summaries)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare node metrics with outcomes")
|
||||
}
|
||||
if result == nil {
|
||||
return []*AnalyzeResult{}, nil
|
||||
}
|
||||
result.Strict = a.analyzer.Strict.BoolOrDefaultFalse()
|
||||
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeNodeMetrics) compareCollectedMetricsWithOutcomes(summaries []kubeletv1alpha1.Summary) (*AnalyzeResult, error) {
|
||||
for _, outcome := range a.analyzer.Outcomes {
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
}
|
||||
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
} else {
|
||||
isMatch, out, err := a.compareNodeMetricConditionalsToStats(outcome.Fail.When, summaries)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare node metrics conditional with summary stats")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = renderTemplate(outcome.Fail.Message, out)
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
} else {
|
||||
isMatch, out, err := a.compareNodeMetricConditionalsToStats(outcome.Warn.When, summaries)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare node metrics conditional with summary stats")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = renderTemplate(outcome.Warn.Message, out)
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
} else {
|
||||
isMatch, out, err := a.compareNodeMetricConditionalsToStats(outcome.Pass.When, summaries)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare node metrics conditional with summary stats")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = renderTemplate(outcome.Pass.Message, out)
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeNodeMetrics) findPVCUsageStats(summaries []kubeletv1alpha1.Summary) ([]pvcUsageStats, error) {
|
||||
// We just collect usage percentages for now. If other stats are needed, we can add them.
|
||||
stats := []pvcUsageStats{}
|
||||
var nameRegex *regexp.Regexp
|
||||
var ns string
|
||||
var err error
|
||||
|
||||
pvcFilter := a.analyzer.Filters.PVC
|
||||
if pvcFilter != nil {
|
||||
if pvcFilter.NameRegex != "" {
|
||||
nameRegex, err = regexp.Compile(pvcFilter.NameRegex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compile PVC name regex")
|
||||
}
|
||||
}
|
||||
|
||||
ns = pvcFilter.Namespace
|
||||
}
|
||||
|
||||
// Analyze PVCs
|
||||
for _, summary := range summaries {
|
||||
for i := range summary.Pods {
|
||||
pod := summary.Pods[i]
|
||||
if ns != "" && ns != pod.PodRef.Namespace {
|
||||
klog.V(2).Infof("Skipping pvcs in %s/%s pod due to namespace filter", pod.PodRef.Namespace, pod.PodRef.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for j := range pod.VolumeStats {
|
||||
volume := pod.VolumeStats[j]
|
||||
|
||||
// This is a persistent volume
|
||||
if volume.PVCRef != nil {
|
||||
if nameRegex != nil && !nameRegex.MatchString(volume.PVCRef.Name) {
|
||||
klog.V(2).Infof("Skipping pvc %s/%s due to name regex filter", volume.PVCRef.Namespace, volume.PVCRef.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate the usage
|
||||
pvcName := fmt.Sprintf("%s/%s", volume.PVCRef.Namespace, volume.PVCRef.Name)
|
||||
|
||||
used := volume.UsedBytes
|
||||
capacity := volume.CapacityBytes
|
||||
if used != nil && capacity != nil {
|
||||
pvcUsedPercentage := float64(*used) / float64(*capacity) * 100
|
||||
stats = append(stats, pvcUsageStats{
|
||||
PvcName: pvcName,
|
||||
Used: pvcUsedPercentage,
|
||||
})
|
||||
klog.V(2).Infof("PVC usage for %s: %0.2f%%", pvcName, pvcUsedPercentage)
|
||||
} else {
|
||||
klog.V(2).Infof("Missing capacity or used bytes for PVC %s", pvcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// compareNodeMetricConditionalsToStats compares the conditional with the collected node metrics
|
||||
// and returns true if the conditional is met. At the moment we only support comparing PVC usage
|
||||
func (a *AnalyzeNodeMetrics) compareNodeMetricConditionalsToStats(conditional string, summaries []kubeletv1alpha1.Summary) (bool, nodeMetricsComparisonResults, error) {
|
||||
klog.V(2).Infof("Comparing node metrics with conditional: %s", conditional)
|
||||
parts := strings.Split(strings.TrimSpace(conditional), " ")
|
||||
out := nodeMetricsComparisonResults{}
|
||||
|
||||
if len(parts) != 3 {
|
||||
return false, out, errors.New("unable to parse conditional")
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "pvcUsedPercentage":
|
||||
// e.g pvcUsedPercentage >= 50.4
|
||||
|
||||
klog.V(2).Infof("Analyzing volume usage stats for PVCs")
|
||||
|
||||
op, err := ParseComparisonOperator(parts[1])
|
||||
if err != nil {
|
||||
return false, out, errors.Wrap(err, "failed to parse comparison operator")
|
||||
}
|
||||
|
||||
expected, err := strconv.ParseFloat(parts[2], 64)
|
||||
if err != nil {
|
||||
return false, out, errors.Wrap(err, "failed to parse bool")
|
||||
}
|
||||
|
||||
// Pick all PVCs from all summaries. Filters will be applied here
|
||||
pvcUsageStats, err := a.findPVCUsageStats(summaries)
|
||||
if err != nil {
|
||||
return false, out, errors.Wrap(err, "failed to find PVC usage stats")
|
||||
}
|
||||
matchedPVCs := []string{}
|
||||
|
||||
for _, pvcUsage := range pvcUsageStats {
|
||||
value := pvcUsage.Used
|
||||
switch op {
|
||||
case Equal:
|
||||
if value == expected {
|
||||
matchedPVCs = append(matchedPVCs, pvcUsage.PvcName)
|
||||
}
|
||||
case NotEqual:
|
||||
if value != expected {
|
||||
matchedPVCs = append(matchedPVCs, pvcUsage.PvcName)
|
||||
}
|
||||
case LessThan:
|
||||
if value < expected {
|
||||
matchedPVCs = append(matchedPVCs, pvcUsage.PvcName)
|
||||
}
|
||||
case GreaterThan:
|
||||
if value > expected {
|
||||
matchedPVCs = append(matchedPVCs, pvcUsage.PvcName)
|
||||
}
|
||||
case LessThanOrEqual:
|
||||
if value <= expected {
|
||||
matchedPVCs = append(matchedPVCs, pvcUsage.PvcName)
|
||||
}
|
||||
case GreaterThanOrEqual:
|
||||
if value >= expected {
|
||||
matchedPVCs = append(matchedPVCs, pvcUsage.PvcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate all matched PVC names
|
||||
out.PVC = pvcTemplateData{
|
||||
Names: matchedPVCs,
|
||||
ConcatenatedNames: strings.Join(matchedPVCs, ", "),
|
||||
}
|
||||
return len(matchedPVCs) > 0, out, nil
|
||||
}
|
||||
|
||||
return false, out, errors.New("unknown node metric conditional")
|
||||
}
|
||||
|
||||
func renderTemplate(tmpMsg string, data any) string {
|
||||
if data == nil {
|
||||
return tmpMsg
|
||||
}
|
||||
|
||||
t, err := template.New("msg").Parse(tmpMsg)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed to parse template: %s", err)
|
||||
return tmpMsg
|
||||
}
|
||||
|
||||
var m bytes.Buffer
|
||||
err = t.Execute(&m, data)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed to execute template: %s", err)
|
||||
return tmpMsg
|
||||
}
|
||||
|
||||
return m.String()
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
kubeletv1alpha1 "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
|
||||
utilptr "k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestAnalyzeNodeMetrics_findPVCUsageStats(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer troubleshootv1beta2.NodeMetricsAnalyze
|
||||
summaries []kubeletv1alpha1.Summary
|
||||
want []pvcUsageStats
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no summaries",
|
||||
summaries: []kubeletv1alpha1.Summary{},
|
||||
want: []pvcUsageStats{},
|
||||
},
|
||||
{
|
||||
name: "one summary",
|
||||
summaries: []kubeletv1alpha1.Summary{
|
||||
{
|
||||
Pods: []kubeletv1alpha1.PodStats{
|
||||
{
|
||||
PodRef: kubeletv1alpha1.PodReference{
|
||||
Namespace: "default",
|
||||
Name: "my-pod",
|
||||
},
|
||||
VolumeStats: []kubeletv1alpha1.VolumeStats{
|
||||
{
|
||||
Name: "volume-1",
|
||||
PVCRef: &kubeletv1alpha1.PVCReference{
|
||||
Namespace: "default",
|
||||
Name: "my-pvc",
|
||||
},
|
||||
FsStats: kubeletv1alpha1.FsStats{
|
||||
AvailableBytes: utilptr.To(uint64(20)),
|
||||
UsedBytes: utilptr.To(uint64(80)),
|
||||
CapacityBytes: utilptr.To(uint64(100)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []pvcUsageStats{
|
||||
{
|
||||
Used: 80,
|
||||
PvcName: "default/my-pvc",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one summary with namespace filter",
|
||||
analyzer: troubleshootv1beta2.NodeMetricsAnalyze{
|
||||
Filters: troubleshootv1beta2.NodeMetricsAnalyzeFilters{
|
||||
PVC: &troubleshootv1beta2.PVCRef{
|
||||
Namespace: "another-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
summaries: []kubeletv1alpha1.Summary{
|
||||
{
|
||||
Pods: []kubeletv1alpha1.PodStats{
|
||||
{
|
||||
PodRef: kubeletv1alpha1.PodReference{
|
||||
Namespace: "default",
|
||||
Name: "my-pod",
|
||||
},
|
||||
VolumeStats: []kubeletv1alpha1.VolumeStats{
|
||||
{
|
||||
Name: "volume-1",
|
||||
PVCRef: &kubeletv1alpha1.PVCReference{
|
||||
Namespace: "default",
|
||||
Name: "my-pvc",
|
||||
},
|
||||
FsStats: kubeletv1alpha1.FsStats{
|
||||
AvailableBytes: utilptr.To(uint64(20)),
|
||||
UsedBytes: utilptr.To(uint64(80)),
|
||||
CapacityBytes: utilptr.To(uint64(100)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []pvcUsageStats{},
|
||||
},
|
||||
{
|
||||
name: "one summary with name regex filter",
|
||||
analyzer: troubleshootv1beta2.NodeMetricsAnalyze{
|
||||
Filters: troubleshootv1beta2.NodeMetricsAnalyzeFilters{
|
||||
PVC: &troubleshootv1beta2.PVCRef{
|
||||
NameRegex: ".*other.*",
|
||||
},
|
||||
},
|
||||
},
|
||||
summaries: []kubeletv1alpha1.Summary{
|
||||
{
|
||||
Pods: []kubeletv1alpha1.PodStats{
|
||||
{
|
||||
PodRef: kubeletv1alpha1.PodReference{
|
||||
Namespace: "default",
|
||||
Name: "my-pod",
|
||||
},
|
||||
VolumeStats: []kubeletv1alpha1.VolumeStats{
|
||||
{
|
||||
Name: "volume-1",
|
||||
PVCRef: &kubeletv1alpha1.PVCReference{
|
||||
Namespace: "default",
|
||||
Name: "my-pvc",
|
||||
},
|
||||
FsStats: kubeletv1alpha1.FsStats{
|
||||
AvailableBytes: utilptr.To(uint64(20)),
|
||||
UsedBytes: utilptr.To(uint64(80)),
|
||||
CapacityBytes: utilptr.To(uint64(100)),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "volume-1",
|
||||
PVCRef: &kubeletv1alpha1.PVCReference{
|
||||
Namespace: "default",
|
||||
Name: "my-other-pvc",
|
||||
},
|
||||
FsStats: kubeletv1alpha1.FsStats{
|
||||
AvailableBytes: utilptr.To(uint64(25)),
|
||||
UsedBytes: utilptr.To(uint64(75)),
|
||||
CapacityBytes: utilptr.To(uint64(100)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []pvcUsageStats{
|
||||
{
|
||||
Used: 75,
|
||||
PvcName: "default/my-other-pvc",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &AnalyzeNodeMetrics{
|
||||
analyzer: &tt.analyzer,
|
||||
}
|
||||
got, err := a.findPVCUsageStats(tt.summaries)
|
||||
assert.Equalf(t, tt.wantErr, err != nil, "AnalyzeNodeMetrics.findPVCUsageStats() error = %v, wantErr %v", err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeNodeMetrics_Analyze(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer troubleshootv1beta2.NodeMetricsAnalyze
|
||||
nodeMetrics string
|
||||
want []*AnalyzeResult
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no node metrics",
|
||||
analyzer: troubleshootv1beta2.NodeMetricsAnalyze{
|
||||
Filters: troubleshootv1beta2.NodeMetricsAnalyzeFilters{},
|
||||
},
|
||||
nodeMetrics: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid node metrics",
|
||||
analyzer: troubleshootv1beta2.NodeMetricsAnalyze{
|
||||
Filters: troubleshootv1beta2.NodeMetricsAnalyzeFilters{},
|
||||
},
|
||||
nodeMetrics: "invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no summaries",
|
||||
analyzer: troubleshootv1beta2.NodeMetricsAnalyze{
|
||||
Filters: troubleshootv1beta2.NodeMetricsAnalyzeFilters{},
|
||||
},
|
||||
nodeMetrics: "{}",
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "one summary with name regex filter",
|
||||
analyzer: troubleshootv1beta2.NodeMetricsAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "pvcUsedPercentage >= 75",
|
||||
Message: "PVC space usage is too high for pvcs [{{ .PVC.ConcatenatedNames }}]",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No PVCs are using more than 80% of storage",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: troubleshootv1beta2.NodeMetricsAnalyzeFilters{
|
||||
PVC: &troubleshootv1beta2.PVCRef{
|
||||
NameRegex: ".*other.*",
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeMetrics: `{
|
||||
"pods": [
|
||||
{
|
||||
"podRef": {
|
||||
"name": "my-pod",
|
||||
"namespace": "my-namespace"
|
||||
},
|
||||
"volume": [
|
||||
{
|
||||
"capacityBytes": 100,
|
||||
"usedBytes": 80,
|
||||
"pvcRef": {
|
||||
"name": "backup-pvc",
|
||||
"namespace": "my-namespace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capacityBytes": 100,
|
||||
"usedBytes": 75,
|
||||
"pvcRef": {
|
||||
"name": "another-pvc",
|
||||
"namespace": "my-namespace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capacityBytes": 100,
|
||||
"usedBytes": 80,
|
||||
"pvcRef": {
|
||||
"name": "the-other-pvc",
|
||||
"namespace": "my-namespace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capacityBytes": 100,
|
||||
"usedBytes": 65,
|
||||
"pvcRef": {
|
||||
"name": "to-other-pvc",
|
||||
"namespace": "my-namespace"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Node Metrics",
|
||||
IsFail: true,
|
||||
Message: "PVC space usage is too high for pvcs [my-namespace/another-pvc, my-namespace/the-other-pvc]",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &AnalyzeNodeMetrics{
|
||||
analyzer: &tt.analyzer,
|
||||
}
|
||||
filesFn := func(string, []string) (map[string][]byte, error) {
|
||||
return map[string][]byte{
|
||||
"node-metrics.json": []byte(tt.nodeMetrics),
|
||||
}, nil
|
||||
}
|
||||
|
||||
got, err := a.Analyze(nil, filesFn)
|
||||
assert.Equalf(t, tt.wantErr, err != nil, "AnalyzeNodeMetrics.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,22 +17,23 @@ import (
|
||||
)
|
||||
|
||||
var Filemap = map[string]string{
|
||||
"Deployment": constants.CLUSTER_RESOURCES_DEPLOYMENTS,
|
||||
"StatefulSet": constants.CLUSTER_RESOURCES_STATEFULSETS,
|
||||
"NetworkPolicy": constants.CLUSTER_RESOURCES_NETWORK_POLICY,
|
||||
"Pod": constants.CLUSTER_RESOURCES_PODS,
|
||||
"Ingress": constants.CLUSTER_RESOURCES_INGRESS,
|
||||
"Service": constants.CLUSTER_RESOURCES_SERVICES,
|
||||
"ResourceQuota": constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
|
||||
"Job": constants.CLUSTER_RESOURCES_JOBS,
|
||||
"PersistentVolumeClaim": constants.CLUSTER_RESOURCES_PVCS,
|
||||
"deployment": constants.CLUSTER_RESOURCES_DEPLOYMENTS,
|
||||
"statefulset": constants.CLUSTER_RESOURCES_STATEFULSETS,
|
||||
"networkpolicy": constants.CLUSTER_RESOURCES_NETWORK_POLICY,
|
||||
"pod": constants.CLUSTER_RESOURCES_PODS,
|
||||
"ingress": constants.CLUSTER_RESOURCES_INGRESS,
|
||||
"service": constants.CLUSTER_RESOURCES_SERVICES,
|
||||
"resourcequota": constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
|
||||
"job": constants.CLUSTER_RESOURCES_JOBS,
|
||||
"persistentvolumeclaim": constants.CLUSTER_RESOURCES_PVCS,
|
||||
"pvc": constants.CLUSTER_RESOURCES_PVCS,
|
||||
"ReplicaSet": constants.CLUSTER_RESOURCES_REPLICASETS,
|
||||
"Namespace": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
|
||||
"PersistentVolume": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
|
||||
"replicaset": constants.CLUSTER_RESOURCES_REPLICASETS,
|
||||
"configmap": constants.CLUSTER_RESOURCES_CONFIGMAPS,
|
||||
"namespace": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
|
||||
"persistentvolume": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
|
||||
"pv": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
|
||||
"Node": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
|
||||
"StorageClass": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
|
||||
"node": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
|
||||
"storageclass": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
|
||||
}
|
||||
|
||||
type AnalyzeClusterResource struct {
|
||||
@@ -66,6 +67,9 @@ func FindResource(kind string, clusterScoped bool, namespace string, name string
|
||||
|
||||
var datapath string
|
||||
|
||||
// lowercase the kind to avoid case sensitivity
|
||||
kind = strings.ToLower(kind)
|
||||
|
||||
resourceLocation, ok := Filemap[kind]
|
||||
|
||||
if !ok {
|
||||
|
||||
@@ -21,13 +21,13 @@ func Test_findResource(t *testing.T) {
|
||||
resourceExists: true,
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
CollectorName: "Check namespaced resource",
|
||||
Kind: "Deployment",
|
||||
Kind: "deployment",
|
||||
Namespace: "kube-system",
|
||||
Name: "coredns",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "check default fallthrough",
|
||||
name: "check default fallthrough with case insensitivity",
|
||||
resourceExists: true,
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
CollectorName: "Check namespaced resource",
|
||||
@@ -40,7 +40,7 @@ func Test_findResource(t *testing.T) {
|
||||
resourceExists: true,
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
CollectorName: "Check namespaced resource",
|
||||
Kind: "Node",
|
||||
Kind: "node",
|
||||
ClusterScoped: true,
|
||||
Name: "repldev-marc",
|
||||
},
|
||||
@@ -50,11 +50,21 @@ func Test_findResource(t *testing.T) {
|
||||
resourceExists: false,
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
CollectorName: "Check namespaced resource",
|
||||
Kind: "Node",
|
||||
Kind: "node",
|
||||
ClusterScoped: true,
|
||||
Name: "resource-does-not-exist",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "configmap does exist",
|
||||
resourceExists: true,
|
||||
analyzer: troubleshootv1beta2.ClusterResource{
|
||||
CollectorName: "Check namespaced resource",
|
||||
Kind: "configmap",
|
||||
Namespace: "kube-public",
|
||||
Name: "kube-root-ca.crt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
longhornv1beta1 "github.com/replicatedhq/troubleshoot/pkg/longhorn/apis/longhorn/v1beta1"
|
||||
longhorntypes "github.com/replicatedhq/troubleshoot/pkg/longhorn/types"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/redact"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@@ -241,21 +237,6 @@ func analyzeLonghornEngine(engine *longhornv1beta1.Engine) *AnalyzeResult {
|
||||
return result
|
||||
}
|
||||
|
||||
func stripRedactedLines(yaml []byte) []byte {
|
||||
buf := bytes.NewBuffer(yaml)
|
||||
scanner := bufio.NewScanner(buf)
|
||||
|
||||
out := []byte{}
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.ReplaceAll(scanner.Text(), redact.MASK_TEXT, "HIDDEN")
|
||||
out = append(out, []byte(line)...)
|
||||
out = append(out, '\n')
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func analyzeLonghornReplicaChecksums(volumeName string, checksums []map[string]string) *AnalyzeResult {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Longhorn Volume Replica Corruption: %s", volumeName),
|
||||
|
||||
@@ -101,6 +101,10 @@ func analyzeOneReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus,
|
||||
}
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ResticRepositorySpec is the specification for a ResticRepository.
|
||||
type ResticRepositorySpec struct {
|
||||
// VolumeNamespace is the namespace this restic repository contains
|
||||
// pod volume backups for.
|
||||
VolumeNamespace string `json:"volumeNamespace"`
|
||||
|
||||
// BackupStorageLocation is the name of the BackupStorageLocation
|
||||
// that should contain this repository.
|
||||
BackupStorageLocation string `json:"backupStorageLocation"`
|
||||
|
||||
// ResticIdentifier is the full restic-compatible string for identifying
|
||||
// this repository.
|
||||
ResticIdentifier string `json:"resticIdentifier"`
|
||||
|
||||
// MaintenanceFrequency is how often maintenance should be run.
|
||||
MaintenanceFrequency metav1.Duration `json:"maintenanceFrequency"`
|
||||
}
|
||||
|
||||
// ResticRepositoryPhase represents the lifecycle phase of a ResticRepository.
|
||||
// +kubebuilder:validation:Enum=New;Ready;NotReady
|
||||
type ResticRepositoryPhase string
|
||||
|
||||
const (
|
||||
ResticRepositoryPhaseNew ResticRepositoryPhase = "New"
|
||||
ResticRepositoryPhaseReady ResticRepositoryPhase = "Ready"
|
||||
ResticRepositoryPhaseNotReady ResticRepositoryPhase = "NotReady"
|
||||
)
|
||||
|
||||
// ResticRepositoryStatus is the current status of a ResticRepository.
|
||||
type ResticRepositoryStatus struct {
|
||||
// Phase is the current state of the ResticRepository.
|
||||
// +optional
|
||||
Phase ResticRepositoryPhase `json:"phase,omitempty"`
|
||||
|
||||
// Message is a message about the current status of the ResticRepository.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// LastMaintenanceTime is the last time maintenance was run.
|
||||
// +optional
|
||||
// +nullable
|
||||
LastMaintenanceTime *metav1.Time `json:"lastMaintenanceTime,omitempty"`
|
||||
}
|
||||
|
||||
type ResticRepository struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// +optional
|
||||
Spec ResticRepositorySpec `json:"spec,omitempty"`
|
||||
|
||||
// +optional
|
||||
Status ResticRepositoryStatus `json:"status,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
appsV1 "k8s.io/api/apps/v1"
|
||||
|
||||
restic_types "github.com/replicatedhq/troubleshoot/pkg/analyze/types"
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
type AnalyzeVelero struct {
|
||||
analyzer *troubleshootv1beta2.VeleroAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeVelero) Title() string {
|
||||
title := a.analyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Velero"
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
func (a *AnalyzeVelero) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.analyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeVelero) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
results, err := a.veleroStatus(a.analyzer, getFile, findFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range results {
|
||||
results[i].Strict = a.analyzer.Strict.BoolOrDefaultFalse()
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (a *AnalyzeVelero) veleroStatus(analyzer *troubleshootv1beta2.VeleroAnalyze, getFileContents getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
results := []*AnalyzeResult{}
|
||||
|
||||
oldVeleroRepoType := false
|
||||
veleroVersion, err := getVeleroVersion(excludeFiles, findFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to find velero deployment")
|
||||
}
|
||||
|
||||
// Check if the version string is valid erer
|
||||
if !semver.IsValid(veleroVersion) {
|
||||
return nil, errors.Errorf("Invalid velero semver: %s", veleroVersion)
|
||||
}
|
||||
|
||||
// check if veleroVersion is less than 1.10.x
|
||||
compareResult := semver.Compare(veleroVersion, "1.10.0")
|
||||
if compareResult < 0 {
|
||||
oldVeleroRepoType = true
|
||||
}
|
||||
|
||||
if oldVeleroRepoType == true {
|
||||
// old velero (v1.9.x) has a BackupRepositoryTypeRestic
|
||||
// get resticrepositories.velero.io
|
||||
resticRepositoriesDir := GetVeleroResticRepositoriesDirectory()
|
||||
resticRepositoriesGlob := filepath.Join(resticRepositoriesDir, "*.json")
|
||||
resticRepositoriesJson, err := findFiles(resticRepositoriesGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero restic repositories files under %s", resticRepositoriesDir)
|
||||
}
|
||||
resticRepositories := []*restic_types.ResticRepository{}
|
||||
for key, resticRepositoryJson := range resticRepositoriesJson {
|
||||
var resticRepositoryArray []*restic_types.ResticRepository
|
||||
err := json.Unmarshal(resticRepositoryJson, &resticRepositories)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal restic repository json from %s", key)
|
||||
}
|
||||
resticRepositories = append(resticRepositories, resticRepositoryArray...)
|
||||
}
|
||||
results = append(results, analyzeResticRepositories(resticRepositories)...)
|
||||
|
||||
} else {
|
||||
|
||||
// velerov1.Version
|
||||
// get backuprepositories.velero.io
|
||||
backupRepositoriesDir := GetVeleroBackupRepositoriesDirectory()
|
||||
backupRepositoriesGlob := filepath.Join(backupRepositoriesDir, "*.json")
|
||||
backupRepositoriesJson, err := findFiles(backupRepositoriesGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero backup repositories files under %s", backupRepositoriesDir)
|
||||
}
|
||||
backupRepositories := []*velerov1.BackupRepository{}
|
||||
for key, backupRepositoryJson := range backupRepositoriesJson {
|
||||
var backupRepositoryArray []*velerov1.BackupRepository
|
||||
err := json.Unmarshal(backupRepositoryJson, &backupRepositoryArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal backup repository json from %s", key)
|
||||
}
|
||||
backupRepositories = append(backupRepositories, backupRepositoryArray...)
|
||||
}
|
||||
results = append(results, analyzeBackupRepositories(backupRepositories)...)
|
||||
|
||||
}
|
||||
|
||||
// get backups.velero.io
|
||||
backupsDir := GetVeleroBackupsDirectory()
|
||||
backupsGlob := filepath.Join(backupsDir, "*.json")
|
||||
veleroJSONs, err := findFiles(backupsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero backup files")
|
||||
}
|
||||
backups := []*velerov1.Backup{}
|
||||
for _, veleroJSON := range veleroJSONs {
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read velero backup file %s", veleroJSON)
|
||||
}
|
||||
var veleroBackups []*velerov1.Backup
|
||||
err = json.Unmarshal(veleroJSON, &veleroBackups)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal velero backup file %s", veleroJSON)
|
||||
}
|
||||
backups = append(backups, veleroBackups...)
|
||||
}
|
||||
|
||||
// get backupstoragelocations.velero.io
|
||||
backupStorageLocationsDir := GetVeleroBackupStorageLocationsDirectory()
|
||||
backupStorageLocationsGlob := filepath.Join(backupStorageLocationsDir, "*.json")
|
||||
backupStorageLocationsJson, err := findFiles(backupStorageLocationsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero backup storage locations files under %s", backupStorageLocationsDir)
|
||||
}
|
||||
backupStorageLocations := []*velerov1.BackupStorageLocation{}
|
||||
for key, backupStorageLocationJson := range backupStorageLocationsJson {
|
||||
var backupStorageLocationArray []*velerov1.BackupStorageLocation
|
||||
err := json.Unmarshal(backupStorageLocationJson, &backupStorageLocationArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal backup storage location json from %s", key)
|
||||
}
|
||||
backupStorageLocations = append(backupStorageLocations, backupStorageLocationArray...)
|
||||
}
|
||||
|
||||
// get deletebackuprequests.velero.io
|
||||
deleteBackupRequestsDir := GetVeleroDeleteBackupRequestsDirectory()
|
||||
deleteBackupRequestsGlob := filepath.Join(deleteBackupRequestsDir, "*.json")
|
||||
deleteBackupRequestsJson, err := findFiles(deleteBackupRequestsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero delete backup requests files under %s", deleteBackupRequestsDir)
|
||||
}
|
||||
deleteBackupRequests := []*velerov1.DeleteBackupRequest{}
|
||||
for key, deleteBackupRequestJson := range deleteBackupRequestsJson {
|
||||
var deleteBackupRequestArray []*velerov1.DeleteBackupRequest
|
||||
err := json.Unmarshal(deleteBackupRequestJson, &deleteBackupRequestArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal delete backup request json from %s", key)
|
||||
}
|
||||
deleteBackupRequests = append(deleteBackupRequests, deleteBackupRequestArray...)
|
||||
}
|
||||
|
||||
// get podvolumebackups.velero.io
|
||||
podVolumeBackupsDir := GetVeleroPodVolumeBackupsDirectory()
|
||||
podVolumeBackupsGlob := filepath.Join(podVolumeBackupsDir, "*.json")
|
||||
podVolumeBackupsJson, err := findFiles(podVolumeBackupsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero pod volume backups files under %s", podVolumeBackupsDir)
|
||||
}
|
||||
podVolumeBackups := []*velerov1.PodVolumeBackup{}
|
||||
for key, podVolumeBackupJson := range podVolumeBackupsJson {
|
||||
var podVolumeBackupArray []*velerov1.PodVolumeBackup
|
||||
err := json.Unmarshal(podVolumeBackupJson, &podVolumeBackupArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal pod volume backup json from %s", key)
|
||||
}
|
||||
podVolumeBackups = append(podVolumeBackups, podVolumeBackupArray...)
|
||||
}
|
||||
|
||||
// get podvolumerestores.velero.io
|
||||
podVolumeRestoresDir := GetVeleroPodVolumeRestoresDirectory()
|
||||
podVolumeRestoresGlob := filepath.Join(podVolumeRestoresDir, "*.json")
|
||||
podVolumeRestoresJson, err := findFiles(podVolumeRestoresGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero pod volume restores files under %s", podVolumeRestoresDir)
|
||||
}
|
||||
podVolumeRestores := []*velerov1.PodVolumeRestore{}
|
||||
for key, podVolumeRestoreJson := range podVolumeRestoresJson {
|
||||
var podVolumeRestoreArray []*velerov1.PodVolumeRestore
|
||||
err := json.Unmarshal(podVolumeRestoreJson, &podVolumeRestoreArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal pod volume restore json from %s", key)
|
||||
}
|
||||
podVolumeRestores = append(podVolumeRestores, podVolumeRestoreArray...)
|
||||
}
|
||||
|
||||
// get restores.velero.io
|
||||
restoresDir := GetVeleroRestoresDirectory()
|
||||
restoresGlob := filepath.Join(restoresDir, "*.json")
|
||||
restoresJson, err := findFiles(restoresGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero restores files under %s", restoresDir)
|
||||
}
|
||||
restores := []*velerov1.Restore{}
|
||||
for key, restoreJson := range restoresJson {
|
||||
var restoreArray []*velerov1.Restore
|
||||
err := json.Unmarshal(restoreJson, &restoreArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal restore json from %s", key)
|
||||
}
|
||||
restores = append(restores, restoreArray...)
|
||||
}
|
||||
|
||||
// get schedules.velero.io
|
||||
schedulesDir := GetVeleroSchedulesDirectory()
|
||||
schedulesGlob := filepath.Join(schedulesDir, "*.json")
|
||||
schedulesJson, err := findFiles(schedulesGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero schedules files under %s", schedulesDir)
|
||||
}
|
||||
schedules := []*velerov1.Schedule{}
|
||||
for key, scheduleJson := range schedulesJson {
|
||||
var scheduleArray []*velerov1.Schedule
|
||||
err := json.Unmarshal(scheduleJson, &scheduleArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal schedule json from %s", key)
|
||||
}
|
||||
schedules = append(schedules, scheduleArray...)
|
||||
}
|
||||
|
||||
// get serverstatusrequests.velero.io
|
||||
serverStatusRequestsDir := GetVeleroServerStatusRequestsDirectory()
|
||||
serverStatusRequestsGlob := filepath.Join(serverStatusRequestsDir, "*.json")
|
||||
serverStatusRequestsJson, err := findFiles(serverStatusRequestsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero server status requests files under %s", serverStatusRequestsDir)
|
||||
}
|
||||
serverStatusRequests := []*velerov1.ServerStatusRequest{}
|
||||
for key, serverStatusRequestJson := range serverStatusRequestsJson {
|
||||
var serverStatusRequestArray []*velerov1.ServerStatusRequest
|
||||
err := json.Unmarshal(serverStatusRequestJson, &serverStatusRequestArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal server status request json from %s", key)
|
||||
}
|
||||
serverStatusRequests = append(serverStatusRequests, serverStatusRequestArray...)
|
||||
}
|
||||
|
||||
// get volumesnapshotlocations.velero.io
|
||||
volumeSnapshotLocationsDir := GetVeleroVolumeSnapshotLocationsDirectory()
|
||||
volumeSnapshotLocationsGlob := filepath.Join(volumeSnapshotLocationsDir, "*.json")
|
||||
volumeSnapshotLocationsJson, err := findFiles(volumeSnapshotLocationsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero volume snapshot locations files under %s", volumeSnapshotLocationsDir)
|
||||
}
|
||||
volumeSnapshotLocations := []*velerov1.VolumeSnapshotLocation{}
|
||||
for key, volumeSnapshotLocationJson := range volumeSnapshotLocationsJson {
|
||||
var volumeSnapshotLocationArray []*velerov1.VolumeSnapshotLocation
|
||||
err := json.Unmarshal(volumeSnapshotLocationJson, &volumeSnapshotLocationArray)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal volume snapshot location json from %s", key)
|
||||
}
|
||||
volumeSnapshotLocations = append(volumeSnapshotLocations, volumeSnapshotLocationArray...)
|
||||
}
|
||||
|
||||
logsDir := GetVeleroLogsDirectory()
|
||||
logsGlob := filepath.Join(logsDir, "node-agent*", "*.log")
|
||||
nodeAgentlogs, err := findFiles(logsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find velero logs files under %s", logsDir)
|
||||
}
|
||||
|
||||
veleroLogsGlob := filepath.Join(logsDir, "velero*", "*.log")
|
||||
veleroLogs, err := findFiles(veleroLogsGlob, excludeFiles)
|
||||
|
||||
results = append(results, analyzeLogs(nodeAgentlogs, "node-agent*")...)
|
||||
results = append(results, analyzeLogs(veleroLogs, "velero*")...)
|
||||
results = append(results, analyzeBackups(backups)...)
|
||||
results = append(results, analyzeBackupStorageLocations(backupStorageLocations)...)
|
||||
results = append(results, analyzeDeleteBackupRequests(deleteBackupRequests)...)
|
||||
results = append(results, analyzePodVolumeBackups(podVolumeBackups)...)
|
||||
results = append(results, analyzePodVolumeRestores(podVolumeRestores)...)
|
||||
results = append(results, analyzeRestores(restores)...)
|
||||
results = append(results, analyzeSchedules(schedules)...)
|
||||
results = append(results, analyzeVolumeSnapshotLocations(volumeSnapshotLocations)...)
|
||||
|
||||
return aggregateResults(results), nil
|
||||
}
|
||||
|
||||
func analyzeBackupRepositories(backupRepositories []*velerov1.BackupRepository) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
readyCount := 0
|
||||
backupRepositoriesResult := &AnalyzeResult{
|
||||
Title: "At least 1 Backup Repository configured",
|
||||
}
|
||||
if len(backupRepositories) == 0 {
|
||||
backupRepositoriesResult.IsFail = true
|
||||
backupRepositoriesResult.Message = "No backup repositories configured"
|
||||
} else {
|
||||
for _, backupRepository := range backupRepositories {
|
||||
if backupRepository.Status.Phase != velerov1.BackupRepositoryPhaseReady {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Backup Repository %s", backupRepository.Name),
|
||||
}
|
||||
result.Message = fmt.Sprintf("Backup Repository [%s] is in phase %s", backupRepository.Name, backupRepository.Status.Phase)
|
||||
result.IsWarn = true
|
||||
results = append(results, result)
|
||||
} else {
|
||||
readyCount++
|
||||
}
|
||||
}
|
||||
if readyCount > 0 {
|
||||
backupRepositoriesResult.IsPass = true
|
||||
backupRepositoriesResult.Message = fmt.Sprintf("Found %d backup repositories configured and %d Ready", len(backupRepositories), readyCount)
|
||||
} else {
|
||||
backupRepositoriesResult.IsWarn = true
|
||||
backupRepositoriesResult.Message = fmt.Sprintf("Found %d configured backup repositories, but none are ready", len(backupRepositories))
|
||||
}
|
||||
}
|
||||
results = append(results, backupRepositoriesResult)
|
||||
return results
|
||||
|
||||
}
|
||||
|
||||
func analyzeResticRepositories(resticRepositories []*restic_types.ResticRepository) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
readyCount := 0
|
||||
resticRepositoriesResult := &AnalyzeResult{
|
||||
Title: "At least 1 Restic Repository configured",
|
||||
}
|
||||
if len(resticRepositories) == 0 {
|
||||
resticRepositoriesResult.IsFail = true
|
||||
resticRepositoriesResult.Message = "No restic repositories configured"
|
||||
} else {
|
||||
for _, resticRepository := range resticRepositories {
|
||||
if resticRepository.Status.Phase != restic_types.ResticRepositoryPhaseReady {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Restic Repository %s", resticRepository.GetName()),
|
||||
}
|
||||
result.Message = fmt.Sprintf("Restic Repository [%s] is in phase %s", resticRepository.Name, resticRepository.Status.Phase)
|
||||
result.IsWarn = true
|
||||
results = append(results, result)
|
||||
} else {
|
||||
readyCount++
|
||||
}
|
||||
}
|
||||
if readyCount > 0 {
|
||||
resticRepositoriesResult.IsPass = true
|
||||
resticRepositoriesResult.Message = fmt.Sprintf("Found %d restic repositories configured and %d Ready", len(resticRepositories), readyCount)
|
||||
} else {
|
||||
resticRepositoriesResult.IsWarn = true
|
||||
resticRepositoriesResult.Message = fmt.Sprintf("Found %d configured restic repositories, but none are ready", len(resticRepositories))
|
||||
}
|
||||
}
|
||||
results = append(results, resticRepositoriesResult)
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeBackups(backups []*velerov1.Backup) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
|
||||
failedPhases := map[velerov1.BackupPhase]bool{
|
||||
velerov1.BackupPhaseFailed: true,
|
||||
velerov1.BackupPhasePartiallyFailed: true,
|
||||
velerov1.BackupPhaseFailedValidation: true,
|
||||
}
|
||||
|
||||
for _, backup := range backups {
|
||||
|
||||
if failedPhases[backup.Status.Phase] {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Backup %s", backup.Name),
|
||||
}
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("Backup %s phase is %s", backup.Name, backup.Status.Phase)
|
||||
results = append(results, result)
|
||||
|
||||
}
|
||||
}
|
||||
if len(backups) > 0 {
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: "Velero Backups",
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d backups", len(backups)),
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeBackupStorageLocations(backupStorageLocations []*velerov1.BackupStorageLocation) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
availableCount := 0
|
||||
bslResult := &AnalyzeResult{
|
||||
Title: "At least 1 Backup Storage Location configured",
|
||||
}
|
||||
|
||||
if len(backupStorageLocations) == 0 {
|
||||
bslResult.IsFail = true
|
||||
bslResult.Message = "No backup storage locations configured"
|
||||
} else {
|
||||
for _, backupStorageLocation := range backupStorageLocations {
|
||||
if backupStorageLocation.Status.Phase != velerov1.BackupStorageLocationPhaseAvailable {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Backup Storage Location %s", backupStorageLocation.Name),
|
||||
}
|
||||
result.Message = fmt.Sprintf("Backup Storage Location [%s] is in phase %s", backupStorageLocation.Name, backupStorageLocation.Status.Phase)
|
||||
result.IsWarn = true
|
||||
results = append(results, result)
|
||||
} else {
|
||||
availableCount++
|
||||
}
|
||||
}
|
||||
if availableCount > 0 {
|
||||
bslResult.IsPass = true
|
||||
bslResult.Message = fmt.Sprintf("Found %d backup storage locations configured and %d Available", len(backupStorageLocations), availableCount)
|
||||
} else {
|
||||
bslResult.IsWarn = true
|
||||
bslResult.Message = fmt.Sprintf("Found %d configured backup storage locations, but none are available", len(backupStorageLocations))
|
||||
}
|
||||
}
|
||||
results = append(results, bslResult)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeDeleteBackupRequests(deleteBackupRequests []*velerov1.DeleteBackupRequest) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
inProgressCount := 0
|
||||
if len(deleteBackupRequests) > 0 {
|
||||
for _, deleteBackupRequest := range deleteBackupRequests {
|
||||
if deleteBackupRequest.Status.Phase == velerov1.DeleteBackupRequestPhaseInProgress {
|
||||
inProgressCount++
|
||||
}
|
||||
}
|
||||
if inProgressCount > 0 {
|
||||
deleteBackupRequestsResult := &AnalyzeResult{
|
||||
Title: "Delete Backup Requests summary",
|
||||
}
|
||||
deleteBackupRequestsResult.IsWarn = true
|
||||
deleteBackupRequestsResult.Message = fmt.Sprintf("Found %d delete backup requests in progress", inProgressCount)
|
||||
results = append(results, deleteBackupRequestsResult)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzePodVolumeBackups(podVolumeBackups []*velerov1.PodVolumeBackup) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
failures := 0
|
||||
if len(podVolumeBackups) > 0 {
|
||||
for _, podVolumeBackup := range podVolumeBackups {
|
||||
if podVolumeBackup.Status.Phase == velerov1.PodVolumeBackupPhaseFailed {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Pod Volume Backup %s", podVolumeBackup.Name),
|
||||
}
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("Pod Volume Backup %s phase is %s", podVolumeBackup.Name, podVolumeBackup.Status.Phase)
|
||||
results = append(results, result)
|
||||
failures++
|
||||
}
|
||||
}
|
||||
|
||||
if failures == 0 {
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: "Pod Volume Backups",
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d pod volume backups", len(podVolumeBackups)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzePodVolumeRestores(podVolumeRestores []*velerov1.PodVolumeRestore) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
failures := 0
|
||||
|
||||
if len(podVolumeRestores) > 0 {
|
||||
for _, podVolumeRestore := range podVolumeRestores {
|
||||
if podVolumeRestore.Status.Phase == velerov1.PodVolumeRestorePhaseFailed {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Pod Volume Restore %s", podVolumeRestore.Name),
|
||||
}
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("Pod Volume Restore %s phase is %s", podVolumeRestore.Name, podVolumeRestore.Status.Phase)
|
||||
results = append(results, result)
|
||||
failures++
|
||||
}
|
||||
}
|
||||
if failures == 0 {
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: "Pod Volume Restores",
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d pod volume restores", len(podVolumeRestores)),
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeRestores(restores []*velerov1.Restore) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
failures := 0
|
||||
|
||||
if len(restores) > 0 {
|
||||
|
||||
failedPhases := map[velerov1.RestorePhase]bool{
|
||||
velerov1.RestorePhaseFailed: true,
|
||||
velerov1.RestorePhasePartiallyFailed: true,
|
||||
velerov1.RestorePhaseFailedValidation: true,
|
||||
}
|
||||
|
||||
for _, restore := range restores {
|
||||
if failedPhases[restore.Status.Phase] {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Restore %s", restore.Name),
|
||||
}
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("Restore %s phase is %s", restore.Name, restore.Status.Phase)
|
||||
results = append(results, result)
|
||||
failures++
|
||||
}
|
||||
}
|
||||
if failures == 0 {
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: "Velero Restores",
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d restores", len(restores)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeSchedules(schedules []*velerov1.Schedule) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
failures := 0
|
||||
if len(schedules) > 0 {
|
||||
for _, schedule := range schedules {
|
||||
if schedule.Status.Phase == velerov1.SchedulePhaseFailedValidation {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Schedule %s", schedule.Name),
|
||||
}
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("Schedule %s phase is %s", schedule.Name, schedule.Status.Phase)
|
||||
results = append(results, result)
|
||||
failures++
|
||||
}
|
||||
}
|
||||
if failures == 0 {
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: "Velero Schedules",
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d schedules", len(schedules)),
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeVolumeSnapshotLocations(volumeSnapshotLocations []*velerov1.VolumeSnapshotLocation) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
failures := 0
|
||||
if len(volumeSnapshotLocations) > 0 {
|
||||
for _, volumeSnapshotLocation := range volumeSnapshotLocations {
|
||||
if volumeSnapshotLocation.Status.Phase == velerov1.VolumeSnapshotLocationPhaseUnavailable {
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Volume Snapshot Location %s", volumeSnapshotLocation.Name),
|
||||
}
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("Volume Snapshot Location %s phase is %s", volumeSnapshotLocation.Name, volumeSnapshotLocation.Status.Phase)
|
||||
results = append(results, result)
|
||||
failures++
|
||||
}
|
||||
}
|
||||
if failures == 0 {
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: "Velero Volume Snapshot Locations",
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d volume snapshot locations", len(volumeSnapshotLocations)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func analyzeLogs(logs map[string][]byte, kind string) []*AnalyzeResult {
|
||||
results := []*AnalyzeResult{}
|
||||
if len(logs) > 0 {
|
||||
for key, logBytes := range logs {
|
||||
logContent := string(logBytes)
|
||||
result := &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Velero logs for pod [%s]", key),
|
||||
}
|
||||
if strings.Contains(logContent, "permission denied") {
|
||||
result.IsWarn = true
|
||||
result.Message = fmt.Sprintf("Found 'permission denied' in %s pod log file(s)", kind)
|
||||
results = append(results, result)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(logContent, "error") || strings.Contains(logContent, "panic") || strings.Contains(logContent, "fatal") {
|
||||
result.IsWarn = true
|
||||
result.Message = fmt.Sprintf("Found error|panic|fatal in %s pod log file(s)", kind)
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, &AnalyzeResult{
|
||||
Title: fmt.Sprintf("Velero Logs analysis for kind [%s]", kind),
|
||||
IsPass: true,
|
||||
Message: fmt.Sprintf("Found %d log files", len(logs)),
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func aggregateResults(results []*AnalyzeResult) []*AnalyzeResult {
|
||||
out := []*AnalyzeResult{}
|
||||
resultFailed := false
|
||||
for _, result := range results {
|
||||
if result.IsFail {
|
||||
resultFailed = true
|
||||
}
|
||||
out = append(out, result)
|
||||
}
|
||||
if len(results) > 0 {
|
||||
if resultFailed == false {
|
||||
out = append(out, &AnalyzeResult{
|
||||
Title: "Velero Status",
|
||||
IsPass: true,
|
||||
Message: "Velero setup is healthy",
|
||||
})
|
||||
}
|
||||
if resultFailed == true {
|
||||
out = append(out, &AnalyzeResult{
|
||||
Title: "Velero Status",
|
||||
IsWarn: true,
|
||||
Message: "Velero setup is not entirely healthy",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func getVeleroVersion(excludedFiles []string, findFiles getChildCollectedFileContents) (string, error) {
|
||||
veleroDeploymentDir := "cluster-resources/deployments"
|
||||
veleroVersion := ""
|
||||
veleroDeploymentGlob := filepath.Join(veleroDeploymentDir, "velero.json")
|
||||
veleroDeploymentJson, err := findFiles(veleroDeploymentGlob, excludedFiles)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to find Velero deployment")
|
||||
}
|
||||
if len(veleroDeploymentJson) == 0 {
|
||||
return "", errors.Errorf("could not find Velero deployment in %s", veleroDeploymentDir)
|
||||
}
|
||||
var deploymentList *appsV1.DeploymentList
|
||||
// should run only once
|
||||
for key, veleroDeploymentJsonBytes := range veleroDeploymentJson {
|
||||
err := json.Unmarshal(veleroDeploymentJsonBytes, &deploymentList)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to unmarshal Velero deployment json from %s", key)
|
||||
}
|
||||
break
|
||||
}
|
||||
if deploymentList == nil {
|
||||
return "", errors.Errorf("could not find Velero deployment")
|
||||
}
|
||||
for _, deployment := range deploymentList.Items {
|
||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||
if container.Name == "velero" {
|
||||
container_image := container.Image
|
||||
veleroVersion = strings.Split(container_image, ":")[1]
|
||||
return veleroVersion, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.Errorf("could not find Velero container in deployment")
|
||||
}
|
||||
|
||||
func GetVeleroBackupsDirectory() string {
|
||||
return "cluster-resources/custom-resources/backups.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroBackupRepositoriesDirectory() string {
|
||||
return "cluster-resources/custom-resources/backuprepositories.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroBackupStorageLocationsDirectory() string {
|
||||
return "cluster-resources/custom-resources/backupstoragelocations.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroDeleteBackupRequestsDirectory() string {
|
||||
return "cluster-resources/custom-resources/deletebackuprequests.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroDownloadRequestsDirectory() string {
|
||||
return "cluster-resources/custom-resources/downloadrequests.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroLogsDirectory() string {
|
||||
return "velero/logs"
|
||||
}
|
||||
|
||||
func GetVeleroPodVolumeBackupsDirectory() string {
|
||||
return "cluster-resources/custom-resources/podvolumebackups.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroPodVolumeRestoresDirectory() string {
|
||||
return "cluster-resources/custom-resources/podvolumerestores.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroRestoresDirectory() string {
|
||||
return "cluster-resources/custom-resources/restores.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroSchedulesDirectory() string {
|
||||
return "cluster-resources/custom-resources/schedules.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroServerStatusRequestsDirectory() string {
|
||||
return "cluster-resources/custom-resources/serverstatusrequests.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroVolumeSnapshotLocationsDirectory() string {
|
||||
return "cluster-resources/custom-resources/volumesnapshotlocations.velero.io"
|
||||
}
|
||||
|
||||
func GetVeleroResticRepositoriesDirectory() string {
|
||||
return "cluster-resources/custom-resources/resticrepositories.velero.io"
|
||||
}
|
||||
@@ -0,0 +1,994 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
restic_types "github.com/replicatedhq/troubleshoot/pkg/analyze/types"
|
||||
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestAnalyzeVelero_BackupRepositories(t *testing.T) {
|
||||
type args struct {
|
||||
backupRepositories []*velerov1.BackupRepository
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no backup repositories",
|
||||
args: args{
|
||||
backupRepositories: []*velerov1.BackupRepository{},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "At least 1 Backup Repository configured",
|
||||
Message: "No backup repositories configured",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 backup repository and 1 Ready",
|
||||
args: args{
|
||||
backupRepositories: []*velerov1.BackupRepository{
|
||||
{
|
||||
Spec: velerov1.BackupRepositorySpec{
|
||||
BackupStorageLocation: "default",
|
||||
VolumeNamespace: "velero",
|
||||
},
|
||||
Status: velerov1.BackupRepositoryStatus{
|
||||
Phase: velerov1.BackupRepositoryPhaseReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "At least 1 Backup Repository configured",
|
||||
Message: "Found 1 backup repositories configured and 1 Ready",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "2 backup repositories and 1 Ready",
|
||||
args: args{
|
||||
backupRepositories: []*velerov1.BackupRepository{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default-default-restic-245sd",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupRepositorySpec{
|
||||
BackupStorageLocation: "default",
|
||||
VolumeNamespace: "velero",
|
||||
},
|
||||
Status: velerov1.BackupRepositoryStatus{
|
||||
Phase: velerov1.BackupRepositoryPhaseReady,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "builders-default-restic-jdtd8",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupRepositorySpec{
|
||||
BackupStorageLocation: "builders-default-restic-jdtd8",
|
||||
VolumeNamespace: "velero",
|
||||
},
|
||||
Status: velerov1.BackupRepositoryStatus{
|
||||
Phase: velerov1.BackupRepositoryPhaseNotReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Backup Repository builders-default-restic-jdtd8",
|
||||
Message: "Backup Repository [builders-default-restic-jdtd8] is in phase NotReady",
|
||||
IsWarn: true,
|
||||
},
|
||||
{
|
||||
Title: "At least 1 Backup Repository configured",
|
||||
Message: "Found 2 backup repositories configured and 1 Ready",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 backup repository and none Ready",
|
||||
args: args{
|
||||
backupRepositories: []*velerov1.BackupRepository{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupRepositorySpec{
|
||||
BackupStorageLocation: "default",
|
||||
VolumeNamespace: "velero",
|
||||
},
|
||||
Status: velerov1.BackupRepositoryStatus{
|
||||
Phase: velerov1.BackupRepositoryPhaseNotReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Backup Repository default",
|
||||
Message: "Backup Repository [default] is in phase NotReady",
|
||||
IsWarn: true,
|
||||
},
|
||||
{
|
||||
Title: "At least 1 Backup Repository configured",
|
||||
Message: "Found 1 configured backup repositories, but none are ready",
|
||||
IsWarn: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeBackupRepositories(tt.args.backupRepositories); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeBackupRepositories() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_ResticRepositories(t *testing.T) {
|
||||
type args struct {
|
||||
resticRepositories []*restic_types.ResticRepository
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no restic repositories",
|
||||
args: args{
|
||||
resticRepositories: []*restic_types.ResticRepository{},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "At least 1 Restic Repository configured",
|
||||
Message: "No restic repositories configured",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 restic repository and 1 Ready",
|
||||
args: args{
|
||||
resticRepositories: []*restic_types.ResticRepository{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default-default-restic-245sd",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: restic_types.ResticRepositorySpec{
|
||||
BackupStorageLocation: "default",
|
||||
VolumeNamespace: "velero",
|
||||
},
|
||||
Status: restic_types.ResticRepositoryStatus{
|
||||
Phase: restic_types.ResticRepositoryPhaseReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "At least 1 Restic Repository configured",
|
||||
Message: "Found 1 restic repositories configured and 1 Ready",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeResticRepositories(tt.args.resticRepositories); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeResticRepositories() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_Backups(t *testing.T) {
|
||||
type args struct {
|
||||
backups []*velerov1.Backup
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no backups",
|
||||
args: args{
|
||||
backups: []*velerov1.Backup{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "1 backup and 1 Completed",
|
||||
args: args{
|
||||
backups: []*velerov1.Backup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "observability-backup",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupSpec{
|
||||
IncludedNamespaces: []string{"monitoring"},
|
||||
},
|
||||
Status: velerov1.BackupStatus{
|
||||
Phase: velerov1.BackupPhaseCompleted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero Backups",
|
||||
Message: "Found 1 backups",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 backup and 1 Failed",
|
||||
args: args{
|
||||
backups: []*velerov1.Backup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "application-backup",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupSpec{
|
||||
IncludedNamespaces: []string{"shazam"},
|
||||
},
|
||||
Status: velerov1.BackupStatus{
|
||||
Phase: velerov1.BackupPhaseFailed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Backup application-backup",
|
||||
Message: "Backup application-backup phase is Failed",
|
||||
IsFail: true,
|
||||
},
|
||||
{
|
||||
Title: "Velero Backups",
|
||||
Message: "Found 1 backups",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeBackups(tt.args.backups); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeBackups() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_BackupStorageLocations(t *testing.T) {
|
||||
type args struct {
|
||||
backupStorageLocations []*velerov1.BackupStorageLocation
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no backup storage locations",
|
||||
args: args{
|
||||
backupStorageLocations: []*velerov1.BackupStorageLocation{},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "At least 1 Backup Storage Location configured",
|
||||
Message: "No backup storage locations configured",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 backup storage location and 1 Available",
|
||||
args: args{
|
||||
backupStorageLocations: []*velerov1.BackupStorageLocation{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
},
|
||||
Status: velerov1.BackupStorageLocationStatus{
|
||||
Phase: velerov1.BackupStorageLocationPhaseAvailable,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "At least 1 Backup Storage Location configured",
|
||||
Message: "Found 1 backup storage locations configured and 1 Available",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 backup storage location and 1 Unavailable",
|
||||
args: args{
|
||||
backupStorageLocations: []*velerov1.BackupStorageLocation{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
},
|
||||
Status: velerov1.BackupStorageLocationStatus{
|
||||
Phase: velerov1.BackupStorageLocationPhaseUnavailable,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Backup Storage Location default",
|
||||
Message: "Backup Storage Location [default] is in phase Unavailable",
|
||||
IsWarn: true,
|
||||
},
|
||||
{
|
||||
Title: "At least 1 Backup Storage Location configured",
|
||||
Message: "Found 1 configured backup storage locations, but none are available",
|
||||
IsWarn: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeBackupStorageLocations(tt.args.backupStorageLocations); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeBackupStorageLocations() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_DeleteBackupRequests(t *testing.T) {
|
||||
type args struct {
|
||||
deleteBackupRequests []*velerov1.DeleteBackupRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no backup delete requests",
|
||||
args: args{
|
||||
deleteBackupRequests: []*velerov1.DeleteBackupRequest{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "backup delete requests completed",
|
||||
args: args{
|
||||
deleteBackupRequests: []*velerov1.DeleteBackupRequest{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "observability-backup-20210308150016",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.DeleteBackupRequestSpec{
|
||||
BackupName: "observability-backup",
|
||||
},
|
||||
Status: velerov1.DeleteBackupRequestStatus{
|
||||
Phase: velerov1.DeleteBackupRequestPhaseProcessed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "backup delete requests summarize in progress",
|
||||
args: args{
|
||||
deleteBackupRequests: []*velerov1.DeleteBackupRequest{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "observability-backup-20210308150016",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.DeleteBackupRequestSpec{
|
||||
BackupName: "observability-backup",
|
||||
},
|
||||
Status: velerov1.DeleteBackupRequestStatus{
|
||||
Phase: velerov1.DeleteBackupRequestPhaseInProgress,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Delete Backup Requests summary",
|
||||
Message: "Found 1 delete backup requests in progress",
|
||||
IsWarn: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeDeleteBackupRequests(tt.args.deleteBackupRequests); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeDeleteBackupRequests() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_PodVolumeBackups(t *testing.T) {
|
||||
type args struct {
|
||||
podVolumeBackups []*velerov1.PodVolumeBackup
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no pod volume backups",
|
||||
args: args{
|
||||
podVolumeBackups: []*velerov1.PodVolumeBackup{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "pod volume backups",
|
||||
args: args{
|
||||
podVolumeBackups: []*velerov1.PodVolumeBackup{
|
||||
{
|
||||
Spec: velerov1.PodVolumeBackupSpec{
|
||||
Node: "test-node-1",
|
||||
Pod: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "kotsadm-76ddbc96c4-fsr88",
|
||||
Namespace: "default",
|
||||
},
|
||||
Volume: "backup",
|
||||
BackupStorageLocation: "default",
|
||||
},
|
||||
Status: velerov1.PodVolumeBackupStatus{
|
||||
Phase: velerov1.PodVolumeBackupPhaseCompleted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Pod Volume Backups",
|
||||
Message: "Found 1 pod volume backups",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzePodVolumeBackups(tt.args.podVolumeBackups); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzePodVolumeBackups() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_PodVolumeRestores(t *testing.T) {
|
||||
type args struct {
|
||||
podVolumeRestores []*velerov1.PodVolumeRestore
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no pod volume restores",
|
||||
args: args{
|
||||
podVolumeRestores: []*velerov1.PodVolumeRestore{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "pod volume restores - no failures",
|
||||
args: args{
|
||||
podVolumeRestores: []*velerov1.PodVolumeRestore{
|
||||
{
|
||||
Spec: velerov1.PodVolumeRestoreSpec{
|
||||
Pod: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "kotsadm-76ddbc96c4-fsr88",
|
||||
Namespace: "default",
|
||||
},
|
||||
Volume: "backup",
|
||||
BackupStorageLocation: "default",
|
||||
},
|
||||
Status: velerov1.PodVolumeRestoreStatus{
|
||||
Phase: velerov1.PodVolumeRestorePhaseCompleted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Pod Volume Restores",
|
||||
Message: "Found 1 pod volume restores",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pod volume restores - failures",
|
||||
args: args{
|
||||
podVolumeRestores: []*velerov1.PodVolumeRestore{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "observability-backup-20210308150016",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.PodVolumeRestoreSpec{
|
||||
Pod: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "kotsadm-76ddbc96c4-fsr88",
|
||||
Namespace: "default",
|
||||
},
|
||||
Volume: "backup",
|
||||
BackupStorageLocation: "default",
|
||||
},
|
||||
Status: velerov1.PodVolumeRestoreStatus{
|
||||
Phase: velerov1.PodVolumeRestorePhaseFailed,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Pod Volume Restore observability-backup-20210308150016",
|
||||
Message: "Pod Volume Restore observability-backup-20210308150016 phase is Failed",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzePodVolumeRestores(tt.args.podVolumeRestores); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzePodVolumeRestores() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_Restores(t *testing.T) {
|
||||
type args struct {
|
||||
restores []*velerov1.Restore
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no restores",
|
||||
args: args{
|
||||
restores: []*velerov1.Restore{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "restores completed",
|
||||
args: args{
|
||||
restores: []*velerov1.Restore{
|
||||
{
|
||||
Spec: velerov1.RestoreSpec{
|
||||
BackupName: "observability-backup",
|
||||
},
|
||||
Status: velerov1.RestoreStatus{
|
||||
Phase: velerov1.RestorePhaseCompleted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero Restores",
|
||||
Message: "Found 1 restores",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeRestores(tt.args.restores); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeRestores() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_Schedules(t *testing.T) {
|
||||
type args struct {
|
||||
schedules []*velerov1.Schedule
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no schedules",
|
||||
args: args{
|
||||
schedules: []*velerov1.Schedule{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "schedules configured",
|
||||
args: args{
|
||||
schedules: []*velerov1.Schedule{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "daily-backup",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.ScheduleSpec{
|
||||
Schedule: "0 0 * * *",
|
||||
Template: velerov1.BackupSpec{
|
||||
StorageLocation: "default",
|
||||
IncludedNamespaces: []string{
|
||||
"default",
|
||||
},
|
||||
IncludedResources: []string{
|
||||
"*",
|
||||
},
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "velero",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: velerov1.ScheduleStatus{
|
||||
Phase: velerov1.SchedulePhaseEnabled,
|
||||
LastBackup: &metav1.Time{
|
||||
Time: time.Date(2023, 3, 8, 15, 0, 16, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero Schedules",
|
||||
Message: "Found 1 schedules",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "schedules - failures",
|
||||
args: args{
|
||||
schedules: []*velerov1.Schedule{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "daily-backup",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.ScheduleSpec{
|
||||
Schedule: "0 0 * * *",
|
||||
Template: velerov1.BackupSpec{
|
||||
StorageLocation: "default",
|
||||
IncludedNamespaces: []string{
|
||||
"default",
|
||||
},
|
||||
IncludedResources: []string{
|
||||
"*",
|
||||
},
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "velero",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: velerov1.ScheduleStatus{
|
||||
Phase: velerov1.SchedulePhaseFailedValidation,
|
||||
LastBackup: &metav1.Time{
|
||||
Time: time.Date(2023, 3, 8, 15, 0, 16, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Schedule daily-backup",
|
||||
Message: "Schedule daily-backup phase is FailedValidation",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeSchedules(tt.args.schedules); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeSchedules() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_VolumeSnapshotLocations(t *testing.T) {
|
||||
type args struct {
|
||||
volumeSnapshotLocations []*velerov1.VolumeSnapshotLocation
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no volume snapshot locations",
|
||||
args: args{
|
||||
volumeSnapshotLocations: []*velerov1.VolumeSnapshotLocation{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "volume snapshot locations configured",
|
||||
args: args{
|
||||
volumeSnapshotLocations: []*velerov1.VolumeSnapshotLocation{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.VolumeSnapshotLocationSpec{
|
||||
Provider: "aws",
|
||||
Config: map[string]string{
|
||||
"region": "us-east-1",
|
||||
},
|
||||
},
|
||||
Status: velerov1.VolumeSnapshotLocationStatus{
|
||||
Phase: velerov1.VolumeSnapshotLocationPhaseAvailable,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero Volume Snapshot Locations",
|
||||
Message: "Found 1 volume snapshot locations",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "volume snapshot locations - failures",
|
||||
args: args{
|
||||
volumeSnapshotLocations: []*velerov1.VolumeSnapshotLocation{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: velerov1.VolumeSnapshotLocationSpec{
|
||||
Provider: "aws",
|
||||
Config: map[string]string{
|
||||
"region": "us-east-1",
|
||||
},
|
||||
},
|
||||
Status: velerov1.VolumeSnapshotLocationStatus{
|
||||
Phase: velerov1.VolumeSnapshotLocationPhaseUnavailable,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Volume Snapshot Location default",
|
||||
Message: "Volume Snapshot Location default phase is Unavailable",
|
||||
IsFail: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeVolumeSnapshotLocations(tt.args.volumeSnapshotLocations); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeVolumeSnapshotLocations() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_Logs(t *testing.T) {
|
||||
type args struct {
|
||||
logs map[string][]byte
|
||||
kind string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no logs",
|
||||
args: args{
|
||||
logs: map[string][]byte{},
|
||||
kind: "node-agent*",
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "logs - no errors in node-agent* pods",
|
||||
args: args{
|
||||
logs: map[string][]byte{
|
||||
"node-agent-m6n9j": []byte("level=info msg=... backup=velero/sample-app controller=podvolumebacku"),
|
||||
},
|
||||
kind: "node-agent*",
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero Logs analysis for kind [node-agent*]",
|
||||
Message: "Found 1 log files",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "logs - no errors in velero* pods",
|
||||
args: args{
|
||||
logs: map[string][]byte{
|
||||
"velero-788ff7c9dd-mslfl": []byte("level=info msg=BackupStorageLocations... controller=backup-storage-location"),
|
||||
},
|
||||
kind: "velero*",
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero Logs analysis for kind [velero*]",
|
||||
Message: "Found 1 log files",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "logs - errors in node-agent* pods",
|
||||
args: args{
|
||||
logs: map[string][]byte{
|
||||
"node-agent-m6n9j": []byte("level=error msg=... backup=velero/sample-app controller=podvolumebacku"),
|
||||
},
|
||||
kind: "node-agent*",
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Velero logs for pod [node-agent-m6n9j]",
|
||||
Message: "Found error|panic|fatal in node-agent* pod log file(s)",
|
||||
IsWarn: true,
|
||||
},
|
||||
{
|
||||
Title: "Velero Logs analysis for kind [node-agent*]",
|
||||
Message: "Found 1 log files",
|
||||
IsPass: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := analyzeLogs(tt.args.logs, tt.args.kind); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("analyzeLogs() = %v, want %v", got, tt.want)
|
||||
gotJSON, _ := json.MarshalIndent(got, "", " ")
|
||||
wantJSON, _ := json.MarshalIndent(tt.want, "", " ")
|
||||
t.Logf("\nGot: %s\nWant: %s", gotJSON, wantJSON)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeVelero_Results(t *testing.T) {
|
||||
type args struct {
|
||||
results []*AnalyzeResult
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "no results",
|
||||
args: args{
|
||||
results: []*AnalyzeResult{},
|
||||
},
|
||||
want: []*AnalyzeResult{},
|
||||
},
|
||||
{
|
||||
name: "results - pass",
|
||||
args: args{
|
||||
results: []*AnalyzeResult{
|
||||
{
|
||||
Title: "random Velero CRD check",
|
||||
IsPass: true,
|
||||
Message: "CRD status is healthy",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "random Velero CRD check",
|
||||
IsPass: true,
|
||||
Message: "CRD status is healthy",
|
||||
},
|
||||
{
|
||||
Title: "Velero Status",
|
||||
IsPass: true,
|
||||
Message: "Velero setup is healthy",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "results - fail",
|
||||
args: args{
|
||||
results: []*AnalyzeResult{
|
||||
{
|
||||
Title: "random Velero CRD check failure",
|
||||
IsFail: true,
|
||||
Message: "CRD status - Failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "random Velero CRD check failure",
|
||||
IsFail: true,
|
||||
Message: "CRD status - Failed",
|
||||
},
|
||||
{
|
||||
Title: "Velero Status",
|
||||
IsWarn: true,
|
||||
Message: "Velero setup is not entirely healthy",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := aggregateResults(tt.args.results); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("aggregateResults() = %v, want %v", got, tt.want)
|
||||
gotJSON, _ := json.MarshalIndent(got, "", " ")
|
||||
wantJSON, _ := json.MarshalIndent(tt.want, "", " ")
|
||||
t.Logf("\nGot: %s\nWant: %s", gotJSON, wantJSON)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
util "github.com/replicatedhq/troubleshoot/internal/util"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
iutils "github.com/replicatedhq/troubleshoot/pkg/interfaceutils"
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -50,6 +51,8 @@ func (a *AnalyzeYamlCompare) analyzeYamlCompare(analyzer *troubleshootv1beta2.Ya
|
||||
return nil, errors.Wrap(err, "failed to parse collected data as yaml doc")
|
||||
}
|
||||
|
||||
originalActual := actual
|
||||
|
||||
if analyzer.Path != "" {
|
||||
actual, err = iutils.GetAtPath(actual, analyzer.Path)
|
||||
if err != nil {
|
||||
@@ -81,6 +84,11 @@ func (a *AnalyzeYamlCompare) analyzeYamlCompare(analyzer *troubleshootv1beta2.Ya
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Fail.Message, err = util.RenderTemplate(outcome.Fail.Message, originalActual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to render template on outcome message")
|
||||
}
|
||||
|
||||
if when == equal {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
@@ -96,6 +104,11 @@ func (a *AnalyzeYamlCompare) analyzeYamlCompare(analyzer *troubleshootv1beta2.Ya
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Warn.Message, err = util.RenderTemplate(outcome.Warn.Message, originalActual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to render template on outcome message")
|
||||
}
|
||||
|
||||
if when == equal {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
@@ -111,6 +124,11 @@ func (a *AnalyzeYamlCompare) analyzeYamlCompare(analyzer *troubleshootv1beta2.Ya
|
||||
}
|
||||
}
|
||||
|
||||
outcome.Pass.Message, err = util.RenderTemplate(outcome.Pass.Message, originalActual)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to render template on outcome message")
|
||||
}
|
||||
|
||||
if when == equal {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
|
||||
@@ -430,6 +430,45 @@ otherstuff:
|
||||
},
|
||||
fileContents: []byte(``),
|
||||
},
|
||||
{
|
||||
name: "basic comparison with outcome message templated",
|
||||
analyzer: troubleshootv1beta2.YamlCompare{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Status: {{ .stuff.status }}, Info: {{ .stuff.info }}",
|
||||
When: "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Status: {{ .stuff.status }}, Info: {{ .stuff.info }}",
|
||||
When: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "yaml-compare",
|
||||
FileName: "yaml-compare.yaml",
|
||||
Value: `ready`,
|
||||
Path: "stuff.status",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "yaml-compare",
|
||||
Message: "Status: ready, Info: stuff is ready",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
fileContents: []byte(`
|
||||
stuff:
|
||||
status: ready
|
||||
info: stuff is ready
|
||||
morestuff:
|
||||
status: notready
|
||||
info: morestuff is not ready`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2019 Replicated, Inc..
|
||||
|
||||
@@ -186,6 +186,10 @@ type CephStatusAnalyze struct {
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
}
|
||||
|
||||
type VeleroAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
type LonghornAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
@@ -221,6 +225,39 @@ type CertificatesAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type GoldpingerAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes,omitempty" yaml:"outcomes,omitempty"`
|
||||
CollectorName string `json:"collectorName" yaml:"collectorName"`
|
||||
FilePath string `json:"filePath,omitempty" yaml:"filePath,omitempty"`
|
||||
}
|
||||
|
||||
type EventAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName" yaml:"collectorName"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||
Reason string `json:"reason" yaml:"reason"`
|
||||
RegexPattern string `json:"regex,omitempty" yaml:"regex,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type NodeMetricsAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName" yaml:"collectorName"`
|
||||
Filters NodeMetricsAnalyzeFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type NodeMetricsAnalyzeFilters struct {
|
||||
PVC *PVCRef `json:"pvc,omitempty" yaml:"pvc,omitempty"`
|
||||
}
|
||||
|
||||
type PVCRef struct {
|
||||
NameRegex string `json:"nameRegex,omitempty" yaml:"nameRegex,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
type Analyze struct {
|
||||
ClusterVersion *ClusterVersion `json:"clusterVersion,omitempty" yaml:"clusterVersion,omitempty"`
|
||||
StorageClass *StorageClass `json:"storageClass,omitempty" yaml:"storageClass,omitempty"`
|
||||
@@ -245,10 +282,15 @@ type Analyze struct {
|
||||
Mysql *DatabaseAnalyze `json:"mysql,omitempty" yaml:"mysql,omitempty"`
|
||||
Redis *DatabaseAnalyze `json:"redis,omitempty" yaml:"redis,omitempty"`
|
||||
CephStatus *CephStatusAnalyze `json:"cephStatus,omitempty" yaml:"cephStatus,omitempty"`
|
||||
Velero *VeleroAnalyze `json:"velero,omitempty" yaml:"velero,omitempty"`
|
||||
Longhorn *LonghornAnalyze `json:"longhorn,omitempty" yaml:"longhorn,omitempty"`
|
||||
RegistryImages *RegistryImagesAnalyze `json:"registryImages,omitempty" yaml:"registryImages,omitempty"`
|
||||
WeaveReport *WeaveReportAnalyze `json:"weaveReport,omitempty" yaml:"weaveReport,omitempty"`
|
||||
Sysctl *SysctlAnalyze `json:"sysctl,omitempty" yaml:"sysctl,omitempty"`
|
||||
ClusterResource *ClusterResource `json:"clusterResource,omitempty" yaml:"clusterResource,omitempty"`
|
||||
Certificates *CertificatesAnalyze `json:"certificates,omitempty" yaml:"certificates,omitempty"`
|
||||
Goldpinger *GoldpingerAnalyze `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"`
|
||||
Event *EventAnalyze `json:"event,omitempty" yaml:"event,omitempty"`
|
||||
NodeMetrics *NodeMetricsAnalyze `json:"nodeMetrics,omitempty" yaml:"nodeMetrics,omitempty"`
|
||||
HTTP *HTTPAnalyze `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
type AnalyzerSpec struct {
|
||||
Analyzers []*Analyze `json:"analyzers,omitempty"`
|
||||
HostAnalyzers []*HostAnalyze `json:"hostAnalyzers,omitempty" yaml:"hostAnalyzers,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// AnalyzerStatus defines the observed state of Analyzer
|
||||
|
||||
@@ -44,6 +44,12 @@ type CustomMetrics struct {
|
||||
MetricRequests []MetricRequest `json:"metricRequests,omitempty" yaml:"metricRequests,omitempty"`
|
||||
}
|
||||
|
||||
type NodeMetrics struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
NodeNames []string `json:"nodeNames,omitempty" yaml:"nodeNames,omitempty"`
|
||||
Selector []string `json:"selector,omitempty" yaml:"selector,omitempty"`
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
@@ -105,6 +111,16 @@ type RunPod struct {
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
ImagePullSecret *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
|
||||
PodSpec corev1.PodSpec `json:"podSpec,omitempty" yaml:"podSpec,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
type RunDaemonSet struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
ImagePullSecret *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
|
||||
PodSpec corev1.PodSpec `json:"podSpec,omitempty" yaml:"podSpec,omitempty"`
|
||||
}
|
||||
|
||||
type ImagePullSecrets struct {
|
||||
@@ -257,6 +273,35 @@ type Helm struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
ReleaseName string `json:"releaseName,omitempty" yaml:"releaseName,omitempty"`
|
||||
CollectValues bool `json:"collectValues,omitempty" yaml:"collectValues,omitempty"`
|
||||
}
|
||||
|
||||
type Goldpinger struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
PodLaunchOptions *PodLaunchOptions `json:"podLaunchOptions,omitempty" yaml:"podLaunchOptions,omitempty"`
|
||||
}
|
||||
|
||||
type PodLaunchOptions struct {
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Image string `json:"image,omitempty" yaml:"image,omitempty"`
|
||||
ImagePullSecret *ImagePullSecrets `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
|
||||
ServiceAccountName string `json:"serviceAccountName,omitempty" yaml:"serviceAccountName,omitempty"`
|
||||
}
|
||||
|
||||
type Sonobuoy struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
type DNS struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type Etcd struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
Image string `json:"image" yaml:"image"`
|
||||
}
|
||||
|
||||
type Collect struct {
|
||||
@@ -268,6 +313,7 @@ type Collect struct {
|
||||
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"`
|
||||
@@ -284,6 +330,11 @@ type Collect struct {
|
||||
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 {
|
||||
@@ -417,6 +468,19 @@ func (c *Collect) AccessReviewSpecs(overrideNS string) []authorizationv1.SelfSub
|
||||
},
|
||||
NonResourceAttributes: nil,
|
||||
})
|
||||
} else if c.RunDaemonSet != nil {
|
||||
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||
Namespace: pickNamespaceOrDefault(c.RunDaemonSet.Namespace, overrideNS),
|
||||
Verb: "create",
|
||||
Group: "",
|
||||
Version: "",
|
||||
Resource: "pods",
|
||||
Subresource: "",
|
||||
Name: "",
|
||||
},
|
||||
NonResourceAttributes: nil,
|
||||
})
|
||||
} else if c.Exec != nil {
|
||||
result = append(result, authorizationv1.SelfSubjectAccessReviewSpec{
|
||||
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
||||
@@ -527,6 +591,10 @@ func (c *Collect) GetName() string {
|
||||
collector = "run-pod"
|
||||
name = c.RunPod.CollectorName
|
||||
}
|
||||
if c.RunDaemonSet != nil {
|
||||
collector = "run-daemonset"
|
||||
name = c.RunDaemonSet.CollectorName
|
||||
}
|
||||
if c.Exec != nil {
|
||||
collector = "exec"
|
||||
name = c.Exec.CollectorName
|
||||
|
||||
@@ -35,6 +35,7 @@ type AfterCollection struct {
|
||||
type CollectorSpec struct {
|
||||
Collectors []*Collect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
|
||||
AfterCollection []*AfterCollection `json:"afterCollection,omitempty" yaml:"afterCollection,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// CollectorStatus defines the observed state of Collector
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
type HostCollectorSpec struct {
|
||||
Collectors []*HostCollect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
|
||||
Analyzers []*HostAnalyze `json:"analyzers,omitempty" yaml:"analyzers,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// HostCollectorStatus defines the observed state of HostCollector
|
||||
|
||||
@@ -122,6 +122,13 @@ type HostOSAnalyze struct {
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type KernelConfigsAnalyze struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
|
||||
SelectedConfigs []string `json:"selectedConfigs" yaml:"selectedConfigs"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
}
|
||||
|
||||
type HostAnalyze struct {
|
||||
CPU *CPUAnalyze `json:"cpu,omitempty" yaml:"cpu,omitempty"`
|
||||
TCPLoadBalancer *TCPLoadBalancerAnalyze `json:"tcpLoadBalancer,omitempty" yaml:"tcpLoadBalancer,omitempty"`
|
||||
@@ -144,4 +151,6 @@ type HostAnalyze struct {
|
||||
HostServices *HostServicesAnalyze `json:"hostServices,omitempty" yaml:"hostServices,omitempty"`
|
||||
HostOS *HostOSAnalyze `json:"hostOS,omitempty" yaml:"hostOS,omitempty"`
|
||||
TextAnalyze *TextAnalyze `json:"textAnalyze,omitempty" yaml:"textAnalyze,omitempty"`
|
||||
KernelConfigs *KernelConfigsAnalyze `json:"kernelConfigs,omitempty" yaml:"kernelConfigs,omitempty"`
|
||||
JsonCompare *JsonCompare `json:"jsonCompare,omitempty" yaml:"jsonCompare,omitempty"`
|
||||
}
|
||||
|
||||
@@ -76,6 +76,11 @@ type HostCopy struct {
|
||||
Path string `json:"path" yaml:"path"`
|
||||
}
|
||||
|
||||
type HostCGroups struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
MountPoint string `json:"mountPoint,omitempty" yaml:"mountPoint,omitempty"`
|
||||
}
|
||||
|
||||
type HostTime struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
@@ -180,8 +185,32 @@ type HostServices struct {
|
||||
|
||||
type HostRun struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
OutputDir string `json:"outputDir,omitempty" yaml:"outputDir,omitempty"`
|
||||
Input map[string]string `json:"input,omitempty" yaml:"input,omitempty"`
|
||||
Env []string `json:"env,omitempty" yaml:"env,omitempty"`
|
||||
InheritEnvs []string `json:"inheritEnvs,omitempty" yaml:"inheritEnvs,omitempty"`
|
||||
IgnoreParentEnvs bool `json:"ignoreParentEnvs,omitempty" yaml:"ignoreParentEnvs,omitempty"`
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type HostKernelConfigs struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
type HostJournald struct {
|
||||
HostCollectorMeta `json:",inline" yaml:",inline"`
|
||||
System bool `json:"system,omitempty" yaml:"system,omitempty"`
|
||||
Dmesg bool `json:"dmesg,omitempty" yaml:"dmesg,omitempty"`
|
||||
Units []string `json:"units,omitempty" yaml:"units,omitempty"`
|
||||
Since string `json:"since,omitempty" yaml:"since,omitempty"`
|
||||
Until string `json:"until,omitempty" yaml:"until,omitempty"`
|
||||
Output string `json:"output,omitempty" yaml:"output,omitempty"`
|
||||
Lines int `json:"lines,omitempty" yaml:"lines,omitempty"`
|
||||
Reverse bool `json:"reverse,omitempty" yaml:"reverse,omitempty"`
|
||||
Utc bool `json:"utc,omitempty" yaml:"utc,omitempty"`
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type HostCollect struct {
|
||||
@@ -208,8 +237,13 @@ type HostCollect struct {
|
||||
HostOS *HostOS `json:"hostOS,omitempty" yaml:"hostOS,omitempty"`
|
||||
HostRun *HostRun `json:"run,omitempty" yaml:"run,omitempty"`
|
||||
HostCopy *HostCopy `json:"copy,omitempty" yaml:"copy,omitempty"`
|
||||
HostKernelConfigs *HostKernelConfigs `json:"kernelConfigs,omitempty" yaml:"kernelConfigs,omitempty"`
|
||||
HostJournald *HostJournald `json:"journald,omitempty" yaml:"journald,omitempty"`
|
||||
HostCGroups *HostCGroups `json:"cgroups,omitempty" yaml:"cgroups,omitempty"`
|
||||
}
|
||||
|
||||
// GetName gets the name of the collector
|
||||
// Deprecated: This function is not used anywhere and should be removed. Do not use it.
|
||||
func (c *HostCollect) GetName() string {
|
||||
// TODO: Is this used anywhere? Should we just remove it?
|
||||
var collector string
|
||||
|
||||
@@ -25,6 +25,7 @@ type HostPreflightSpec struct {
|
||||
Collectors []*HostCollect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
|
||||
RemoteCollectors []*RemoteCollect `json:"remoteCollectors,omitempty" yaml:"remoteCollectors,omitempty"`
|
||||
Analyzers []*HostAnalyze `json:"analyzers,omitempty" yaml:"analyzers,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// HostPreflightStatus defines the observed state of HostPreflight
|
||||
|
||||
@@ -26,6 +26,7 @@ type PreflightSpec struct {
|
||||
Collectors []*Collect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
|
||||
RemoteCollectors []*RemoteCollect `json:"remoteCollectors,omitempty" yaml:"remoteCollectors,omitempty"`
|
||||
Analyzers []*Analyze `json:"analyzers,omitempty" yaml:"analyzers,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// PreflightStatus defines the observed state of Preflight
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
// RedactorSpec defines the desired state of Redactor
|
||||
type RedactorSpec struct {
|
||||
Redactors []*Redact `json:"redactors,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// RedactorStatus defines the observed state of Redactor
|
||||
|
||||
@@ -25,6 +25,7 @@ type RemoteCollectorSpec struct {
|
||||
Collectors []*RemoteCollect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
|
||||
AfterCollection []*AfterCollection `json:"afterCollection,omitempty" yaml:"afterCollection,omitempty"`
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"`
|
||||
Uri string `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2019 Replicated, Inc..
|
||||
@@ -169,6 +168,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
|
||||
*out = new(CephStatusAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Velero != nil {
|
||||
in, out := &in.Velero, &out.Velero
|
||||
*out = new(VeleroAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Longhorn != nil {
|
||||
in, out := &in.Longhorn, &out.Longhorn
|
||||
*out = new(LonghornAnalyze)
|
||||
@@ -199,6 +203,26 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
|
||||
*out = new(CertificatesAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Goldpinger != nil {
|
||||
in, out := &in.Goldpinger, &out.Goldpinger
|
||||
*out = new(GoldpingerAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Event != nil {
|
||||
in, out := &in.Event, &out.Event
|
||||
*out = new(EventAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeMetrics != nil {
|
||||
in, out := &in.NodeMetrics, &out.NodeMetrics
|
||||
*out = new(NodeMetricsAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HTTP != nil {
|
||||
in, out := &in.HTTP, &out.HTTP
|
||||
*out = new(HTTPAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
|
||||
@@ -807,6 +831,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
|
||||
*out = new(RunPod)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RunDaemonSet != nil {
|
||||
in, out := &in.RunDaemonSet, &out.RunDaemonSet
|
||||
*out = new(RunDaemonSet)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Exec != nil {
|
||||
in, out := &in.Exec, &out.Exec
|
||||
*out = new(Exec)
|
||||
@@ -887,6 +916,31 @@ func (in *Collect) DeepCopyInto(out *Collect) {
|
||||
*out = new(Helm)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Goldpinger != nil {
|
||||
in, out := &in.Goldpinger, &out.Goldpinger
|
||||
*out = new(Goldpinger)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Sonobuoy != nil {
|
||||
in, out := &in.Sonobuoy, &out.Sonobuoy
|
||||
*out = new(Sonobuoy)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeMetrics != nil {
|
||||
in, out := &in.NodeMetrics, &out.NodeMetrics
|
||||
*out = new(NodeMetrics)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.DNS != nil {
|
||||
in, out := &in.DNS, &out.DNS
|
||||
*out = new(DNS)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Etcd != nil {
|
||||
in, out := &in.Etcd, &out.Etcd
|
||||
*out = new(Etcd)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
|
||||
@@ -1216,6 +1270,22 @@ func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DNS) DeepCopyInto(out *DNS) {
|
||||
*out = *in
|
||||
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNS.
|
||||
func (in *DNS) DeepCopy() *DNS {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DNS)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Data) DeepCopyInto(out *Data) {
|
||||
*out = *in
|
||||
@@ -1387,6 +1457,49 @@ func (in *Distribution) DeepCopy() *Distribution {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Etcd) DeepCopyInto(out *Etcd) {
|
||||
*out = *in
|
||||
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Etcd.
|
||||
func (in *Etcd) DeepCopy() *Etcd {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Etcd)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EventAnalyze) DeepCopyInto(out *EventAnalyze) {
|
||||
*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 EventAnalyze.
|
||||
func (in *EventAnalyze) DeepCopy() *EventAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EventAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Exec) DeepCopyInto(out *Exec) {
|
||||
*out = *in
|
||||
@@ -1503,6 +1616,54 @@ func (in *Get) DeepCopy() *Get {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Goldpinger) DeepCopyInto(out *Goldpinger) {
|
||||
*out = *in
|
||||
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
|
||||
if in.PodLaunchOptions != nil {
|
||||
in, out := &in.PodLaunchOptions, &out.PodLaunchOptions
|
||||
*out = new(PodLaunchOptions)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Goldpinger.
|
||||
func (in *Goldpinger) DeepCopy() *Goldpinger {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Goldpinger)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GoldpingerAnalyze) DeepCopyInto(out *GoldpingerAnalyze) {
|
||||
*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 GoldpingerAnalyze.
|
||||
func (in *GoldpingerAnalyze) DeepCopy() *GoldpingerAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GoldpingerAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HTTP) DeepCopyInto(out *HTTP) {
|
||||
*out = *in
|
||||
@@ -1728,6 +1889,16 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
|
||||
*out = new(TextAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.KernelConfigs != nil {
|
||||
in, out := &in.KernelConfigs, &out.KernelConfigs
|
||||
*out = new(KernelConfigsAnalyze)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.JsonCompare != nil {
|
||||
in, out := &in.JsonCompare, &out.JsonCompare
|
||||
*out = new(JsonCompare)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostAnalyze.
|
||||
@@ -1756,6 +1927,22 @@ func (in *HostBlockDevices) DeepCopy() *HostBlockDevices {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostCGroups) DeepCopyInto(out *HostCGroups) {
|
||||
*out = *in
|
||||
in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCGroups.
|
||||
func (in *HostCGroups) DeepCopy() *HostCGroups {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostCGroups)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostCertificatesCollection) DeepCopyInto(out *HostCertificatesCollection) {
|
||||
*out = *in
|
||||
@@ -1922,6 +2109,21 @@ func (in *HostCollect) DeepCopyInto(out *HostCollect) {
|
||||
*out = new(HostCopy)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostKernelConfigs != nil {
|
||||
in, out := &in.HostKernelConfigs, &out.HostKernelConfigs
|
||||
*out = new(HostKernelConfigs)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostJournald != nil {
|
||||
in, out := &in.HostJournald, &out.HostJournald
|
||||
*out = new(HostJournald)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HostCGroups != nil {
|
||||
in, out := &in.HostCGroups, &out.HostCGroups
|
||||
*out = new(HostCGroups)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostCollect.
|
||||
@@ -2112,6 +2314,43 @@ func (in *HostHTTP) DeepCopy() *HostHTTP {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostJournald) DeepCopyInto(out *HostJournald) {
|
||||
*out = *in
|
||||
in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta)
|
||||
if in.Units != nil {
|
||||
in, out := &in.Units, &out.Units
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostJournald.
|
||||
func (in *HostJournald) DeepCopy() *HostJournald {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostJournald)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostKernelConfigs) DeepCopyInto(out *HostKernelConfigs) {
|
||||
*out = *in
|
||||
in.HostCollectorMeta.DeepCopyInto(&out.HostCollectorMeta)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostKernelConfigs.
|
||||
func (in *HostKernelConfigs) DeepCopy() *HostKernelConfigs {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostKernelConfigs)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostKernelModules) DeepCopyInto(out *HostKernelModules) {
|
||||
*out = *in
|
||||
@@ -2302,6 +2541,23 @@ func (in *HostRun) DeepCopyInto(out *HostRun) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Input != nil {
|
||||
in, out := &in.Input, &out.Input
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Env != nil {
|
||||
in, out := &in.Env, &out.Env
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.InheritEnvs != nil {
|
||||
in, out := &in.InheritEnvs, &out.InheritEnvs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostRun.
|
||||
@@ -2672,6 +2928,38 @@ func (in *JsonCompare) DeepCopy() *JsonCompare {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KernelConfigsAnalyze) DeepCopyInto(out *KernelConfigsAnalyze) {
|
||||
*out = *in
|
||||
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
|
||||
if in.SelectedConfigs != nil {
|
||||
in, out := &in.SelectedConfigs, &out.SelectedConfigs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
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 KernelConfigsAnalyze.
|
||||
func (in *KernelConfigsAnalyze) DeepCopy() *KernelConfigsAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KernelConfigsAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KernelModulesAnalyze) DeepCopyInto(out *KernelModulesAnalyze) {
|
||||
*out = *in
|
||||
@@ -2863,6 +3151,80 @@ func (in *MetricRequest) DeepCopy() *MetricRequest {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeMetrics) DeepCopyInto(out *NodeMetrics) {
|
||||
*out = *in
|
||||
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
|
||||
if in.NodeNames != nil {
|
||||
in, out := &in.NodeNames, &out.NodeNames
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Selector != nil {
|
||||
in, out := &in.Selector, &out.Selector
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetrics.
|
||||
func (in *NodeMetrics) DeepCopy() *NodeMetrics {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeMetrics)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeMetricsAnalyze) DeepCopyInto(out *NodeMetricsAnalyze) {
|
||||
*out = *in
|
||||
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
|
||||
in.Filters.DeepCopyInto(&out.Filters)
|
||||
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 NodeMetricsAnalyze.
|
||||
func (in *NodeMetricsAnalyze) DeepCopy() *NodeMetricsAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeMetricsAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeMetricsAnalyzeFilters) DeepCopyInto(out *NodeMetricsAnalyzeFilters) {
|
||||
*out = *in
|
||||
if in.PVC != nil {
|
||||
in, out := &in.PVC, &out.PVC
|
||||
*out = new(PVCRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetricsAnalyzeFilters.
|
||||
func (in *NodeMetricsAnalyzeFilters) DeepCopy() *NodeMetricsAnalyzeFilters {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeMetricsAnalyzeFilters)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeResourceFilters) DeepCopyInto(out *NodeResourceFilters) {
|
||||
*out = *in
|
||||
@@ -2967,6 +3329,41 @@ func (in *Outcome) DeepCopy() *Outcome {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PVCRef) DeepCopyInto(out *PVCRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PVCRef.
|
||||
func (in *PVCRef) DeepCopy() *PVCRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PVCRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodLaunchOptions) DeepCopyInto(out *PodLaunchOptions) {
|
||||
*out = *in
|
||||
if in.ImagePullSecret != nil {
|
||||
in, out := &in.ImagePullSecret, &out.ImagePullSecret
|
||||
*out = new(ImagePullSecrets)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLaunchOptions.
|
||||
func (in *PodLaunchOptions) DeepCopy() *PodLaunchOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodLaunchOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Post) DeepCopyInto(out *Post) {
|
||||
*out = *in
|
||||
@@ -3978,6 +4375,28 @@ func (in *Run) DeepCopy() *Run {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RunDaemonSet) DeepCopyInto(out *RunDaemonSet) {
|
||||
*out = *in
|
||||
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
|
||||
if in.ImagePullSecret != nil {
|
||||
in, out := &in.ImagePullSecret, &out.ImagePullSecret
|
||||
*out = new(ImagePullSecrets)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.PodSpec.DeepCopyInto(&out.PodSpec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunDaemonSet.
|
||||
func (in *RunDaemonSet) DeepCopy() *RunDaemonSet {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RunDaemonSet)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RunPod) DeepCopyInto(out *RunPod) {
|
||||
*out = *in
|
||||
@@ -3988,6 +4407,13 @@ func (in *RunPod) DeepCopyInto(out *RunPod) {
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.PodSpec.DeepCopyInto(&out.PodSpec)
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunPod.
|
||||
@@ -4036,6 +4462,22 @@ func (in *SingleOutcome) DeepCopy() *SingleOutcome {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Sonobuoy) DeepCopyInto(out *Sonobuoy) {
|
||||
*out = *in
|
||||
in.CollectorMeta.DeepCopyInto(&out.CollectorMeta)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sonobuoy.
|
||||
func (in *Sonobuoy) DeepCopy() *Sonobuoy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Sonobuoy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StatefulsetStatus) DeepCopyInto(out *StatefulsetStatus) {
|
||||
*out = *in
|
||||
@@ -4654,6 +5096,22 @@ func (in *UDPPortStatusAnalyze) DeepCopy() *UDPPortStatusAnalyze {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VeleroAnalyze) DeepCopyInto(out *VeleroAnalyze) {
|
||||
*out = *in
|
||||
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroAnalyze.
|
||||
func (in *VeleroAnalyze) DeepCopy() *VeleroAnalyze {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(VeleroAnalyze)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WeaveReportAnalyze) DeepCopyInto(out *WeaveReportAnalyze) {
|
||||
*out = *in
|
||||
|
||||
@@ -19,6 +19,7 @@ package troubleshootclientset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta2"
|
||||
@@ -33,8 +34,7 @@ type Interface interface {
|
||||
TroubleshootV1beta2() troubleshootv1beta2.TroubleshootV1beta2Interface
|
||||
}
|
||||
|
||||
// Clientset contains the clients for groups. Each group has exactly one
|
||||
// version included in a Clientset.
|
||||
// Clientset contains the clients for groups.
|
||||
type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
troubleshootV1beta1 *troubleshootv1beta1.TroubleshootV1beta1Client
|
||||
@@ -62,26 +62,49 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
// NewForConfig creates a new Clientset for the given config.
|
||||
// If config's RateLimiter is not set and QPS and Burst are acceptable,
|
||||
// NewForConfig will generate a rate-limiter in configShallowCopy.
|
||||
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
configShallowCopy := *c
|
||||
|
||||
if configShallowCopy.UserAgent == "" {
|
||||
configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
// share the transport between all clients
|
||||
httpClient, err := rest.HTTPClientFor(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||
}
|
||||
|
||||
// NewForConfigAndClient creates a new Clientset for the given config and http client.
|
||||
// Note the http client provided takes precedence over the configured transport values.
|
||||
// If config's RateLimiter is not set and QPS and Burst are acceptable,
|
||||
// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
|
||||
func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
|
||||
configShallowCopy := *c
|
||||
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
||||
if configShallowCopy.Burst <= 0 {
|
||||
return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
|
||||
}
|
||||
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
||||
}
|
||||
|
||||
var cs Clientset
|
||||
var err error
|
||||
cs.troubleshootV1beta1, err = troubleshootv1beta1.NewForConfig(&configShallowCopy)
|
||||
cs.troubleshootV1beta1, err = troubleshootv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.troubleshootV1beta2, err = troubleshootv1beta2.NewForConfig(&configShallowCopy)
|
||||
cs.troubleshootV1beta2, err = troubleshootv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,12 +114,11 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
// NewForConfigOrDie creates a new Clientset for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
var cs Clientset
|
||||
cs.troubleshootV1beta1 = troubleshootv1beta1.NewForConfigOrDie(c)
|
||||
cs.troubleshootV1beta2 = troubleshootv1beta2.NewForConfigOrDie(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||
return &cs
|
||||
cs, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
// New creates a new Clientset for the given RESTClient.
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
@@ -35,9 +34,9 @@ type FakeAnalyzers struct {
|
||||
ns string
|
||||
}
|
||||
|
||||
var analyzersResource = schema.GroupVersionResource{Group: "troubleshoot.replicated.com", Version: "v1beta1", Resource: "analyzers"}
|
||||
var analyzersResource = v1beta1.SchemeGroupVersion.WithResource("analyzers")
|
||||
|
||||
var analyzersKind = schema.GroupVersionKind{Group: "troubleshoot.replicated.com", Version: "v1beta1", Kind: "Analyzer"}
|
||||
var analyzersKind = v1beta1.SchemeGroupVersion.WithKind("Analyzer")
|
||||
|
||||
// Get takes name of the analyzer, and returns the corresponding analyzer object, and an error if there is any.
|
||||
func (c *FakeAnalyzers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Analyzer, err error) {
|
||||
@@ -116,7 +115,7 @@ func (c *FakeAnalyzers) UpdateStatus(ctx context.Context, analyzer *v1beta1.Anal
|
||||
// Delete takes name of the analyzer and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeAnalyzers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(analyzersResource, c.ns, name), &v1beta1.Analyzer{})
|
||||
Invokes(testing.NewDeleteActionWithOptions(analyzersResource, c.ns, name, opts), &v1beta1.Analyzer{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
@@ -35,9 +34,9 @@ type FakeCollectors struct {
|
||||
ns string
|
||||
}
|
||||
|
||||
var collectorsResource = schema.GroupVersionResource{Group: "troubleshoot.replicated.com", Version: "v1beta1", Resource: "collectors"}
|
||||
var collectorsResource = v1beta1.SchemeGroupVersion.WithResource("collectors")
|
||||
|
||||
var collectorsKind = schema.GroupVersionKind{Group: "troubleshoot.replicated.com", Version: "v1beta1", Kind: "Collector"}
|
||||
var collectorsKind = v1beta1.SchemeGroupVersion.WithKind("Collector")
|
||||
|
||||
// Get takes name of the collector, and returns the corresponding collector object, and an error if there is any.
|
||||
func (c *FakeCollectors) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Collector, err error) {
|
||||
@@ -116,7 +115,7 @@ func (c *FakeCollectors) UpdateStatus(ctx context.Context, collector *v1beta1.Co
|
||||
// Delete takes name of the collector and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeCollectors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(collectorsResource, c.ns, name), &v1beta1.Collector{})
|
||||
Invokes(testing.NewDeleteActionWithOptions(collectorsResource, c.ns, name, opts), &v1beta1.Collector{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
@@ -35,9 +34,9 @@ type FakePreflights struct {
|
||||
ns string
|
||||
}
|
||||
|
||||
var preflightsResource = schema.GroupVersionResource{Group: "troubleshoot.replicated.com", Version: "v1beta1", Resource: "preflights"}
|
||||
var preflightsResource = v1beta1.SchemeGroupVersion.WithResource("preflights")
|
||||
|
||||
var preflightsKind = schema.GroupVersionKind{Group: "troubleshoot.replicated.com", Version: "v1beta1", Kind: "Preflight"}
|
||||
var preflightsKind = v1beta1.SchemeGroupVersion.WithKind("Preflight")
|
||||
|
||||
// Get takes name of the preflight, and returns the corresponding preflight object, and an error if there is any.
|
||||
func (c *FakePreflights) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Preflight, err error) {
|
||||
@@ -116,7 +115,7 @@ func (c *FakePreflights) UpdateStatus(ctx context.Context, preflight *v1beta1.Pr
|
||||
// Delete takes name of the preflight and deletes it. Returns an error if one occurs.
|
||||
func (c *FakePreflights) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(preflightsResource, c.ns, name), &v1beta1.Preflight{})
|
||||
Invokes(testing.NewDeleteActionWithOptions(preflightsResource, c.ns, name, opts), &v1beta1.Preflight{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
@@ -35,9 +34,9 @@ type FakeRedactors struct {
|
||||
ns string
|
||||
}
|
||||
|
||||
var redactorsResource = schema.GroupVersionResource{Group: "troubleshoot.replicated.com", Version: "v1beta1", Resource: "redactors"}
|
||||
var redactorsResource = v1beta1.SchemeGroupVersion.WithResource("redactors")
|
||||
|
||||
var redactorsKind = schema.GroupVersionKind{Group: "troubleshoot.replicated.com", Version: "v1beta1", Kind: "Redactor"}
|
||||
var redactorsKind = v1beta1.SchemeGroupVersion.WithKind("Redactor")
|
||||
|
||||
// Get takes name of the redactor, and returns the corresponding redactor object, and an error if there is any.
|
||||
func (c *FakeRedactors) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Redactor, err error) {
|
||||
@@ -116,7 +115,7 @@ func (c *FakeRedactors) UpdateStatus(ctx context.Context, redactor *v1beta1.Reda
|
||||
// Delete takes name of the redactor and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeRedactors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(redactorsResource, c.ns, name), &v1beta1.Redactor{})
|
||||
Invokes(testing.NewDeleteActionWithOptions(redactorsResource, c.ns, name, opts), &v1beta1.Redactor{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
+3
-4
@@ -23,7 +23,6 @@ import (
|
||||
v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
@@ -35,9 +34,9 @@ type FakeSupportBundles struct {
|
||||
ns string
|
||||
}
|
||||
|
||||
var supportbundlesResource = schema.GroupVersionResource{Group: "troubleshoot.replicated.com", Version: "v1beta1", Resource: "supportbundles"}
|
||||
var supportbundlesResource = v1beta1.SchemeGroupVersion.WithResource("supportbundles")
|
||||
|
||||
var supportbundlesKind = schema.GroupVersionKind{Group: "troubleshoot.replicated.com", Version: "v1beta1", Kind: "SupportBundle"}
|
||||
var supportbundlesKind = v1beta1.SchemeGroupVersion.WithKind("SupportBundle")
|
||||
|
||||
// Get takes name of the supportBundle, and returns the corresponding supportBundle object, and an error if there is any.
|
||||
func (c *FakeSupportBundles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.SupportBundle, err error) {
|
||||
@@ -116,7 +115,7 @@ func (c *FakeSupportBundles) UpdateStatus(ctx context.Context, supportBundle *v1
|
||||
// Delete takes name of the supportBundle and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeSupportBundles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(supportbundlesResource, c.ns, name), &v1beta1.SupportBundle{})
|
||||
Invokes(testing.NewDeleteActionWithOptions(supportbundlesResource, c.ns, name, opts), &v1beta1.SupportBundle{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ limitations under the License.
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
v1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
@@ -58,12 +60,28 @@ func (c *TroubleshootV1beta1Client) SupportBundles(namespace string) SupportBund
|
||||
}
|
||||
|
||||
// NewForConfig creates a new TroubleshootV1beta1Client for the given config.
|
||||
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||
func NewForConfig(c *rest.Config) (*TroubleshootV1beta1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
httpClient, err := rest.HTTPClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewForConfigAndClient(&config, httpClient)
|
||||
}
|
||||
|
||||
// NewForConfigAndClient creates a new TroubleshootV1beta1Client 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) (*TroubleshootV1beta1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientForConfigAndClient(&config, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user