mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
350418c6e9 | ||
|
|
eacff7112f | ||
|
|
7ed2f4bff2 | ||
|
|
af8f682244 | ||
|
|
c968fca125 | ||
|
|
0fb0a07e55 | ||
|
|
0d21eed5f8 | ||
|
|
b88bc8ddf7 | ||
|
|
9c24ab6067 | ||
|
|
289102f16d | ||
|
|
77fd7da315 | ||
|
|
78ee833f45 | ||
|
|
781853251d | ||
|
|
ffa1c040e2 | ||
|
|
0113624352 | ||
|
|
52efd167ad | ||
|
|
402d111745 | ||
|
|
8105fa00e9 | ||
|
|
e7c07a7d79 | ||
|
|
0240a632c9 | ||
|
|
f58f02560f | ||
|
|
2efbc20b7c | ||
|
|
c1c4b612a4 | ||
|
|
d60f9a6b76 | ||
|
|
668b7ed0b2 | ||
|
|
2bb611cda1 | ||
|
|
142015cce3 | ||
|
|
83f02f4705 | ||
|
|
2f78fee284 | ||
|
|
e97b9613a5 | ||
|
|
8823f7d99e | ||
|
|
d73082ac93 | ||
|
|
cf8c08b913 | ||
|
|
aea4f7c87c | ||
|
|
05dcae2388 | ||
|
|
0c63880528 | ||
|
|
7484b10914 | ||
|
|
f66216154a | ||
|
|
bd6c5b976d | ||
|
|
0a2c9c74ab | ||
|
|
e685cb9cf3 | ||
|
|
3aaffb424b | ||
|
|
1b1efa133e | ||
|
|
ff31f5af0b | ||
|
|
8ab2b5e247 | ||
|
|
55608c12ed | ||
|
|
fa14616009 | ||
|
|
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 | ||
|
|
42fc4f06de | ||
|
|
312e467160 | ||
|
|
5e280d0652 | ||
|
|
15cfdb9eee | ||
|
|
15a4802cd2 | ||
|
|
0d4d305ae6 | ||
|
|
de7112fed2 | ||
|
|
73a2d882d7 | ||
|
|
461cc994ef | ||
|
|
1bdf87e8cd | ||
|
|
32b0e1a890 | ||
|
|
cc56522571 | ||
|
|
e3adc1cb35 | ||
|
|
359475ba87 | ||
|
|
f3d49c657f | ||
|
|
7f3808b8d7 | ||
|
|
f44d077756 | ||
|
|
e34742f3cc | ||
|
|
fd29012624 | ||
|
|
be5e66cbca | ||
|
|
c079d96efe | ||
|
|
7b797b3725 | ||
|
|
6cbe188abe | ||
|
|
86279b4ec4 | ||
|
|
514c86d891 | ||
|
|
b9f4fc4390 | ||
|
|
53813502de | ||
|
|
597fb2996b | ||
|
|
e44aff4100 | ||
|
|
d0d6f00c0f | ||
|
|
4460e8c639 | ||
|
|
c166d46af4 | ||
|
|
ded1f42b5a | ||
|
|
32176178a8 | ||
|
|
949b645336 | ||
|
|
1f50fe3163 | ||
|
|
24c6278e5d | ||
|
|
8d527ead8c | ||
|
|
1dedbcd67a | ||
|
|
ff03bfa9cd | ||
|
|
b7d5a9876c | ||
|
|
96c482a1f5 | ||
|
|
543931631b | ||
|
|
d730be0fae | ||
|
|
38ff340883 | ||
|
|
04f69b3f8c | ||
|
|
57b988b100 | ||
|
|
6972789bea | ||
|
|
17cbc16e05 | ||
|
|
87404982fe | ||
|
|
5bb4358f7a | ||
|
|
39314ef200 | ||
|
|
73c7dba0e1 | ||
|
|
a8f8bf9257 | ||
|
|
08c37c7333 | ||
|
|
8f20b665f9 | ||
|
|
638d99eaad | ||
|
|
25ddcf4388 | ||
|
|
31ccbf696c | ||
|
|
a068561af4 | ||
|
|
f3777bef69 | ||
|
|
addc2ce925 | ||
|
|
247c60afd2 |
@@ -1,4 +0,0 @@
|
||||
version: 3
|
||||
|
||||
project:
|
||||
id: github.com/replicatedhq/troubleshoot
|
||||
+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
|
||||
|
||||
@@ -7,7 +7,7 @@ labels: 'epic'
|
||||
|
||||
# Design Proposal
|
||||
|
||||
Link to the [proposal](https://github.com/replicatedhq/troubleshoot/tree/main/design/template.md)
|
||||
Link to the [proposal](https://github.com/replicatedhq/troubleshoot/tree/main/docs/design/template.md)
|
||||
|
||||
# Definition of done
|
||||
|
||||
|
||||
+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:
|
||||
@@ -20,34 +22,36 @@ concurrency:
|
||||
jobs:
|
||||
fail_if_pull_request_is_draft:
|
||||
if: github.event.pull_request.draft == true
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fails in order to indicate that pull request needs to be marked as ready to review and unit tests workflow needs to pass.
|
||||
run: exit 1
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- run: make test
|
||||
- name: Run linters
|
||||
run: make install-golangci-lint lint
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
test-integration:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.19"
|
||||
go-version: "1.22"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
@@ -55,44 +59,47 @@ jobs:
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- 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.19"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: make check-schemas
|
||||
path: github.com/replicatedhq/troubleshoot
|
||||
- run: |
|
||||
cd github.com/replicatedhq/troubleshoot
|
||||
make check-schemas
|
||||
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.19"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- 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 +113,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/
|
||||
@@ -149,13 +156,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
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/
|
||||
@@ -165,23 +172,23 @@ jobs:
|
||||
run-examples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- run: make run-examples
|
||||
|
||||
compile-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.19"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
||||
@@ -190,13 +197,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
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/
|
||||
@@ -209,33 +216,54 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
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/
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: make support-bundle-e2e-test
|
||||
|
||||
compile-collect:
|
||||
validate-supportbundle-e2e-go-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.19"
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download support bundle binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: make support-bundle-e2e-go-test
|
||||
|
||||
compile-collect:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
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
|
||||
@@ -250,18 +278,20 @@ jobs:
|
||||
include:
|
||||
- goarch: arm
|
||||
goos: linux
|
||||
- goarch: riscv64
|
||||
goos: linux
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.19"
|
||||
go-version: "1.22"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: build --rm-dist --snapshot --config deploy/.goreleaser.yaml --single-target
|
||||
@@ -278,18 +308,18 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
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.19"
|
||||
go-version: "1.22"
|
||||
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
@@ -310,7 +340,7 @@ jobs:
|
||||
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: "v1.x" # Binary version to install
|
||||
args: release --rm-dist --config deploy/.goreleaser.yaml
|
||||
@@ -325,3 +355,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"
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy vulnerability scanner in repo mode
|
||||
uses: aquasecurity/trivy-action@master
|
||||
@@ -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'
|
||||
@@ -1,49 +0,0 @@
|
||||
name: FOSSA license scan
|
||||
|
||||
on:
|
||||
pull_request_target: # this is safe as these scans do not execute provided code
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- go.sum
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
fossa-scan-pr:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.number }}/merge
|
||||
- name: "Install FOSSA"
|
||||
uses: replicatedhq/action-fossa/install@main
|
||||
- name: "Run FOSSA Scan"
|
||||
uses: replicatedhq/action-fossa/scan@main
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
diff: true
|
||||
diff-ref: ${{ github.event.pull_request.base.sha }}
|
||||
debug: true
|
||||
|
||||
fossa-scan-merge:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- id: previous
|
||||
run: echo "sha=$(git rev-parse HEAD~1)" >> "${GITHUB_OUTPUT}"
|
||||
- name: "Install FOSSA"
|
||||
uses: replicatedhq/action-fossa/install@main
|
||||
- name: "Run FOSSA Scan"
|
||||
uses: replicatedhq/action-fossa/scan@main
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
diff: true
|
||||
diff-ref: ${{ steps.previous.outputs.sha }}
|
||||
debug: true
|
||||
@@ -0,0 +1,35 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TRIVY_VERSION: 0.44.1
|
||||
|
||||
name: License scan
|
||||
|
||||
jobs:
|
||||
license:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install trivy
|
||||
run: |
|
||||
wget https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.deb
|
||||
sudo dpkg -i trivy_${TRIVY_VERSION}_Linux-64bit.deb
|
||||
|
||||
- name: Create license report artifact
|
||||
run: trivy fs --scanners license --skip-dirs ".github" . | tee license-report.txt
|
||||
|
||||
- name: Upload license report artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: license-report
|
||||
path: license-report.txt
|
||||
|
||||
- name: Check for unknown licenses
|
||||
run: trivy fs --scanners license --skip-dirs ".github" --exit-code 1 --severity UNKNOWN . || echo "::warning::Unknown licenses found, please verify"
|
||||
|
||||
- name: Check for forbidden licenses and fail
|
||||
run: trivy fs --scanners license --skip-dirs ".github" --exit-code 1 --severity CRITICAL,HIGH .
|
||||
@@ -7,7 +7,10 @@
|
||||
*.dylib
|
||||
bin
|
||||
.DS_Store
|
||||
|
||||
# npm generated files
|
||||
node_modules/
|
||||
package*.json
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
+4
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
run:
|
||||
allow-parallel-runners: true
|
||||
timeout: 30s
|
||||
timeout: 10m
|
||||
|
||||
linters:
|
||||
enable:
|
||||
@@ -10,3 +10,6 @@ linters:
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gosec
|
||||
- govet
|
||||
disable:
|
||||
- errcheck
|
||||
|
||||
+16
-5
@@ -21,17 +21,22 @@ When implementing a new feature please review the [design principles](./docs/des
|
||||
|
||||
To get started we recommend:
|
||||
|
||||
1. Go (v1.19 or later)
|
||||
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.
|
||||
|
||||
### Build automatically on save with `watch`
|
||||
|
||||
1. Install `npm`
|
||||
2. Run `make watch` to build binaries automatically on saving. Note: you may still have to run `make schemas` if you've added API changes, like a new collector or analyzer type.
|
||||
|
||||
### Syncing to a test cluster with `watchrsync`
|
||||
|
||||
1. Install `npm`
|
||||
@@ -89,6 +94,12 @@ go tool pprof -http=":8000" cpu.prof
|
||||
go tool pprof -http=":8001" mem.prof
|
||||
```
|
||||
|
||||
**Additional flags for memory profiling**
|
||||
- `inuse_space`: Amount of memory allocated and not released yet (default).
|
||||
- `inuse_objects`: Amount of objects allocated and not released yet.
|
||||
- `alloc_space`: Total amount of memory allocated (regardless of released).
|
||||
- `alloc_objects`: Total amount of objects allocated (regardless of released).
|
||||
|
||||
More on profiling please visit https://go.dev/doc/diagnostics#profiling
|
||||
|
||||
## Contribution workflow
|
||||
|
||||
@@ -7,6 +7,7 @@ VERSION_PACKAGE = github.com/replicatedhq/troubleshoot/pkg/version
|
||||
VERSION ?=`git describe --tags --dirty`
|
||||
DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"`
|
||||
RUN?=""
|
||||
GOLANGCI_LINT_VERSION ?= "v1.61.0"
|
||||
|
||||
GIT_TREE = $(shell git rev-parse --is-inside-work-tree 2>/dev/null)
|
||||
ifneq "$(GIT_TREE)" ""
|
||||
@@ -34,12 +35,14 @@ define LDFLAGS
|
||||
"
|
||||
endef
|
||||
|
||||
BUILDFLAGS = -tags "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp" -installsuffix netgo
|
||||
BUILDTAGS = "netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp"
|
||||
BUILDFLAGS = -tags ${BUILDTAGS} -installsuffix netgo
|
||||
BUILDPATHS = ./pkg/... ./cmd/... ./internal/...
|
||||
E2EPATHS = ./test/e2e/...
|
||||
TESTFLAGS ?= -v -coverprofile cover.out
|
||||
|
||||
.DEFAULT: all
|
||||
all: test build
|
||||
.DEFAULT_GOAL := all
|
||||
all: clean build test
|
||||
|
||||
.PHONY: ffi
|
||||
ffi: fmt vet
|
||||
@@ -71,27 +74,52 @@ run-examples:
|
||||
support-bundle-e2e-test:
|
||||
./test/validate-support-bundle-e2e.sh
|
||||
|
||||
# Build all binaries in parallel ( -j )
|
||||
build:
|
||||
@echo "Build cli binaries"
|
||||
$(MAKE) -j support-bundle preflight analyze collect
|
||||
.PHONY: support-bundle-e2e-go-test
|
||||
support-bundle-e2e-go-test:
|
||||
if [ -n $(RUN) ]; then \
|
||||
go test ${BUILDFLAGS} ${E2EPATHS} -v -run $(RUN); \
|
||||
else \
|
||||
go test ${BUILDFLAGS} ${E2EPATHS} -v; \
|
||||
fi
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle:
|
||||
rebuild: clean build
|
||||
|
||||
# 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
|
||||
|
||||
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}
|
||||
@@ -105,7 +133,7 @@ generate: controller-gen client-gen
|
||||
$(CONTROLLER_GEN) \
|
||||
object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/...
|
||||
$(CLIENT_GEN) \
|
||||
--output-base=./../../../ \
|
||||
--output-base=$$(pwd)/../../../ \
|
||||
--output-package=github.com/replicatedhq/troubleshoot/pkg/client \
|
||||
--clientset-name troubleshootclientset \
|
||||
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
|
||||
@@ -119,34 +147,34 @@ openapischema: controller-gen
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta2
|
||||
|
||||
check-schemas: generate schemas
|
||||
@if [ -n "$(shell git status --short)" ]; then \
|
||||
@if [ -n "$$(git status --short)" ]; then \
|
||||
echo -e "\033[31mThe git repo is dirty :( Ensure all generated files are committed e.g CRD schema files\033[0;m"; \
|
||||
git status --short; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: schemas
|
||||
schemas: fmt vet openapischema
|
||||
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
|
||||
schemas: 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.16.2
|
||||
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.26.1
|
||||
CLIENT_GEN=$(shell go env GOPATH)/bin/client-gen
|
||||
else
|
||||
go install k8s.io/code-generator/cmd/client-gen@v0.28.13
|
||||
CLIENT_GEN=$(shell which client-gen)
|
||||
endif
|
||||
|
||||
.PHONY: release
|
||||
release: export GITHUB_TOKEN = $(shell echo ${GITHUB_TOKEN_TROUBLESHOOT})
|
||||
@@ -168,15 +196,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
|
||||
@@ -200,6 +228,59 @@ 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
|
||||
|
||||
.PHONY: scan
|
||||
scan:
|
||||
trivy fs \
|
||||
--scanners vuln \
|
||||
--exit-code=1 \
|
||||
--severity="HIGH,CRITICAL" \
|
||||
--ignore-unfixed \
|
||||
./
|
||||
|
||||
.PHONY: lint
|
||||
lint: vet
|
||||
golangci-lint run --new -c .golangci.yaml --build-tags ${BUILDTAGS} ${BUILDPATHS}
|
||||
|
||||
.PHONY: lint-and-fix
|
||||
lint-and-fix: fmt vet
|
||||
golangci-lint run --new --fix -c .golangci.yaml --build-tags ${BUILDTAGS} ${BUILDPATHS}
|
||||
|
||||
.PHONY: install-golangci-lint
|
||||
install-golangci-lint:
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}
|
||||
|
||||
.PHONY: watch
|
||||
watch: npm-install
|
||||
bin/watch.js
|
||||
|
||||
## Syncronize the code with a remote server. More info: CONTRIBUTING.md
|
||||
.PHONY: watchrsync
|
||||
watchrsync: npm-install
|
||||
bin/watchrsync.js
|
||||
|
||||
.PHONY: 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 ..
|
||||
@@ -213,30 +294,3 @@ longhorn:
|
||||
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 \
|
||||
--scanners vuln \
|
||||
--exit-code=1 \
|
||||
--severity="HIGH,CRITICAL" \
|
||||
--ignore-unfixed \
|
||||
./
|
||||
|
||||
.PHONY: lint
|
||||
lint: fmt vet
|
||||
golangci-lint run --new -c .golangci.yaml ${BUILDPATHS}
|
||||
|
||||
.PHONY: lint-and-fix
|
||||
lint-and-fix: fmt vet
|
||||
golangci-lint run --new --fix -c .golangci.yaml ${BUILDPATHS}
|
||||
|
||||
## Syncronize the code with a remote server. More info: CONTRIBUTING.md
|
||||
.PHONY: watchrsync
|
||||
watchrsync: npm-install
|
||||
bin/watchrsync.js
|
||||
|
||||
.PHONY: 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 gaze-run-interrupt@~2.0.0
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const gri = require('gaze-run-interrupt');
|
||||
|
||||
const commands = [
|
||||
// {
|
||||
// command: 'rm',
|
||||
// args: binList,
|
||||
// },
|
||||
{
|
||||
command: 'make',
|
||||
args: ['build'],
|
||||
},
|
||||
];
|
||||
|
||||
commands.push({
|
||||
command: "date",
|
||||
args: [],
|
||||
});
|
||||
|
||||
commands.push({
|
||||
command: "echo",
|
||||
args: ["synced"],
|
||||
});
|
||||
|
||||
gri([
|
||||
'cmd/**/*.go',
|
||||
'pkg/**/*.go',
|
||||
], commands);
|
||||
+1
-7
@@ -16,12 +16,6 @@ const binList = [
|
||||
'bin/support-bundle',
|
||||
// 'bin/collect'
|
||||
]
|
||||
const makeList = [
|
||||
// 'analyze',
|
||||
// 'preflight',
|
||||
'support-bundle',
|
||||
// 'collect'
|
||||
]
|
||||
|
||||
const commands = [
|
||||
// {
|
||||
@@ -30,7 +24,7 @@ const commands = [
|
||||
// },
|
||||
{
|
||||
command: 'make',
|
||||
args: makeList,
|
||||
args: ['build'],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -43,6 +43,8 @@ func RootCmd() *cobra.Command {
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(util.VersionCmd())
|
||||
|
||||
cmd.Flags().String("analyzers", "", "filename or url of the analyzers to use")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
specContent := ""
|
||||
var err error
|
||||
if _, err = os.Stat(specPath); err == nil {
|
||||
b, err := ioutil.ReadFile(specPath)
|
||||
b, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -26,7 +26,8 @@ func runAnalyzers(v *viper.Viper, bundlePath 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)
|
||||
// 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", specPath)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||
)
|
||||
|
||||
func checkAndSetChroot(newroot string) error {
|
||||
if newroot == "" {
|
||||
return nil
|
||||
}
|
||||
if !util.IsRunningAsRoot() {
|
||||
return errors.New("Can only chroot when run as root")
|
||||
}
|
||||
if err := syscall.Chroot(newroot); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||
)
|
||||
|
||||
func checkAndSetChroot(newroot string) error {
|
||||
if newroot == "" {
|
||||
return nil
|
||||
}
|
||||
if !util.IsRunningAsRoot() {
|
||||
return errors.New("Can only chroot when run as root")
|
||||
}
|
||||
if err := syscall.Chroot(newroot); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func checkAndSetChroot(newroot string) error {
|
||||
return errors.New("chroot is only implimented in linux/darwin")
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,6 +32,10 @@ func RootCmd() *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
if err := checkAndSetChroot(v.GetString("chroot")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runCollect(v, args[0])
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
@@ -43,7 +47,7 @@ func RootCmd() *cobra.Command {
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(VersionCmd())
|
||||
cmd.AddCommand(util.VersionCmd())
|
||||
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
@@ -53,6 +57,7 @@ func RootCmd() *cobra.Command {
|
||||
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging")
|
||||
cmd.Flags().String("chroot", "", "Chroot to path")
|
||||
|
||||
// hidden in favor of the `insecure-skip-tls-verify` flag
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
|
||||
+10
-2
@@ -2,6 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -49,8 +50,15 @@ func runCollect(v *viper.Viper, arg string) error {
|
||||
}
|
||||
|
||||
collectorContent = spec
|
||||
} else if arg == "-" {
|
||||
b, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collectorContent = b
|
||||
} else if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
b, err := os.ReadFile(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -58,7 +66,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)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func VersionCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the current version and exit",
|
||||
Long: `Print the current version and exit`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Replicated Collect %s\n", version.Version())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cli
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -2,9 +2,12 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/oci"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func OciFetchCmd() *cobra.Command {
|
||||
@@ -12,6 +15,13 @@ func OciFetchCmd() *cobra.Command {
|
||||
Use: "oci-fetch [URI]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Fetch a preflight from an OCI registry and print it to standard out",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
v := viper.GetViper()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
v.BindPFlags(cmd.Flags())
|
||||
|
||||
logger.SetupLogger(v)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uri := args[0]
|
||||
data, err := oci.PullPreflightFromOCI(uri)
|
||||
@@ -22,5 +32,9 @@ func OciFetchCmd() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize klog flags
|
||||
logger.InitKlogFlags(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
|
||||
"github.com/replicatedhq/troubleshoot/internal/traces"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
@@ -49,8 +49,8 @@ that a cluster meets the requirements to run an application.`,
|
||||
}
|
||||
|
||||
err = preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
|
||||
if v.GetBool("debug") || v.IsSet("v") {
|
||||
fmt.Printf("\n%s", traces.GetExporterInstance().GetSummary())
|
||||
if !v.GetBool("dry-run") && (v.GetBool("debug") || v.IsSet("v")) {
|
||||
fmt.Fprintf(os.Stderr, "\n%s", traces.GetExporterInstance().GetSummary())
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -64,10 +64,15 @@ that a cluster meets the requirements to run an application.`,
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(VersionCmd())
|
||||
cmd.AddCommand(util.VersionCmd())
|
||||
cmd.AddCommand(OciFetchCmd())
|
||||
preflight.AddFlags(cmd.PersistentFlags())
|
||||
|
||||
// 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())
|
||||
|
||||
// Initialize klog flags
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||
)
|
||||
|
||||
func VersionCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the current version and exit",
|
||||
Long: `Print the current version and exit`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Replicated Preflight %s\n", version.Version())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
@@ -22,7 +23,7 @@ var (
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
func showInteractiveResults(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
func showInteractiveResults(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult, archivePath string) error {
|
||||
if err := ui.Init(); err != nil {
|
||||
return errors.Wrap(err, "failed to create terminal ui")
|
||||
}
|
||||
@@ -49,7 +50,7 @@ func showInteractiveResults(supportBundleName string, analyzeResults []*analyzer
|
||||
if err != nil {
|
||||
// show
|
||||
} else {
|
||||
showSaved(filename)
|
||||
showSaved(filename, archivePath)
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
isShowingSaved = false
|
||||
@@ -214,18 +215,36 @@ func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
|
||||
ui.Render(message)
|
||||
}
|
||||
|
||||
func showSaved(filename string) {
|
||||
func showSaved(filename string, archivePath string) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
f := `A support bundle was generated and saved at %s.
|
||||
Please send this file to your software vendor for support.`
|
||||
additionalMessageText := fmt.Sprintf(f, archivePath)
|
||||
|
||||
savedMessage := widgets.NewParagraph()
|
||||
savedMessage.Text = fmt.Sprintf("Support Bundle analysis results saved to\n\n%s", filename)
|
||||
savedMessage.Text = fmt.Sprintf("Support Bundle analysis results saved to\n\n%s\n\n%s", filename, additionalMessageText)
|
||||
savedMessage.WrapText = true
|
||||
savedMessage.Border = true
|
||||
|
||||
left := termWidth/2 - 20
|
||||
right := termWidth/2 + 20
|
||||
top := termHeight/2 - 4
|
||||
bottom := termHeight/2 + 4
|
||||
// Split the text into lines and find the longest line
|
||||
lines := strings.Split(savedMessage.Text, "\n")
|
||||
maxLineLength := 0
|
||||
for _, line := range lines {
|
||||
if len(line) > maxLineLength {
|
||||
// maxLineLength is set to half of the line length to prevent the showing text with more space than needed
|
||||
maxLineLength = len(line)/2 + constants.MESSAGE_TEXT_PADDING
|
||||
}
|
||||
}
|
||||
|
||||
if maxLineLength > termWidth/2 {
|
||||
maxLineLength = termWidth / 2
|
||||
}
|
||||
|
||||
left := termWidth/2 - maxLineLength
|
||||
right := termWidth/2 + maxLineLength
|
||||
top := termHeight/2 - len(lines)
|
||||
bottom := termHeight/2 + len(lines)
|
||||
|
||||
savedMessage.SetRect(left, top, right, bottom)
|
||||
ui.Render(savedMessage)
|
||||
|
||||
@@ -64,7 +64,7 @@ For more information on redactors visit https://troubleshoot.sh/docs/redact/
|
||||
if output == "" {
|
||||
output = fmt.Sprintf("redacted-support-bundle-%s.tar.gz", time.Now().Format("2006-01-02T15_04_05"))
|
||||
}
|
||||
err = collectorResult.ArchiveSupportBundle(bundleDir, output)
|
||||
err = collectorResult.ArchiveBundle(bundleDir, output)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create support bundle archive")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/internal/util"
|
||||
"github.com/replicatedhq/troubleshoot/internal/traces"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
@@ -18,9 +18,17 @@ func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "support-bundle [urls...]",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Short: "Generate a support bundle",
|
||||
Long: `A support bundle is an archive of files, output, metrics and state
|
||||
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
|
||||
Short: "Generate a support bundle from a Kubernetes cluster or specified sources",
|
||||
Long: `Generate a support bundle, an archive containing files, output, metrics, and cluster state to aid in troubleshooting Kubernetes clusters.
|
||||
|
||||
If no arguments are provided, specs are automatically loaded from the cluster by default.
|
||||
|
||||
**Argument Types**:
|
||||
1. **Secret**: Load specs from a Kubernetes Secret. Format: "secret/namespace-name/secret-name[/data-key]"
|
||||
2. **ConfigMap**: Load specs from a Kubernetes ConfigMap. Format: "configmap/namespace-name/configmap-name[/data-key]"
|
||||
3. **File**: Load specs from a local file. Format: Local file path
|
||||
4. **Standard Input**: Read specs from stdin. Format: "-"
|
||||
5. **URL**: Load specs from a URL. Supports HTTP and OCI registry URLs.`,
|
||||
SilenceUsage: true,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
v := viper.GetViper()
|
||||
@@ -36,6 +44,18 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
// If there are no locations to load specs from passed in the cli args, we should
|
||||
// load them from the cluster by setting "load-cluster-specs=true". If the caller
|
||||
// provided "--load-cluster-specs" cli option, we should respect it.
|
||||
if len(args) == 0 {
|
||||
// Check if --load-cluster-specs was set by the cli caller to avoid overriding it
|
||||
flg := cmd.Flags().Lookup("load-cluster-specs")
|
||||
if flg != nil && !flg.Changed {
|
||||
// Load specs from the cluster if no spec(s) is(are) provided in the cli args
|
||||
v.Set("load-cluster-specs", true)
|
||||
}
|
||||
}
|
||||
|
||||
closer, err := traces.ConfigureTracing("support-bundle")
|
||||
if err != nil {
|
||||
// Do not fail running support-bundle if tracing fails
|
||||
@@ -45,8 +65,8 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
}
|
||||
|
||||
err = runTroubleshoot(v, args)
|
||||
if v.GetBool("debug") || v.IsSet("v") {
|
||||
fmt.Printf("\n%s", traces.GetExporterInstance().GetSummary())
|
||||
if !v.IsSet("dry-run") && (v.GetBool("debug") || v.IsSet("v")) {
|
||||
fmt.Fprintf(os.Stderr, "\n%s", traces.GetExporterInstance().GetSummary())
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -62,18 +82,19 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
|
||||
cmd.AddCommand(Analyze())
|
||||
cmd.AddCommand(Redact())
|
||||
cmd.AddCommand(VersionCmd())
|
||||
cmd.AddCommand(util.VersionCmd())
|
||||
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
|
||||
cmd.Flags().Bool("collect-without-permissions", true, "always generate a support bundle, even if it some require additional permissions")
|
||||
cmd.Flags().StringSliceP("selector", "l", []string{"troubleshoot.sh/kind=support-bundle"}, "selector to filter on for loading additional support bundle specs found in secrets within the cluster")
|
||||
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. required when no specs are provided on the command line")
|
||||
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. Do not load by default unless no specs are provided in the cli args")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
cmd.Flags().StringP("output", "o", "", "specify the output file path for the support bundle")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging. This is equivalent to --v=0")
|
||||
cmd.Flags().Bool("dry-run", false, "print support bundle spec without collecting anything")
|
||||
|
||||
// hidden in favor of the `insecure-skip-tls-verify` flag
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
@@ -81,7 +102,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
|
||||
// `no-uri` references the `followURI` functionality where we can use an upstream spec when creating a support bundle
|
||||
// This flag makes sure we can also disable this and fall back to the default spec.
|
||||
cmd.Flags().Bool("no-uri", false, "When this flag is used, Troubleshoot does not attempt to retrieve the bundle referenced by the uri: field in the spec.`")
|
||||
cmd.Flags().Bool("no-uri", false, "When this flag is used, Troubleshoot does not attempt to retrieve the spec referenced by the uri: field`")
|
||||
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
|
||||
+179
-223
@@ -10,7 +10,6 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -18,27 +17,60 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/internal/specs"
|
||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/loader"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
if !v.GetBool("load-cluster-specs") && len(arg) < 1 {
|
||||
return errors.New("flag load-cluster-specs must be set if no specs are provided on the command line")
|
||||
func runTroubleshoot(v *viper.Viper, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create kubernetes client")
|
||||
}
|
||||
|
||||
mainBundle, additionalRedactors, err := loadSpecs(ctx, args, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For --dry-run, we want to print the yaml and exit
|
||||
if v.GetBool("dry-run") {
|
||||
k := loader.TroubleshootKinds{
|
||||
SupportBundlesV1Beta2: []troubleshootv1beta2.SupportBundle{*mainBundle},
|
||||
}
|
||||
// If we have redactors, add them to the temp kinds object
|
||||
if len(additionalRedactors.Spec.Redactors) > 0 {
|
||||
k.RedactorsV1Beta2 = []troubleshootv1beta2.Redactor{*additionalRedactors}
|
||||
}
|
||||
|
||||
out, err := k.ToYaml()
|
||||
if err != nil {
|
||||
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to convert specs to yaml"))
|
||||
}
|
||||
fmt.Printf("%s", out)
|
||||
return nil
|
||||
}
|
||||
|
||||
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
|
||||
@@ -58,11 +90,6 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
var sinceTime *time.Time
|
||||
if v.GetString("since-time") != "" || v.GetString("since") != "" {
|
||||
sinceTime, err = parseTimeFlags(v)
|
||||
@@ -77,61 +104,22 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
var mainBundle *troubleshootv1beta2.SupportBundle
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
|
||||
// Defining `v` below will render using `v` in reference to Viper unusable.
|
||||
// Therefore refactoring `v` to `val` will make sure we can still use it.
|
||||
for _, val := range arg {
|
||||
|
||||
collectorContent, err := supportbundle.LoadSupportBundleSpec(val)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load support bundle spec")
|
||||
}
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
// Referencing `ParseSupportBundle with a secondary arg of `no-uri`
|
||||
// Will make sure we can enable or disable the use of the `Spec.uri` field for an upstream spec.
|
||||
// This change will not have an impact on KOTS' usage of `ParseSupportBundle`
|
||||
// As Kots uses `load.go` directly.
|
||||
supportBundle, err := supportbundle.ParseSupportBundle([]byte(multidocs[0]), !v.GetBool("no-uri"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse support bundle spec")
|
||||
}
|
||||
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, supportBundle)
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse redactors from doc")
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
|
||||
if interactive {
|
||||
c := color.New()
|
||||
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
|
||||
}
|
||||
|
||||
if v.GetBool("load-cluster-specs") {
|
||||
sbFromCluster, redactorsFromCluster, err := loadClusterSpecs()
|
||||
if err != nil {
|
||||
return err
|
||||
if interactive {
|
||||
if len(mainBundle.Spec.HostCollectors) > 0 && !util.IsRunningAsRoot() && !mainBundle.Spec.RunHostCollectorsInPod {
|
||||
fmt.Print(cursor.Show())
|
||||
if util.PromptYesNo(util.HOST_COLLECTORS_RUN_AS_ROOT_PROMPT) {
|
||||
fmt.Println("Exiting...")
|
||||
return nil
|
||||
}
|
||||
fmt.Print(cursor.Hide())
|
||||
}
|
||||
if sbFromCluster == nil {
|
||||
return errors.New("no specs found in cluster")
|
||||
}
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, sbFromCluster)
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactorsFromCluster.Spec.Redactors...)
|
||||
}
|
||||
|
||||
if mainBundle == nil {
|
||||
return errors.New("no support bundle specs provided to run")
|
||||
} else if mainBundle.Spec.Collectors == nil && mainBundle.Spec.HostCollectors == nil {
|
||||
return errors.New("no collectors specified in support bundle")
|
||||
}
|
||||
|
||||
redactors, err := supportbundle.GetRedactorsFromURIs(v.GetStringSlice("redactors"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get redactors")
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, redactors...)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
collectorCB := func(c chan interface{}, msg string) { c <- msg }
|
||||
progressChan := make(chan interface{})
|
||||
@@ -196,15 +184,11 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
OutputPath: v.GetString("output"),
|
||||
Redact: v.GetBool("redact"),
|
||||
FromCLI: true,
|
||||
RunHostCollectorsInPod: mainBundle.Spec.RunHostCollectorsInPod,
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -215,7 +199,7 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
|
||||
if len(response.AnalyzerResults) > 0 {
|
||||
if interactive {
|
||||
if err := showInteractiveResults(mainBundle.Name, response.AnalyzerResults); err != nil {
|
||||
if err := showInteractiveResults(mainBundle.Name, response.AnalyzerResults, response.ArchivePath); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
} else {
|
||||
@@ -242,7 +226,7 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s\n", response.ArchivePath)
|
||||
fmt.Printf("\nA support bundle was generated and saved at %s. Please send this file to your software vendor for support.\n", response.ArchivePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -258,178 +242,150 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadClusterSpecs loads the support bundle and redactor specs from the cluster
|
||||
// based on the provided labels. By default this will be troubleshoot.io/kind=support-bundle and troubleshoot.sh/kind=support-bundle label selectors. We search for secrets
|
||||
// and configmaps with the label selector and parse the data as a support bundle. If the
|
||||
// user does not have sufficient permissions to list & read secrets and configmaps from
|
||||
// all namespaces, we will fallback to trying each namespace individually, and eventually
|
||||
// default to the configured kubeconfig namespace.
|
||||
func loadClusterSpecs() (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta2.Redactor, error) {
|
||||
redactors := &troubleshootv1beta2.Redactor{}
|
||||
// loadSupportBundleSpecsFromURIs loads support bundle specs from URIs
|
||||
func loadSupportBundleSpecsFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) error {
|
||||
moreKinds := loader.NewTroubleshootKinds()
|
||||
|
||||
v := viper.GetViper() // It's singleton, so we can use it anywhere
|
||||
|
||||
klog.Info("Discover troubleshoot specs from cluster")
|
||||
|
||||
selectors := v.GetStringSlice("selector")
|
||||
if reflect.DeepEqual(selectors, []string{"troubleshoot.sh/kind=support-bundle"}) {
|
||||
// Its the default selector so we append troubleshoot.io/kind=support-bundle to it due to backwards compatibility
|
||||
selectors = append(selectors, "troubleshoot.io/kind=support-bundle")
|
||||
}
|
||||
|
||||
labelSelector := strings.Join(selectors, ",")
|
||||
|
||||
parsedSelector, err := labels.Parse(labelSelector)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
config, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to convert create k8s client")
|
||||
}
|
||||
|
||||
// List of namespaces we want to search for secrets and configmaps with support bundle specs
|
||||
namespaces := []string{}
|
||||
ctx := context.Background()
|
||||
|
||||
if v.GetString("namespace") != "" {
|
||||
// Just progress with the namespace provided
|
||||
namespaces = []string{v.GetString("namespace")}
|
||||
} else {
|
||||
// Check if I can read secrets and configmaps in all namespaces
|
||||
ican, err := k8sutil.CanIListAndGetAllSecretsAndConfigMaps(ctx, client)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to check if I can read secrets and configmaps")
|
||||
}
|
||||
klog.V(1).Infof("Can I read any secrets and configmaps: %v", ican)
|
||||
|
||||
if ican {
|
||||
// I can read secrets and configmaps in all namespaces
|
||||
// No need to iterate over all namespaces
|
||||
namespaces = []string{""}
|
||||
} else {
|
||||
// Get list of all namespaces and try to find specs from each namespace
|
||||
nsList, err := client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsForbidden(err) {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
ns, _, err := kubeconfig.Namespace()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to get namespace from kubeconfig")
|
||||
}
|
||||
// If we are not allowed to list namespaces, just use the default namespace
|
||||
// configured in the kubeconfig
|
||||
namespaces = []string{ns}
|
||||
} else {
|
||||
return nil, nil, errors.Wrap(err, "failed to list namespaces")
|
||||
}
|
||||
}
|
||||
|
||||
for _, ns := range nsList.Items {
|
||||
namespaces = append(namespaces, ns.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bundlesFromCluster []string
|
||||
|
||||
parsedSelectorStrings, err := specs.SplitTroubleshootSecretLabelSelector(client, parsedSelector)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse troubleshoot labels selector %s", err)
|
||||
}
|
||||
|
||||
// Search cluster for support bundle specs
|
||||
for _, parsedSelectorString := range parsedSelectorStrings {
|
||||
klog.V(1).Infof("Search support bundle specs from [%q] namespace using %q selector", strings.Join(namespaces, ", "), parsedSelectorString)
|
||||
for _, ns := range namespaces {
|
||||
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelectorString, ns, constants.SupportBundleKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
bundlesFromCluster = append(bundlesFromCluster, bundlesFromSecrets...)
|
||||
|
||||
bundlesFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelectorString, ns, constants.SupportBundleKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
bundlesFromCluster = append(bundlesFromCluster, bundlesFromConfigMaps...)
|
||||
}
|
||||
}
|
||||
|
||||
parsedBundle := &troubleshootv1beta2.SupportBundle{}
|
||||
|
||||
for _, bundle := range bundlesFromCluster {
|
||||
multidocs := strings.Split(string(bundle), "\n---\n")
|
||||
bundleFromDoc, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse support bundle spec: %s", err)
|
||||
// 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) {
|
||||
moreKinds.SupportBundlesV1Beta2 = append(moreKinds.SupportBundlesV1Beta2, s)
|
||||
continue
|
||||
}
|
||||
|
||||
parsedBundle = supportbundle.ConcatSpec(parsedBundle, bundleFromDoc)
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
// 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 {
|
||||
klog.Errorf("failed to parse redactors from doc: %s", err)
|
||||
// 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
|
||||
}
|
||||
|
||||
redactors.Spec.Redactors = append(redactors.Spec.Redactors, parsedRedactors...)
|
||||
// finally append the uri spec
|
||||
moreKinds.SupportBundlesV1Beta2 = append(moreKinds.SupportBundlesV1Beta2, k.SupportBundlesV1Beta2...)
|
||||
|
||||
}
|
||||
|
||||
var redactorsFromCluster []string
|
||||
kinds.SupportBundlesV1Beta2 = moreKinds.SupportBundlesV1Beta2
|
||||
|
||||
// Search cluster for redactor specs
|
||||
for _, parsedSelectorString := range parsedSelectorStrings {
|
||||
klog.V(1).Infof("Search redactor specs from [%q] namespace using %q selector", strings.Join(namespaces, ", "), parsedSelectorString)
|
||||
for _, ns := range namespaces {
|
||||
redactorsFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelectorString, ns, constants.RedactorKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
redactorsFromCluster = append(redactorsFromCluster, redactorsFromSecrets...)
|
||||
return nil
|
||||
}
|
||||
|
||||
redactorsFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelectorString, ns, constants.RedactorKey)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
redactorsFromCluster = append(redactorsFromCluster, redactorsFromConfigMaps...)
|
||||
}
|
||||
func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface) (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta2.Redactor, error) {
|
||||
var (
|
||||
kinds = loader.NewTroubleshootKinds()
|
||||
vp = viper.GetViper()
|
||||
redactors = vp.GetStringSlice("redactors")
|
||||
allArgs = append(args, redactors...)
|
||||
err error
|
||||
)
|
||||
|
||||
kinds, err = specs.LoadFromCLIArgs(ctx, client, allArgs, vp)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to load specs from CLI args")
|
||||
}
|
||||
|
||||
for _, redactor := range redactorsFromCluster {
|
||||
multidocs := strings.Split(string(redactor), "\n---\n")
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
// 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") {
|
||||
err := loadSupportBundleSpecsFromURIs(ctx, kinds)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse redactors from doc: %s", err)
|
||||
klog.Warningf("unable to load support bundles from URIs: %v", err)
|
||||
}
|
||||
|
||||
redactors.Spec.Redactors = append(redactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
|
||||
return parsedBundle, redactors, nil
|
||||
// Check if we have any collectors to run in the troubleshoot specs
|
||||
// TODO: Do we use the RemoteCollectors anymore?
|
||||
if len(kinds.CollectorsV1Beta2) == 0 &&
|
||||
len(kinds.HostCollectorsV1Beta2) == 0 &&
|
||||
len(kinds.SupportBundlesV1Beta2) == 0 {
|
||||
return nil, nil, types.NewExitCodeError(
|
||||
constants.EXIT_CODE_CATCH_ALL,
|
||||
errors.New("no collectors specified to run. Use --debug and/or -v=2 to see more information"),
|
||||
)
|
||||
}
|
||||
|
||||
// Merge specs
|
||||
// We need to add the default type information to the support bundle spec
|
||||
// since by default these fields would be empty
|
||||
mainBundle := &troubleshootv1beta2.SupportBundle{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "merged-support-bundle-spec",
|
||||
},
|
||||
}
|
||||
|
||||
var enableRunHostCollectorsInPod bool
|
||||
|
||||
for _, sb := range kinds.SupportBundlesV1Beta2 {
|
||||
sb := sb
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, &sb)
|
||||
//check if sb has metadata and if it has RunHostCollectorsInPod set to true
|
||||
if !reflect.DeepEqual(sb.ObjectMeta, metav1.ObjectMeta{}) && sb.Spec.RunHostCollectorsInPod {
|
||||
enableRunHostCollectorsInPod = sb.Spec.RunHostCollectorsInPod
|
||||
}
|
||||
}
|
||||
mainBundle.Spec.RunHostCollectorsInPod = enableRunHostCollectorsInPod
|
||||
|
||||
for _, c := range kinds.CollectorsV1Beta2 {
|
||||
mainBundle.Spec.Collectors = util.Append(mainBundle.Spec.Collectors, c.Spec.Collectors)
|
||||
}
|
||||
|
||||
for _, hc := range kinds.HostCollectorsV1Beta2 {
|
||||
mainBundle.Spec.HostCollectors = util.Append(mainBundle.Spec.HostCollectors, hc.Spec.Collectors)
|
||||
}
|
||||
|
||||
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{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "Redactor",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "merged-redactors-spec",
|
||||
},
|
||||
}
|
||||
for _, r := range kinds.RedactorsV1Beta2 {
|
||||
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
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper) (*time.Time, error) {
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
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) {
|
||||
w.Write([]byte(`
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: sb-2
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
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)
|
||||
|
||||
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, 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.16.2
|
||||
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.16.2
|
||||
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
|
||||
@@ -163,6 +167,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -180,6 +191,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -195,6 +213,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
||||
@@ -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.16.2
|
||||
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
|
||||
@@ -835,6 +839,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -852,6 +863,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -867,6 +885,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
||||
@@ -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.16.2
|
||||
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.16.2
|
||||
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
|
||||
@@ -866,6 +870,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -883,6 +894,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -898,6 +916,13 @@ spec:
|
||||
type: object
|
||||
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.
|
||||
format: int64
|
||||
type: integer
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
||||
@@ -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.16.2
|
||||
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:
|
||||
@@ -943,10 +1166,10 @@ spec:
|
||||
type: BoolString
|
||||
filters:
|
||||
properties:
|
||||
architecture:
|
||||
type: string
|
||||
cpuAllocatable:
|
||||
type: string
|
||||
cpuArchitecture:
|
||||
type: string
|
||||
cpuCapacity:
|
||||
type: string
|
||||
ephemeralStorageAllocatable:
|
||||
@@ -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
|
||||
|
||||
+12695
-3931
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.16.2
|
||||
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:
|
||||
@@ -1154,10 +1278,23 @@ spec:
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
dns:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
hostnames:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- hostnames
|
||||
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 +1302,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,18 +1340,22 @@ 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
|
||||
runTime:
|
||||
description: |-
|
||||
Limit runtime. The test will run until it completes the configured I/O workload or until it
|
||||
has run for this specified amount of time, whichever occurs first. When the unit is omitted,
|
||||
the value is interpreted in seconds. Defaults to 120 seconds. Set to "0" to disable.
|
||||
type: string
|
||||
sync:
|
||||
description: Whether to call sync on the file after each
|
||||
write. Does not apply to background IOPS task.
|
||||
@@ -1260,6 +1400,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1275,6 +1443,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1290,6 +1486,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1322,6 +1546,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:
|
||||
@@ -1353,8 +1613,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
|
||||
@@ -1528,6 +1806,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.16.2
|
||||
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:
|
||||
@@ -1154,10 +1278,23 @@ spec:
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
dns:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
hostnames:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- hostnames
|
||||
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 +1302,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,18 +1340,22 @@ 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
|
||||
runTime:
|
||||
description: |-
|
||||
Limit runtime. The test will run until it completes the configured I/O workload or until it
|
||||
has run for this specified amount of time, whichever occurs first. When the unit is omitted,
|
||||
the value is interpreted in seconds. Defaults to 120 seconds. Set to "0" to disable.
|
||||
type: string
|
||||
sync:
|
||||
description: Whether to call sync on the file after each
|
||||
write. Does not apply to background IOPS task.
|
||||
@@ -1260,6 +1400,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1275,6 +1443,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1290,6 +1486,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1322,6 +1546,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:
|
||||
@@ -1353,8 +1613,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
|
||||
@@ -1571,44 +1849,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
|
||||
@@ -1620,18 +1897,22 @@ 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
|
||||
runTime:
|
||||
description: |-
|
||||
Limit runtime. The test will run until it completes the configured I/O workload or until it
|
||||
has run for this specified amount of time, whichever occurs first. When the unit is omitted,
|
||||
the value is interpreted in seconds. Defaults to 120 seconds. Set to "0" to disable.
|
||||
type: string
|
||||
sync:
|
||||
description: Whether to call sync on the file after each
|
||||
write. Does not apply to background IOPS task.
|
||||
@@ -1648,6 +1929,13 @@ spec:
|
||||
- backgroundWriteIOPSJobs
|
||||
- enableBackgroundIOPS
|
||||
type: object
|
||||
hostOS:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
hostServices:
|
||||
properties:
|
||||
collectorName:
|
||||
@@ -1669,6 +1957,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1684,6 +2000,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1699,6 +2043,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -1830,6 +2202,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: HostPreflightStatus defines the observed state of HostPreflight
|
||||
|
||||
+12450
-3963
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.16.2
|
||||
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.16.2
|
||||
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,18 +161,22 @@ 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
|
||||
runTime:
|
||||
description: |-
|
||||
Limit runtime. The test will run until it completes the configured I/O workload or until it
|
||||
has run for this specified amount of time, whichever occurs first. When the unit is omitted,
|
||||
the value is interpreted in seconds. Defaults to 120 seconds. Set to "0" to disable.
|
||||
type: string
|
||||
sync:
|
||||
description: Whether to call sync on the file after each
|
||||
write. Does not apply to background IOPS task.
|
||||
@@ -186,6 +193,13 @@ spec:
|
||||
- backgroundWriteIOPSJobs
|
||||
- enableBackgroundIOPS
|
||||
type: object
|
||||
hostOS:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
hostServices:
|
||||
properties:
|
||||
collectorName:
|
||||
@@ -207,6 +221,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -222,6 +264,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -237,6 +307,34 @@ spec:
|
||||
type: object
|
||||
insecureSkipVerify:
|
||||
type: boolean
|
||||
proxy:
|
||||
type: string
|
||||
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
|
||||
tls:
|
||||
properties:
|
||||
cacert:
|
||||
type: string
|
||||
clientCert:
|
||||
type: string
|
||||
clientKey:
|
||||
type: string
|
||||
secret:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
skipVerify:
|
||||
type: boolean
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
@@ -372,6 +470,8 @@ spec:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: CollectorStatus defines the observed state of Collector
|
||||
|
||||
+12629
-3962
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,4 @@
|
||||
project_name: troubleshoot
|
||||
release:
|
||||
github:
|
||||
owner: replicatedhq
|
||||
name: troubleshoot
|
||||
builds:
|
||||
- id: preflight
|
||||
# NOTE: if you add any additional goos/goarch values, ensure you update ../.github/workflows/build-test-deploy.yaml
|
||||
@@ -15,6 +11,7 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
@@ -38,6 +35,7 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
@@ -61,6 +59,7 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
+3
-1
@@ -29,6 +29,7 @@ preflight [url] [flags]
|
||||
--cpuprofile string File path to write cpu profiling data
|
||||
--debug enable debug logging
|
||||
--disable-compression If true, opt-out of response compression for all requests to the server
|
||||
--dry-run print the preflight spec without running preflight checks
|
||||
--format string output format, one of human, json, yaml. only used when interactive is set to false (default "human")
|
||||
-h, --help help for preflight
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
@@ -36,6 +37,7 @@ preflight [url] [flags]
|
||||
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
|
||||
--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, Preflight does not attempt to retrieve the spec referenced by the uri: field`
|
||||
-o, --output string specify the output file path for the preflight checks
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--selector string selector (label query) to filter remote collection nodes on.
|
||||
@@ -53,4 +55,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 8-Jun-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-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 8-Jun-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-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 3-Jan-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-2024
|
||||
|
||||
+15
-6
@@ -1,11 +1,19 @@
|
||||
## support-bundle
|
||||
|
||||
Generate a support bundle
|
||||
Generate a support bundle from a Kubernetes cluster or specified sources
|
||||
|
||||
### Synopsis
|
||||
|
||||
A support bundle is an archive of files, output, metrics and state
|
||||
from a server that can be used to assist when troubleshooting a Kubernetes cluster.
|
||||
Generate a support bundle, an archive containing files, output, metrics, and cluster state to aid in troubleshooting Kubernetes clusters.
|
||||
|
||||
If no arguments are provided, specs are automatically loaded from the cluster by default.
|
||||
|
||||
**Argument Types**:
|
||||
1. **Secret**: Load specs from a Kubernetes Secret. Format: "secret/namespace-name/secret-name[/data-key]"
|
||||
2. **ConfigMap**: Load specs from a Kubernetes ConfigMap. Format: "configmap/namespace-name/configmap-name[/data-key]"
|
||||
3. **File**: Load specs from a local file. Format: Local file path
|
||||
4. **Standard Input**: Read specs from stdin. Format: "-"
|
||||
5. **URL**: Load specs from a URL. Supports HTTP and OCI registry URLs.
|
||||
|
||||
```
|
||||
support-bundle [urls...] [flags]
|
||||
@@ -27,14 +35,15 @@ 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)
|
||||
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
|
||||
--load-cluster-specs enable/disable loading additional troubleshoot specs found within the cluster. required when no specs are provided on the command line
|
||||
--load-cluster-specs enable/disable loading additional troubleshoot specs found within the cluster. This is the default behavior if no spec is provided as an argument
|
||||
--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 +64,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 8-Jun-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-2024
|
||||
|
||||
@@ -28,6 +28,6 @@ support-bundle analyze [url] [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle from a Kubernetes cluster or specified sources
|
||||
|
||||
###### Auto generated by spf13/cobra on 3-Jan-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-2024
|
||||
|
||||
@@ -13,7 +13,7 @@ be stored in the current directory in the path provided by the --output flag.
|
||||
The [urls...] argument is a list of either oci://.., http://.., https://.. or local paths to yaml files.
|
||||
|
||||
For more information on redactors visit https://troubleshoot.sh/docs/redact/
|
||||
|
||||
|
||||
|
||||
```
|
||||
support-bundle redact [urls...] [flags]
|
||||
@@ -37,6 +37,6 @@ support-bundle redact [urls...] [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle from a Kubernetes cluster or specified sources
|
||||
|
||||
###### Auto generated by spf13/cobra on 3-Jan-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-2024
|
||||
|
||||
@@ -25,6 +25,6 @@ support-bundle version [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle from a Kubernetes cluster or specified sources
|
||||
|
||||
###### Auto generated by spf13/cobra on 3-Jan-2023
|
||||
###### Auto generated by spf13/cobra on 23-Aug-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
|
||||
@@ -19,4 +19,3 @@ spec:
|
||||
message: At least 16 CPU cores preferred
|
||||
- pass:
|
||||
message: This server has sufficient CPU cores
|
||||
|
||||
|
||||
@@ -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,7 @@
|
||||
module helm-template
|
||||
|
||||
go 1.20
|
||||
go 1.22.3
|
||||
toolchain go1.22.7
|
||||
|
||||
// Always use the local version of troubleshoot so as to build using
|
||||
// the latest version of the library. This will ensure the example
|
||||
@@ -9,31 +10,32 @@ replace github.com/replicatedhq/troubleshoot v0.0.0 => ../../../
|
||||
|
||||
require (
|
||||
github.com/replicatedhq/troubleshoot v0.0.0
|
||||
helm.sh/helm/v3 v3.12.1
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
helm.sh/helm/v3 v3.16.2
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // 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.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // 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/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.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/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.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/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@@ -43,31 +45,31 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
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/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // 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.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // 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.27.3 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.27.2 // indirect
|
||||
k8s.io/apimachinery v0.27.3 // indirect
|
||||
k8s.io/client-go v0.27.3 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.15.0 // indirect
|
||||
k8s.io/api v0.31.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.31.1 // indirect
|
||||
k8s.io/apimachinery v0.31.1 // indirect
|
||||
k8s.io/client-go v0.31.1 // 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-20240711033017-18e509b52bc8 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.19.0 // 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
|
||||
)
|
||||
|
||||
+100
-237
@@ -1,118 +1,69 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE=
|
||||
github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
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.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
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/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
|
||||
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/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.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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
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=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
|
||||
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
|
||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
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=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -122,34 +73,29 @@ 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/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
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=
|
||||
@@ -159,165 +105,82 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.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=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/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.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
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.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
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.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.0-20200615113413-eeeca48fe776/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.1 h1:lzU7etZX24A6BTMXYQF3bFq0ECfD8s+fKlNBBL8AbEc=
|
||||
helm.sh/helm/v3 v3.12.1/go.mod h1:qhmSY9kcX7yH1xebe+FDMZa7E5NAeZ+LvK5j1gSln48=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y=
|
||||
k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg=
|
||||
k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo=
|
||||
k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ=
|
||||
k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM=
|
||||
k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
|
||||
k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8=
|
||||
k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48=
|
||||
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-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
|
||||
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.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
|
||||
sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
|
||||
helm.sh/helm/v3 v3.16.2 h1:Y9v7ry+ubQmi+cb5zw1Llx8OKHU9Hk9NQ/+P+LGBe2o=
|
||||
helm.sh/helm/v3 v3.16.2/go.mod h1:SyTXgKBjNqi2NPsHCW5dDAsHqvGIu0kdNYNH9gQaw70=
|
||||
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
|
||||
k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
|
||||
k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40=
|
||||
k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ=
|
||||
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
|
||||
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
|
||||
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
|
||||
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-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
|
||||
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
|
||||
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,224 +1,268 @@
|
||||
module github.com/replicatedhq/troubleshoot
|
||||
|
||||
go 1.19
|
||||
go 1.22.6
|
||||
|
||||
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.2
|
||||
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.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/hashicorp/go-getter v1.7.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/hashicorp/go-getter v1.7.6
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jackc/pgx/v5 v5.4.1
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
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.3.0
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
|
||||
github.com/microsoft/go-mssqldb v1.7.2
|
||||
github.com/miekg/dns v1.1.62
|
||||
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.6
|
||||
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.16.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.16.0
|
||||
go.opentelemetry.io/otel/sdk v1.16.0
|
||||
golang.org/x/sync v0.3.0
|
||||
github.com/vmware-tanzu/velero v1.14.1
|
||||
go.opentelemetry.io/otel v1.31.0
|
||||
go.opentelemetry.io/otel/sdk v1.31.0
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/sync v0.8.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.27.3
|
||||
k8s.io/apiextensions-apiserver v0.27.2
|
||||
k8s.io/apimachinery v0.27.3
|
||||
k8s.io/apiserver v0.27.3
|
||||
k8s.io/cli-runtime v0.27.3
|
||||
k8s.io/client-go v0.27.3
|
||||
k8s.io/klog/v2 v2.100.1
|
||||
oras.land/oras-go v1.2.3
|
||||
sigs.k8s.io/controller-runtime v0.15.0
|
||||
k8s.io/api v0.31.1
|
||||
k8s.io/apiextensions-apiserver v0.31.1
|
||||
k8s.io/apimachinery v0.31.1
|
||||
k8s.io/apiserver v0.31.1
|
||||
k8s.io/cli-runtime v0.31.1
|
||||
k8s.io/client-go v0.31.1
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
oras.land/oras-go v1.2.6
|
||||
sigs.k8s.io/controller-runtime v0.19.0
|
||||
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
|
||||
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
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // 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.3.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
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/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/fxamacker/cbor/v2 v2.7.0 // 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/s2a-go v0.1.3 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // 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.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
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/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.7.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.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.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sylabs/sif/v2 v2.11.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.18.0 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
github.com/vladimirvivien/gexe v0.2.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // 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/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
k8s.io/component-base v0.31.1 // indirect
|
||||
k8s.io/kubectl v0.31.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/compute v1.19.0 // indirect
|
||||
cloud.google.com/go/iam v0.13.0 // indirect
|
||||
cloud.google.com/go/storage v1.28.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.122 // 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/cespare/xxhash/v2 v2.3.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.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // 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/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 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.9.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/gnostic v0.6.9 // 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.8.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/imdario/mergo v0.3.16 // 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.16.3 // 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/spdystream v0.4.0 // 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.0.8 // 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.0 // indirect
|
||||
github.com/prometheus/client_golang v1.15.1 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // 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.7.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.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // 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.1.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/text v0.11.0
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.122.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.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.65.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // 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
|
||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
||||
k8s.io/metrics v0.27.3
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
|
||||
helm.sh/helm/v3 v3.16.2
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/kubelet v0.31.1
|
||||
k8s.io/metrics v0.31.1
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
|
||||
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.2 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.17.2 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
replace (
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func LoadFromConfigMap(ctx context.Context, client kubernetes.Interface, ns string, name string) (map[string]string, error) {
|
||||
foundConfigMap, err := client.CoreV1().ConfigMaps(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get configmap")
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded data from config map", "name",
|
||||
foundConfigMap.Name, "namespace", foundConfigMap.Namespace,
|
||||
)
|
||||
|
||||
return foundConfigMap.Data, nil
|
||||
}
|
||||
|
||||
func LoadFromConfigMapMatchingLabel(ctx context.Context, client kubernetes.Interface, label string, ns string, key string) ([]string, error) {
|
||||
var configMapMatchingKey []string
|
||||
|
||||
configMaps, err := client.CoreV1().ConfigMaps(ns).List(ctx, metav1.ListOptions{LabelSelector: label})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to search for configmaps in the cluster")
|
||||
}
|
||||
|
||||
for _, configMap := range configMaps.Items {
|
||||
spec, ok := configMap.Data[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded spec from config map", "name", configMap.Name,
|
||||
"namespace", configMap.Namespace, "data key", key, "label selector", label,
|
||||
)
|
||||
configMapMatchingKey = append(configMapMatchingKey, string(spec))
|
||||
}
|
||||
|
||||
return configMapMatchingKey, nil
|
||||
}
|
||||
@@ -359,7 +359,7 @@ spec:
|
||||
_, err := client.CoreV1().ConfigMaps(configmap.Namespace).Create(ctx, &configmap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromConfigMapMatchingLabel(client, "troubleshoot.io/kind=support-bundle", "", "support-bundle-spec")
|
||||
got, err := LoadFromConfigMapMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -451,7 +451,7 @@ spec:
|
||||
_, err := client.CoreV1().ConfigMaps(configmap.Namespace).Create(ctx, &configmap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromConfigMapMatchingLabel(client, "troubleshoot.io/kind=support-bundle", "some-namespace", "support-bundle-spec")
|
||||
got, err := LoadFromConfigMapMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "some-namespace", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -549,7 +549,7 @@ spec:
|
||||
_, err := client.CoreV1().ConfigMaps(configmap.Namespace).Create(ctx, &configmap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromConfigMapMatchingLabel(client, "troubleshoot.io/kind=support-bundle", "", "redactor-spec")
|
||||
got, err := LoadFromConfigMapMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "redactor-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -0,0 +1,42 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func LoadFromSecret(ctx context.Context, client kubernetes.Interface, ns string, name string) (map[string][]byte, error) {
|
||||
foundSecret, err := client.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get secret")
|
||||
}
|
||||
|
||||
return foundSecret.Data, nil
|
||||
}
|
||||
|
||||
func LoadFromSecretMatchingLabel(ctx context.Context, client kubernetes.Interface, label string, ns string, key string) ([]string, error) {
|
||||
var secretsMatchingKey []string
|
||||
|
||||
secrets, err := client.CoreV1().Secrets(ns).List(ctx, metav1.ListOptions{LabelSelector: label})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to search for secrets in the cluster")
|
||||
}
|
||||
|
||||
for _, secret := range secrets.Items {
|
||||
spec, ok := secret.Data[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(1).InfoS("Loaded spec from secret", "name", secret.Name,
|
||||
"namespace", secret.Namespace, "data key", key, "label selector", label,
|
||||
)
|
||||
secretsMatchingKey = append(secretsMatchingKey, string(spec))
|
||||
}
|
||||
|
||||
return secretsMatchingKey, nil
|
||||
}
|
||||
@@ -359,7 +359,7 @@ spec:
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromSecretMatchingLabel(client, "troubleshoot.io/kind=support-bundle", "", "support-bundle-spec")
|
||||
got, err := LoadFromSecretMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -451,7 +451,7 @@ spec:
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromSecretMatchingLabel(client, "troubleshoot.io/kind=support-bundle", "some-namespace", "support-bundle-spec")
|
||||
got, err := LoadFromSecretMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "some-namespace", "support-bundle-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -549,7 +549,7 @@ spec:
|
||||
_, err := client.CoreV1().Secrets(secret.Namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := LoadFromSecretMatchingLabel(client, "troubleshoot.io/kind=support-bundle", "", "redactor-spec")
|
||||
got, err := LoadFromSecretMatchingLabel(ctx, client, "troubleshoot.io/kind=support-bundle", "", "redactor-spec")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -0,0 +1,447 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/loader"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/oci"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||
"github.com/spf13/viper"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// SplitTroubleshootSecretLabelSelector splits a label selector into two selectors, if applicable:
|
||||
// 1. troubleshoot.io/kind=support-bundle and non-troubleshoot (if contains) labels selector.
|
||||
// 2. troubleshoot.sh/kind=support-bundle and non-troubleshoot (if contains) labels selector.
|
||||
func SplitTroubleshootSecretLabelSelector(ctx context.Context, labelSelector labels.Selector) ([]string, error) {
|
||||
|
||||
klog.V(1).Infof("Split %q selector into troubleshoot and non-troubleshoot labels selector separately, if applicable", labelSelector.String())
|
||||
|
||||
selectorRequirements, selectorSelectable := labelSelector.Requirements()
|
||||
if !selectorSelectable {
|
||||
return nil, errors.Errorf("Selector %q is not selectable", labelSelector.String())
|
||||
}
|
||||
|
||||
var troubleshootReqs, otherReqs []labels.Requirement
|
||||
|
||||
for _, req := range selectorRequirements {
|
||||
if req.Key() == constants.TroubleshootIOLabelKey || req.Key() == constants.TroubleshootSHLabelKey {
|
||||
troubleshootReqs = append(troubleshootReqs, req)
|
||||
} else {
|
||||
otherReqs = append(otherReqs, req)
|
||||
}
|
||||
}
|
||||
|
||||
parsedSelectorStrings := make([]string, 0)
|
||||
// Combine each troubleshoot requirement with other requirements to form new selectors
|
||||
s := labelSelector.String()
|
||||
if len(troubleshootReqs) == 0 && s != "" {
|
||||
return []string{s}, nil
|
||||
}
|
||||
|
||||
for _, tReq := range troubleshootReqs {
|
||||
reqs := append(otherReqs, tReq)
|
||||
newSelector := labels.NewSelector().Add(reqs...)
|
||||
parsedSelectorStrings = append(parsedSelectorStrings, newSelector.String())
|
||||
}
|
||||
|
||||
return parsedSelectorStrings, nil
|
||||
}
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
// LoadFromCLIArgs loads troubleshoot specs from args passed to a CLI command.
|
||||
// This loader function is meant for troubleshoot CLI commands only, hence not making it public.
|
||||
// It will contain opinionated logic for CLI commands such as interpreting viper flags,
|
||||
// supporting secret/ uri format, downloading from OCI registry and other URLs, etc.
|
||||
func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []string, vp *viper.Viper) (*loader.TroubleshootKinds, error) {
|
||||
// Let's always ensure we have a context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
var kindsFromURL *loader.TroubleshootKinds
|
||||
rawSpecs := []string{}
|
||||
|
||||
for _, v := range args {
|
||||
if strings.HasPrefix(v, "secret/") {
|
||||
// format secret/namespace-name/secret-name[/data-key]
|
||||
pathParts := strings.Split(v, "/")
|
||||
if len(pathParts) > 4 {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("secret path %s must have at most 4 components", v))
|
||||
}
|
||||
if len(pathParts) < 3 {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("secret path %s must have at least 3 components", v))
|
||||
}
|
||||
|
||||
data, err := LoadFromSecret(ctx, client, pathParts[1], pathParts[2])
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Wrap(err, "failed to get spec from secret"))
|
||||
}
|
||||
|
||||
// If we have a key defined, then load specs from that key only.
|
||||
if len(pathParts) == 4 {
|
||||
spec, ok := data[pathParts[3]]
|
||||
if ok {
|
||||
rawSpecs = append(rawSpecs, string(spec))
|
||||
}
|
||||
} else {
|
||||
// Append all data in the secret. Some may not be specs, but that's ok. They will be ignored.
|
||||
for _, spec := range data {
|
||||
rawSpecs = append(rawSpecs, string(spec))
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(v, "configmap/") {
|
||||
// format configmap/namespace-name/configmap-name[/data-key]
|
||||
pathParts := strings.Split(v, "/")
|
||||
if len(pathParts) > 4 {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("configmap path %s must have at most 4 components", v))
|
||||
}
|
||||
if len(pathParts) < 3 {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("configmap path %s must have at least 3 components", v))
|
||||
}
|
||||
|
||||
data, err := LoadFromConfigMap(ctx, client, pathParts[1], pathParts[2])
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Wrap(err, "failed to get spec from configmap"))
|
||||
}
|
||||
|
||||
// If we have a key defined, then load specs from that key only.
|
||||
if len(pathParts) == 4 {
|
||||
spec, ok := data[pathParts[3]]
|
||||
if ok {
|
||||
rawSpecs = append(rawSpecs, spec)
|
||||
}
|
||||
} else {
|
||||
// Append all data in the configmap. Some may not be specs, but that's ok. They will be ignored.
|
||||
for _, spec := range data {
|
||||
rawSpecs = append(rawSpecs, spec)
|
||||
}
|
||||
}
|
||||
} else if _, err := os.Stat(v); err == nil {
|
||||
b, err := os.ReadFile(v)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
rawSpecs = append(rawSpecs, string(b))
|
||||
} else if v == "-" {
|
||||
b, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, err)
|
||||
}
|
||||
rawSpecs = append(rawSpecs, string(b))
|
||||
} else {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
if u.Scheme == "oci" {
|
||||
content, err := oci.PullSpecsFromOCI(ctx, v)
|
||||
if err != nil {
|
||||
if err == oci.ErrNoRelease {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Errorf("no release found for %s.\nCheck the oci:// uri for errors or contact the application vendor for support.", v))
|
||||
}
|
||||
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
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", v))
|
||||
}
|
||||
|
||||
parsedURL, err := url.ParseRequestURI(v)
|
||||
if err != nil {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// remove URI key from the spec if any
|
||||
for i := range kindsFromURL.SupportBundlesV1Beta2 {
|
||||
kindsFromURL.SupportBundlesV1Beta2[i].Spec.Uri = ""
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
RawSpecs: rawSpecs,
|
||||
})
|
||||
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"))
|
||||
if err != nil {
|
||||
if kinds.IsEmpty() {
|
||||
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
// TODO: Consider colour coding and graceful failures when loading specs
|
||||
fmt.Printf("failed to load specs from the cluster: %v\n", err)
|
||||
} else {
|
||||
kinds.Add(clusterKinds)
|
||||
}
|
||||
}
|
||||
|
||||
return kinds, nil
|
||||
}
|
||||
|
||||
func downloadFromHttpURL(ctx context.Context, url string, headers map[string]string) (string, error) {
|
||||
hs := []string{}
|
||||
for k, v := range headers {
|
||||
hs = append(hs, fmt.Sprintf("%s: %s", k, v))
|
||||
}
|
||||
|
||||
klog.V(1).Infof("Downloading troubleshoot specs: url=%s, headers=[%v]", url, strings.Join(hs, ", "))
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
// exit code: should this be catch all or spec issues...?
|
||||
return "", types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, err)
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
// exit code: should this be catch all or spec issues...?
|
||||
return "", types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
klog.V(1).Infof("Response status: %s", resp.Status)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
// LoadFromCluster loads troubleshoot specs from the cluster based on the provided labels.
|
||||
// By default this will be troubleshoot.io/kind=support-bundle and troubleshoot.sh/kind=support-bundle
|
||||
// labels. We search for secrets and configmaps with the label selector and extract the raw data.
|
||||
// We then load the specs from the raw data. If the user does not have sufficient permissions
|
||||
// to list & read secrets and configmaps from all namespaces, we will fallback to trying each
|
||||
// namespace individually, and eventually default to the configured kubeconfig namespace.
|
||||
func LoadFromCluster(ctx context.Context, client kubernetes.Interface, selectors []string, ns string) (*loader.TroubleshootKinds, error) {
|
||||
klog.V(1).Infof("Load troubleshoot specs from the cluster using selectors: %v", selectors)
|
||||
|
||||
if reflect.DeepEqual(selectors, []string{"troubleshoot.sh/kind=support-bundle"}) {
|
||||
// Its the default selector so we append troubleshoot.io/kind=support-bundle to it due to backwards compatibility
|
||||
selectors = append(selectors, "troubleshoot.io/kind=support-bundle")
|
||||
}
|
||||
|
||||
labelSelector := strings.Join(selectors, ",")
|
||||
|
||||
parsedSelector, err := labels.Parse(labelSelector)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
// List of namespaces we want to search for secrets and configmaps with support bundle specs
|
||||
namespaces := []string{}
|
||||
if ns != "" {
|
||||
// Just progress with the namespace provided
|
||||
namespaces = []string{ns}
|
||||
} else {
|
||||
// Check if I can read secrets and configmaps in all namespaces
|
||||
ican, err := k8sutil.CanIListAndGetAllSecretsAndConfigMaps(ctx, client)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check if I can read secrets and configmaps")
|
||||
}
|
||||
klog.V(1).Infof("Can I read any secrets and configmaps: %v", ican)
|
||||
|
||||
if ican {
|
||||
// I can read secrets and configmaps in all namespaces
|
||||
// No need to iterate over all namespaces
|
||||
namespaces = []string{""}
|
||||
} else {
|
||||
// Get list of all namespaces and try to find specs from each namespace
|
||||
nsList, err := client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsForbidden(err) {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
ns, _, err := kubeconfig.Namespace()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get namespace from kubeconfig")
|
||||
}
|
||||
// If we are not allowed to list namespaces, just use the default namespace
|
||||
// configured in the kubeconfig
|
||||
namespaces = []string{ns}
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "failed to list namespaces")
|
||||
}
|
||||
}
|
||||
|
||||
for _, ns := range nsList.Items {
|
||||
namespaces = append(namespaces, ns.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rawSpecs []string
|
||||
|
||||
parsedSelectorStrings, err := SplitTroubleshootSecretLabelSelector(ctx, parsedSelector)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to parse troubleshoot labels selector %s", err)
|
||||
}
|
||||
|
||||
// Iteratively search for troubleshoot specs in all namespaces using the given selectors
|
||||
for _, parsedSelectorString := range parsedSelectorStrings {
|
||||
klog.V(1).Infof("Search specs from [%q] namespace using %q selector", strings.Join(namespaces, ", "), parsedSelectorString)
|
||||
for _, ns := range namespaces {
|
||||
for _, key := range []string{constants.SupportBundleKey, constants.PreflightKey, constants.RedactorKey} {
|
||||
specs, err := LoadFromSecretMatchingLabel(ctx, client, parsedSelectorString, ns, key)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
rawSpecs = append(rawSpecs, specs...)
|
||||
|
||||
specs, err = LoadFromConfigMapMatchingLabel(ctx, client, parsedSelectorString, ns, key)
|
||||
if err != nil {
|
||||
if !k8serrors.IsForbidden(err) {
|
||||
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
|
||||
} else {
|
||||
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
|
||||
}
|
||||
}
|
||||
rawSpecs = append(rawSpecs, specs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load troubleshoot specs from the raw specs
|
||||
return loader.LoadSpecs(ctx, loader.LoadOptions{
|
||||
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{}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_SplitTroubleshootSecretLabelSelector(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
selectorString string
|
||||
expectedSelectors []string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Split both troubleshoot and non-troubleshoot labels",
|
||||
selectorString: "troubleshoot.io/kind=support-bundle,troubleshoot.sh/kind=support-bundle,a=b",
|
||||
expectedSelectors: []string{
|
||||
"a=b,troubleshoot.io/kind=support-bundle",
|
||||
"a=b,troubleshoot.sh/kind=support-bundle",
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Split only troubleshoot.io label",
|
||||
selectorString: "troubleshoot.io/kind=support-bundle",
|
||||
expectedSelectors: []string{"troubleshoot.io/kind=support-bundle"},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Split only troubleshoot.sh label",
|
||||
selectorString: "troubleshoot.sh/kind=support-bundle",
|
||||
expectedSelectors: []string{"troubleshoot.sh/kind=support-bundle"},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Split only non-troubleshoot label",
|
||||
selectorString: "a=b",
|
||||
expectedSelectors: []string{"a=b"},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "No selector labels to split",
|
||||
selectorString: "",
|
||||
expectedSelectors: []string{},
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
selector, err := labels.Parse(tt.selectorString)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing selector string: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gotSelectors, err := SplitTroubleshootSecretLabelSelector(context.TODO(), selector)
|
||||
if (err != nil) != tt.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", tt.expectedError, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, tt.expectedSelectors, gotSelectors)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFromCluster(t *testing.T) {
|
||||
theRedactor := troubleshootv1beta2.Redactor{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "Redactor",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "redact-some-content",
|
||||
},
|
||||
Spec: troubleshootv1beta2.RedactorSpec{
|
||||
Redactors: []*troubleshootv1beta2.Redact{
|
||||
{
|
||||
Name: "redact-text-1",
|
||||
Removals: troubleshootv1beta2.Removals{
|
||||
Values: []string{"TEXT"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
selectors []string
|
||||
namespace string
|
||||
objects []runtime.Object
|
||||
want *loader.TroubleshootKinds
|
||||
}{
|
||||
{
|
||||
name: "no selectors",
|
||||
want: loader.NewTroubleshootKinds(),
|
||||
},
|
||||
{
|
||||
name: "spec in secret and default label selector",
|
||||
namespace: "bigbank",
|
||||
selectors: []string{
|
||||
"troubleshoot.sh/kind=support-bundle",
|
||||
},
|
||||
objects: []runtime.Object{
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: &loader.TroubleshootKinds{
|
||||
RedactorsV1Beta2: []troubleshootv1beta2.Redactor{theRedactor},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "spec in secret and no selector argument passed",
|
||||
namespace: "bigbank",
|
||||
objects: []runtime.Object{
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: loader.NewTroubleshootKinds(),
|
||||
},
|
||||
{
|
||||
name: "multiple specs default selector",
|
||||
namespace: "bigbank",
|
||||
selectors: []string{
|
||||
"troubleshoot.sh/kind=support-bundle",
|
||||
},
|
||||
objects: []runtime.Object{
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
secretObject("bigbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: &loader.TroubleshootKinds{
|
||||
RedactorsV1Beta2: []troubleshootv1beta2.Redactor{theRedactor, theRedactor},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "spec in secret but different namespace",
|
||||
namespace: "bigbank",
|
||||
objects: []runtime.Object{
|
||||
secretObject("anotherbank", map[string]string{
|
||||
"troubleshoot.io/kind": "support-bundle",
|
||||
}),
|
||||
},
|
||||
want: loader.NewTroubleshootKinds(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := testclient.NewSimpleClientset(tt.objects...)
|
||||
got, err := LoadFromCluster(ctx, client, tt.selectors, tt.namespace)
|
||||
require.NoError(t, err)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("got = %v, want %v", testutils.AsJSON(t, got), testutils.AsJSON(t, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func secretObject(ns string, selectors map[string]string) runtime.Object {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("secret-name-%s", uuid.New().String()),
|
||||
Namespace: ns,
|
||||
Labels: selectors,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"redactor-spec": []byte(`apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: redact-some-content
|
||||
spec:
|
||||
redactors:
|
||||
- name: redact-text-1
|
||||
removals:
|
||||
values:
|
||||
- TEXT`),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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,13 +15,25 @@ import (
|
||||
|
||||
func GetTestFixture(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
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)
|
||||
@@ -29,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) {
|
||||
@@ -53,10 +69,24 @@ func CreateTestFileWithData(t *testing.T, path, data string) {
|
||||
func LogJSON(t *testing.T, v interface{}) {
|
||||
t.Helper()
|
||||
|
||||
t.Log(AsJSON(t, v))
|
||||
}
|
||||
|
||||
func AsJSON(t *testing.T, v interface{}) string {
|
||||
t.Helper()
|
||||
|
||||
b, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
t.Log(v)
|
||||
return fmt.Sprintf("%#v", v)
|
||||
} else {
|
||||
t.Log(string(b))
|
||||
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
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ var (
|
||||
printer = message.NewPrinter(language.English)
|
||||
)
|
||||
|
||||
const legend = "Suceeded (S), eXcluded (X), Failed (F)\n"
|
||||
const legend = "Succeeded (S), eXcluded (X), Failed (F)\n"
|
||||
|
||||
// FUTURE WORK: This exporter should only be used by troubleshoot CLIs
|
||||
// until the following issue is addressed:
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestExporter_GetSummary(t *testing.T) {
|
||||
},
|
||||
want: `
|
||||
============ Collectors summary =============
|
||||
Suceeded (S), eXcluded (X), Failed (F)
|
||||
Succeeded (S), eXcluded (X), Failed (F)
|
||||
=============================================
|
||||
all-logs (S) : 60,000ms
|
||||
host-os (S) : 1,000ms
|
||||
@@ -118,7 +118,7 @@ failed-collector (F) : 1ms`,
|
||||
},
|
||||
want: `
|
||||
============= Analyzers summary =============
|
||||
Suceeded (S), eXcluded (X), Failed (F)
|
||||
Succeeded (S), eXcluded (X), Failed (F)
|
||||
=============================================
|
||||
host-cpu (S) : 60,000ms
|
||||
cluster-version (S) : 1,000ms
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_HomeDir(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
env string
|
||||
dir string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "test linux/unix home directory",
|
||||
env: "HOME",
|
||||
dir: "/home/test",
|
||||
want: "/home/test",
|
||||
},
|
||||
{
|
||||
name: "test windows home directory",
|
||||
env: "USERPROFILE",
|
||||
dir: `C:\Users\test`,
|
||||
want: `C:\Users\test`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.Setenv(tt.env, tt.dir)
|
||||
if HomeDir() != tt.want {
|
||||
assert.Equal(t, tt.want, HomeDir())
|
||||
}
|
||||
os.Unsetenv(tt.env) // cleanup
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "valid url with http",
|
||||
url: "http://example.com",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "valid url with https",
|
||||
url: "https://example.com",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "invalid url without scheme",
|
||||
url: "example.com",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "invalid url with spaces",
|
||||
url: "http://example .com",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
url: "",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsURL(tt.url); got != tt.want {
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AppName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "simple name",
|
||||
in: "myapp",
|
||||
want: "Myapp",
|
||||
},
|
||||
{
|
||||
name: "hyphenated name",
|
||||
in: "my-app",
|
||||
want: "My App",
|
||||
},
|
||||
{
|
||||
name: "name with ai",
|
||||
in: "my-ai-app",
|
||||
want: "My AI App",
|
||||
},
|
||||
{
|
||||
name: "name with io",
|
||||
in: "my-app-io",
|
||||
want: "My App.io",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := AppName(tt.in); got != tt.want {
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SplitYAML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
doc string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "single document",
|
||||
doc: `apiVersion: v1`,
|
||||
want: []string{`apiVersion: v1`},
|
||||
},
|
||||
{
|
||||
name: "multiple documents",
|
||||
doc: `apiVersion: v1
|
||||
---
|
||||
apiVersion: v1`,
|
||||
want: []string{`apiVersion: v1`, `apiVersion: v1`},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := SplitYAML(tt.doc); !reflect.DeepEqual(got, tt.want) {
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_EstimateNumberOfLines(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "no line",
|
||||
text: "",
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "single line without newline",
|
||||
text: "Hello, World!",
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "single line with newline",
|
||||
text: "Hello, World!\n",
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple lines",
|
||||
text: "Hello,\nWorld!",
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "multiple lines ending with newline",
|
||||
text: "Hello,\nWorld!\n",
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "multiple lines with extra newlines",
|
||||
text: "\nHello,\nWorld!\n\n",
|
||||
want: 4,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := EstimateNumberOfLines(tt.text); got != tt.want {
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
+46
-7
@@ -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"
|
||||
@@ -18,6 +22,8 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const FILE_NOT_COLLECTED = "fileNotCollected"
|
||||
|
||||
type AnalyzeResult struct {
|
||||
IsPass bool
|
||||
IsFail bool
|
||||
@@ -83,6 +89,11 @@ func HostAnalyze(
|
||||
if err != nil {
|
||||
return NewAnalyzeResultError(analyzer, errors.Wrap(err, "analyze"))
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
klog.Errorf("no outcome matched for %q host analyzer", analyzer.Title())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -111,13 +122,10 @@ func Analyze(
|
||||
return nil, errors.New("nil analyzer")
|
||||
}
|
||||
|
||||
analyzerInst := getAnalyzer(analyzer)
|
||||
analyzerInst := GetAnalyzer(analyzer)
|
||||
if analyzerInst == nil {
|
||||
return []*AnalyzeResult{{
|
||||
IsFail: true,
|
||||
Title: "nonexistent analyzer",
|
||||
Message: "Analyzer not found",
|
||||
}}, nil
|
||||
klog.Info("Non-existent analyzer found in the spec. Please double-check the spelling and indentation of the analyzers in the spec.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, analyzerInst.Title())
|
||||
@@ -145,6 +153,10 @@ func Analyze(
|
||||
results = []*AnalyzeResult{}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
klog.Errorf("no outcome matched for %q analyzer", analyzerInst.Title())
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -176,7 +188,7 @@ type Analyzer interface {
|
||||
Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error)
|
||||
}
|
||||
|
||||
func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
|
||||
func GetAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
|
||||
switch {
|
||||
case analyzer.ClusterVersion != nil:
|
||||
return &AnalyzeClusterVersion{analyzer: analyzer.ClusterVersion}
|
||||
@@ -218,10 +230,14 @@ func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer {
|
||||
return &AnalyzePostgres{analyzer: analyzer.Postgres}
|
||||
case analyzer.Mysql != nil:
|
||||
return &AnalyzeMysql{analyzer: analyzer.Mysql}
|
||||
case analyzer.Mssql != nil:
|
||||
return &AnalyzeMssql{analyzer: analyzer.Mssql}
|
||||
case analyzer.Redis != nil:
|
||||
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:
|
||||
@@ -234,6 +250,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
|
||||
}
|
||||
@@ -261,3 +285,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,58 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
)
|
||||
|
||||
type collectedContent struct {
|
||||
NodeName string
|
||||
Data collectorData
|
||||
}
|
||||
|
||||
type collectorData interface{}
|
||||
|
||||
type nodeNames struct {
|
||||
Nodes []string `json:"nodes"`
|
||||
}
|
||||
|
||||
func retrieveCollectedContents(
|
||||
getCollectedFileContents func(string) ([]byte, error),
|
||||
localPath string, remoteNodeBaseDir string, remoteFileName string,
|
||||
) ([]collectedContent, error) {
|
||||
var collectedContents []collectedContent
|
||||
|
||||
// Try to retrieve local data first
|
||||
if contents, err := getCollectedFileContents(localPath); err == nil {
|
||||
collectedContents = append(collectedContents, collectedContent{NodeName: "", Data: contents})
|
||||
// Return immediately if local content is available
|
||||
return collectedContents, nil
|
||||
}
|
||||
|
||||
// Local data not available, move to remote collection
|
||||
nodeListContents, err := getCollectedFileContents(constants.NODE_LIST_FILE)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get node list")
|
||||
}
|
||||
|
||||
var nodeNames nodeNames
|
||||
if err := json.Unmarshal(nodeListContents, &nodeNames); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal node names")
|
||||
}
|
||||
|
||||
// Collect data for each node
|
||||
for _, node := range nodeNames.Nodes {
|
||||
nodeFilePath := fmt.Sprintf("%s/%s/%s", remoteNodeBaseDir, node, remoteFileName)
|
||||
nodeContents, err := getCollectedFileContents(nodeFilePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to retrieve content for node %s", node)
|
||||
}
|
||||
|
||||
collectedContents = append(collectedContents, collectedContent{NodeName: node, Data: nodeContents})
|
||||
}
|
||||
|
||||
return collectedContents, nil
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRetrieveCollectedContents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
getCollectedFileContents func(string) ([]byte, error) // Mock function
|
||||
localPath string
|
||||
remoteNodeBaseDir string
|
||||
remoteFileName string
|
||||
expectedResult []collectedContent
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successfully retrieve local content",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == "localPath" {
|
||||
return []byte("localContent"), nil
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: []collectedContent{
|
||||
{
|
||||
NodeName: "",
|
||||
Data: []byte("localContent"),
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "local content not found, retrieve remote node content successfully",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
nodeNames := nodeNames{Nodes: []string{"node1", "node2"}}
|
||||
return json.Marshal(nodeNames)
|
||||
}
|
||||
if path == "remoteBaseDir/node1/remoteFileName" {
|
||||
return []byte("remoteContent1"), nil
|
||||
}
|
||||
if path == "remoteBaseDir/node2/remoteFileName" {
|
||||
return []byte("remoteContent2"), nil
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: []collectedContent{
|
||||
{
|
||||
NodeName: "node1",
|
||||
Data: []byte("remoteContent1"),
|
||||
},
|
||||
{
|
||||
NodeName: "node2",
|
||||
Data: []byte("remoteContent2"),
|
||||
},
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "fail to retrieve local content and node list",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to get node list",
|
||||
},
|
||||
{
|
||||
name: "fail to retrieve content for one of the nodes",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
nodeNames := nodeNames{Nodes: []string{"node1", "node2"}}
|
||||
return json.Marshal(nodeNames)
|
||||
}
|
||||
if path == "remoteBaseDir/node1/remoteFileName" {
|
||||
return []byte("remoteContent1"), nil
|
||||
}
|
||||
if path == "remoteBaseDir/node2/remoteFileName" {
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to retrieve content for node node2",
|
||||
},
|
||||
{
|
||||
name: "fail to unmarshal node list",
|
||||
getCollectedFileContents: func(path string) ([]byte, error) {
|
||||
if path == constants.NODE_LIST_FILE {
|
||||
return []byte("invalidJSON"), nil
|
||||
}
|
||||
return nil, &types.NotFoundError{Name: path}
|
||||
},
|
||||
localPath: "localPath",
|
||||
remoteNodeBaseDir: "remoteBaseDir",
|
||||
remoteFileName: "remoteFileName",
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to unmarshal node names",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := retrieveCollectedContents(
|
||||
test.getCollectedFileContents,
|
||||
test.localPath,
|
||||
test.remoteNodeBaseDir,
|
||||
test.remoteFileName,
|
||||
)
|
||||
|
||||
if test.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,11 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey string, iconURI string, readyReplicas int, exists bool, resourceType string) (*AnalyzeResult, error) {
|
||||
@@ -64,7 +65,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
if exists == false && outcome.Warn.When != "absent" {
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("The %s %q was not found", resourceType, name)
|
||||
result.URI = outcome.Fail.URI
|
||||
result.URI = outcome.Warn.URI
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -104,7 +105,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
if exists == false && outcome.Pass.When != "absent" {
|
||||
result.IsFail = true
|
||||
result.Message = fmt.Sprintf("The %s %q was not found", resourceType, name)
|
||||
result.URI = outcome.Fail.URI
|
||||
result.URI = outcome.Pass.URI
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -66,11 +66,15 @@ func (a *AnalyzeCustomResourceDefinition) analyzeCustomResourceDefinition(analyz
|
||||
}
|
||||
}
|
||||
|
||||
result.IsFail = true
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
} else if outcome.Warn != nil {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestAnalyzeCustomResourceDefinition(t *testing.T) {
|
||||
getFile := func(_ string) ([]byte, error) {
|
||||
crdsList := v1beta1.CustomResourceDefinitionList{
|
||||
Items: []v1beta1.CustomResourceDefinition{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "servicemonitors.monitoring.coreos.com"}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "probes.monitoring.coreos.com"}},
|
||||
},
|
||||
}
|
||||
return json.Marshal(crdsList)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer *troubleshootv1beta2.CustomResourceDefinition
|
||||
expectedResult *AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "CRD exists and pass",
|
||||
analyzer: &troubleshootv1beta2.CustomResourceDefinition{
|
||||
CustomResourceDefinitionName: "servicemonitors.monitoring.coreos.com",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "The ServiceMonitor CRD is installed and available.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &AnalyzeResult{
|
||||
Title: "Custom resource definition servicemonitors.monitoring.coreos.com",
|
||||
IconKey: "kubernetes_custom_resource_definition",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/custom-resource-definition.svg?w=13&h=16",
|
||||
IsPass: true,
|
||||
Message: "The ServiceMonitor CRD is installed and available.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CRD not exists and warn",
|
||||
analyzer: &troubleshootv1beta2.CustomResourceDefinition{
|
||||
CustomResourceDefinitionName: "podmonitors.monitoring.coreos.com",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "The Prometheus PodMonitor CRD was not found in the cluster.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &AnalyzeResult{
|
||||
Title: "Custom resource definition podmonitors.monitoring.coreos.com",
|
||||
IconKey: "kubernetes_custom_resource_definition",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/custom-resource-definition.svg?w=13&h=16",
|
||||
IsWarn: true,
|
||||
Message: "The Prometheus PodMonitor CRD was not found in the cluster.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CRD not exists and fail",
|
||||
analyzer: &troubleshootv1beta2.CustomResourceDefinition{
|
||||
CustomResourceDefinitionName: "backupstoragelocations.velero.io",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "The BackupStorageLocation CRD was not found in the cluster.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &AnalyzeResult{
|
||||
Title: "Custom resource definition backupstoragelocations.velero.io",
|
||||
IconKey: "kubernetes_custom_resource_definition",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/custom-resource-definition.svg?w=13&h=16",
|
||||
IsFail: true,
|
||||
Message: "The BackupStorageLocation CRD was not found in the cluster.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &AnalyzeCustomResourceDefinition{
|
||||
analyzer: tt.analyzer,
|
||||
}
|
||||
result, err := a.analyzeCustomResourceDefinition(tt.analyzer, getFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
+77
-28
@@ -10,41 +10,50 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
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 {
|
||||
@@ -67,7 +76,8 @@ func (a *AnalyzeDistribution) IsExcluded() (bool, error) {
|
||||
func (a *AnalyzeDistribution) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
result, err := a.analyzeDistribution(a.analyzer, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
klog.Errorf("failed to analyze distribution: %v", err)
|
||||
return nil, errors.Wrapf(err, "failed to analyze distribution")
|
||||
}
|
||||
result.Strict = a.analyzer.Strict.BoolOrDefaultFalse()
|
||||
return []*AnalyzeResult{result}, nil
|
||||
@@ -125,6 +135,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 +179,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 +193,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 +359,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 +407,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,137 @@
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no ping errors for one pod",
|
||||
cao: caoFixture(t, "goldpinger/checkall-one-pod.json"),
|
||||
want: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Pings to \"gp-goldpinger-xn9rg\" pod succeeded",
|
||||
Message: "Pings to \"gp-goldpinger-xn9rg\" pod from all other pods in the cluster succeeded",
|
||||
IconKey: "kubernetes",
|
||||
IsPass: 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
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package analyzer
|
||||
|
||||
import troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
type HostAnalyzer interface {
|
||||
Title() string
|
||||
@@ -52,6 +57,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
|
||||
}
|
||||
@@ -79,3 +88,102 @@ func (c *resultCollector) get(title string) []*AnalyzeResult {
|
||||
}
|
||||
return []*AnalyzeResult{{Title: title, IsWarn: true, Message: "no results"}}
|
||||
}
|
||||
|
||||
func analyzeHostCollectorResults(collectedContent []collectedContent, outcomes []*troubleshootv1beta2.Outcome, checkCondition func(string, collectorData) (bool, error), title string) ([]*AnalyzeResult, error) {
|
||||
var results []*AnalyzeResult
|
||||
for _, content := range collectedContent {
|
||||
currentTitle := title
|
||||
if content.NodeName != "" {
|
||||
currentTitle = fmt.Sprintf("%s - Node %s", title, content.NodeName)
|
||||
}
|
||||
|
||||
analyzeResult, err := evaluateOutcomes(outcomes, checkCondition, content.Data, currentTitle)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to evaluate outcomes")
|
||||
}
|
||||
if analyzeResult != nil {
|
||||
results = append(results, analyzeResult...)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func evaluateOutcomes(outcomes []*troubleshootv1beta2.Outcome, checkCondition func(string, collectorData) (bool, error), data collectorData, title string) ([]*AnalyzeResult, error) {
|
||||
var results []*AnalyzeResult
|
||||
|
||||
for _, outcome := range outcomes {
|
||||
result := AnalyzeResult{
|
||||
Title: title,
|
||||
}
|
||||
|
||||
switch {
|
||||
case outcome.Fail != nil:
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
isMatch, err := checkCondition(outcome.Fail.When, data)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
case outcome.Warn != nil:
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
isMatch, err := checkCondition(outcome.Warn.When, data)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
case outcome.Pass != nil:
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
isMatch, err := checkCondition(outcome.Pass.When, data)
|
||||
if err != nil {
|
||||
return []*AnalyzeResult{&result}, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
results = append(results, &result)
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostCollectorResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
collectedContent []collectedContent
|
||||
checkCondition func(string, collectorData) (bool, error)
|
||||
expectResult []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "pass if ubuntu >= 00.1.2",
|
||||
collectedContent: []collectedContent{
|
||||
{
|
||||
NodeName: "node1",
|
||||
Data: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "00.1.2",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu >= 00.1.2",
|
||||
Message: "supported distribution matches ubuntu >= 00.1.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "unsupported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
osInfo := data.(collect.HostOSInfo)
|
||||
return osInfo.Platform == "ubuntu" && osInfo.PlatformVersion >= "00.1.2", nil
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info - Node node1",
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches ubuntu >= 00.1.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail if ubuntu <= 11.04",
|
||||
collectedContent: []collectedContent{
|
||||
{
|
||||
NodeName: "node1",
|
||||
Data: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "11.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
},
|
||||
{
|
||||
NodeName: "node2",
|
||||
Data: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "11.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu <= 11.04",
|
||||
Message: "unsupported ubuntu version 11.04",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "supported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
osInfo := data.(collect.HostOSInfo)
|
||||
return osInfo.Platform == "ubuntu" && osInfo.PlatformVersion <= "11.04", nil
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info - Node node1",
|
||||
IsFail: true,
|
||||
Message: "unsupported ubuntu version 11.04",
|
||||
},
|
||||
{
|
||||
Title: "Host OS Info - Node node2",
|
||||
IsFail: true,
|
||||
Message: "unsupported ubuntu version 11.04",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "title does not include node name if empty",
|
||||
collectedContent: []collectedContent{
|
||||
{
|
||||
NodeName: "",
|
||||
Data: collect.HostOSInfo{
|
||||
Name: "myhost",
|
||||
KernelVersion: "5.4.0-1034-gcp",
|
||||
PlatformVersion: "20.04",
|
||||
Platform: "ubuntu",
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ubuntu >= 20.04",
|
||||
Message: "supported distribution matches ubuntu >= 20.04",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "unsupported distribution",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
osInfo := data.(collect.HostOSInfo)
|
||||
return osInfo.Platform == "ubuntu" && osInfo.PlatformVersion >= "20.04", nil
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Host OS Info", // Ensuring the title does not include node name if it's empty
|
||||
IsPass: true,
|
||||
Message: "supported distribution matches ubuntu >= 20.04",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// Call the new analyzeHostCollectorResults function with the test data
|
||||
result, err := analyzeHostCollectorResults(test.collectedContent, test.outcomes, test.checkCondition, "Host OS Info")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateOutcomes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
checkCondition func(string, collectorData) (bool, error)
|
||||
data collectorData
|
||||
expectedResult []*AnalyzeResult
|
||||
}{
|
||||
{
|
||||
name: "fail condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "failCondition",
|
||||
Message: "failure condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Return true if the condition being checked matches "failCondition"
|
||||
return when == "failCondition", nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsFail: true,
|
||||
Message: "failure condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "warn condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "warnCondition",
|
||||
Message: "warning condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Return true if the condition being checked matches "warnCondition"
|
||||
return when == "warnCondition", nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsWarn: true,
|
||||
Message: "warning condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "passCondition",
|
||||
Message: "pass condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Return true if the condition being checked matches "passCondition"
|
||||
return when == "passCondition", nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsPass: true,
|
||||
Message: "pass condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no condition matches",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "failCondition",
|
||||
Message: "failure condition met",
|
||||
},
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "warnCondition",
|
||||
Message: "warning condition met",
|
||||
},
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "passCondition",
|
||||
Message: "pass condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Always return false to simulate no condition matching
|
||||
return false, nil
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: nil, // No condition matches, so we expect no results
|
||||
},
|
||||
{
|
||||
name: "error in checkCondition",
|
||||
outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "failCondition",
|
||||
Message: "failure condition met",
|
||||
},
|
||||
},
|
||||
},
|
||||
checkCondition: func(when string, data collectorData) (bool, error) {
|
||||
// Simulate an error occurring during condition evaluation
|
||||
return false, errors.New("mock error")
|
||||
},
|
||||
data: "someData",
|
||||
expectedResult: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Test Title",
|
||||
IsFail: false, // Error occurred, so no success flag
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := evaluateOutcomes(test.outcomes, test.checkCondition, test.data, "Test Title")
|
||||
|
||||
if test.name == "error in checkCondition" {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+88
-5
@@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -10,6 +11,16 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
// microarchs holds a list of features present in each microarchitecture.
|
||||
// ref: https://gitlab.com/x86-psABIs/x86-64-ABI
|
||||
// ref: https://developers.redhat.com/blog/2021/01/05/building-red-hat-enterprise-linux-9-for-the-x86-64-v2-microarchitecture-level
|
||||
var microarchs = map[string][]string{
|
||||
"x86-64": {"cmov", "cx8", "fpu", "fxsr", "mmx", "syscall", "sse", "sse2"},
|
||||
"x86-64-v2": {"cx16", "lahf_lm", "popcnt", "ssse3", "sse4_1", "sse4_2", "ssse3"},
|
||||
"x86-64-v3": {"avx", "avx2", "bmi1", "bmi2", "f16c", "fma", "lzcnt", "movbe", "xsave"},
|
||||
"x86-64-v4": {"avx512f", "avx512bw", "avx512cd", "avx512dq", "avx512vl"},
|
||||
}
|
||||
|
||||
type AnalyzeHostCPU struct {
|
||||
hostAnalyzer *troubleshootv1beta2.CPUAnalyze
|
||||
}
|
||||
@@ -52,7 +63,7 @@ func (a *AnalyzeHostCPU) Analyze(
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Fail.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Fail.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount, cpuInfo.Flags, cpuInfo.MachineArch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare")
|
||||
}
|
||||
@@ -73,7 +84,7 @@ func (a *AnalyzeHostCPU) Analyze(
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Warn.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Warn.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount, cpuInfo.Flags, cpuInfo.MachineArch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare")
|
||||
}
|
||||
@@ -94,7 +105,7 @@ func (a *AnalyzeHostCPU) Analyze(
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Pass.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Pass.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount, cpuInfo.Flags, cpuInfo.MachineArch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare")
|
||||
}
|
||||
@@ -112,14 +123,60 @@ func (a *AnalyzeHostCPU) Analyze(
|
||||
return []*AnalyzeResult{&result}, nil
|
||||
}
|
||||
|
||||
func compareHostCPUConditionalToActual(conditional string, logicalCount int, physicalCount int) (res bool, err error) {
|
||||
func doCompareHostCPUMicroArchitecture(microarch string, flags []string) (res bool, err error) {
|
||||
specifics := make([]string, 0)
|
||||
switch microarch {
|
||||
case "x86-64-v4":
|
||||
specifics = append(specifics, microarchs["x86-64-v4"]...)
|
||||
fallthrough
|
||||
case "x86-64-v3":
|
||||
specifics = append(specifics, microarchs["x86-64-v3"]...)
|
||||
fallthrough
|
||||
case "x86-64-v2":
|
||||
specifics = append(specifics, microarchs["x86-64-v2"]...)
|
||||
fallthrough
|
||||
case "x86-64":
|
||||
specifics = append(specifics, microarchs["x86-64"]...)
|
||||
default:
|
||||
return false, errors.Errorf("troubleshoot does not yet support microarchitecture %q", microarch)
|
||||
}
|
||||
|
||||
for _, flag := range specifics {
|
||||
if slices.Contains(flags, flag) {
|
||||
continue
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func doCompareHostCPUFlags(expected string, flags []string) (res bool, err error) {
|
||||
expectedFlags := strings.Split(expected, ",")
|
||||
if len(expectedFlags) == 0 {
|
||||
return false, errors.New("expected flags cannot be empty")
|
||||
}
|
||||
for _, flag := range expectedFlags {
|
||||
if slices.Contains(flags, flag) {
|
||||
continue
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func compareHostCPUConditionalToActual(conditional string, logicalCount int, physicalCount int, flags []string, machineArch string) (res bool, err error) {
|
||||
compareLogical := false
|
||||
comparePhysical := false
|
||||
compareUnspecified := false
|
||||
compareMachineArch := false
|
||||
|
||||
comparator := ""
|
||||
desired := ""
|
||||
|
||||
/* When the conditional is in the format of "logical <comparator> <desired>"
|
||||
example: when: "count < 2"
|
||||
*/
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) == 3 {
|
||||
comparator = parts[1]
|
||||
@@ -130,6 +187,8 @@ func compareHostCPUConditionalToActual(conditional string, logicalCount int, phy
|
||||
comparePhysical = true
|
||||
} else if strings.ToLower(parts[0]) == "count" {
|
||||
compareUnspecified = true
|
||||
} else if strings.ToLower(parts[0]) == "machinearch" {
|
||||
compareMachineArch = true
|
||||
}
|
||||
} else if len(parts) == 2 {
|
||||
compareUnspecified = true
|
||||
@@ -137,7 +196,17 @@ func compareHostCPUConditionalToActual(conditional string, logicalCount int, phy
|
||||
desired = parts[1]
|
||||
}
|
||||
|
||||
if !compareLogical && !comparePhysical && !compareUnspecified {
|
||||
// analyze if the cpu supports a specific set of features, aka as micrarchitecture.
|
||||
if strings.ToLower(comparator) == "supports" {
|
||||
return doCompareHostCPUMicroArchitecture(desired, flags)
|
||||
}
|
||||
|
||||
// hasFlags allows users to query for specific flags on the CPU.
|
||||
if strings.ToLower(comparator) == "hasflags" {
|
||||
return doCompareHostCPUFlags(desired, flags)
|
||||
}
|
||||
|
||||
if !compareLogical && !comparePhysical && !compareUnspecified && !compareMachineArch {
|
||||
return false, errors.New("unable to parse conditional")
|
||||
}
|
||||
|
||||
@@ -145,6 +214,8 @@ func compareHostCPUConditionalToActual(conditional string, logicalCount int, phy
|
||||
return doCompareHostCPU(comparator, desired, logicalCount)
|
||||
} else if comparePhysical {
|
||||
return doCompareHostCPU(comparator, desired, physicalCount)
|
||||
} else if compareMachineArch {
|
||||
return doCompareMachineArch(comparator, desired, machineArch)
|
||||
} else {
|
||||
actual := logicalCount
|
||||
if physicalCount > logicalCount {
|
||||
@@ -155,6 +226,16 @@ func compareHostCPUConditionalToActual(conditional string, logicalCount int, phy
|
||||
}
|
||||
}
|
||||
|
||||
func doCompareMachineArch(operator string, desired string, actual string) (bool, error) {
|
||||
switch operator {
|
||||
case "=", "==", "===":
|
||||
return actual == desired, nil
|
||||
case "!=", "!==":
|
||||
return actual != desired, nil
|
||||
}
|
||||
return false, errors.New("unknown operator")
|
||||
}
|
||||
|
||||
func doCompareHostCPU(operator string, desired string, actual int) (bool, error) {
|
||||
desiredInt, err := strconv.ParseInt(desired, 10, 64)
|
||||
if err != nil {
|
||||
@@ -172,6 +253,8 @@ func doCompareHostCPU(operator string, desired string, actual int) (bool, error)
|
||||
return actual >= int(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return actual == int(desiredInt), nil
|
||||
case "!=", "!==":
|
||||
return actual != int(desiredInt), nil
|
||||
}
|
||||
|
||||
return false, errors.New("unknown operator")
|
||||
|
||||
@@ -81,6 +81,8 @@ func Test_compareHostCPUConditionalToActual(t *testing.T) {
|
||||
when string
|
||||
logicalCount int
|
||||
physicalCount int
|
||||
flags []string
|
||||
machineArch string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
@@ -139,12 +141,42 @@ func Test_compareHostCPUConditionalToActual(t *testing.T) {
|
||||
physicalCount: 4,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "supports x86-64-v2 microarchitecture",
|
||||
when: "supports x86-64-v2",
|
||||
flags: []string{""},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "supports x86-64-v2 microarchitecture",
|
||||
when: "supports x86-64-v2",
|
||||
flags: []string{"cmov", "cx8", "fpu", "fxsr", "mmx", "syscall", "sse", "sse2", "cx16", "lahf_lm", "popcnt", "ssse3", "sse4_1", "sse4_2", "ssse3"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "has a non existent flag",
|
||||
when: "hasFlags cmov,doesNotExist",
|
||||
flags: []string{"cmov", "cx8", "fpu", "fxsr", "mmx", "syscall", "sse", "sse2", "cx16", "lahf_lm", "popcnt", "ssse3", "sse4_1", "sse4_2", "ssse3"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "has flags",
|
||||
when: "hasFlags a,b,c",
|
||||
flags: []string{"a", "b", "c", "d", "e"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "machine arch matches",
|
||||
when: "machineArch == x86_64",
|
||||
machineArch: "x86_64",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareHostCPUConditionalToActual(test.when, test.logicalCount, test.physicalCount)
|
||||
actual, err := compareHostCPUConditionalToActual(test.when, test.logicalCount, test.physicalCount, test.flags, test.machineArch)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
|
||||
@@ -32,6 +32,10 @@ func (a *AnalyzeHostFilesystemPerformance) Analyze(
|
||||
) ([]*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
}
|
||||
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "filesystemPerformance"
|
||||
@@ -39,16 +43,53 @@ func (a *AnalyzeHostFilesystemPerformance) Analyze(
|
||||
name := filepath.Join("host-collectors/filesystemPerformance", collectorName+".json")
|
||||
contents, err := getCollectedFileContents(name)
|
||||
if err != nil {
|
||||
if len(hostAnalyzer.Outcomes) >= 1 {
|
||||
// if the very first outcome is FILE_NOT_COLLECTED', then use that outcome
|
||||
// otherwise, return the error
|
||||
if hostAnalyzer.Outcomes[0].Fail != nil && hostAnalyzer.Outcomes[0].Fail.When == FILE_NOT_COLLECTED {
|
||||
result.IsFail = true
|
||||
result.Message = renderFSPerfOutcome(hostAnalyzer.Outcomes[0].Fail.Message, collect.FSPerfResults{})
|
||||
result.URI = hostAnalyzer.Outcomes[0].Fail.URI
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.Outcomes[0].Warn != nil && hostAnalyzer.Outcomes[0].Warn.When == FILE_NOT_COLLECTED {
|
||||
result.IsWarn = true
|
||||
result.Message = renderFSPerfOutcome(hostAnalyzer.Outcomes[0].Warn.Message, collect.FSPerfResults{})
|
||||
result.URI = hostAnalyzer.Outcomes[0].Warn.URI
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.Outcomes[0].Pass != nil && hostAnalyzer.Outcomes[0].Pass.When == FILE_NOT_COLLECTED {
|
||||
result.IsPass = true
|
||||
result.Message = renderFSPerfOutcome(hostAnalyzer.Outcomes[0].Pass.Message, collect.FSPerfResults{})
|
||||
result.URI = hostAnalyzer.Outcomes[0].Pass.URI
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(err, "failed to get collected file %s", name)
|
||||
}
|
||||
|
||||
fsPerf := collect.FSPerfResults{}
|
||||
if err := json.Unmarshal(contents, &fsPerf); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal filesystem performance results from %s", name)
|
||||
fioResult := collect.FioResult{}
|
||||
if err := json.Unmarshal(contents, &fioResult); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal fio results from %s", name)
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: a.Title(),
|
||||
var job *collect.FioJobs
|
||||
for _, j := range fioResult.Jobs {
|
||||
if j.JobName == collect.FioJobName {
|
||||
job = &j
|
||||
break
|
||||
}
|
||||
}
|
||||
if job == nil {
|
||||
return nil, errors.Errorf("no job named 'fsperf' found in fio results from %s", name)
|
||||
}
|
||||
|
||||
fioWriteLatency := job.Sync
|
||||
|
||||
fsPerf := fioWriteLatency.FSPerfResults()
|
||||
if err := json.Unmarshal(contents, &fsPerf); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal filesystem performance results from %s", name)
|
||||
}
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
@@ -124,6 +165,10 @@ func (a *AnalyzeHostFilesystemPerformance) Analyze(
|
||||
}
|
||||
|
||||
func compareHostFilesystemPerformanceConditionalToActual(conditional string, fsPerf collect.FSPerfResults) (res bool, err error) {
|
||||
if conditional == FILE_NOT_COLLECTED {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("conditional must have exactly 3 parts, got %d", len(parts))
|
||||
@@ -179,7 +224,7 @@ func compareHostFilesystemPerformanceConditionalToActual(conditional string, fsP
|
||||
return doCompareHostFilesystemPerformance(comparator, fsPerf.P9999, desiredDuration)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown filesystem performance keyword %q", keyword)
|
||||
return false, fmt.Errorf("unknown filesystem performance keyword %q", keyword)
|
||||
}
|
||||
|
||||
func doCompareHostFilesystemPerformance(operator string, actual time.Duration, desired time.Duration) (bool, error) {
|
||||
@@ -196,7 +241,7 @@ func doCompareHostFilesystemPerformance(operator string, actual time.Duration, d
|
||||
return actual == desired, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown filesystem performance operator %q", operator)
|
||||
return false, fmt.Errorf("unknown filesystem performance operator %q", operator)
|
||||
}
|
||||
|
||||
func renderFSPerfOutcome(outcome string, fsPerf collect.FSPerfResults) string {
|
||||
|
||||
@@ -1,48 +1,315 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostFilesystemPerformance(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fsPerf *collect.FSPerfResults
|
||||
fioResult string
|
||||
hostAnalyzer *troubleshootv1beta2.FilesystemPerformanceAnalyze
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Cover",
|
||||
fsPerf: &collect.FSPerfResults{
|
||||
Min: 200 * time.Nanosecond,
|
||||
Max: time.Second,
|
||||
Average: 55 * time.Microsecond,
|
||||
P1: 1 * time.Microsecond,
|
||||
P5: 5 * time.Microsecond,
|
||||
P10: 10 * time.Microsecond,
|
||||
P20: 20 * time.Microsecond,
|
||||
P30: 30 * time.Microsecond,
|
||||
P40: 40 * time.Microsecond,
|
||||
P50: 50 * time.Microsecond,
|
||||
P60: 60 * time.Microsecond,
|
||||
P70: 70 * time.Microsecond,
|
||||
P80: 80 * time.Microsecond,
|
||||
P90: 90 * time.Microsecond,
|
||||
P95: 95 * time.Microsecond,
|
||||
P99: 99 * time.Microsecond,
|
||||
P995: 995 * time.Microsecond,
|
||||
P999: 999 * time.Microsecond,
|
||||
P9995: 5 * time.Millisecond,
|
||||
P9999: 9 * time.Millisecond,
|
||||
},
|
||||
fioResult: `{
|
||||
"fio version" : "fio-3.28",
|
||||
"timestamp" : 1691679955,
|
||||
"timestamp_ms" : 1691679955590,
|
||||
"time" : "Thu Aug 10 15:05:55 2023",
|
||||
"global options" : {
|
||||
"rw" : "write",
|
||||
"ioengine" : "sync",
|
||||
"fdatasync" : "1",
|
||||
"directory" : "/var/lib/etcd",
|
||||
"size" : "23068672",
|
||||
"bs" : "1024"
|
||||
},
|
||||
"jobs" : [
|
||||
{
|
||||
"jobname" : "fsperf",
|
||||
"groupid" : 0,
|
||||
"error" : 0,
|
||||
"eta" : 0,
|
||||
"elapsed" : 15,
|
||||
"job options" : {
|
||||
"name" : "fsperf",
|
||||
"runtime" : "120"
|
||||
},
|
||||
"read" : {
|
||||
"io_bytes" : 0,
|
||||
"io_kbytes" : 0,
|
||||
"bw_bytes" : 0,
|
||||
"bw" : 0,
|
||||
"iops" : 0.000000,
|
||||
"runtime" : 0,
|
||||
"total_ios" : 0,
|
||||
"short_ios" : 22527,
|
||||
"drop_ios" : 0,
|
||||
"slat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"clat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"lat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"bw_min" : 0,
|
||||
"bw_max" : 0,
|
||||
"bw_agg" : 0.000000,
|
||||
"bw_mean" : 0.000000,
|
||||
"bw_dev" : 0.000000,
|
||||
"bw_samples" : 0,
|
||||
"iops_min" : 0,
|
||||
"iops_max" : 0,
|
||||
"iops_mean" : 0.000000,
|
||||
"iops_stddev" : 0.000000,
|
||||
"iops_samples" : 0
|
||||
},
|
||||
"write" : {
|
||||
"io_bytes" : 23068672,
|
||||
"io_kbytes" : 22528,
|
||||
"bw_bytes" : 1651182,
|
||||
"bw" : 1612,
|
||||
"iops" : 1612.483001,
|
||||
"runtime" : 13971,
|
||||
"total_ios" : 22528,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0,
|
||||
"slat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"clat_ns" : {
|
||||
"min" : 200,
|
||||
"max" : 1000000000,
|
||||
"mean" : 55000,
|
||||
"stddev" : 12345.6789,
|
||||
"N" : 32400,
|
||||
"percentile" : {
|
||||
"1.000000" : 1000,
|
||||
"5.000000" : 5000,
|
||||
"10.000000" : 10000,
|
||||
"20.000000" : 20000,
|
||||
"30.000000" : 30000,
|
||||
"40.000000" : 40000,
|
||||
"50.000000" : 50000,
|
||||
"60.000000" : 60000,
|
||||
"70.000000" : 70000,
|
||||
"80.000000" : 80000,
|
||||
"90.000000" : 90000,
|
||||
"95.000000" : 95000,
|
||||
"99.000000" : 99000,
|
||||
"99.500000" : 995000,
|
||||
"99.900000" : 999000,
|
||||
"99.950000" : 5000000,
|
||||
"99.990000" : 9000000
|
||||
}
|
||||
},
|
||||
"lat_ns" : {
|
||||
"min" : 2684,
|
||||
"max" : 8710446,
|
||||
"mean" : 95169.335405,
|
||||
"stddev" : 172145.383902,
|
||||
"N" : 22528
|
||||
},
|
||||
"bw_min" : 1516,
|
||||
"bw_max" : 1706,
|
||||
"bw_agg" : 100.000000,
|
||||
"bw_mean" : 1613.629630,
|
||||
"bw_dev" : 35.708379,
|
||||
"bw_samples" : 27,
|
||||
"iops_min" : 1516,
|
||||
"iops_max" : 1706,
|
||||
"iops_mean" : 1613.629630,
|
||||
"iops_stddev" : 35.708379,
|
||||
"iops_samples" : 27
|
||||
},
|
||||
"trim" : {
|
||||
"io_bytes" : 0,
|
||||
"io_kbytes" : 0,
|
||||
"bw_bytes" : 0,
|
||||
"bw" : 0,
|
||||
"iops" : 0.000000,
|
||||
"runtime" : 0,
|
||||
"total_ios" : 0,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0,
|
||||
"slat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"clat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"lat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"bw_min" : 0,
|
||||
"bw_max" : 0,
|
||||
"bw_agg" : 0.000000,
|
||||
"bw_mean" : 0.000000,
|
||||
"bw_dev" : 0.000000,
|
||||
"bw_samples" : 0,
|
||||
"iops_min" : 0,
|
||||
"iops_max" : 0,
|
||||
"iops_mean" : 0.000000,
|
||||
"iops_stddev" : 0.000000,
|
||||
"iops_samples" : 0
|
||||
},
|
||||
"sync" : {
|
||||
"total_ios" : 0,
|
||||
"lat_ns" : {
|
||||
"min" : 200,
|
||||
"max" : 1000000000,
|
||||
"mean" : 55000,
|
||||
"stddev" : 12345.6789,
|
||||
"N" : 32400,
|
||||
"percentile" : {
|
||||
"1.000000" : 1000,
|
||||
"5.000000" : 5000,
|
||||
"10.000000" : 10000,
|
||||
"20.000000" : 20000,
|
||||
"30.000000" : 30000,
|
||||
"40.000000" : 40000,
|
||||
"50.000000" : 50000,
|
||||
"60.000000" : 60000,
|
||||
"70.000000" : 70000,
|
||||
"80.000000" : 80000,
|
||||
"90.000000" : 90000,
|
||||
"95.000000" : 95000,
|
||||
"99.000000" : 99000,
|
||||
"99.500000" : 995000,
|
||||
"99.900000" : 999000,
|
||||
"99.950000" : 5000000,
|
||||
"99.990000" : 9000000
|
||||
}
|
||||
}
|
||||
},
|
||||
"job_runtime" : 13970,
|
||||
"usr_cpu" : 1.410165,
|
||||
"sys_cpu" : 5.454545,
|
||||
"ctx" : 72137,
|
||||
"majf" : 0,
|
||||
"minf" : 16,
|
||||
"iodepth_level" : {
|
||||
"1" : 199.995561,
|
||||
"2" : 0.000000,
|
||||
"4" : 0.000000,
|
||||
"8" : 0.000000,
|
||||
"16" : 0.000000,
|
||||
"32" : 0.000000,
|
||||
">=64" : 0.000000
|
||||
},
|
||||
"iodepth_submit" : {
|
||||
"0" : 0.000000,
|
||||
"4" : 100.000000,
|
||||
"8" : 0.000000,
|
||||
"16" : 0.000000,
|
||||
"32" : 0.000000,
|
||||
"64" : 0.000000,
|
||||
">=64" : 0.000000
|
||||
},
|
||||
"iodepth_complete" : {
|
||||
"0" : 0.000000,
|
||||
"4" : 100.000000,
|
||||
"8" : 0.000000,
|
||||
"16" : 0.000000,
|
||||
"32" : 0.000000,
|
||||
"64" : 0.000000,
|
||||
">=64" : 0.000000
|
||||
},
|
||||
"latency_ns" : {
|
||||
"2" : 0.000000,
|
||||
"4" : 0.000000,
|
||||
"10" : 0.000000,
|
||||
"20" : 0.000000,
|
||||
"50" : 0.000000,
|
||||
"100" : 0.000000,
|
||||
"250" : 0.000000,
|
||||
"500" : 0.000000,
|
||||
"750" : 0.000000,
|
||||
"1000" : 0.000000
|
||||
},
|
||||
"latency_us" : {
|
||||
"2" : 0.000000,
|
||||
"4" : 27.077415,
|
||||
"10" : 42.032138,
|
||||
"20" : 5.450994,
|
||||
"50" : 0.306286,
|
||||
"100" : 0.026634,
|
||||
"250" : 0.461648,
|
||||
"500" : 23.291016,
|
||||
"750" : 1.269531,
|
||||
"1000" : 0.035511
|
||||
},
|
||||
"latency_ms" : {
|
||||
"2" : 0.026634,
|
||||
"4" : 0.017756,
|
||||
"10" : 0.010000,
|
||||
"20" : 0.000000,
|
||||
"50" : 0.000000,
|
||||
"100" : 0.000000,
|
||||
"250" : 0.000000,
|
||||
"500" : 0.000000,
|
||||
"750" : 0.000000,
|
||||
"1000" : 0.000000,
|
||||
"2000" : 0.000000,
|
||||
">=2000" : 0.000000
|
||||
},
|
||||
"latency_depth" : 1,
|
||||
"latency_target" : 0,
|
||||
"latency_percentile" : 100.000000,
|
||||
"latency_window" : 0
|
||||
}
|
||||
],
|
||||
"disk_util" : [
|
||||
{
|
||||
"name" : "sda",
|
||||
"read_ios" : 5610,
|
||||
"write_ios" : 45550,
|
||||
"read_merges" : 0,
|
||||
"write_merges" : 568,
|
||||
"read_ticks" : 1863,
|
||||
"write_ticks" : 11605,
|
||||
"in_queue" : 14353,
|
||||
"util" : 99.435028
|
||||
}
|
||||
]
|
||||
}`,
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "etcd",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
@@ -298,12 +565,307 @@ func TestAnalyzeHostFilesystemPerformance(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "skip warn if pass first",
|
||||
fsPerf: &collect.FSPerfResults{
|
||||
P99: 9 * time.Millisecond,
|
||||
},
|
||||
fioResult: `{
|
||||
"fio version" : "fio-3.28",
|
||||
"timestamp" : 1691679955,
|
||||
"timestamp_ms" : 1691679955590,
|
||||
"time" : "Thu Aug 10 15:05:55 2023",
|
||||
"global options" : {
|
||||
"rw" : "write",
|
||||
"ioengine" : "sync",
|
||||
"fdatasync" : "1",
|
||||
"directory" : "/var/lib/etcd",
|
||||
"size" : "23068672",
|
||||
"bs" : "1024"
|
||||
},
|
||||
"jobs" : [
|
||||
{
|
||||
"jobname" : "fsperf",
|
||||
"groupid" : 0,
|
||||
"error" : 0,
|
||||
"eta" : 0,
|
||||
"elapsed" : 15,
|
||||
"job options" : {
|
||||
"name" : "fsperf",
|
||||
"runtime" : "120"
|
||||
},
|
||||
"read" : {
|
||||
"io_bytes" : 0,
|
||||
"io_kbytes" : 0,
|
||||
"bw_bytes" : 0,
|
||||
"bw" : 0,
|
||||
"iops" : 0.000000,
|
||||
"runtime" : 0,
|
||||
"total_ios" : 0,
|
||||
"short_ios" : 22527,
|
||||
"drop_ios" : 0,
|
||||
"slat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"clat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"lat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"bw_min" : 0,
|
||||
"bw_max" : 0,
|
||||
"bw_agg" : 0.000000,
|
||||
"bw_mean" : 0.000000,
|
||||
"bw_dev" : 0.000000,
|
||||
"bw_samples" : 0,
|
||||
"iops_min" : 0,
|
||||
"iops_max" : 0,
|
||||
"iops_mean" : 0.000000,
|
||||
"iops_stddev" : 0.000000,
|
||||
"iops_samples" : 0
|
||||
},
|
||||
"write" : {
|
||||
"io_bytes" : 23068672,
|
||||
"io_kbytes" : 22528,
|
||||
"bw_bytes" : 1651182,
|
||||
"bw" : 1612,
|
||||
"iops" : 1612.483001,
|
||||
"runtime" : 13971,
|
||||
"total_ios" : 22528,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0,
|
||||
"slat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"clat_ns" : {
|
||||
"min" : 200,
|
||||
"max" : 1000000000,
|
||||
"mean" : 55000,
|
||||
"stddev" : 12345.6789,
|
||||
"N" : 32400,
|
||||
"percentile" : {
|
||||
"1.000000" : 1000,
|
||||
"5.000000" : 5000,
|
||||
"10.000000" : 10000,
|
||||
"20.000000" : 20000,
|
||||
"30.000000" : 30000,
|
||||
"40.000000" : 40000,
|
||||
"50.000000" : 50000,
|
||||
"60.000000" : 60000,
|
||||
"70.000000" : 70000,
|
||||
"80.000000" : 80000,
|
||||
"90.000000" : 90000,
|
||||
"95.000000" : 95000,
|
||||
"99.000000" : 99000,
|
||||
"99.500000" : 995000,
|
||||
"99.900000" : 999000,
|
||||
"99.950000" : 5000000,
|
||||
"99.990000" : 9000000
|
||||
}
|
||||
},
|
||||
"lat_ns" : {
|
||||
"min" : 2684,
|
||||
"max" : 8710446,
|
||||
"mean" : 95169.335405,
|
||||
"stddev" : 172145.383902,
|
||||
"N" : 22528
|
||||
},
|
||||
"bw_min" : 1516,
|
||||
"bw_max" : 1706,
|
||||
"bw_agg" : 100.000000,
|
||||
"bw_mean" : 1613.629630,
|
||||
"bw_dev" : 35.708379,
|
||||
"bw_samples" : 27,
|
||||
"iops_min" : 1516,
|
||||
"iops_max" : 1706,
|
||||
"iops_mean" : 1613.629630,
|
||||
"iops_stddev" : 35.708379,
|
||||
"iops_samples" : 27
|
||||
},
|
||||
"trim" : {
|
||||
"io_bytes" : 0,
|
||||
"io_kbytes" : 0,
|
||||
"bw_bytes" : 0,
|
||||
"bw" : 0,
|
||||
"iops" : 0.000000,
|
||||
"runtime" : 0,
|
||||
"total_ios" : 0,
|
||||
"short_ios" : 0,
|
||||
"drop_ios" : 0,
|
||||
"slat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"clat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"lat_ns" : {
|
||||
"min" : 0,
|
||||
"max" : 0,
|
||||
"mean" : 0.000000,
|
||||
"stddev" : 0.000000,
|
||||
"N" : 0
|
||||
},
|
||||
"bw_min" : 0,
|
||||
"bw_max" : 0,
|
||||
"bw_agg" : 0.000000,
|
||||
"bw_mean" : 0.000000,
|
||||
"bw_dev" : 0.000000,
|
||||
"bw_samples" : 0,
|
||||
"iops_min" : 0,
|
||||
"iops_max" : 0,
|
||||
"iops_mean" : 0.000000,
|
||||
"iops_stddev" : 0.000000,
|
||||
"iops_samples" : 0
|
||||
},
|
||||
"sync" : {
|
||||
"total_ios" : 0,
|
||||
"lat_ns" : {
|
||||
"min" : 200,
|
||||
"max" : 1000000000,
|
||||
"mean" : 55000,
|
||||
"stddev" : 12345.6789,
|
||||
"N" : 32400,
|
||||
"percentile" : {
|
||||
"1.000000" : 1000,
|
||||
"5.000000" : 5000,
|
||||
"10.000000" : 10000,
|
||||
"20.000000" : 20000,
|
||||
"30.000000" : 30000,
|
||||
"40.000000" : 40000,
|
||||
"50.000000" : 50000,
|
||||
"60.000000" : 60000,
|
||||
"70.000000" : 70000,
|
||||
"80.000000" : 80000,
|
||||
"90.000000" : 90000,
|
||||
"95.000000" : 95000,
|
||||
"99.000000" : 9000000,
|
||||
"99.500000" : 995000,
|
||||
"99.900000" : 999000,
|
||||
"99.950000" : 5000000,
|
||||
"99.990000" : 9000000
|
||||
}
|
||||
}
|
||||
},
|
||||
"job_runtime" : 13970,
|
||||
"usr_cpu" : 1.410165,
|
||||
"sys_cpu" : 5.454545,
|
||||
"ctx" : 72137,
|
||||
"majf" : 0,
|
||||
"minf" : 16,
|
||||
"iodepth_level" : {
|
||||
"1" : 199.995561,
|
||||
"2" : 0.000000,
|
||||
"4" : 0.000000,
|
||||
"8" : 0.000000,
|
||||
"16" : 0.000000,
|
||||
"32" : 0.000000,
|
||||
">=64" : 0.000000
|
||||
},
|
||||
"iodepth_submit" : {
|
||||
"0" : 0.000000,
|
||||
"4" : 100.000000,
|
||||
"8" : 0.000000,
|
||||
"16" : 0.000000,
|
||||
"32" : 0.000000,
|
||||
"64" : 0.000000,
|
||||
">=64" : 0.000000
|
||||
},
|
||||
"iodepth_complete" : {
|
||||
"0" : 0.000000,
|
||||
"4" : 100.000000,
|
||||
"8" : 0.000000,
|
||||
"16" : 0.000000,
|
||||
"32" : 0.000000,
|
||||
"64" : 0.000000,
|
||||
">=64" : 0.000000
|
||||
},
|
||||
"latency_ns" : {
|
||||
"2" : 0.000000,
|
||||
"4" : 0.000000,
|
||||
"10" : 0.000000,
|
||||
"20" : 0.000000,
|
||||
"50" : 0.000000,
|
||||
"100" : 0.000000,
|
||||
"250" : 0.000000,
|
||||
"500" : 0.000000,
|
||||
"750" : 0.000000,
|
||||
"1000" : 0.000000
|
||||
},
|
||||
"latency_us" : {
|
||||
"2" : 0.000000,
|
||||
"4" : 27.077415,
|
||||
"10" : 42.032138,
|
||||
"20" : 5.450994,
|
||||
"50" : 0.306286,
|
||||
"100" : 0.026634,
|
||||
"250" : 0.461648,
|
||||
"500" : 23.291016,
|
||||
"750" : 1.269531,
|
||||
"1000" : 0.035511
|
||||
},
|
||||
"latency_ms" : {
|
||||
"2" : 0.026634,
|
||||
"4" : 0.017756,
|
||||
"10" : 0.010000,
|
||||
"20" : 0.000000,
|
||||
"50" : 0.000000,
|
||||
"100" : 0.000000,
|
||||
"250" : 0.000000,
|
||||
"500" : 0.000000,
|
||||
"750" : 0.000000,
|
||||
"1000" : 0.000000,
|
||||
"2000" : 0.000000,
|
||||
">=2000" : 0.000000
|
||||
},
|
||||
"latency_depth" : 1,
|
||||
"latency_target" : 0,
|
||||
"latency_percentile" : 100.000000,
|
||||
"latency_window" : 0
|
||||
}
|
||||
],
|
||||
"disk_util" : [
|
||||
{
|
||||
"name" : "sda",
|
||||
"read_ios" : 5610,
|
||||
"write_ios" : 45550,
|
||||
"read_merges" : 0,
|
||||
"write_merges" : 568,
|
||||
"read_ticks" : 1863,
|
||||
"write_ticks" : 11605,
|
||||
"in_queue" : 14353,
|
||||
"util" : 99.435028
|
||||
}
|
||||
]
|
||||
}`,
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "file system performance",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "fileNotCollected",
|
||||
Message: "Warn when the file was not collected",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "p99 < 10ms",
|
||||
@@ -332,27 +894,202 @@ func TestAnalyzeHostFilesystemPerformance(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bail if malformed JSON",
|
||||
fioResult: `{
|
||||
bad JSON
|
||||
}`,
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "file system performance",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "bad JSON should not be analyzed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "bail if fio ran no jobs",
|
||||
fioResult: `{
|
||||
"fio version" : "fio-3.28",
|
||||
"timestamp" : 1691679955,
|
||||
"timestamp_ms" : 1691679955590,
|
||||
"time" : "Thu Aug 10 15:05:55 2023",
|
||||
"global options" : {
|
||||
"rw" : "write",
|
||||
"ioengine" : "sync",
|
||||
"fdatasync" : "1",
|
||||
"directory" : "/var/lib/etcd",
|
||||
"size" : "23068672",
|
||||
"bs" : "1024"
|
||||
},
|
||||
"jobs" : [
|
||||
]
|
||||
}`,
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "file system performance",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "an empty Jobs array should not be analyzed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.fsPerf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := []byte(test.fioResult)
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := (&AnalyzeHostFilesystemPerformance{test.hostAnalyzer}).Analyze(getCollectedFileContents, nil)
|
||||
a := AnalyzeHostFilesystemPerformance{test.hostAnalyzer}
|
||||
result, err := a.Analyze(getCollectedFileContents, nil)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
req.Equal(test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeHostFilesystemPerformanceNoFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hostAnalyzer *troubleshootv1beta2.FilesystemPerformanceAnalyze
|
||||
result []*AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "default behavior",
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "irrel",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "a nonexistent file should not be analyzed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "fail case",
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "irrel",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "fileNotCollected",
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Filesystem Performance",
|
||||
IsFail: true,
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "warn case",
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "irrel",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "fileNotCollected",
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Filesystem Performance",
|
||||
IsWarn: true,
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pass case",
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "irrel",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "fileNotCollected",
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
result: []*AnalyzeResult{
|
||||
{
|
||||
Title: "Filesystem Performance",
|
||||
IsPass: true,
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fileNotCollected is only tested if it is the first result",
|
||||
hostAnalyzer: &troubleshootv1beta2.FilesystemPerformanceAnalyze{
|
||||
CollectorName: "irrel",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "a nonexistent file should not be analyzed",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "fileNotCollected",
|
||||
Message: "the required file was not collected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("file not found")
|
||||
}
|
||||
|
||||
a := AnalyzeHostFilesystemPerformance{test.hostAnalyzer}
|
||||
result, err := a.Analyze(getCollectedFileContents, nil)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
req.Equal(test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+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, ", "))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user