mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c31e61367 | ||
|
|
8e174fb46c | ||
|
|
2a61a8686a | ||
|
|
c85bf9a9a6 | ||
|
|
bfb77ad601 | ||
|
|
c4c66633e5 | ||
|
|
f13a9598b2 | ||
|
|
4fa8411259 | ||
|
|
b693e6650d | ||
|
|
7e3a59cfc0 | ||
|
|
48faf0c144 | ||
|
|
fbbcf87405 | ||
|
|
6530cb364f | ||
|
|
c9b48aa716 | ||
|
|
511671f699 | ||
|
|
7a3bcfc74a | ||
|
|
e88f309391 | ||
|
|
da48621eff | ||
|
|
e51a25dc2f | ||
|
|
7a20aa1934 | ||
|
|
78bcafe489 | ||
|
|
233966d4be | ||
|
|
ebc569fcdb | ||
|
|
0521b0e82b | ||
|
|
34f29f2914 | ||
|
|
1de1d00680 | ||
|
|
92a5f3569f | ||
|
|
495b1a760d | ||
|
|
7bcfd01d43 | ||
|
|
f2ffef80af | ||
|
|
a5244a262c | ||
|
|
18d9a16ceb | ||
|
|
473738d709 | ||
|
|
c34d80c300 | ||
|
|
38c1193ace | ||
|
|
6b87cdb267 | ||
|
|
80cca8a487 | ||
|
|
6e79c169e0 | ||
|
|
7edbec3e45 | ||
|
|
8f8e0dc6e3 | ||
|
|
e2c20cfda4 | ||
|
|
ccb7b1619b | ||
|
|
d5c0dd4857 | ||
|
|
8c5cd5a460 | ||
|
|
9073a14c02 | ||
|
|
3513eeca19 | ||
|
|
34817b67d0 | ||
|
|
388e7ca77d | ||
|
|
d8091909b3 | ||
|
|
9ae416ab23 | ||
|
|
e373c71874 | ||
|
|
e2ac7bf715 | ||
|
|
3d4bd4b601 | ||
|
|
3b2aae1866 | ||
|
|
8a4f539173 | ||
|
|
89b98c197e | ||
|
|
000abae68b | ||
|
|
764f0ac8b6 | ||
|
|
04c7a18da3 | ||
|
|
9b37b98a93 | ||
|
|
32f622a714 | ||
|
|
bcaaa9e59a | ||
|
|
2298ec3030 | ||
|
|
afa2eb552a | ||
|
|
771058980a | ||
|
|
d789119634 | ||
|
|
48beb303be | ||
|
|
e30d279cad | ||
|
|
9c986a74a6 | ||
|
|
e02074941e | ||
|
|
56a68a4fef | ||
|
|
b3e662c988 | ||
|
|
a0fb06f0b9 | ||
|
|
906fa88119 | ||
|
|
eb40b9422f | ||
|
|
f6531b8434 | ||
|
|
e80235f0a8 | ||
|
|
c7b84ad1e5 | ||
|
|
44ae409081 | ||
|
|
d1318ff4fd | ||
|
|
169f3c6ea7 | ||
|
|
7a7507b5e4 | ||
|
|
d5c7522be5 | ||
|
|
f5b6a108a1 | ||
|
|
c4746e766b | ||
|
|
4fadd471e6 | ||
|
|
c397171b79 | ||
|
|
a20f9d9408 | ||
|
|
ebd49e70df | ||
|
|
b66145e514 | ||
|
|
be070c98e1 | ||
|
|
7eecf6c526 | ||
|
|
126346e1fe | ||
|
|
e7fe012f2f | ||
|
|
ec6ec59303 | ||
|
|
e53871b4dc | ||
|
|
93da8b6ac7 | ||
|
|
ad4cfc6d9e | ||
|
|
ad23763d6b | ||
|
|
7120516b13 | ||
|
|
ed34afc51b | ||
|
|
d4e5c0dba4 | ||
|
|
a50263c725 | ||
|
|
818a9bc9a2 | ||
|
|
e98497b78b | ||
|
|
60c2547cc0 | ||
|
|
934bf8ed87 | ||
|
|
05d3e66de9 | ||
|
|
d70ecf7e5f | ||
|
|
7403e39210 | ||
|
|
92382e600f | ||
|
|
b5a56e5239 | ||
|
|
b6f8ac211e | ||
|
|
63510fc89d | ||
|
|
8bdb09ff7d | ||
|
|
b20a8e5321 | ||
|
|
f5652df9da | ||
|
|
b716374070 | ||
|
|
1a637f3c4f | ||
|
|
3b3ae4e7bf |
@@ -0,0 +1,4 @@
|
||||
version: 3
|
||||
|
||||
project:
|
||||
id: github.com/replicatedhq/troubleshoot
|
||||
+17
-1
@@ -1 +1,17 @@
|
||||
* @replicatedhq/day2
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
# More details are here: https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# The '*' pattern is global owners.
|
||||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
# The folders are ordered as follows:
|
||||
|
||||
# In each subsection folders are ordered first by depth, then alphabetically.
|
||||
# This should make it easy to add new rules without breaking existing ones.
|
||||
|
||||
## RULES
|
||||
|
||||
* @replicatedhq/troubleshoot
|
||||
*.md @replicatedhq/cre
|
||||
@@ -7,11 +7,11 @@ labels: 'feature'
|
||||
|
||||
**Describe the rationale for the suggested feature.**
|
||||
|
||||
Insert a description of the rationale for the new feature here. For example, you could describe a problem that Troubleshoot doesn't address, a limitation, or an idea to improve Troubleshoot.
|
||||
Insert a description of the rationale for the new feature here. For example, you could describe a problem that Troubleshoot doesn't address, a limitation, or an idea to improve Troubleshoot.
|
||||
|
||||
**Describe the feature**
|
||||
|
||||
Insert a description of the feature here. Be specific about how it addresses the any problems, limitations, or suggested improvements outlined in the rationale for the feature.
|
||||
Insert a description of the feature here. Be specific about how it addresses the problem. Mention any limitations, or suggested improvements outlined in the rationale for the feature.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
@@ -19,4 +19,4 @@ Describe alternative solutions here. Include any workarounds you've considered.
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add additional context about the feature request. If the change is substantial, consider attaching files to the issue outlining architectural changes, data flows, file formats etc., anything that helps describe the requested change.
|
||||
Add additional context about the feature request. If the change is substantial, consider attaching files to the issue outlining architectural changes, data flows, file formats etc., anything that helps describe the requested change.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
## Description, Motivation and Context
|
||||
|
||||
Please include a summary of the change or what problem it solves. Please also include relevant motivation and context.
|
||||
|
||||
<!--- If it relates to an open issue, please link to the issue here.
|
||||
e.g.
|
||||
Fixes: #414
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] New and existing tests pass locally with introduced changes.
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] The commit message(s) are informative and highlight any breaking changes
|
||||
- [ ] Any documentation required has been added/updated. For changes to https://troubleshoot.sh/ create a PR [here](https://github.com/replicatedhq/troubleshoot.sh/pulls)
|
||||
|
||||
## Does this PR introduce a breaking change?
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
@@ -13,3 +13,15 @@ updates:
|
||||
- "type::chore"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
# Workflow files stored in the
|
||||
# default location of `.github/workflows`
|
||||
directory: "/"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
- "type::chore"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
@@ -26,14 +26,14 @@ jobs:
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- run: make test
|
||||
|
||||
compile-preflight:
|
||||
ensure-schemas-are-generated:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
- name: setup env
|
||||
@@ -41,9 +41,23 @@ jobs:
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
- run: make check-schemas
|
||||
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
- 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 generate preflight
|
||||
- uses: actions/upload-artifact@v1
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/preflight
|
||||
@@ -57,7 +71,7 @@ jobs:
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
@@ -93,23 +107,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- run: chmod +x bin/preflight
|
||||
- run: make e2e-test
|
||||
- run: make preflight-e2e-test
|
||||
|
||||
compile-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
- name: setup env
|
||||
@@ -117,9 +131,9 @@ jobs:
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
- run: make generate support-bundle
|
||||
- uses: actions/upload-artifact@v1
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/support-bundle
|
||||
@@ -128,13 +142,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download support-bundle binary
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
@@ -143,10 +157,27 @@ jobs:
|
||||
- run: ./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
|
||||
- run: ./bin/support-bundle https://kots.io
|
||||
|
||||
validate-supportbundle-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: replicatedhq/action-k3s@main
|
||||
id: k3s
|
||||
with:
|
||||
version: v1.23.6-k3s1
|
||||
- name: Download support bundle binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: make support-bundle-e2e-test
|
||||
|
||||
compile-collect:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
- name: setup env
|
||||
@@ -154,9 +185,9 @@ jobs:
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
- run: make generate collect
|
||||
- uses: actions/upload-artifact@v1
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: collect
|
||||
path: bin/collect
|
||||
@@ -166,17 +197,17 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v') != true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- uses: actions/setup-go@v1
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: build --rm-dist --snapshot --config deploy/.goreleaser.yaml
|
||||
@@ -190,7 +221,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: azure/docker-login@v1
|
||||
with:
|
||||
@@ -200,7 +231,7 @@ jobs:
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- uses: actions/setup-go@v1
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
@@ -222,7 +253,7 @@ jobs:
|
||||
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: "v0.183.0"
|
||||
args: release --rm-dist --config deploy/.goreleaser.yaml
|
||||
@@ -231,10 +262,10 @@ jobs:
|
||||
|
||||
|
||||
- name: Update new preflight version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.38
|
||||
uses: rajatjindal/krew-release-bot@v0.0.43
|
||||
with:
|
||||
krew_template_file: deploy/krew/preflight.yaml
|
||||
- name: Update new support-bundle version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.38
|
||||
uses: rajatjindal/krew-release-bot@v0.0.43
|
||||
with:
|
||||
krew_template_file: deploy/krew/support-bundle.yaml
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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@v1
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
@@ -0,0 +1,18 @@
|
||||
name: PR license scan
|
||||
|
||||
on:
|
||||
pull_request_target: # this is safe as these scans do not execute provided code
|
||||
|
||||
jobs:
|
||||
fossa-scan:
|
||||
runs-on: ubuntu-latest
|
||||
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 }}
|
||||
@@ -12,15 +12,11 @@ on:
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
require_pr_labels:
|
||||
require-pr-labels:
|
||||
uses: replicatedhq/reusable-workflows/.github/workflows/pr-enforce-labels.yaml@main
|
||||
require-bug-labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v2
|
||||
with:
|
||||
mode: exactly
|
||||
count: 1
|
||||
labels: "type::feature, type::bug, type::chore, type::tests, type::security, type::docs"
|
||||
|
||||
- uses: mheap/github-action-required-labels@v2
|
||||
if: ${{ github.event.label.name == 'type::bug' }}
|
||||
with:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*.so
|
||||
*.dylib
|
||||
bin
|
||||
.DS_Store
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
@@ -34,3 +35,9 @@ workspace.*
|
||||
|
||||
cosign.key
|
||||
sbom/
|
||||
|
||||
# Ignore local pre-commit config
|
||||
.pre-commit-config.yaml
|
||||
|
||||
# Ignore generated support bundles
|
||||
*.tar.gz
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# https://golangci-lint.run/usage/configuration/#config-file
|
||||
|
||||
run:
|
||||
allow-parallel-runners: true
|
||||
timeout: 30s
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gosec
|
||||
+34
-4
@@ -1,10 +1,16 @@
|
||||
# Contributing to Troubleshoot
|
||||
|
||||
Thank you for your interest in Troubleshoot, we welcome your participation. Please familiarize yourself with our [Code of Conduct](https://github.com/replicatedhq/troubleshoot/blob/master/CODE_OF_CONDUCT.md) prior to contributing. There are a number of ways to participate in Troubleshoot as outlined below:
|
||||
Thank you for your interest in Troubleshoot, we welcome your participation. Please familiarize yourself with our [Code of Conduct](https://github.com/replicatedhq/troubleshoot/blob/main/CODE_OF_CONDUCT.md) prior to contributing. There are a number of ways to participate in Troubleshoot as outlined below:
|
||||
|
||||
# Community
|
||||
|
||||
For discussions about developing Troubleshoot, there's an [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot), plus IRC using [Libera](ircs://irc.libera.chat:6697/#troubleshoot) (#troubleshoot).
|
||||
|
||||
There are [community meetings](https://calendar.google.com/calendar/u/0?cid=Y19mMGx1aGhiZGtscGllOGo5dWpicXMwNnN1a0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t) on a regular basis, with a shared calendar and [public notes](https://hackmd.io/yZbotEHdTg6TfRZBzb8Tcg)
|
||||
|
||||
## Issues
|
||||
|
||||
- [Request a New Feature](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=feature&template=feature_enhancement.md) Create an issue to add functionality that addresses a problem or adds an enhancement.
|
||||
- [Request a New Feature](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=feature&template=feature_enhancement.md) Create an issue to add functionality that addresses a problem or adds an enhancement.
|
||||
- [Report a Bug](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=bug&template=bug_report.md) Report a problem or unexpected behaviour with Troubleshoot.
|
||||
|
||||
## Design Principles
|
||||
@@ -15,7 +21,7 @@ When implementing a new feature please review the [design principles](./design/d
|
||||
|
||||
To get started we recommend:
|
||||
|
||||
1. Go (v1.17 or later)
|
||||
1. Go (v1.19 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
|
||||
@@ -24,6 +30,8 @@ To get started we recommend:
|
||||
> 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`
|
||||
|
||||
6. Install [golangci-lint] linter and run `make lint` to execute additional code linters.
|
||||
|
||||
### Testing
|
||||
|
||||
To run the tests locally run the following:
|
||||
@@ -47,12 +55,34 @@ This is a rough outline of how to prepare a contribution:
|
||||
- Create a fork of this repo.
|
||||
- Create a topic branch from where you want to base your work (branched from `main` is a safe choice).
|
||||
- Make commits of logical units.
|
||||
- When your changes are ready to merge, squash your history to 1 commit.
|
||||
- For example, if you want to squash your last 3 commits and write a new commit message:
|
||||
```
|
||||
git reset --soft HEAD~3 &&
|
||||
git commit
|
||||
```
|
||||
|
||||
- If you want to keep the previous commit messages and concatenate them all into a new commit, you can do something like this instead:
|
||||
```
|
||||
git reset --soft HEAD~3 &&
|
||||
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
|
||||
```
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Submit a pull request to the original repository. It will be reviewed in a timely manner.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
A pull request should address a single issue, feature or bug. For example, lets say you've written code that fixes two issues. That's great! However, you should submit two small pull requests, one for each issue as opposed to combining them into a single larger pull request. In general the size of the pull request should be kept small in order to make it easy for a reviewer to understand, and to minimize risks from integrating many changes at the same time. For example, if you are working on a large feature you should break it into several smaller PRs by implementing the feature as changes to several packages and submitting a separate pull request for each one.
|
||||
A pull request should address a single issue, feature or bug. For example, lets say you've written code that fixes two issues. That's great! However, you should submit two small pull requests, one for each issue as opposed to combining them into a single larger pull request. In general the size of the pull request should be kept small in order to make it easy for a reviewer to understand, and to minimize risks from integrating many changes at the same time. For example, if you are working on a large feature you should break it into several smaller PRs by implementing the feature as changes to several packages and submitting a separate pull request for each one. Squash commit history when preparing your PR so it merges as 1 commit.
|
||||
|
||||
Code submitted in pull requests must be properly documented, formatted and tested in order to be approved and merged. The following guidelines describe the things a reviewer will look for when they evaluate your pull request. Here's a tip. If your reviewer doesn't understand what the code is doing, they won't approve the pull request. Strive to make code clear and well documented. If possible, request a reviewer that has some context on the PR.
|
||||
|
||||
### Commit messages
|
||||
|
||||
Commit messages should follow the general guidelines:
|
||||
|
||||
- Breaking changes should be highlighted in the heading of the commit message.
|
||||
- Commits should be clear about their purpose (and a single commit per thing that changed)
|
||||
- Messages should be descriptive:
|
||||
- First line, 50 chars or less, as a heading/title that people can find
|
||||
- Then a paragraph explaining things
|
||||
- Consider a footer with links to which bugs they fix etc, bearing in mind that Github does some of this magic already
|
||||
@@ -45,10 +45,14 @@ ffi: fmt vet
|
||||
test: generate fmt vet
|
||||
go test ${BUILDFLAGS} ./pkg/... ./cmd/... -coverprofile cover.out
|
||||
|
||||
.PHONY: e2e-test
|
||||
e2e-test:
|
||||
.PHONY: preflight-e2e-test
|
||||
preflight-e2e-test:
|
||||
./test/validate-preflight-e2e.sh
|
||||
|
||||
.PHONY: support-bundle-e2e-test
|
||||
support-bundle-e2e-test:
|
||||
./test/validate-support-bundle-e2e.sh
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
@@ -60,10 +64,10 @@ preflight:
|
||||
.PHONY: analyze
|
||||
analyze:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
|
||||
.PHONY: collect
|
||||
collect:
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
|
||||
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@@ -91,11 +95,23 @@ openapischema: controller-gen
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta1
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta2
|
||||
|
||||
check-schemas: generate schemas
|
||||
@if [ -n "$(shell 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
|
||||
./bin/schemagen --output-dir ./schemas
|
||||
|
||||
.PHONY: docs
|
||||
docs: fmt vet
|
||||
go build ${LDFLAGS} -o bin/docsgen github.com/replicatedhq/troubleshoot/cmd/docsgen
|
||||
./bin/docsgen
|
||||
|
||||
controller-gen:
|
||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
@@ -155,7 +171,7 @@ generate-sbom: install-spdx-sbom-generator
|
||||
$(SPDX_GENERATOR) -o ./sbom/spdx
|
||||
|
||||
sbom/assets/troubleshoot-sbom.tgz: generate-sbom
|
||||
tar -czf sbom/assets/troubleshoot-sbom.tgz sbom/spdx/*.spdx
|
||||
tar -czf sbom/assets/troubleshoot-sbom.tgz sbom/spdx/*.spdx
|
||||
|
||||
sbom: sbom/assets/troubleshoot-sbom.tgz
|
||||
cosign sign-blob -key cosign.key sbom/assets/troubleshoot-sbom.tgz > sbom/assets/troubleshoot-sbom.tgz.sig
|
||||
@@ -182,4 +198,12 @@ scan:
|
||||
--exit-code=1 \
|
||||
--severity="HIGH,CRITICAL" \
|
||||
--ignore-unfixed \
|
||||
./
|
||||
./
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run -c .golangci.yaml
|
||||
|
||||
.PHONY: lint-and-fix
|
||||
lint-and-fix:
|
||||
golangci-lint run --fix -c .golangci.yaml
|
||||
|
||||
@@ -9,15 +9,17 @@ Preflight checks are an easy-to-run set of conformance tests that can be written
|
||||
|
||||
To run a sample preflight check from a sample application, install the preflight kubectl plugin:
|
||||
|
||||
```shell
|
||||
```
|
||||
curl https://krew.sh/preflight | bash
|
||||
```
|
||||
and run:
|
||||
and run, where https://preflight.replicated.com provides an **example** preflight spec:
|
||||
|
||||
```shell
|
||||
```
|
||||
kubectl preflight https://preflight.replicated.com
|
||||
```
|
||||
|
||||
**NOTE** this is an example. Do **not** use to validate real scenarios.
|
||||
|
||||
For more details on creating the custom resource files that drive preflight checks, visit [creating preflight checks](https://troubleshoot.sh/docs/preflight/introduction/).
|
||||
|
||||
|
||||
@@ -26,16 +28,21 @@ A support bundle is an archive that's created in-cluster, by collecting logs and
|
||||
|
||||
To collect a sample support bundle, install the troubleshoot kubectl plugin:
|
||||
|
||||
```shell
|
||||
```
|
||||
curl https://krew.sh/support-bundle | bash
|
||||
```
|
||||
and run:
|
||||
and run, where https://support-bundle.replicated.com provides an **example** support bundle spec:
|
||||
|
||||
```shell
|
||||
```
|
||||
kubectl support-bundle https://support-bundle.replicated.com
|
||||
```
|
||||
|
||||
**NOTE** this is an example. Do **not** use to validate real scenarios.
|
||||
|
||||
For more details on creating the custom resource files that drive support-bundle collection, visit [creating collectors](https://troubleshoot.sh/docs/collect/) and [creating analyzers](https://troubleshoot.sh/docs/analyze/).
|
||||
|
||||
And see our other tool [sbctl](https://github.com/replicatedhq/sbctl) that makes it easier to interact with support bundles using `kubectl` commands you already know
|
||||
|
||||
# Community
|
||||
|
||||
For questions about using Troubleshoot, there's a [Replicated Community](https://help.replicated.com/community) forum, and a [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot).
|
||||
@@ -48,7 +55,7 @@ A signed SBOM that includes Troubleshoot dependencies is included in each relea
|
||||
|
||||
The following example illustrates using [cosign](https://github.com/sigstore/cosign) to verify that **troubleshoot-sbom.tgz** has
|
||||
not been tampered with.
|
||||
```shell
|
||||
```
|
||||
$ cosign verify-blob -key key.pub -signature troubleshoot-sbom.tgz.sig troubleshoot-sbom.tgz
|
||||
Verified OK
|
||||
```
|
||||
```
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
preflightcli "github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
|
||||
troubleshootcli "github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "docsgen",
|
||||
Short: "Generate markdown docs for the commands in this project",
|
||||
}
|
||||
preflight := preflightcli.RootCmd()
|
||||
troubleshoot := troubleshootcli.RootCmd()
|
||||
commands := []*cobra.Command{preflight, troubleshoot}
|
||||
|
||||
for _, command := range commands {
|
||||
err := doc.GenMarkdownTree(command, "./docs")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/docsgen/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -29,24 +30,14 @@ that a cluster meets the requirements to run an application.`,
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
return runPreflights(v, args[0])
|
||||
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().Bool("interactive", true, "interactive preflights")
|
||||
cmd.Flags().String("format", "human", "output format, one of human, json, yaml. only used when interactive is set to false")
|
||||
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
|
||||
cmd.Flags().Bool("collect-without-permissions", true, "always run preflight checks even if some require permissions that preflight does not have")
|
||||
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
|
||||
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 preflight checks")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging")
|
||||
preflight.AddFlags(cmd.PersistentFlags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
|
||||
@@ -204,10 +204,11 @@ func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
|
||||
uri := widgets.NewParagraph()
|
||||
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
|
||||
uri.Border = false
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2)
|
||||
// For long urls that lead to wrapping text, make the rectangle bigger by
|
||||
// increasing the calculated height by 2
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2) + 2
|
||||
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(uri)
|
||||
currentTop = currentTop + height + 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "support-bundle [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
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.`,
|
||||
@@ -34,7 +33,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
v := viper.GetViper()
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
return runTroubleshoot(v, args[0])
|
||||
return runTroubleshoot(v, args)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,6 +46,8 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
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.io/kind=supportbundle-spec"}, "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().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")
|
||||
@@ -56,6 +57,10 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
cmd.Flags().MarkHidden("allow-insecure-connections")
|
||||
|
||||
// `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.`")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
@@ -76,19 +81,6 @@ func initConfig() {
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
|
||||
for _, inList := range list {
|
||||
if collector.ClusterResources != nil && inList.ClusterResources != nil {
|
||||
return list
|
||||
}
|
||||
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
return append(list, &collector)
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
|
||||
return err
|
||||
|
||||
+142
-38
@@ -22,16 +22,23 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/httputil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
if v.GetBool("load-cluster-specs") == false && len(arg) < 1 {
|
||||
return errors.New("flag load-cluster-specs must be set if no specs are provided on the command line")
|
||||
}
|
||||
|
||||
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
if interactive {
|
||||
@@ -68,23 +75,139 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
})
|
||||
}
|
||||
|
||||
collectorContent, err := supportbundle.LoadSupportBundleSpec(arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
// we support both raw collector kinds and supportbundle kinds here
|
||||
supportBundle, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse collector")
|
||||
}
|
||||
var mainBundle *troubleshootv1beta2.SupportBundle
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
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 i, 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")
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
mainBundle = supportBundle
|
||||
} else {
|
||||
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 v.GetBool("load-cluster-specs") {
|
||||
labelSelector := strings.Join(v.GetStringSlice("selector"), ",")
|
||||
|
||||
parsedSelector, err := labels.Parse(labelSelector)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
namespace := ""
|
||||
if v.GetString("namespace") != "" {
|
||||
namespace = v.GetString("namespace")
|
||||
}
|
||||
|
||||
config, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert create k8s client")
|
||||
}
|
||||
|
||||
var bundlesFromCluster []string
|
||||
|
||||
// Search cluster for Troubleshoot objects in cluster
|
||||
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), namespace, specs.SupportBundleKey)
|
||||
if err != nil {
|
||||
logger.Printf("failed to load support bundle spec from secrets: %s", err)
|
||||
}
|
||||
bundlesFromCluster = append(bundlesFromCluster, bundlesFromSecrets...)
|
||||
|
||||
bundlesFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), namespace, specs.SupportBundleKey)
|
||||
if err != nil {
|
||||
logger.Printf("failed to load support bundle spec from secrets: %s", err)
|
||||
}
|
||||
bundlesFromCluster = append(bundlesFromCluster, bundlesFromConfigMaps...)
|
||||
|
||||
for _, bundle := range bundlesFromCluster {
|
||||
multidocs := strings.Split(string(bundle), "\n---\n")
|
||||
parsedBundleFromSecret, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
logger.Printf("failed to parse support bundle spec: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if mainBundle == nil {
|
||||
mainBundle = parsedBundleFromSecret
|
||||
} else {
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, parsedBundleFromSecret)
|
||||
}
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
logger.Printf("failed to parse redactors from doc: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
|
||||
var redactorsFromCluster []string
|
||||
|
||||
// Search cluster for Troubleshoot objects in ConfigMaps
|
||||
redactorsFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), namespace, specs.RedactorKey)
|
||||
if err != nil {
|
||||
logger.Printf("failed to load redactor specs from config maps: %s", err)
|
||||
}
|
||||
redactorsFromCluster = append(redactorsFromCluster, redactorsFromSecrets...)
|
||||
|
||||
redactorsFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), namespace, specs.RedactorKey)
|
||||
if err != nil {
|
||||
logger.Printf("failed to load redactor specs from config maps: %s", err)
|
||||
}
|
||||
redactorsFromCluster = append(redactorsFromCluster, redactorsFromConfigMaps...)
|
||||
|
||||
for _, redactor := range redactorsFromCluster {
|
||||
multidocs := strings.Split(string(redactor), "\n---\n")
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
logger.Printf("failed to parse redactors from doc: %s", err)
|
||||
}
|
||||
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
|
||||
if mainBundle == nil {
|
||||
return errors.New("no specs found in cluster")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorObj, err := supportbundle.GetRedactorFromURI(redactor)
|
||||
if err != nil {
|
||||
@@ -96,25 +219,6 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
}
|
||||
}
|
||||
|
||||
for i, additionalDoc := range multidocs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode(additionalDoc, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse additional doc %d", i)
|
||||
}
|
||||
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
|
||||
}
|
||||
|
||||
var collectorCB func(chan interface{}, string)
|
||||
progressChan := make(chan interface{}) // non-zero buffer can result in missed messages
|
||||
finishedCh := make(chan bool, 1)
|
||||
@@ -193,7 +297,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
|
||||
}
|
||||
|
||||
response, err := supportbundle.CollectSupportBundleFromSpec(&supportBundle.Spec, additionalRedactors, createOpts)
|
||||
response, err := supportbundle.CollectSupportBundleFromSpec(&mainBundle.Spec, additionalRedactors, createOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run collect and analyze process")
|
||||
}
|
||||
@@ -202,7 +306,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
close(finishedCh) // this removes the spinner
|
||||
isFinishedChClosed = true
|
||||
|
||||
if err := showInteractiveResults(supportBundle.Name, response.AnalyzerResults); err != nil {
|
||||
if err := showInteractiveResults(mainBundle.Name, response.AnalyzerResults); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
} else {
|
||||
@@ -211,7 +315,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
}
|
||||
|
||||
if !response.FileUploaded {
|
||||
if appName := supportBundle.Labels["applicationName"]; appName != "" {
|
||||
if appName := mainBundle.Labels["applicationName"]; appName != "" {
|
||||
f := `A support bundle for %s has been created in this directory
|
||||
named %s. Please upload it on the Troubleshoot page of
|
||||
the %s Admin Console to begin analysis.`
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostCollector
|
||||
metadata:
|
||||
name: modules
|
||||
spec:
|
||||
collectors:
|
||||
- cpu: {}
|
||||
- memory: {}
|
||||
# - kernelModules: {}
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: RemoteCollector
|
||||
metadata:
|
||||
name: modules
|
||||
spec:
|
||||
collectors:
|
||||
- cpu: {}
|
||||
- memory: {}
|
||||
# - kernelModules: {}
|
||||
@@ -687,7 +687,6 @@ spec:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
- storageClassName
|
||||
type: object
|
||||
textAnalyze:
|
||||
properties:
|
||||
|
||||
@@ -687,7 +687,6 @@ spec:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
- storageClassName
|
||||
type: object
|
||||
textAnalyze:
|
||||
properties:
|
||||
|
||||
@@ -718,7 +718,6 @@ spec:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
- storageClassName
|
||||
type: object
|
||||
textAnalyze:
|
||||
properties:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -367,6 +367,27 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
@@ -382,6 +403,27 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
@@ -397,6 +439,27 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
@@ -2114,14 +2177,14 @@ spec:
|
||||
type: string
|
||||
ports:
|
||||
description: List of ports to expose from the
|
||||
container. Exposing a port here gives the system
|
||||
additional information about the network connections
|
||||
a container uses, but is primarily informational.
|
||||
Not specifying a port here DOES NOT prevent
|
||||
that port from being exposed. Any port which
|
||||
is listening on the default "0.0.0.0" address
|
||||
inside a container will be accessible from the
|
||||
network. Cannot be updated.
|
||||
container. Not specifying a port here DOES NOT
|
||||
prevent that port from being exposed. Any port
|
||||
which is listening on the default "0.0.0.0"
|
||||
address inside a container will be accessible
|
||||
from the network. Modifying this array with
|
||||
strategic merge patch may corrupt the data.
|
||||
For more information See https://github.com/kubernetes/kubernetes/issues/108255.
|
||||
Cannot be updated.
|
||||
items:
|
||||
description: ContainerPort represents a network
|
||||
port in a single container.
|
||||
@@ -2933,9 +2996,6 @@ spec:
|
||||
and it cannot be modified by updating the pod spec.
|
||||
In order to add an ephemeral container to an existing
|
||||
pod, use the pod's ephemeralcontainers subresource.
|
||||
This field is beta-level and available on clusters
|
||||
that haven't disabled the EphemeralContainers feature
|
||||
gate.
|
||||
items:
|
||||
description: "An EphemeralContainer is a temporary
|
||||
container that you may add to an existing Pod for
|
||||
@@ -2947,9 +3007,7 @@ spec:
|
||||
the Pod to exceed its resource allocation. \n To
|
||||
add an ephemeral container, use the ephemeralcontainers
|
||||
subresource of an existing Pod. Ephemeral containers
|
||||
may not be removed or restarted. \n This is a beta
|
||||
feature available on clusters that haven't disabled
|
||||
the EphemeralContainers feature gate."
|
||||
may not be removed or restarted."
|
||||
properties:
|
||||
args:
|
||||
description: 'Arguments to the entrypoint. The
|
||||
@@ -4354,6 +4412,20 @@ spec:
|
||||
description: 'Use the host''s pid namespace. Optional:
|
||||
Default to false.'
|
||||
type: boolean
|
||||
hostUsers:
|
||||
description: 'Use the host''s user namespace. Optional:
|
||||
Default to true. If set to true or not present, the
|
||||
pod will be run in the host user namespace, useful
|
||||
for when the pod needs a feature only available to
|
||||
the host user namespace, such as loading a kernel
|
||||
module with CAP_SYS_MODULE. When set to false, a new
|
||||
userns is created for the pod. Setting false is useful
|
||||
for mitigating container breakout vulnerabilities
|
||||
even allowing users to run their containers as root
|
||||
without actually having root privileges on the host.
|
||||
This field is alpha-level and is only honored by servers
|
||||
that enable the UserNamespacesSupport feature.'
|
||||
type: boolean
|
||||
hostname:
|
||||
description: Specifies the hostname of the Pod If not
|
||||
specified, the pod's hostname will be set to a system-defined
|
||||
@@ -5022,14 +5094,14 @@ spec:
|
||||
type: string
|
||||
ports:
|
||||
description: List of ports to expose from the
|
||||
container. Exposing a port here gives the system
|
||||
additional information about the network connections
|
||||
a container uses, but is primarily informational.
|
||||
Not specifying a port here DOES NOT prevent
|
||||
that port from being exposed. Any port which
|
||||
is listening on the default "0.0.0.0" address
|
||||
inside a container will be accessible from the
|
||||
network. Cannot be updated.
|
||||
container. Not specifying a port here DOES NOT
|
||||
prevent that port from being exposed. Any port
|
||||
which is listening on the default "0.0.0.0"
|
||||
address inside a container will be accessible
|
||||
from the network. Modifying this array with
|
||||
strategic merge patch may corrupt the data.
|
||||
For more information See https://github.com/kubernetes/kubernetes/issues/108255.
|
||||
Cannot be updated.
|
||||
items:
|
||||
description: ContainerPort represents a network
|
||||
port in a single container.
|
||||
@@ -5800,20 +5872,19 @@ spec:
|
||||
if this is set. \n If the OS field is set to linux,
|
||||
the following fields must be unset: -securityContext.windowsOptions
|
||||
\n If the OS field is set to windows, following fields
|
||||
must be unset: - spec.hostPID - spec.hostIPC - spec.securityContext.seLinuxOptions
|
||||
- spec.securityContext.seccompProfile - spec.securityContext.fsGroup
|
||||
- spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls
|
||||
- spec.shareProcessNamespace - spec.securityContext.runAsUser
|
||||
- spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups
|
||||
- spec.containers[*].securityContext.seLinuxOptions
|
||||
must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers
|
||||
- spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile
|
||||
- spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy
|
||||
- spec.securityContext.sysctls - spec.shareProcessNamespace
|
||||
- spec.securityContext.runAsUser - spec.securityContext.runAsGroup
|
||||
- spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions
|
||||
- spec.containers[*].securityContext.seccompProfile
|
||||
- spec.containers[*].securityContext.capabilities
|
||||
- spec.containers[*].securityContext.readOnlyRootFilesystem
|
||||
- spec.containers[*].securityContext.privileged -
|
||||
spec.containers[*].securityContext.allowPrivilegeEscalation
|
||||
- spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser
|
||||
- spec.containers[*].securityContext.runAsGroup This
|
||||
is a beta field and requires the IdentifyPodOS feature"
|
||||
- spec.containers[*].securityContext.runAsGroup"
|
||||
properties:
|
||||
name:
|
||||
description: 'Name is the name of the operating
|
||||
@@ -6258,6 +6329,21 @@ spec:
|
||||
"value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
matchLabelKeys:
|
||||
description: MatchLabelKeys is a set of pod label
|
||||
keys to select the pods over which spreading
|
||||
will be calculated. The keys are used to lookup
|
||||
values from the incoming pod labels, those key-value
|
||||
labels are ANDed with labelSelector to select
|
||||
the group of existing pods over which spreading
|
||||
will be calculated for the incoming pod. Keys
|
||||
that don't exist in the incoming pod labels
|
||||
will be ignored. A null or empty list means
|
||||
only match against labelSelector.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
maxSkew:
|
||||
description: 'MaxSkew describes the degree to
|
||||
which pods may be unevenly distributed. When
|
||||
@@ -6308,11 +6394,37 @@ spec:
|
||||
with the same labelSelector cannot be scheduled,
|
||||
because computed skew will be 3(3 - 0) if new
|
||||
Pod is scheduled to any of the three zones,
|
||||
it will violate MaxSkew. \n This is an alpha
|
||||
field and requires enabling MinDomainsInPodTopologySpread
|
||||
feature gate."
|
||||
it will violate MaxSkew. \n This is a beta field
|
||||
and requires the MinDomainsInPodTopologySpread
|
||||
feature gate to be enabled (enabled by default)."
|
||||
format: int32
|
||||
type: integer
|
||||
nodeAffinityPolicy:
|
||||
description: "NodeAffinityPolicy indicates how
|
||||
we will treat Pod's nodeAffinity/nodeSelector
|
||||
when calculating pod topology spread skew. Options
|
||||
are: - Honor: only nodes matching nodeAffinity/nodeSelector
|
||||
are included in the calculations. - Ignore:
|
||||
nodeAffinity/nodeSelector are ignored. All nodes
|
||||
are included in the calculations. \n If this
|
||||
value is nil, the behavior is equivalent to
|
||||
the Honor policy. This is a alpha-level feature
|
||||
enabled by the NodeInclusionPolicyInPodTopologySpread
|
||||
feature flag."
|
||||
type: string
|
||||
nodeTaintsPolicy:
|
||||
description: "NodeTaintsPolicy indicates how we
|
||||
will treat node taints when calculating pod
|
||||
topology spread skew. Options are: - Honor:
|
||||
nodes without taints, along with tainted nodes
|
||||
for which the incoming pod has a toleration,
|
||||
are included. - Ignore: node taints are ignored.
|
||||
All nodes are included. \n If this value is
|
||||
nil, the behavior is equivalent to the Ignore
|
||||
policy. This is a alpha-level feature enabled
|
||||
by the NodeInclusionPolicyInPodTopologySpread
|
||||
feature flag."
|
||||
type: string
|
||||
topologyKey:
|
||||
description: TopologyKey is the key of node labels.
|
||||
Nodes that have a label with this key and identical
|
||||
@@ -6321,8 +6433,9 @@ spec:
|
||||
and try to put balanced number of pods into
|
||||
each bucket. We define a domain as a particular
|
||||
instance of a topology. Also, we define an eligible
|
||||
domain as a domain whose nodes match the node
|
||||
selector. e.g. If TopologyKey is "kubernetes.io/hostname",
|
||||
domain as a domain whose nodes meet the requirements
|
||||
of nodeAffinityPolicy and nodeTaintsPolicy.
|
||||
e.g. If TopologyKey is "kubernetes.io/hostname",
|
||||
each Node is a domain of that topology. And,
|
||||
if TopologyKey is "topology.kubernetes.io/zone",
|
||||
each zone is a domain of that topology. It's
|
||||
|
||||
@@ -41,6 +41,10 @@ spec:
|
||||
properties:
|
||||
blockDevices:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -93,6 +97,10 @@ spec:
|
||||
type: object
|
||||
certificate:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -138,6 +146,10 @@ spec:
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -183,6 +195,10 @@ spec:
|
||||
type: object
|
||||
diskUsage:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -228,6 +244,10 @@ spec:
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -273,6 +293,10 @@ spec:
|
||||
type: object
|
||||
hostOS:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -318,6 +342,10 @@ spec:
|
||||
type: object
|
||||
hostServices:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -363,6 +391,10 @@ spec:
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -408,6 +440,10 @@ spec:
|
||||
type: object
|
||||
httpLoadBalancer:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -453,6 +489,10 @@ spec:
|
||||
type: object
|
||||
ipv4Interfaces:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -498,6 +538,10 @@ spec:
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -543,6 +587,10 @@ spec:
|
||||
type: object
|
||||
memory:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -588,6 +636,10 @@ spec:
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -633,6 +685,10 @@ spec:
|
||||
type: object
|
||||
tcpConnect:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -678,6 +734,10 @@ spec:
|
||||
type: object
|
||||
tcpLoadBalancer:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -723,6 +783,10 @@ spec:
|
||||
type: object
|
||||
tcpPortStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -768,6 +832,10 @@ spec:
|
||||
type: object
|
||||
time:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -1044,6 +1112,22 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorName:
|
||||
type: string
|
||||
command:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
required:
|
||||
- args
|
||||
- command
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
amzn:
|
||||
|
||||
@@ -41,6 +41,10 @@ spec:
|
||||
properties:
|
||||
blockDevices:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -93,6 +97,10 @@ spec:
|
||||
type: object
|
||||
certificate:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -138,6 +146,10 @@ spec:
|
||||
type: object
|
||||
cpu:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -183,6 +195,10 @@ spec:
|
||||
type: object
|
||||
diskUsage:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -228,6 +244,10 @@ spec:
|
||||
type: object
|
||||
filesystemPerformance:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -273,6 +293,10 @@ spec:
|
||||
type: object
|
||||
hostOS:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -318,6 +342,10 @@ spec:
|
||||
type: object
|
||||
hostServices:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -363,6 +391,10 @@ spec:
|
||||
type: object
|
||||
http:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -408,6 +440,10 @@ spec:
|
||||
type: object
|
||||
httpLoadBalancer:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -453,6 +489,10 @@ spec:
|
||||
type: object
|
||||
ipv4Interfaces:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -498,6 +538,10 @@ spec:
|
||||
type: object
|
||||
kernelModules:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -543,6 +587,10 @@ spec:
|
||||
type: object
|
||||
memory:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -588,6 +636,10 @@ spec:
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -633,6 +685,10 @@ spec:
|
||||
type: object
|
||||
tcpConnect:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -678,6 +734,10 @@ spec:
|
||||
type: object
|
||||
tcpLoadBalancer:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -723,6 +783,10 @@ spec:
|
||||
type: object
|
||||
tcpPortStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -768,6 +832,10 @@ spec:
|
||||
type: object
|
||||
time:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -1044,6 +1112,22 @@ spec:
|
||||
exclude:
|
||||
type: BoolString
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorName:
|
||||
type: string
|
||||
command:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
required:
|
||||
- args
|
||||
- command
|
||||
type: object
|
||||
systemPackages:
|
||||
properties:
|
||||
amzn:
|
||||
|
||||
@@ -41,6 +41,10 @@ spec:
|
||||
properties:
|
||||
cephStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -89,6 +93,10 @@ spec:
|
||||
type: object
|
||||
clusterPodStatuses:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -136,6 +144,10 @@ spec:
|
||||
type: object
|
||||
clusterVersion:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -179,6 +191,10 @@ spec:
|
||||
type: object
|
||||
configMap:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
configMapName:
|
||||
@@ -230,6 +246,10 @@ spec:
|
||||
type: object
|
||||
containerRuntime:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -273,6 +293,10 @@ spec:
|
||||
type: object
|
||||
customResourceDefinition:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
customResourceDefinitionName:
|
||||
@@ -319,6 +343,10 @@ spec:
|
||||
type: object
|
||||
deploymentStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -371,6 +399,10 @@ spec:
|
||||
type: object
|
||||
distribution:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -414,6 +446,10 @@ spec:
|
||||
type: object
|
||||
imagePullSecret:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -460,6 +496,10 @@ spec:
|
||||
type: object
|
||||
ingress:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -509,6 +549,10 @@ spec:
|
||||
type: object
|
||||
jobStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -559,8 +603,67 @@ spec:
|
||||
- name
|
||||
- outcomes
|
||||
type: object
|
||||
jsonCompare:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
path:
|
||||
type: string
|
||||
strict:
|
||||
type: BoolString
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
longhorn:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -609,6 +712,10 @@ spec:
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -657,6 +764,10 @@ spec:
|
||||
type: object
|
||||
nodeResources:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -726,6 +837,10 @@ spec:
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -774,6 +889,10 @@ spec:
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -822,6 +941,10 @@ spec:
|
||||
type: object
|
||||
registryImages:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
@@ -868,6 +991,10 @@ spec:
|
||||
type: object
|
||||
replicasetStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -925,6 +1052,10 @@ spec:
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -976,6 +1107,10 @@ spec:
|
||||
type: object
|
||||
statefulsetStatus:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -1028,6 +1163,10 @@ spec:
|
||||
type: object
|
||||
storageClass:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -1070,10 +1209,13 @@ spec:
|
||||
type: BoolString
|
||||
required:
|
||||
- outcomes
|
||||
- storageClassName
|
||||
type: object
|
||||
sysctl:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -1117,12 +1259,20 @@ spec:
|
||||
type: object
|
||||
textAnalyze:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
excludeFiles:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
fileName:
|
||||
type: string
|
||||
ignoreIfNoFiles:
|
||||
@@ -1170,6 +1320,10 @@ spec:
|
||||
type: object
|
||||
weaveReport:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
@@ -1181,6 +1335,61 @@ spec:
|
||||
required:
|
||||
- reportFileGlob
|
||||
type: object
|
||||
yamlCompare:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
path:
|
||||
type: string
|
||||
strict:
|
||||
type: BoolString
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
collectors:
|
||||
@@ -1483,6 +1692,27 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
@@ -1498,6 +1728,27 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
@@ -1513,6 +1764,27 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
@@ -3230,14 +3502,14 @@ spec:
|
||||
type: string
|
||||
ports:
|
||||
description: List of ports to expose from the
|
||||
container. Exposing a port here gives the system
|
||||
additional information about the network connections
|
||||
a container uses, but is primarily informational.
|
||||
Not specifying a port here DOES NOT prevent
|
||||
that port from being exposed. Any port which
|
||||
is listening on the default "0.0.0.0" address
|
||||
inside a container will be accessible from the
|
||||
network. Cannot be updated.
|
||||
container. Not specifying a port here DOES NOT
|
||||
prevent that port from being exposed. Any port
|
||||
which is listening on the default "0.0.0.0"
|
||||
address inside a container will be accessible
|
||||
from the network. Modifying this array with
|
||||
strategic merge patch may corrupt the data.
|
||||
For more information See https://github.com/kubernetes/kubernetes/issues/108255.
|
||||
Cannot be updated.
|
||||
items:
|
||||
description: ContainerPort represents a network
|
||||
port in a single container.
|
||||
@@ -4049,9 +4321,6 @@ spec:
|
||||
and it cannot be modified by updating the pod spec.
|
||||
In order to add an ephemeral container to an existing
|
||||
pod, use the pod's ephemeralcontainers subresource.
|
||||
This field is beta-level and available on clusters
|
||||
that haven't disabled the EphemeralContainers feature
|
||||
gate.
|
||||
items:
|
||||
description: "An EphemeralContainer is a temporary
|
||||
container that you may add to an existing Pod for
|
||||
@@ -4063,9 +4332,7 @@ spec:
|
||||
the Pod to exceed its resource allocation. \n To
|
||||
add an ephemeral container, use the ephemeralcontainers
|
||||
subresource of an existing Pod. Ephemeral containers
|
||||
may not be removed or restarted. \n This is a beta
|
||||
feature available on clusters that haven't disabled
|
||||
the EphemeralContainers feature gate."
|
||||
may not be removed or restarted."
|
||||
properties:
|
||||
args:
|
||||
description: 'Arguments to the entrypoint. The
|
||||
@@ -5470,6 +5737,20 @@ spec:
|
||||
description: 'Use the host''s pid namespace. Optional:
|
||||
Default to false.'
|
||||
type: boolean
|
||||
hostUsers:
|
||||
description: 'Use the host''s user namespace. Optional:
|
||||
Default to true. If set to true or not present, the
|
||||
pod will be run in the host user namespace, useful
|
||||
for when the pod needs a feature only available to
|
||||
the host user namespace, such as loading a kernel
|
||||
module with CAP_SYS_MODULE. When set to false, a new
|
||||
userns is created for the pod. Setting false is useful
|
||||
for mitigating container breakout vulnerabilities
|
||||
even allowing users to run their containers as root
|
||||
without actually having root privileges on the host.
|
||||
This field is alpha-level and is only honored by servers
|
||||
that enable the UserNamespacesSupport feature.'
|
||||
type: boolean
|
||||
hostname:
|
||||
description: Specifies the hostname of the Pod If not
|
||||
specified, the pod's hostname will be set to a system-defined
|
||||
@@ -6138,14 +6419,14 @@ spec:
|
||||
type: string
|
||||
ports:
|
||||
description: List of ports to expose from the
|
||||
container. Exposing a port here gives the system
|
||||
additional information about the network connections
|
||||
a container uses, but is primarily informational.
|
||||
Not specifying a port here DOES NOT prevent
|
||||
that port from being exposed. Any port which
|
||||
is listening on the default "0.0.0.0" address
|
||||
inside a container will be accessible from the
|
||||
network. Cannot be updated.
|
||||
container. Not specifying a port here DOES NOT
|
||||
prevent that port from being exposed. Any port
|
||||
which is listening on the default "0.0.0.0"
|
||||
address inside a container will be accessible
|
||||
from the network. Modifying this array with
|
||||
strategic merge patch may corrupt the data.
|
||||
For more information See https://github.com/kubernetes/kubernetes/issues/108255.
|
||||
Cannot be updated.
|
||||
items:
|
||||
description: ContainerPort represents a network
|
||||
port in a single container.
|
||||
@@ -6916,20 +7197,19 @@ spec:
|
||||
if this is set. \n If the OS field is set to linux,
|
||||
the following fields must be unset: -securityContext.windowsOptions
|
||||
\n If the OS field is set to windows, following fields
|
||||
must be unset: - spec.hostPID - spec.hostIPC - spec.securityContext.seLinuxOptions
|
||||
- spec.securityContext.seccompProfile - spec.securityContext.fsGroup
|
||||
- spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls
|
||||
- spec.shareProcessNamespace - spec.securityContext.runAsUser
|
||||
- spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups
|
||||
- spec.containers[*].securityContext.seLinuxOptions
|
||||
must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers
|
||||
- spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile
|
||||
- spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy
|
||||
- spec.securityContext.sysctls - spec.shareProcessNamespace
|
||||
- spec.securityContext.runAsUser - spec.securityContext.runAsGroup
|
||||
- spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions
|
||||
- spec.containers[*].securityContext.seccompProfile
|
||||
- spec.containers[*].securityContext.capabilities
|
||||
- spec.containers[*].securityContext.readOnlyRootFilesystem
|
||||
- spec.containers[*].securityContext.privileged -
|
||||
spec.containers[*].securityContext.allowPrivilegeEscalation
|
||||
- spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser
|
||||
- spec.containers[*].securityContext.runAsGroup This
|
||||
is a beta field and requires the IdentifyPodOS feature"
|
||||
- spec.containers[*].securityContext.runAsGroup"
|
||||
properties:
|
||||
name:
|
||||
description: 'Name is the name of the operating
|
||||
@@ -7374,6 +7654,21 @@ spec:
|
||||
"value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
matchLabelKeys:
|
||||
description: MatchLabelKeys is a set of pod label
|
||||
keys to select the pods over which spreading
|
||||
will be calculated. The keys are used to lookup
|
||||
values from the incoming pod labels, those key-value
|
||||
labels are ANDed with labelSelector to select
|
||||
the group of existing pods over which spreading
|
||||
will be calculated for the incoming pod. Keys
|
||||
that don't exist in the incoming pod labels
|
||||
will be ignored. A null or empty list means
|
||||
only match against labelSelector.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
maxSkew:
|
||||
description: 'MaxSkew describes the degree to
|
||||
which pods may be unevenly distributed. When
|
||||
@@ -7424,11 +7719,37 @@ spec:
|
||||
with the same labelSelector cannot be scheduled,
|
||||
because computed skew will be 3(3 - 0) if new
|
||||
Pod is scheduled to any of the three zones,
|
||||
it will violate MaxSkew. \n This is an alpha
|
||||
field and requires enabling MinDomainsInPodTopologySpread
|
||||
feature gate."
|
||||
it will violate MaxSkew. \n This is a beta field
|
||||
and requires the MinDomainsInPodTopologySpread
|
||||
feature gate to be enabled (enabled by default)."
|
||||
format: int32
|
||||
type: integer
|
||||
nodeAffinityPolicy:
|
||||
description: "NodeAffinityPolicy indicates how
|
||||
we will treat Pod's nodeAffinity/nodeSelector
|
||||
when calculating pod topology spread skew. Options
|
||||
are: - Honor: only nodes matching nodeAffinity/nodeSelector
|
||||
are included in the calculations. - Ignore:
|
||||
nodeAffinity/nodeSelector are ignored. All nodes
|
||||
are included in the calculations. \n If this
|
||||
value is nil, the behavior is equivalent to
|
||||
the Honor policy. This is a alpha-level feature
|
||||
enabled by the NodeInclusionPolicyInPodTopologySpread
|
||||
feature flag."
|
||||
type: string
|
||||
nodeTaintsPolicy:
|
||||
description: "NodeTaintsPolicy indicates how we
|
||||
will treat node taints when calculating pod
|
||||
topology spread skew. Options are: - Honor:
|
||||
nodes without taints, along with tainted nodes
|
||||
for which the incoming pod has a toleration,
|
||||
are included. - Ignore: node taints are ignored.
|
||||
All nodes are included. \n If this value is
|
||||
nil, the behavior is equivalent to the Ignore
|
||||
policy. This is a alpha-level feature enabled
|
||||
by the NodeInclusionPolicyInPodTopologySpread
|
||||
feature flag."
|
||||
type: string
|
||||
topologyKey:
|
||||
description: TopologyKey is the key of node labels.
|
||||
Nodes that have a label with this key and identical
|
||||
@@ -7437,8 +7758,9 @@ spec:
|
||||
and try to put balanced number of pods into
|
||||
each bucket. We define a domain as a particular
|
||||
instance of a topology. Also, we define an eligible
|
||||
domain as a domain whose nodes match the node
|
||||
selector. e.g. If TopologyKey is "kubernetes.io/hostname",
|
||||
domain as a domain whose nodes meet the requirements
|
||||
of nodeAffinityPolicy and nodeTaintsPolicy.
|
||||
e.g. If TopologyKey is "kubernetes.io/hostname",
|
||||
each Node is a domain of that topology. And,
|
||||
if TopologyKey is "topology.kubernetes.io/zone",
|
||||
each zone is a domain of that topology. It's
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
# Consolidate collector code
|
||||
|
||||
## Goals
|
||||
|
||||
Reduce code maintenance needs.
|
||||
|
||||
Improve consistency between preflight and support-bundle.
|
||||
|
||||
## Non Goals
|
||||
|
||||
## Background
|
||||
|
||||
In the current Troubleshoot code base, there are three separate paths to running collectors. These include preflights, support-bundle, and the collect package. The three have diverged over time and are different to one another, but do not appear to have any need to be separate. This is confusing and likely to introduce errors in the future.
|
||||
|
||||
## High-Level Design
|
||||
|
||||
* Add a `collect` package public API that can be called to run the collect logic, from any other package
|
||||
* change the preflight, support-bundle and collect binaries to call that API rather than their own collect routines
|
||||
|
||||
## Detailed Design
|
||||
|
||||
Package `preflight`:
|
||||
* Remove `CollectHost`, `Collect` and `CollectRemote`
|
||||
|
||||
Package `supportbundle`:
|
||||
* Remove `runCollectors`, `CollectSupportBundleFromSpec`, and associated code
|
||||
|
||||
Package `collect`:
|
||||
* Add a replacement for `runCollectors`, `CollectSupportBundleFromSpec` taken from `supportbundle`
|
||||
* Add replacements for `CollectHost`, `Collect` and `CollectRemote` taken from `preflight`
|
||||
* Where the above duplicate functionality, alter the `supportbundle` or `preflight` packages to ensure that a single new function handles the requirement
|
||||
|
||||
CLI packages:
|
||||
* Alter to use the public functions from `collect`
|
||||
|
||||
## Limitations
|
||||
|
||||
Breaking change - KOTS at least imports `CollectSupportBundleFromSpec` plus potentially others.
|
||||
|
||||
This does not affect the analysis and redaction portions of code.
|
||||
|
||||
## Assumptions
|
||||
|
||||
* there is no need to run collectors differently between preflight and support-bundle
|
||||
|
||||
## Testing
|
||||
|
||||
Any new function will need unit tests.
|
||||
|
||||
Existing tests will need to be altered, and possibly consolidated.
|
||||
|
||||
## Documentation
|
||||
|
||||
The public function to call collection should be documented and at that point regarded stable.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
## Security Considerations
|
||||
|
||||
None identified.
|
||||
|
||||
## Related changes
|
||||
|
||||
TODO:
|
||||
* make an API for analyze and redact
|
||||
* create an API that calls collect, redact, and analyze - and decide if that can be called by preflight and support-bundle
|
||||
@@ -0,0 +1,76 @@
|
||||
# ADR 001: remove IP address redaction by default
|
||||
|
||||
## Context
|
||||
|
||||
Since PR #8 IP addresses have automatically been redacted throughout support bundles. This was originally
|
||||
added so that folks with security requirements that include not sharing IP addresses can avoid needing to add
|
||||
individual redactors for that purpose.
|
||||
|
||||
The general requirement for environments that protect network infrastructure from being communicated outside also
|
||||
includes hostnames, port numbers, and mac addresses. The Troubleshoot code does not redact these.
|
||||
|
||||
The experience of support engineers making use of Troubleshoot support bundles has been that in many cases,
|
||||
a further set of logs, and data collection information needs to be collected for issues that involve network
|
||||
infrastructure in order to be able to assist. This involves collection that does not redact IP addresses, and
|
||||
introduces significant delays in the resolution of issues due to back and forth.
|
||||
|
||||
A review of Replicated support cases where end users posted an IP address in GitHub found 71 different issues
|
||||
in 2022 (as of October 2022). This doesn't cover uploaded files, phone calls, screen shares, etc. It also
|
||||
doesn't include anything else networking like mac address, hostnames, ports.
|
||||
|
||||
The problem discussed in this document:
|
||||
|
||||
* Users of Troubleshoot need to avoid redaction if they have networking issues, in order to share IP addresses
|
||||
that are redacted by default.
|
||||
* The user experience of the product is based around "define what you want", however with default redactors built
|
||||
into the product in addition to the defined spec is confusing and, in some cases, problematic if you do not want
|
||||
the built in redactors.
|
||||
* A default set of redactors gives product users a false sense of security in that though the default redactors
|
||||
do cover some redaction, they do not cover every possible combination for every particular sensitive item.
|
||||
Redaction of hostnames is almost impossible given the freeform nature of hostnames, and the built in Password
|
||||
redactor does not cover all passwords, merely a particular json combination.
|
||||
|
||||
## Decision
|
||||
|
||||
We will remove the code that redacts IP addresses by default from Troubleshoot. Folks that wish to redact IP addresses will
|
||||
need to add that redactor to their individual Troubleshoot specs.
|
||||
|
||||
## Solution
|
||||
|
||||
The following changes need to be made:
|
||||
|
||||
* [Documentation](https://troubleshoot.sh/docs/redact/ip-addresses/) for the IP address redaction needs altering to reflect that Troubleshoot
|
||||
does not automatically redact IP addresses, but if users wish to there is an example yaml spec available.
|
||||
* Removal of the IP address redactor from [the code](https://github.com/replicatedhq/troubleshoot/blob/v0.45.0/pkg/redact/redact.go#L170)
|
||||
* Clear release notes on release of this change, communicating that those wishing to redact IP addresses need to add that redactor to their spec.
|
||||
* Possible broadcast communication since this is a change to default behavior.
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Consequences
|
||||
|
||||
Describe the resulting context, after applying the decision. All consequences should be listed here,
|
||||
not just the "positive" ones. A particular decision may have positive, negative, and neutral consequences,
|
||||
but all of them affect the team and project in the future.
|
||||
|
||||
Those that wish to have IP addresses redacted by Troubleshoot need to ensure that the redactor specified during `support-bundle` runs includes a
|
||||
regex for IP address redaction.
|
||||
|
||||
Example regex for IP redaction:
|
||||
|
||||
```yaml
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Redactor
|
||||
metadata:
|
||||
name: IP Addresses
|
||||
spec:
|
||||
redactors:
|
||||
- name: Redact ipv4 addresses
|
||||
removals:
|
||||
regex:
|
||||
- redactor: '(?P<mask>\b(?P<drop>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?P<drop>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?P<drop>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?P<drop>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b)'
|
||||
```
|
||||
|
||||
Folks that have not specified the IP address regex above will, on support bundle creation, share their IP address information in that bundle by default.
|
||||
@@ -0,0 +1,37 @@
|
||||
# ADR 002: Mergeable preflight specs
|
||||
|
||||
When the `preflight` binary, or pacakge, is called, it supports only one spec definition at a time.
|
||||
|
||||
Recent changes in Troubleshoot allow the `support-bundle` binary to be called with multiple specs at a time. This allows cluster components to contribute independant Troubleshoot specs for their scope, and have Troubleshoot assemble them at run time.
|
||||
|
||||
Tools such as kURL have components which are maintained with a degree of separation to one another. It would be helpful to the maintainers of such projects to be able to call `preflight` specifying a number of specs at runtime, allowing Troubleshoot to assemble them into one spec for collection/analysis.
|
||||
|
||||
Currently if the `preflight` binary is called with multiple specs, it simply ignores all after the first.
|
||||
|
||||
## Decision
|
||||
|
||||
Modify the `preflight` CLI and package to be able to read multiple args rather than just one.
|
||||
|
||||
Introduce a merge mechanism in the same way that the `support-bundle` binary runs, to merge and deduplicate Preflight specs.
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Consequences
|
||||
|
||||
There are no backward compatibility consequences or breaking changes in this proposal.
|
||||
|
||||
The project benefits:
|
||||
* Folks maintaining kURL add-ons can contribute unique preflight specs for their add-on (and the same for other projects simlarly structured)
|
||||
* Folks using `preflight` from the CLI without other applications (e.g. for a Helm install) are able to specify a list of preflights for their application rather than having to assemble one spec for each environment.
|
||||
|
||||
## Design notes
|
||||
|
||||
This proposal does not include adding the `uri:` field to `kind: Preflight`.
|
||||
|
||||
The file `cmd/preflight/cli/root.go` calls `preflight.RunPreflights` with `args[0]` which is likely to need to change to just `args`.
|
||||
|
||||
Func `RunPreflights` takes a single string arg (`arg string`) for the spec definition. This is likely to need to change to `arg []string`.
|
||||
|
||||
File `cmd/troubleshoot/cli/run.go` loops through the list of args, concatenating them together. A similar process is suitable for this change.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
_ADR_ for short.
|
||||
|
||||
## What is an Architecturally Significant Decision?
|
||||
|
||||
See [Documenting Architecture
|
||||
Decisions](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)
|
||||
for more information.
|
||||
|
||||
The basic idea is to capture key decisions having to do with anything _architectural_
|
||||
in a way that promotes better communication than simple word-of-mouth.
|
||||
|
||||
What is an _architectural_ decision? If one or more of the following ideas apply you
|
||||
might be dealing with an architectural decision.
|
||||
|
||||
Does the design decision...
|
||||
|
||||
* Alter externally visible system properties?
|
||||
* Modify public interfaces?
|
||||
* Directly influence high priority quality attributes?
|
||||
* Include or remove dependencies?
|
||||
* Result from a discussion where you learned more about technical or business constraints?
|
||||
* Involve taking on strategic technical debt?
|
||||
* Change the structures of the system (static, dynamic, or physical)?
|
||||
* Require other developers to update construction techniques or development environments?
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
Use this template in any new ADRs. Replace the help text as you write the ADR.
|
||||
|
||||
```
|
||||
# ADR N: Brief Decision Title
|
||||
|
||||
Context goes here.
|
||||
|
||||
Describe the forces at play, including technological, political, social, and project local.
|
||||
These forces are likely in tension, and should be called out as such. The language in this
|
||||
section is value-neutral. It is simply describing facts.
|
||||
|
||||
## Decision
|
||||
|
||||
This section describes our response to these forces. It is stated in full sentences,
|
||||
with active voice. "We will ..."
|
||||
|
||||
|
||||
## Status
|
||||
|
||||
choose one: [Proposed | Accepted | Deprecated | Superseded]
|
||||
|
||||
if deprecated, include a rationale.
|
||||
|
||||
If superseded, include a link to the new ADR
|
||||
|
||||
|
||||
## Consequences
|
||||
|
||||
Describe the resulting context, after applying the decision. All consequences should be listed here,
|
||||
not just the "positive" ones. A particular decision may have positive, negative, and neutral consequences,
|
||||
but all of them affect the team and project in the future.
|
||||
```
|
||||
|
||||
## Tips and Hints
|
||||
|
||||
* Titles should be descriptive, concise, and precise
|
||||
* The whole document should be one or two pages long at most.
|
||||
* Think of the document as a conversation with a future developer. This means write well and use full
|
||||
sentences.
|
||||
* Update consequences as they become known. The ADR becomes like a diary for seeing how the design
|
||||
decisions we make impact the system over time.
|
||||
* Include diagrams as necessary.
|
||||
|
||||
|
||||
## References
|
||||
|
||||
Nygard, Michael. Documenting Architecture Decisions, from _Think Relevance_ blog. [Web](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)
|
||||
|
||||
Kruchten, Philippe. _The Decision View's Role in Software Architecture Practice_, IEEE Software 26:36-42, February 2009
|
||||
|
||||
Tyree, J. and Akerman, A. _Architecture Decisions: Demystifying Architecture_, IEEE Software 22:2:19-27, March-April 2005 [PDF](http://www.utdallas.edu/~chung/SA/zz-Impreso-architecture_decisions-tyree-05.pdf)
|
||||
@@ -0,0 +1,51 @@
|
||||
## preflight
|
||||
|
||||
Run and retrieve preflight checks in a cluster
|
||||
|
||||
### Synopsis
|
||||
|
||||
A preflight check is a set of validations that can and should be run to ensure
|
||||
that a cluster meets the requirements to run an application.
|
||||
|
||||
```
|
||||
preflight [url] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation.
|
||||
--cache-dir string Default cache directory (default "/Users/xavpaice/.kube/cache")
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--collect-without-permissions always run preflight checks even if some require permissions that preflight does not have (default true)
|
||||
--collector-image string the full name of the collector image to use
|
||||
--collector-pullpolicy string the pull policy of the collector image
|
||||
--context string The name of the kubeconfig context to use
|
||||
--debug enable debug logging
|
||||
--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
|
||||
--interactive interactive preflights (default true)
|
||||
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
-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.
|
||||
-s, --server string The address and port of the Kubernetes API server
|
||||
--since string force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.
|
||||
--since-time string force pod logs collectors to return logs after a specific date (RFC3339)
|
||||
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [preflight version](preflight_version.md) - Print the current version and exit
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Nov-2022
|
||||
@@ -0,0 +1,38 @@
|
||||
## preflight version
|
||||
|
||||
Print the current version and exit
|
||||
|
||||
### Synopsis
|
||||
|
||||
Print the current version and exit
|
||||
|
||||
```
|
||||
preflight version [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for version
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--collect-without-permissions always run preflight checks even if some require permissions that preflight does not have (default true)
|
||||
--collector-image string the full name of the collector image to use
|
||||
--collector-pullpolicy string the pull policy of the collector image
|
||||
--debug enable debug logging
|
||||
--format string output format, one of human, json, yaml. only used when interactive is set to false (default "human")
|
||||
--interactive interactive preflights (default true)
|
||||
-o, --output string specify the output file path for the preflight checks
|
||||
--selector string selector (label query) to filter remote collection nodes on.
|
||||
--since string force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.
|
||||
--since-time string force pod logs collectors to return logs after a specific date (RFC3339)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [preflight](preflight.md) - Run and retrieve preflight checks in a cluster
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Nov-2022
|
||||
@@ -0,0 +1,53 @@
|
||||
## support-bundle
|
||||
|
||||
Generate a support bundle
|
||||
|
||||
### 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.
|
||||
|
||||
```
|
||||
support-bundle [url] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation.
|
||||
--cache-dir string Default cache directory (default "/Users/xavpaice/.kube/cache")
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--collect-without-permissions always generate a support bundle, even if it some require additional permissions (default true)
|
||||
--context string The name of the kubeconfig context to use
|
||||
--debug enable debug logging
|
||||
-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
|
||||
-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.`
|
||||
-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
|
||||
--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")
|
||||
-l, --selector strings selector to filter on for loading additional support bundle specs found in secrets within the cluster (default [troubleshoot.io/kind=supportbundle-spec])
|
||||
-s, --server string The address and port of the Kubernetes API server
|
||||
--since string force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.
|
||||
--since-time string force pod logs collectors to return logs after a specific date (RFC3339)
|
||||
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [support-bundle analyze](support-bundle_analyze.md) - analyze a support bundle
|
||||
* [support-bundle version](support-bundle_version.md) - Print the current version and exit
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Nov-2022
|
||||
@@ -0,0 +1,26 @@
|
||||
## support-bundle analyze
|
||||
|
||||
analyze a support bundle
|
||||
|
||||
### Synopsis
|
||||
|
||||
Analyze a support bundle using the Analyzer definitions provided
|
||||
|
||||
```
|
||||
support-bundle analyze [url] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--bundle string filename of the support bundle to analyze
|
||||
-h, --help help for analyze
|
||||
--output string output format: json, yaml
|
||||
--quiet enable/disable error messaging and only show parseable output
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Nov-2022
|
||||
@@ -0,0 +1,23 @@
|
||||
## support-bundle version
|
||||
|
||||
Print the current version and exit
|
||||
|
||||
### Synopsis
|
||||
|
||||
Print the current version and exit
|
||||
|
||||
```
|
||||
support-bundle version [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for version
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [support-bundle](support-bundle.md) - Generate a support bundle
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Nov-2022
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
data: "5"
|
||||
- run:
|
||||
collectorName: "static-hi"
|
||||
image: 'alpine:3.5'
|
||||
image: 'alpine:3'
|
||||
command: ["echo", "hi static!"]
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
|
||||
@@ -20,4 +20,3 @@ spec:
|
||||
message: The postgres server must be at least version 10
|
||||
- pass:
|
||||
message: The postgres connection checks out
|
||||
|
||||
@@ -6,7 +6,9 @@ spec:
|
||||
collectors:
|
||||
- redis:
|
||||
collectorName: my-redis
|
||||
uri: rediss://default:password@hostname:6379
|
||||
uri: rediss://default:replicated@server:6380
|
||||
tls:
|
||||
skipVerify: true
|
||||
analyzers:
|
||||
- redis:
|
||||
checkName: Must be redis 5.x or later
|
||||
@@ -14,10 +16,9 @@ spec:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connected == false"
|
||||
message: Cannot connect to postgres server
|
||||
message: Cannot connect to redis server
|
||||
- fail:
|
||||
when: "version < 5.0.0"
|
||||
message: The redis server must be at least version 5
|
||||
- pass:
|
||||
message: The redis connection checks out
|
||||
|
||||
@@ -88,11 +88,11 @@ spec:
|
||||
- pass:
|
||||
message: This cluster has enough nodes.
|
||||
- nodeResources:
|
||||
checkName: Every node in the cluster must have at least 10 GB of memory, with 32 GB recommended
|
||||
checkName: Every node in the cluster must have at least 8 GB of memory, with 32 GB recommended
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "min(memoryCapacity) < 10Gi"
|
||||
message: All nodes must have at least 10 GB of memory.
|
||||
when: "min(memoryCapacity) < 8Gi"
|
||||
message: All nodes must have at least 8 GB of memory.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- warn:
|
||||
when: "min(memoryCapacity) < 32Gi"
|
||||
|
||||
+4
-1
@@ -1,7 +1,7 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: mysql
|
||||
name: dbs-collector
|
||||
spec:
|
||||
collectors:
|
||||
- mysql:
|
||||
@@ -15,3 +15,6 @@ spec:
|
||||
- innodb_large_prefix
|
||||
- innodb_strict_mode
|
||||
- log_bin_trust_function_creators
|
||||
- redis:
|
||||
collectorName: my-redis
|
||||
uri: rediss://default:replicated@server:6380
|
||||
@@ -0,0 +1,103 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
collectors:
|
||||
- clusterInfo: {}
|
||||
- clusterResources: {}
|
||||
- data:
|
||||
name: config/replicas.txt
|
||||
data: "5"
|
||||
- runPod:
|
||||
collectorName: "static-hi"
|
||||
podSpec:
|
||||
containers:
|
||||
- name: static-hi
|
||||
image: alpine:3
|
||||
command: ["echo", "hi static!"]
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1.20.0"
|
||||
message: This application requires at least Kubernetes 1.13.0 or later, and recommends 1.15.0.
|
||||
uri: https://www.kubernetes.io
|
||||
- warn:
|
||||
when: "< 1.21.0"
|
||||
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.
|
||||
uri: https://kubernetes.io
|
||||
- pass:
|
||||
when: ">= 1.22.0"
|
||||
message: Your cluster meets the recommended and required versions of Kubernetes.
|
||||
- distribution:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "== k3s"
|
||||
message: K3S is a supported distribution
|
||||
- warn:
|
||||
message: Unable to determine the distribution of Kubernetes
|
||||
- textAnalyze:
|
||||
checkName: Replica Count
|
||||
fileName: config/replicas.txt
|
||||
regexGroups: '(?P<Replicas>\d+)'
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "Replicas < 5"
|
||||
message: That's not enough replicas!
|
||||
- pass:
|
||||
message: You have at least 5 replicas
|
||||
- textAnalyze:
|
||||
checkName: Said hi!
|
||||
fileName: /static-hi.log
|
||||
regex: 'hi static'
|
||||
outcomes:
|
||||
- fail:
|
||||
message: Didn't say hi.
|
||||
- pass:
|
||||
message: Said hi!
|
||||
- nodeResources:
|
||||
checkName: Must be exactly 1 node in the cluster
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "= 1"
|
||||
message: This cluster has exactly 1 node
|
||||
- fail:
|
||||
message: This application requires exactly 1 node
|
||||
- nodeResources:
|
||||
checkName: Must have 1 node with at least 2 cores
|
||||
filters:
|
||||
cpuCapacity: "2"
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "= 1"
|
||||
message: This cluster has exactly 1 node with at least 2 cores
|
||||
- fail:
|
||||
message: This application requires exactly 1 node with at least 2 cores
|
||||
- nodeResources:
|
||||
checkName: Must have 1 node with 2Gi (available) memory and at least 2 cores (on a single node)
|
||||
filters:
|
||||
allocatableMemory: 2Gi
|
||||
cpuCapacity: "2"
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "= 1"
|
||||
message: This cluster has exactly 1 node with at least 2Gi available memory and 2 cores
|
||||
- fail:
|
||||
message: This application requires exactly 1 node with at least 2Gi available memory and 2 cores
|
||||
- nodeResources:
|
||||
checkName: There must be exactly 1 node in the cluster
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "count() = 1"
|
||||
message: This has exactly 1 node in the cluster
|
||||
- fail:
|
||||
message: This application requires exactly 1 node in the cluster
|
||||
- nodeResources:
|
||||
checkName: There must be a total of at least 2Gi of memory on all nodes
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "sum(memoryCapacity) < 2Gi"
|
||||
message: This application requires that 2Gi or more memory be available to the cluster
|
||||
- pass:
|
||||
message: This cluster has sufficient memory
|
||||
@@ -12,6 +12,16 @@ spec:
|
||||
limits:
|
||||
maxAge: 720h # 30*24
|
||||
maxLines: 10000
|
||||
- logs:
|
||||
collectorName: all-logs
|
||||
name: all-logs
|
||||
- runPod:
|
||||
collectorName: "static-hi"
|
||||
podSpec:
|
||||
containers:
|
||||
- name: static-hi
|
||||
image: alpine:3
|
||||
command: ["echo", "hi static!"]
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
@@ -30,6 +40,7 @@ spec:
|
||||
outcomes:
|
||||
- fail:
|
||||
message: The Rook CRD was not found in the cluster.
|
||||
uri: https://kurl.sh/docs/add-ons/rook/some/path/that/does/not/exist/for/the/sake/of/having/a/very/long/url.md
|
||||
- pass:
|
||||
message: Rook is installed and available.
|
||||
- containerRuntime:
|
||||
@@ -99,3 +110,12 @@ spec:
|
||||
message: The API deployment has only a single ready replica.
|
||||
- pass:
|
||||
message: There are multiple replicas of the API deployment ready.
|
||||
- textAnalyze:
|
||||
checkName: Said hi!
|
||||
fileName: /static-hi.log
|
||||
regex: 'hi static'
|
||||
outcomes:
|
||||
- fail:
|
||||
message: Didn't say hi.
|
||||
- pass:
|
||||
message: Said hi!
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.19
|
||||
require (
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/containers/image/v5 v5.19.3
|
||||
github.com/containers/image/v5 v5.23.1
|
||||
github.com/docker/distribution v2.8.1+incompatible
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-logr/logr v1.2.3
|
||||
@@ -18,42 +18,54 @@ require (
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/hashicorp/go-getter v1.6.2
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/jackc/pgx/v5 v5.1.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.16
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2
|
||||
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.21.11+incompatible
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/tj/go-spin v1.1.0
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
golang.org/x/sync v0.1.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.25.0
|
||||
k8s.io/apiextensions-apiserver v0.24.2
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/apiserver v0.25.0
|
||||
k8s.io/cli-runtime v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
k8s.io/klog/v2 v2.70.1
|
||||
oras.land/oras-go v1.2.0
|
||||
sigs.k8s.io/controller-runtime v0.12.3
|
||||
k8s.io/api v0.25.4
|
||||
k8s.io/apiextensions-apiserver v0.25.0
|
||||
k8s.io/apimachinery v0.25.4
|
||||
k8s.io/apiserver v0.25.4
|
||||
k8s.io/cli-runtime v0.25.4
|
||||
k8s.io/client-go v0.25.4
|
||||
k8s.io/klog/v2 v2.80.1
|
||||
oras.land/oras-go v1.2.1
|
||||
sigs.k8s.io/controller-runtime v0.13.1
|
||||
)
|
||||
|
||||
require github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/go-type-adapters v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/mistifyio/go-zfs/v3 v3.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.100.2 // indirect
|
||||
cloud.google.com/go/compute v1.6.1 // indirect
|
||||
cloud.google.com/go v0.104.0 // indirect
|
||||
cloud.google.com/go/compute v1.12.1 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
cloud.google.com/go/storage v1.14.0 // indirect
|
||||
cloud.google.com/go/storage v1.23.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
@@ -61,9 +73,9 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/BurntSushi/toml v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.3 // indirect
|
||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.4 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.0.1 // indirect
|
||||
@@ -72,27 +84,26 @@ require (
|
||||
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.1.2 // indirect
|
||||
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/containerd/cgroups v1.0.3 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/containerd/cgroups v1.0.4 // indirect
|
||||
github.com/containerd/containerd v1.6.6 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.11.0 // indirect
|
||||
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b // indirect
|
||||
github.com/containers/ocicrypt v1.1.4 // indirect
|
||||
github.com/containers/storage v1.38.3 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
|
||||
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a // indirect
|
||||
github.com/containers/ocicrypt v1.1.5 // indirect
|
||||
github.com/containers/storage v1.43.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||
github.com/docker/docker v20.10.17+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/docker v20.10.18+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
@@ -106,10 +117,10 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // 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.4.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@@ -117,12 +128,12 @@ require (
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // 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.14.2 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
@@ -131,14 +142,13 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
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.5.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -148,25 +158,24 @@ require (
|
||||
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.3 // indirect
|
||||
github.com/opencontainers/runc v1.1.4 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
|
||||
github.com/opencontainers/selinux v1.10.1 // indirect
|
||||
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 // indirect
|
||||
github.com/opencontainers/selinux v1.10.2 // indirect
|
||||
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // 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.12.1 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.3.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
@@ -178,25 +187,25 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
google.golang.org/api v0.81.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.102.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
periph.io/x/host/v3 v3.7.2
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
|
||||
periph.io/x/host/v3 v3.8.0
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||
|
||||
@@ -27,7 +27,7 @@ type AnalyzeResult struct {
|
||||
}
|
||||
|
||||
type getCollectedFileContents func(string) ([]byte, error)
|
||||
type getChildCollectedFileContents func(string) (map[string][]byte, error)
|
||||
type getChildCollectedFileContents func(string, []string) (map[string][]byte, error)
|
||||
|
||||
func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
|
||||
if excludeVal == nil {
|
||||
|
||||
@@ -14,8 +14,9 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func clusterPodStatuses(analyzer *troubleshootv1beta2.ClusterPodStatuses, getChildCollectedFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
collected, err := getChildCollectedFileContents(filepath.Join("cluster-resources", "pods", "*.json"))
|
||||
func clusterPodStatuses(analyzer *troubleshootv1beta2.ClusterPodStatuses, getChildCollectedFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
collected, err := getChildCollectedFileContents(filepath.Join("cluster-resources", "pods", "*.json"), excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected pods")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey string, iconURI string, readyReplicas int, exists bool, resourceType string) (*AnalyzeResult, error) {
|
||||
@@ -36,7 +36,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if outcome.Fail.When == "absent" {
|
||||
if outcome.Fail.When == "absent" {
|
||||
if exists == false {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
func analyzeDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if analyzer.Name == "" {
|
||||
return analyzeAllDeploymentStatuses(analyzer, getFileContents)
|
||||
} else {
|
||||
@@ -18,8 +18,9 @@ func analyzeDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, get
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeOneDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
func analyzeOneDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
|
||||
}
|
||||
@@ -61,7 +62,8 @@ func analyzeOneDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus,
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func analyzeAllDeploymentStatuses(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeAllDeploymentStatuses(analyzer *troubleshootv1beta2.DeploymentStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
fileNames := make([]string, 0)
|
||||
if analyzer.Namespace != "" {
|
||||
fileNames = append(fileNames, filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
@@ -77,7 +79,7 @@ func analyzeAllDeploymentStatuses(analyzer *troubleshootv1beta2.DeploymentStatus
|
||||
|
||||
results := []*AnalyzeResult{}
|
||||
for _, fileName := range fileNames {
|
||||
files, err := getFileContents(fileName)
|
||||
files, err := getFileContents(fileName, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected deployments from file")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "absent",
|
||||
When: "absent",
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -33,14 +33,14 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Namespace: "default",
|
||||
Name: "nonexistant-deployment",
|
||||
Name: "nonexistant-deployment",
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "nonexistant-deployment Status",
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "nonexistant-deployment Status",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_deployment_status",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
|
||||
@@ -58,7 +58,7 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "absent",
|
||||
When: "absent",
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -100,7 +100,7 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "absent",
|
||||
When: "absent",
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -142,7 +142,7 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "absent",
|
||||
When: "absent",
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -221,7 +221,7 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) (map[string][]byte, error) {
|
||||
getFiles := func(n string, _ []string) (map[string][]byte, error) {
|
||||
if file, ok := test.files[n]; ok {
|
||||
return map[string][]byte{n: file}, nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type providers struct {
|
||||
gke bool
|
||||
digitalOcean bool
|
||||
openShift bool
|
||||
tanzu bool
|
||||
kurl bool
|
||||
aks bool
|
||||
ibm bool
|
||||
@@ -36,6 +37,7 @@ const (
|
||||
gke Provider = iota
|
||||
digitalOcean Provider = iota
|
||||
openShift Provider = iota
|
||||
tanzu Provider = iota
|
||||
kurl Provider = iota
|
||||
aks Provider = iota
|
||||
ibm Provider = iota
|
||||
@@ -44,12 +46,16 @@ const (
|
||||
k3s Provider = iota
|
||||
)
|
||||
|
||||
func CheckOpenShift(foundProviders *providers, apiResources []*metav1.APIResourceList, provider string) string {
|
||||
func CheckApiResourcesForProviders(foundProviders *providers, apiResources []*metav1.APIResourceList, provider string) string {
|
||||
for _, resource := range apiResources {
|
||||
if strings.HasPrefix(resource.GroupVersion, "apps.openshift.io/") {
|
||||
foundProviders.openShift = true
|
||||
return "openShift"
|
||||
}
|
||||
if strings.HasPrefix(resource.GroupVersion, "run.tanzu.vmware.com/") {
|
||||
foundProviders.tanzu = true
|
||||
return "tanzu"
|
||||
}
|
||||
}
|
||||
|
||||
return provider
|
||||
@@ -157,8 +163,9 @@ func analyzeDistribution(analyzer *troubleshootv1beta2.Distribution, getCollecte
|
||||
if err := json.Unmarshal(apiResourcesBytes, &apiResources); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal api resource list")
|
||||
}
|
||||
_ = CheckOpenShift(&foundProviders, apiResources, "")
|
||||
_ = CheckApiResourcesForProviders(&foundProviders, apiResources, "")
|
||||
}
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Kubernetes Distribution"
|
||||
@@ -322,6 +329,8 @@ func mustNormalizeDistributionName(raw string) Provider {
|
||||
return digitalOcean
|
||||
case "openshift":
|
||||
return openShift
|
||||
case "tanzu":
|
||||
return tanzu
|
||||
case "kurl":
|
||||
return kurl
|
||||
case "aks":
|
||||
|
||||
+32
-1
@@ -268,11 +268,42 @@ func (f fileContentProvider) getFileContents(fileName string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filepath.Join(f.rootDir, fileName))
|
||||
}
|
||||
|
||||
func (f fileContentProvider) getChildFileContents(dirName string) (map[string][]byte, error) {
|
||||
func excludeFilePaths(files, excludeFiles []string) []string {
|
||||
mapExcludeFiles := make(map[string]struct{}, len(excludeFiles))
|
||||
|
||||
for _, path := range excludeFiles {
|
||||
mapExcludeFiles[path] = struct{}{}
|
||||
}
|
||||
|
||||
var nonexcludedFiles []string
|
||||
|
||||
for _, path := range files {
|
||||
if _, found := mapExcludeFiles[path]; !found {
|
||||
nonexcludedFiles = append(nonexcludedFiles, path)
|
||||
}
|
||||
}
|
||||
|
||||
return nonexcludedFiles
|
||||
}
|
||||
|
||||
func (f fileContentProvider) getChildFileContents(dirName string, excludeFiles []string) (map[string][]byte, error) {
|
||||
files, err := filepath.Glob(filepath.Join(f.rootDir, dirName))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid glob %q", dirName)
|
||||
}
|
||||
|
||||
if len(excludeFiles) > 0 {
|
||||
excludeFileNames := []string{}
|
||||
for _, excludeFile := range excludeFiles {
|
||||
excludeFileName, err := filepath.Glob(filepath.Join(f.rootDir, excludeFile))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid glob %q", excludeFile)
|
||||
}
|
||||
excludeFileNames = append(excludeFileNames, excludeFileName...)
|
||||
}
|
||||
files = excludeFilePaths(files, excludeFileNames)
|
||||
}
|
||||
|
||||
fileArr := map[string][]byte{}
|
||||
for _, filePath := range files {
|
||||
bytes, err := ioutil.ReadFile(filePath)
|
||||
|
||||
@@ -2,6 +2,7 @@ package analyzer
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
@@ -33,13 +34,15 @@ func (a *AnalyzeHostCertificate) Analyze(getCollectedFileContents func(string) (
|
||||
}
|
||||
status := string(contents)
|
||||
|
||||
trimmedStatus := strings.TrimSpace(status)
|
||||
|
||||
var coll resultCollector
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
result := &AnalyzeResult{Title: a.Title()}
|
||||
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" || outcome.Fail.When == status {
|
||||
if outcome.Fail.When == "" || outcome.Fail.When == trimmedStatus {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
@@ -47,7 +50,7 @@ func (a *AnalyzeHostCertificate) Analyze(getCollectedFileContents func(string) (
|
||||
coll.push(result)
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" || outcome.Warn.When == status {
|
||||
if outcome.Warn.When == "" || outcome.Warn.When == trimmedStatus {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
@@ -55,7 +58,7 @@ func (a *AnalyzeHostCertificate) Analyze(getCollectedFileContents func(string) (
|
||||
coll.push(result)
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" || outcome.Pass.When == status {
|
||||
if outcome.Pass.When == "" || outcome.Pass.When == trimmedStatus {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
@@ -40,7 +40,6 @@ func (a *AnalyzeHostKernelModules) IsExcluded() (bool, error) {
|
||||
// - a default fail will only trigger if there are no matching non-default pass outcomes.
|
||||
// - a default warn will only trigger if there are no matching non-default pass or fail outcomes.
|
||||
// - a default pass will only trigger if there are no matching non-default fail outcomes.
|
||||
//
|
||||
func (a *AnalyzeHostKernelModules) Analyze(getCollectedFileContents func(string) ([]byte, error)) ([]*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
contents, err := getCollectedFileContents(collect.HostKernelModulesPath)
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func analyzeImagePullSecret(analyzer *troubleshootv1beta2.ImagePullSecret, getChildCollectedFileContents func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
|
||||
imagePullSecrets, err := getChildCollectedFileContents("cluster-resources/image-pull-secrets")
|
||||
func analyzeImagePullSecret(analyzer *troubleshootv1beta2.ImagePullSecret, getChildCollectedFileContents getChildCollectedFileContents) (*AnalyzeResult, error) {
|
||||
var excludeFiles = []string{}
|
||||
imagePullSecrets, err := getChildCollectedFileContents("cluster-resources/image-pull-secrets", excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get file contents for image pull secrets")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
)
|
||||
|
||||
func analyzeJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if analyzer.Name == "" {
|
||||
return analyzeAllJobStatuses(analyzer, getFileContents)
|
||||
} else {
|
||||
@@ -20,8 +20,9 @@ func analyzeJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents f
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeOneJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "jobs", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
func analyzeOneJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "jobs", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected jobs from namespace")
|
||||
}
|
||||
@@ -67,7 +68,7 @@ func analyzeOneJobStatus(analyzer *troubleshootv1beta2.JobStatus, getFileContent
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func analyzeAllJobStatuses(analyzer *troubleshootv1beta2.JobStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeAllJobStatuses(analyzer *troubleshootv1beta2.JobStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
fileNames := make([]string, 0)
|
||||
if analyzer.Namespace != "" {
|
||||
fileNames = append(fileNames, filepath.Join("cluster-resources", "jobs", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
@@ -81,9 +82,10 @@ func analyzeAllJobStatuses(analyzer *troubleshootv1beta2.JobStatus, getFileConte
|
||||
fileNames = append(fileNames, filepath.Join("cluster-resources", "jobs", "*.json"))
|
||||
}
|
||||
|
||||
excludeFiles := []string{}
|
||||
results := []*AnalyzeResult{}
|
||||
for _, fileName := range fileNames {
|
||||
files, err := getFileContents(fileName)
|
||||
files, err := getFileContents(fileName, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected jobs from file")
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func Test_JobStatus(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) (map[string][]byte, error) {
|
||||
getFiles := func(n string, _ []string) (map[string][]byte, error) {
|
||||
if file, ok := test.files[n]; ok {
|
||||
return map[string][]byte{n: file}, nil
|
||||
}
|
||||
|
||||
@@ -17,16 +17,17 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileContents func(string) ([]byte, error), findFiles func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getFileContents getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
ns := collect.DefaultLonghornNamespace
|
||||
if analyzer.Namespace != "" {
|
||||
ns = analyzer.Namespace
|
||||
}
|
||||
|
||||
excludeFiles := []string{}
|
||||
// get nodes.longhorn.io
|
||||
nodesDir := collect.GetLonghornNodesDirectory(ns)
|
||||
nodesGlob := filepath.Join(nodesDir, "*")
|
||||
nodesYaml, err := findFiles(nodesGlob)
|
||||
nodesYaml, err := findFiles(nodesGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find longhorn nodes files under %s", nodesDir)
|
||||
}
|
||||
@@ -44,7 +45,7 @@ func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileCon
|
||||
// get replicas.longhorn.io
|
||||
replicasDir := collect.GetLonghornReplicasDirectory(ns)
|
||||
replicasGlob := filepath.Join(replicasDir, "*")
|
||||
replicasYaml, err := findFiles(replicasGlob)
|
||||
replicasYaml, err := findFiles(replicasGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find longhorn replicas files under %s", replicasDir)
|
||||
}
|
||||
@@ -62,7 +63,7 @@ func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileCon
|
||||
// get engines.longhorn.io
|
||||
enginesDir := collect.GetLonghornEnginesDirectory(ns)
|
||||
enginesGlob := filepath.Join(enginesDir, "*")
|
||||
enginesYaml, err := findFiles(enginesGlob)
|
||||
enginesYaml, err := findFiles(enginesGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find longhorn engines files under %s", enginesDir)
|
||||
}
|
||||
@@ -80,7 +81,7 @@ func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileCon
|
||||
// get volumes.longhorn.io
|
||||
volumesDir := collect.GetLonghornVolumesDirectory(ns)
|
||||
volumesGlob := filepath.Join(volumesDir, "*.yaml")
|
||||
volumesYaml, err := findFiles(volumesGlob)
|
||||
volumesYaml, err := findFiles(volumesGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to find longhorn volumes files under %s", volumesDir)
|
||||
}
|
||||
@@ -112,7 +113,7 @@ func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileCon
|
||||
for _, volume := range volumes {
|
||||
// get replica checksums for each volume if provided
|
||||
checksumsGlob := filepath.Join(volumesDir, volume.Name, "replicachecksums", "*")
|
||||
checksumFiles, err := findFiles(checksumsGlob)
|
||||
checksumFiles, err := findFiles(checksumsGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to find longhorn replica checksums under %s", checksumsGlob)
|
||||
}
|
||||
@@ -133,8 +134,8 @@ func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileCon
|
||||
// Check Volume replicas
|
||||
if volume.Spec.NumberOfReplicas < 2 {
|
||||
result := &AnalyzeResult{
|
||||
Title: "Longhorn volume with low replicas",
|
||||
IsWarn: true,
|
||||
Title: "Longhorn volume with low replicas",
|
||||
IsWarn: true,
|
||||
Message: fmt.Sprintf("Longhorn volume %s has less than two replicas, this could lead to issues with volume availability", volume.Name),
|
||||
}
|
||||
results = append(results, result)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func analyzeReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if analyzer.Name == "" {
|
||||
return analyzeAllReplicaSetStatuses(analyzer, getFileContents)
|
||||
} else {
|
||||
@@ -21,8 +21,9 @@ func analyzeReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, get
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeOneReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "replicasets", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
func analyzeOneReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "replicasets", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected replicasets from namespace")
|
||||
}
|
||||
@@ -64,7 +65,7 @@ func analyzeOneReplicaSetStatus(analyzer *troubleshootv1beta2.ReplicaSetStatus,
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func analyzeAllReplicaSetStatuses(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeAllReplicaSetStatuses(analyzer *troubleshootv1beta2.ReplicaSetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
fileNames := make([]string, 0)
|
||||
if analyzer.Namespace != "" {
|
||||
fileNames = append(fileNames, filepath.Join("cluster-resources", "replicasets", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
@@ -78,8 +79,9 @@ func analyzeAllReplicaSetStatuses(analyzer *troubleshootv1beta2.ReplicaSetStatus
|
||||
}
|
||||
|
||||
results := []*AnalyzeResult{}
|
||||
excludeFiles := []string{}
|
||||
for _, fileName := range fileNames {
|
||||
files, err := getFileContents(fileName)
|
||||
files, err := getFileContents(fileName, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected replicaset from file")
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func Test_analyzeReplicaSetStatus(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) (map[string][]byte, error) {
|
||||
getFiles := func(n string, _ []string) (map[string][]byte, error) {
|
||||
if file, ok := test.files[n]; ok {
|
||||
return map[string][]byte{n: file}, nil
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
func analyzeStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if analyzer.Name == "" {
|
||||
return analyzeAllStatefulsetStatuses(analyzer, getFileContents)
|
||||
} else {
|
||||
@@ -18,8 +18,9 @@ func analyzeStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, g
|
||||
}
|
||||
}
|
||||
|
||||
func analyzeOneStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
func analyzeOneStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
files, err := getFileContents(filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)), excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected statefulsets from namespace")
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func analyzeOneStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
func analyzeAllStatefulsetStatuses(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeAllStatefulsetStatuses(analyzer *troubleshootv1beta2.StatefulsetStatus, getFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
fileNames := make([]string, 0)
|
||||
if analyzer.Namespace != "" {
|
||||
fileNames = append(fileNames, filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
@@ -79,9 +80,10 @@ func analyzeAllStatefulsetStatuses(analyzer *troubleshootv1beta2.StatefulsetStat
|
||||
fileNames = append(fileNames, filepath.Join("cluster-resources", "statefulsets", "*.json"))
|
||||
}
|
||||
|
||||
excludeFiles := []string{}
|
||||
results := []*AnalyzeResult{}
|
||||
for _, fileName := range fileNames {
|
||||
files, err := getFileContents(fileName)
|
||||
files, err := getFileContents(fileName, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected statefulsets from namespace")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func Test_analyzeStatefulsetStatus(t *testing.T) {
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "absent",
|
||||
When: "absent",
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -33,21 +33,21 @@ func Test_analyzeStatefulsetStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Namespace: "default",
|
||||
Name: "nonexistant",
|
||||
Name: "nonexistant",
|
||||
},
|
||||
expectResult: []*AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "nonexistant Status",
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "nonexistant Status",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_statefulset_status",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/statefulset-status.svg?w=23&h=14",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/statefulsets/default.json": []byte(defaultStatefulSets),
|
||||
"cluster-resources/statefulsets/default.json": []byte(defaultStatefulSets),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -105,7 +105,7 @@ func Test_analyzeStatefulsetStatus(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) (map[string][]byte, error) {
|
||||
getFiles := func(n string, _ []string) (map[string][]byte, error) {
|
||||
if file, ok := test.files[n]; ok {
|
||||
return map[string][]byte{n: file}, nil
|
||||
}
|
||||
|
||||
+94
-3
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -16,8 +17,9 @@ import (
|
||||
// The when condition for outcomes in this analyzer is interpreted as "for some node".
|
||||
// For example, "when: net.ipv4.ip_forward = 0" is true if at least one node has IP forwarding
|
||||
// disabled.
|
||||
func analyzeSysctl(analyzer *troubleshootv1beta2.SysctlAnalyze, findFiles func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
|
||||
files, err := findFiles("sysctl/*")
|
||||
func analyzeSysctl(analyzer *troubleshootv1beta2.SysctlAnalyze, findFiles getChildCollectedFileContents) (*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
files, err := findFiles("sysctl/*", excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find collected sysctl parameters")
|
||||
}
|
||||
@@ -123,7 +125,7 @@ func evalSysctlOutcome(nodeParams map[string]map[string]string, outcome *trouble
|
||||
}
|
||||
|
||||
// Example: net.ipv4.ip_forward = 0
|
||||
var sysctlWhenRX = regexp.MustCompile(`([^\s]+)\s+(=+)\s+(.+)`)
|
||||
var sysctlWhenRX = regexp.MustCompile(`([^\s]+)\s+([><=]=*)\s+(.+)`)
|
||||
|
||||
// Returns the list of node names the condition is true for. The condition is not considered true
|
||||
// if the parameter is missing for the node.
|
||||
@@ -150,6 +152,95 @@ func evalSysctlWhen(nodeParams map[string]map[string]string, when string) ([]str
|
||||
sort.Strings(nodes)
|
||||
|
||||
return nodes, nil
|
||||
|
||||
case "<":
|
||||
var nodes []string
|
||||
|
||||
for nodeName, params := range nodeParams {
|
||||
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
|
||||
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
if nodeValue < expectedValue {
|
||||
nodes = append(nodes, nodeName)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(nodes)
|
||||
|
||||
return nodes, nil
|
||||
|
||||
case "<=":
|
||||
var nodes []string
|
||||
|
||||
for nodeName, params := range nodeParams {
|
||||
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
|
||||
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
if nodeValue <= expectedValue {
|
||||
nodes = append(nodes, nodeName)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(nodes)
|
||||
|
||||
return nodes, nil
|
||||
|
||||
case ">":
|
||||
var nodes []string
|
||||
|
||||
for nodeName, params := range nodeParams {
|
||||
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
|
||||
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
if nodeValue > expectedValue {
|
||||
nodes = append(nodes, nodeName)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(nodes)
|
||||
|
||||
return nodes, nil
|
||||
|
||||
case ">=":
|
||||
var nodes []string
|
||||
|
||||
for nodeName, params := range nodeParams {
|
||||
nodeValue, err := strconv.ParseInt(params[matches[1]], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
|
||||
expectedValue, err := strconv.ParseInt(strings.TrimSpace(matches[3]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse when %q", when)
|
||||
}
|
||||
if nodeValue >= expectedValue {
|
||||
nodes = append(nodes, nodeName)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(nodes)
|
||||
|
||||
return nodes, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown operator %q", matches[2])
|
||||
}
|
||||
|
||||
+127
-1
@@ -12,12 +12,14 @@ func TestParseSysctlParameters(t *testing.T) {
|
||||
/proc/sys/net/ipv4/ip_forward = 1
|
||||
/proc/sys/net/ipv4/ip_local_port_range = 32768 60999
|
||||
/proc/sys/net/bridge/bridge-nf-call-iptables = 0
|
||||
/proc/sys/vm/max_map_count = 65530
|
||||
`
|
||||
got := parseSysctlParameters([]byte(parameters))
|
||||
expect := map[string]string{
|
||||
"net.ipv4.ip_forward": "1",
|
||||
"net.ipv4.ip_local_port_range": "32768 60999",
|
||||
"net.bridge.bridge-nf-call-iptables": "0",
|
||||
"vm.max_map_count": "65530",
|
||||
}
|
||||
|
||||
assert.Equal(t, expect, got)
|
||||
@@ -69,6 +71,36 @@ func TestEvalSysctlWhen(t *testing.T) {
|
||||
expect: []string{"node-b"},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "No nodes have max map count > 65530",
|
||||
when: "vm.max_map_count > 65530",
|
||||
nodeParams: map[string]map[string]string{
|
||||
"node-a": {"vm.max_map_count": "65530"},
|
||||
"node-b": {"vm.max_map_count": "65530"},
|
||||
},
|
||||
expect: []string{},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "One node has max map count > 65530",
|
||||
when: "vm.max_map_count > 65530",
|
||||
nodeParams: map[string]map[string]string{
|
||||
"node-a": {"vm.max_map_count": "65530"},
|
||||
"node-b": {"vm.max_map_count": "262144"},
|
||||
},
|
||||
expect: []string{"node-b"},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "All nodes have max map count > 65530",
|
||||
when: "vm.max_map_count > 65530",
|
||||
nodeParams: map[string]map[string]string{
|
||||
"node-a": {"vm.max_map_count": "262144"},
|
||||
"node-b": {"vm.max_map_count": "262144"},
|
||||
},
|
||||
expect: []string{"node-a", "node-b"},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -270,11 +302,105 @@ func TestAnalyzeSysctl(t *testing.T) {
|
||||
},
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
name: "Fail one node too low on max map count",
|
||||
files: map[string][]byte{
|
||||
"a": []byte(`
|
||||
/proc/sys/vm/max_map_count = 65530
|
||||
`),
|
||||
"b": []byte(`
|
||||
/proc/sys/vm/max_map_count = 262144
|
||||
`),
|
||||
},
|
||||
analyzer: &troubleshootv1beta2.SysctlAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "vm.max_map_count < 262144 ",
|
||||
Message: "Max map count too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Max map count sufficient",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: &AnalyzeResult{
|
||||
Title: "Sysctl",
|
||||
IsFail: true,
|
||||
Message: "Node a: Max map count too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fail two nodes too low on max map count",
|
||||
files: map[string][]byte{
|
||||
"a": []byte(`
|
||||
/proc/sys/vm/max_map_count = 65530
|
||||
`),
|
||||
"b": []byte(`
|
||||
/proc/sys/vm/max_map_count = 65530
|
||||
`),
|
||||
},
|
||||
analyzer: &troubleshootv1beta2.SysctlAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "vm.max_map_count < 262144 ",
|
||||
Message: "Max map count too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Max map count sufficient",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: &AnalyzeResult{
|
||||
Title: "Sysctl",
|
||||
IsFail: true,
|
||||
Message: "Nodes a, b: Max map count too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pass sufficient max map count on both nodes",
|
||||
files: map[string][]byte{
|
||||
"a": []byte(`
|
||||
/proc/sys/vm/max_map_count = 262144
|
||||
`),
|
||||
"b": []byte(`
|
||||
/proc/sys/vm/max_map_count = 262144
|
||||
`),
|
||||
},
|
||||
analyzer: &troubleshootv1beta2.SysctlAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "vm.max_map_count < 262144 ",
|
||||
Message: "Max map count too low",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "vm.max_map_count >= 262144 ",
|
||||
Message: "Max map count sufficient",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: &AnalyzeResult{
|
||||
Title: "Sysctl",
|
||||
IsPass: true,
|
||||
Message: "Nodes a, b: Max map count sufficient",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var findFiles = func(glob string) (map[string][]byte, error) {
|
||||
var findFiles = func(glob string, _ []string) (map[string][]byte, error) {
|
||||
return test.files, nil
|
||||
}
|
||||
got, err := analyzeSysctl(test.analyzer, findFiles)
|
||||
|
||||
@@ -13,9 +13,14 @@ import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func analyzeTextAnalyze(analyzer *troubleshootv1beta2.TextAnalyze, getCollectedFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
func analyzeTextAnalyze(analyzer *troubleshootv1beta2.TextAnalyze, getCollectedFileContents getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
fullPath := filepath.Join(analyzer.CollectorName, analyzer.FileName)
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
excludeFiles := []string{}
|
||||
for _, excludeFile := range analyzer.ExcludeFiles {
|
||||
excludeFiles = append(excludeFiles, filepath.Join(analyzer.CollectorName, excludeFile))
|
||||
}
|
||||
|
||||
collected, err := getCollectedFileContents(fullPath, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
|
||||
}
|
||||
|
||||
@@ -582,13 +582,122 @@ func Test_textAnalyze(t *testing.T) {
|
||||
"text-collector-templated-regex-message/cfile-1.txt": []byte(`{"level":"ERROR","timestamp":"2022-05-17T20:37:41Z","caller":"controller/controller.go:317","message":"Reconciler error","context":{"name":"insert-cr-name-here","namespace":"default","error":"myerror"}}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exclude files case 1 globbing",
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile*.txt",
|
||||
ExcludeFiles: []string{"*previous.txt"},
|
||||
RegexPattern: "success",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-1",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"),
|
||||
"text-collector-1/cfile-previous.txt": []byte("no success here"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exclude files case 2 globbing",
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile*.txt",
|
||||
ExcludeFiles: []string{"*previous.txt", "cfile-2.txt"},
|
||||
RegexPattern: "success",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-1",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"),
|
||||
"text-collector-1/cfile-previous.txt": []byte("no success here"),
|
||||
"text-collector-1/cfile-2.txt": []byte("no success here"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exclude files case 3 globbing",
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "success",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile*.txt",
|
||||
ExcludeFiles: []string{"*previous.txt"},
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-1",
|
||||
Message: "success",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-1",
|
||||
Message: "success",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"),
|
||||
"text-collector-1/cfile-previous.txt": []byte("no success here"),
|
||||
"text-collector-1/cfile-2.txt": []byte("Yes it all succeeded"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) (map[string][]byte, error) {
|
||||
getFiles := func(n string, excludeFiles []string) (map[string][]byte, error) {
|
||||
matching := make(map[string][]byte)
|
||||
for k, v := range test.files {
|
||||
if strings.HasPrefix(k, n) {
|
||||
@@ -602,6 +711,16 @@ func Test_textAnalyze(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(excludeFiles) > 0 {
|
||||
for k := range matching {
|
||||
for _, ex := range excludeFiles {
|
||||
if ok, _ := filepath.Match(ex, k); ok {
|
||||
delete(matching, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(matching) == 0 {
|
||||
return nil, fmt.Errorf("File not found: %s", n)
|
||||
}
|
||||
|
||||
@@ -39,8 +39,9 @@ type WeaveAttributes struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func analyzeWeaveReport(analyzer *troubleshootv1beta2.WeaveReportAnalyze, findFiles func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
files, err := findFiles(analyzer.ReportFileGlob)
|
||||
func analyzeWeaveReport(analyzer *troubleshootv1beta2.WeaveReportAnalyze, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
excludeFiles := []string{}
|
||||
files, err := findFiles(analyzer.ReportFileGlob, excludeFiles)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find weave report files in %q", analyzer.ReportFileGlob)
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestWeaveReport(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var findFiles = func(glob string) (map[string][]byte, error) {
|
||||
var findFiles = func(glob string, _ []string) (map[string][]byte, error) {
|
||||
return map[string][]byte{
|
||||
"report1": []byte(test.report),
|
||||
}, nil
|
||||
|
||||
@@ -24,7 +24,7 @@ type ClusterVersion struct {
|
||||
type StorageClass struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
StorageClassName string `json:"storageClassName" yaml:"storageClassName"`
|
||||
StorageClassName string `json:"storageClassName,omitempty" yaml:"storageClassName,omitempty"`
|
||||
}
|
||||
|
||||
type CustomResourceDefinition struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ type ClusterVersion struct {
|
||||
type StorageClass struct {
|
||||
AnalyzeMeta `json:",inline" yaml:",inline"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
StorageClassName string `json:"storageClassName" yaml:"storageClassName"`
|
||||
StorageClassName string `json:"storageClassName,omitempty" yaml:"storageClassName,omitempty"`
|
||||
}
|
||||
|
||||
type CustomResourceDefinition struct {
|
||||
@@ -129,6 +129,7 @@ type TextAnalyze struct {
|
||||
RegexGroups string `json:"regexGroups,omitempty" yaml:"regexGroups,omitempty"`
|
||||
IgnoreIfNoFiles bool `json:"ignoreIfNoFiles,omitempty" yaml:"ignoreIfNoFiles,omitempty"`
|
||||
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
|
||||
ExcludeFiles []string `json:"excludeFiles,omitempty" yaml:"excludeFiles,omitempty"`
|
||||
}
|
||||
|
||||
type YamlCompare struct {
|
||||
|
||||
@@ -58,7 +58,7 @@ type Logs struct {
|
||||
Selector []string `json:"selector" yaml:"selector"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
ContainerNames []string `json:"containerNames,omitempty" yaml:"containerNames,omitempty"`
|
||||
Limits *LogLimits `json:"limits,omitempty" yaml:"omitempty"`
|
||||
Limits *LogLimits `json:"limits,omitempty" yaml:"limits,omitempty"`
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
@@ -168,8 +168,22 @@ type Put struct {
|
||||
|
||||
type Database struct {
|
||||
CollectorMeta `json:",inline" yaml:",inline"`
|
||||
URI string `json:"uri" yaml:"uri"`
|
||||
Parameters []string `json:"parameters,omitempty"`
|
||||
URI string `json:"uri" yaml:"uri"`
|
||||
Parameters []string `json:"parameters,omitempty"`
|
||||
TLS *TLSParams `json:"tls,omitempty" yaml:"tls,omitempty"`
|
||||
}
|
||||
|
||||
type TLSParams struct {
|
||||
SkipVerify bool `json:"skipVerify,omitempty" yaml:"skipVerify,omitempty"`
|
||||
Secret *TLSSecret `json:"secret,omitempty" yaml:"secret,omitempty"`
|
||||
CACert string `json:"cacert,omitempty" yaml:"cacert,omitempty"`
|
||||
ClientCert string `json:"clientCert,omitempty" yaml:"clientCert,omitempty"`
|
||||
ClientKey string `json:"clientKey,omitempty" yaml:"clientKey,omitempty"`
|
||||
}
|
||||
|
||||
type TLSSecret struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
}
|
||||
|
||||
type Collectd struct {
|
||||
|
||||
@@ -1081,6 +1081,11 @@ func (in *Database) DeepCopyInto(out *Database) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.TLS != nil {
|
||||
in, out := &in.TLS, &out.TLS
|
||||
*out = new(TLSParams)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Database.
|
||||
@@ -4102,6 +4107,41 @@ func (in *TCPPortStatusAnalyze) DeepCopy() *TCPPortStatusAnalyze {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLSParams) DeepCopyInto(out *TLSParams) {
|
||||
*out = *in
|
||||
if in.Secret != nil {
|
||||
in, out := &in.Secret, &out.Secret
|
||||
*out = new(TLSSecret)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSParams.
|
||||
func (in *TLSParams) DeepCopy() *TLSParams {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TLSParams)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLSSecret) DeepCopyInto(out *TLSSecret) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSecret.
|
||||
func (in *TLSSecret) DeepCopy() *TLSSecret {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TLSSecret)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TextAnalyze) DeepCopyInto(out *TextAnalyze) {
|
||||
*out = *in
|
||||
@@ -4117,6 +4157,11 @@ func (in *TextAnalyze) DeepCopyInto(out *TextAnalyze) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.ExcludeFiles != nil {
|
||||
in, out := &in.ExcludeFiles, &out.ExcludeFiles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TextAnalyze.
|
||||
|
||||
@@ -38,14 +38,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
|
||||
@@ -38,14 +38,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
|
||||
+34
-11
@@ -11,6 +11,7 @@ import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -105,23 +106,41 @@ var CephCommands = []CephCommand{
|
||||
},
|
||||
}
|
||||
|
||||
func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (CollectorResult, error) {
|
||||
type CollectCeph struct {
|
||||
Collector *troubleshootv1beta2.Ceph
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCeph) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCeph) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectCeph) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
if cephCollector.Namespace == "" {
|
||||
cephCollector.Namespace = DefaultCephNamespace
|
||||
if c.Namespace == "" {
|
||||
c.Namespace = DefaultCephNamespace
|
||||
}
|
||||
|
||||
pod, err := findRookCephToolsPod(ctx, c, cephCollector.Namespace)
|
||||
pod, err := findRookCephToolsPod(ctx, c, c.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := NewResult()
|
||||
for _, command := range CephCommands {
|
||||
err := cephCommandExec(ctx, c, cephCollector, pod, command, output)
|
||||
err := cephCommandExec(ctx, progressChan, c, c.Collector, pod, command, output)
|
||||
if err != nil {
|
||||
pathPrefix := GetCephCollectorFilepath(cephCollector.CollectorName, cephCollector.Namespace)
|
||||
pathPrefix := GetCephCollectorFilepath(c.Collector.CollectorName, c.Namespace)
|
||||
dstFileName := path.Join(pathPrefix, fmt.Sprintf("%s.%s-error", command.ID, command.Format))
|
||||
output.SaveResult(c.BundlePath, dstFileName, strings.NewReader(err.Error()))
|
||||
}
|
||||
@@ -130,26 +149,30 @@ func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (CollectorResul
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand, output CollectorResult) error {
|
||||
func cephCommandExec(ctx context.Context, progressChan chan<- interface{}, c *CollectCeph, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand, output CollectorResult) error {
|
||||
timeout := cephCollector.Timeout
|
||||
if timeout == "" {
|
||||
timeout = command.DefaultTimeout
|
||||
}
|
||||
|
||||
execCollector := &troubleshootv1beta2.Exec{
|
||||
execSpec := &troubleshootv1beta2.Exec{
|
||||
Selector: labelsToSelector(pod.Labels),
|
||||
Namespace: pod.Namespace,
|
||||
Command: command.Command,
|
||||
Args: command.Args,
|
||||
Timeout: timeout,
|
||||
}
|
||||
results, err := Exec(c, execCollector)
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
execCollector := &CollectExec{execSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
|
||||
|
||||
results, err := execCollector.Collect(progressChan)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to exec command")
|
||||
}
|
||||
|
||||
pathPrefix := GetCephCollectorFilepath(cephCollector.CollectorName, cephCollector.Namespace)
|
||||
for srcFilename, _ := range results {
|
||||
for srcFilename := range results {
|
||||
var dstFileName string
|
||||
switch {
|
||||
case strings.HasSuffix(srcFilename, "-stdout.txt"):
|
||||
@@ -171,7 +194,7 @@ func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troublesh
|
||||
return nil
|
||||
}
|
||||
|
||||
func findRookCephToolsPod(ctx context.Context, c *Collector, namespace string) (*corev1.Pod, error) {
|
||||
func findRookCephToolsPod(ctx context.Context, c *CollectCeph, namespace string) (*corev1.Pod, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create kubernetes client")
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ClusterVersion struct {
|
||||
@@ -15,7 +17,23 @@ type ClusterVersion struct {
|
||||
String string `json:"string"`
|
||||
}
|
||||
|
||||
func ClusterInfo(c *Collector) (CollectorResult, error) {
|
||||
type CollectClusterInfo struct {
|
||||
Collector *troubleshootv1beta2.ClusterInfo
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to create kubernetes clientset")
|
||||
|
||||
@@ -34,7 +34,28 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil/discovery"
|
||||
)
|
||||
|
||||
func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1beta2.ClusterResources) (CollectorResult, error) {
|
||||
type CollectClusterResources struct {
|
||||
Collector *troubleshootv1beta2.ClusterResources
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Merge(allCollectors []Collector) ([]Collector, error) {
|
||||
result := append(allCollectors, c)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,9 +72,9 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
// namespaces
|
||||
nsListedFromCluster := false
|
||||
var namespaceNames []string
|
||||
if len(clusterResourcesCollector.Namespaces) > 0 {
|
||||
namespaces, namespaceErrors := getNamespaces(ctx, client, clusterResourcesCollector.Namespaces)
|
||||
namespaceNames = clusterResourcesCollector.Namespaces
|
||||
if len(c.Collector.Namespaces) > 0 {
|
||||
namespaces, namespaceErrors := getNamespaces(ctx, client, c.Collector.Namespaces)
|
||||
namespaceNames = c.Collector.Namespaces
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/namespaces.json", bytes.NewBuffer(namespaces))
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/namespaces-errors.json", marshalErrors(namespaceErrors))
|
||||
} else if c.Namespace != "" {
|
||||
@@ -82,7 +103,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/auth-cani-list-errors.json", marshalErrors(reviewStatusErrors))
|
||||
|
||||
if nsListedFromCluster && !clusterResourcesCollector.IgnoreRBAC {
|
||||
if nsListedFromCluster && !c.Collector.IgnoreRBAC {
|
||||
filteredNamespaces := []string{}
|
||||
for _, ns := range namespaceNames {
|
||||
status := reviewStatuses[ns]
|
||||
@@ -110,7 +131,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
limits := &troubleshootv1beta2.LogLimits{
|
||||
MaxLines: 500,
|
||||
}
|
||||
podLogs, err := savePodLogs(ctx, logsRoot, client, pod, "", container.Name, limits, false)
|
||||
podLogs, err := savePodLogs(ctx, logsRoot, client, &pod, "", container.Name, limits, false)
|
||||
if err != nil {
|
||||
errPath := filepath.Join("cluster-resources", "pods", "logs", pod.Namespace, pod.Name, fmt.Sprintf("%s-logs-errors.log", container.Name))
|
||||
output.SaveResult(c.BundlePath, errPath, bytes.NewBuffer([]byte(err.Error())))
|
||||
@@ -121,7 +142,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
}
|
||||
}
|
||||
|
||||
// pod disruption budgets (for all existing namespaces)
|
||||
// pod disruption budgets
|
||||
|
||||
PodDisruptionBudgets, pdbError := getPodDisruptionBudgets(ctx, client, namespaceNames)
|
||||
for k, v := range PodDisruptionBudgets {
|
||||
@@ -185,6 +206,12 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/network-policy-errors.json", marshalErrors(networkPolicyErrors))
|
||||
|
||||
// resource quotas
|
||||
resourceQuota, resourceQuotaErrors := resourceQuota(ctx, client, namespaceNames)
|
||||
for k, v := range resourceQuota {
|
||||
output.SaveResult(c.BundlePath, path.Join("cluster-resources/resource-quotas", k), bytes.NewBuffer(v))
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/resource-quota-errors.json", marshalErrors(resourceQuotaErrors))
|
||||
|
||||
// storage classes
|
||||
storageClasses, storageErrors := storageClasses(ctx, client)
|
||||
@@ -246,6 +273,30 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/pvcs-errors.json", marshalErrors(pvcsErrors))
|
||||
|
||||
//Roles
|
||||
roles, rolesErrors := roles(ctx, client, namespaceNames)
|
||||
for k, v := range roles {
|
||||
output.SaveResult(c.BundlePath, path.Join("cluster-resources/roles", k), bytes.NewBuffer(v))
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/roles-errors.json", marshalErrors(rolesErrors))
|
||||
|
||||
//Role Bindings
|
||||
roleBindings, roleBindingsErrors := roleBindings(ctx, client, namespaceNames)
|
||||
for k, v := range roleBindings {
|
||||
output.SaveResult(c.BundlePath, path.Join("cluster-resources/rolebindings", k), bytes.NewBuffer(v))
|
||||
}
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/rolebindings-errors.json", marshalErrors(roleBindingsErrors))
|
||||
|
||||
//Cluster Roles
|
||||
clusterRoles, clusterRolesErrors := clusterRoles(ctx, client)
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/clusterroles.json", bytes.NewBuffer(clusterRoles))
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/clusterroles-errors.json", marshalErrors(clusterRolesErrors))
|
||||
|
||||
//Cluster Role Bindings
|
||||
clusterRoleBindings, clusterRoleBindingsErrors := clusterRoleBindings(ctx, client)
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/clusterRoleBindings.json", bytes.NewBuffer(clusterRoleBindings))
|
||||
output.SaveResult(c.BundlePath, "cluster-resources/clusterRoleBindings-errors.json", marshalErrors(clusterRoleBindingsErrors))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -359,6 +410,55 @@ func pods(ctx context.Context, client *kubernetes.Clientset, namespaces []string
|
||||
}
|
||||
|
||||
func getPodDisruptionBudgets(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
ok, err := discovery.HasResource(client, "policy.k8s.io/v1", "PodDisruptionBudgets")
|
||||
if err != nil {
|
||||
return nil, map[string]string{"": err.Error()}
|
||||
}
|
||||
if ok {
|
||||
return pdbV1(ctx, client, namespaces)
|
||||
}
|
||||
|
||||
return pdbV1beta(ctx, client, namespaces)
|
||||
}
|
||||
|
||||
// TODO: The below function (`pdbV1`) needs to be DRY'd and moved into the main `getPodDisruptionBudgets` function.
|
||||
func pdbV1(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
pdbByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
PodDisruptionBudgets, err := client.PolicyV1().PodDisruptionBudgets(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(PodDisruptionBudgets, scheme.Scheme)
|
||||
if err == nil {
|
||||
PodDisruptionBudgets.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range PodDisruptionBudgets.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
PodDisruptionBudgets.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(PodDisruptionBudgets, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
pdbByNamespace[namespace+".json"] = b
|
||||
}
|
||||
|
||||
return pdbByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
// This block/function can remain as is
|
||||
func pdbV1beta(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
pdbByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
@@ -374,6 +474,13 @@ func getPodDisruptionBudgets(ctx context.Context, client *kubernetes.Clientset,
|
||||
PodDisruptionBudgets.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range PodDisruptionBudgets.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
PodDisruptionBudgets.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(PodDisruptionBudgets, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
@@ -562,29 +669,76 @@ func jobs(ctx context.Context, client *kubernetes.Clientset, namespaces []string
|
||||
}
|
||||
|
||||
func cronJobs(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
ok, err := discovery.HasResource(client, "batch.k8s.io/v1", "CronJobs")
|
||||
if err != nil {
|
||||
return nil, map[string]string{"": err.Error()}
|
||||
}
|
||||
if ok {
|
||||
return cronJobsV1(ctx, client, namespaces)
|
||||
}
|
||||
|
||||
return cronJobsV1beta(ctx, client, namespaces)
|
||||
}
|
||||
|
||||
func cronJobsV1(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
cronJobsByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
nsCronJobs, err := client.BatchV1beta1().CronJobs(namespace).List(ctx, metav1.ListOptions{})
|
||||
cronJobs, err := client.BatchV1().CronJobs(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(nsCronJobs, scheme.Scheme)
|
||||
gvk, err := apiutil.GVKForObject(cronJobs, scheme.Scheme)
|
||||
if err == nil {
|
||||
nsCronJobs.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
cronJobs.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range nsCronJobs.Items {
|
||||
for i, o := range cronJobs.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
nsCronJobs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
cronJobs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(nsCronJobs, "", " ")
|
||||
b, err := json.MarshalIndent(cronJobs, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
cronJobsByNamespace[namespace+".json"] = b
|
||||
}
|
||||
|
||||
return cronJobsByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
func cronJobsV1beta(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
cronJobsByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
cronJobs, err := client.BatchV1beta1().CronJobs(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(cronJobs, scheme.Scheme)
|
||||
if err == nil {
|
||||
cronJobs.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range cronJobs.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
cronJobs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(cronJobs, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
@@ -713,6 +867,41 @@ func networkPolicy(ctx context.Context, client *kubernetes.Clientset, namespaces
|
||||
return networkPolicyByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
func resourceQuota(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
resourceQuotaByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
resourceQuota, err := client.CoreV1().ResourceQuotas(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(resourceQuota, scheme.Scheme)
|
||||
if err == nil {
|
||||
resourceQuota.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range resourceQuota.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
resourceQuota.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(resourceQuota, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
resourceQuotaByNamespace[namespace+".json"] = b
|
||||
}
|
||||
|
||||
return resourceQuotaByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
func storageClasses(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
|
||||
ok, err := discovery.HasResource(client, "storage.k8s.io/v1", "StorageClass")
|
||||
if err != nil {
|
||||
@@ -1424,3 +1613,123 @@ func pvcs(ctx context.Context, client *kubernetes.Clientset, namespaces []string
|
||||
|
||||
return pvcsByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
func roles(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
rolesByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
roles, err := client.RbacV1().Roles(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(roles, scheme.Scheme)
|
||||
if err == nil {
|
||||
roles.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range roles.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
roles.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(roles, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
rolesByNamespace[namespace+".json"] = b
|
||||
}
|
||||
|
||||
return rolesByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
func roleBindings(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
|
||||
roleBindingsByNamespace := make(map[string][]byte)
|
||||
errorsByNamespace := make(map[string]string)
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
roleBindings, err := client.RbacV1().RoleBindings(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(roleBindings, scheme.Scheme)
|
||||
if err == nil {
|
||||
roleBindings.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range roleBindings.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
roleBindings.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(roleBindings, "", " ")
|
||||
if err != nil {
|
||||
errorsByNamespace[namespace] = err.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
roleBindingsByNamespace[namespace+".json"] = b
|
||||
}
|
||||
|
||||
return roleBindingsByNamespace, errorsByNamespace
|
||||
}
|
||||
|
||||
func clusterRoles(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
|
||||
clusterRoles, err := client.RbacV1().ClusterRoles().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, []string{err.Error()}
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(clusterRoles, scheme.Scheme)
|
||||
if err == nil {
|
||||
clusterRoles.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range clusterRoles.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
clusterRoles.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(clusterRoles, "", " ")
|
||||
if err != nil {
|
||||
return nil, []string{err.Error()}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func clusterRoleBindings(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
|
||||
clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, []string{err.Error()}
|
||||
}
|
||||
|
||||
gvk, err := apiutil.GVKForObject(clusterRoleBindings, scheme.Scheme)
|
||||
if err == nil {
|
||||
clusterRoleBindings.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
for i, o := range clusterRoleBindings.Items {
|
||||
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
|
||||
if err == nil {
|
||||
clusterRoleBindings.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(clusterRoleBindings, "", " ")
|
||||
if err != nil {
|
||||
return nil, []string{err.Error()}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -190,3 +190,31 @@ func CollectRemote(c *troubleshootv1beta2.RemoteCollector, additionalRedactors *
|
||||
collectResult.AllCollectedData = allCollectedData
|
||||
return collectResult, nil
|
||||
}
|
||||
|
||||
// Ensure that the specified collector is in the list of collectors
|
||||
func EnsureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
|
||||
for _, inList := range list {
|
||||
if collector.ClusterResources != nil && inList.ClusterResources != nil {
|
||||
return list
|
||||
}
|
||||
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
return append(list, &collector)
|
||||
}
|
||||
|
||||
// collect ClusterResources earliest in the list so the pod list does not include pods started by collectors
|
||||
func EnsureClusterResourcesFirst(list []*troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
|
||||
sliceOfClusterResources := []*troubleshootv1beta2.Collect{}
|
||||
sliceOfOtherCollectors := []*troubleshootv1beta2.Collect{}
|
||||
for _, collector := range list {
|
||||
if collector.ClusterResources != nil {
|
||||
sliceOfClusterResources = append(sliceOfClusterResources, []*troubleshootv1beta2.Collect{collector}...)
|
||||
} else {
|
||||
sliceOfOtherCollectors = append(sliceOfOtherCollectors, []*troubleshootv1beta2.Collect{collector}...)
|
||||
}
|
||||
}
|
||||
return append(sliceOfClusterResources, sliceOfOtherCollectors...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ensureClusterResourcesFirst(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want []*troubleshootv1beta2.Collect
|
||||
list []*troubleshootv1beta2.Collect
|
||||
}{
|
||||
{
|
||||
name: "Reorg OK",
|
||||
want: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
{
|
||||
Data: &troubleshootv1beta2.Data{},
|
||||
},
|
||||
},
|
||||
list: []*troubleshootv1beta2.Collect{
|
||||
{
|
||||
Data: &troubleshootv1beta2.Data{},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EnsureClusterResourcesFirst(tc.list)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
+32
-10
@@ -5,19 +5,41 @@ import (
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Collectd(ctx context.Context, c *Collector, collector *troubleshootv1beta2.Collectd, namespace string, clientConfig *restclient.Config, client kubernetes.Interface) (CollectorResult, error) {
|
||||
type CollectCollectd struct {
|
||||
Collector *troubleshootv1beta2.Collectd
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
copyFromHost := &troubleshootv1beta2.CopyFromHost{
|
||||
CollectorMeta: collector.CollectorMeta,
|
||||
CollectorMeta: c.Collector.CollectorMeta,
|
||||
Name: "collectd/rrd",
|
||||
Namespace: collector.Namespace,
|
||||
Image: collector.Image,
|
||||
ImagePullPolicy: collector.ImagePullPolicy,
|
||||
ImagePullSecret: collector.ImagePullSecret,
|
||||
Timeout: collector.Timeout,
|
||||
HostPath: collector.HostPath,
|
||||
Namespace: c.Collector.Namespace,
|
||||
Image: c.Collector.Image,
|
||||
ImagePullPolicy: c.Collector.ImagePullPolicy,
|
||||
ImagePullSecret: c.Collector.ImagePullSecret,
|
||||
Timeout: c.Collector.Timeout,
|
||||
HostPath: c.Collector.HostPath,
|
||||
}
|
||||
return CopyFromHost(ctx, c, copyFromHost, namespace, clientConfig, client)
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
copyFromHostCollector := &CollectCopyFromHost{copyFromHost, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
|
||||
|
||||
return copyFromHostCollector.Collect(progressChan)
|
||||
}
|
||||
|
||||
+140
-300
@@ -2,29 +2,33 @@ package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/multitype"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type Collector struct {
|
||||
Collect *troubleshootv1beta2.Collect
|
||||
Redact bool
|
||||
RBACErrors []error
|
||||
ClientConfig *rest.Config
|
||||
Namespace string
|
||||
BundlePath string
|
||||
type Collector interface {
|
||||
Title() string
|
||||
IsExcluded() (bool, error)
|
||||
GetRBACErrors() []error
|
||||
HasRBACErrors() bool
|
||||
CheckRBAC(ctx context.Context, c Collector, collector *troubleshootv1beta2.Collect, clientConfig *rest.Config, namespace string) error
|
||||
Collect(progressChan chan<- interface{}) (CollectorResult, error)
|
||||
}
|
||||
|
||||
type Collectors []*Collector
|
||||
type MergeableCollector interface {
|
||||
Collector
|
||||
Merge(allCollectors []Collector) ([]Collector, error)
|
||||
}
|
||||
|
||||
//type Collectors []*Collector
|
||||
|
||||
func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
|
||||
if excludeVal == nil {
|
||||
@@ -47,305 +51,141 @@ func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// checks if a given collector has a spec with 'exclude' that evaluates to true.
|
||||
func (c *Collector) IsExcluded() bool {
|
||||
if c.Collect.ClusterInfo != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.ClusterInfo.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.ClusterResources != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.ClusterResources.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Secret != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Secret.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.ConfigMap != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.ConfigMap.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Logs != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Logs.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Run != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Run.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.RunPod != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.RunPod.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Exec != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Exec.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Data != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Data.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Copy != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Copy.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.CopyFromHost != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.CopyFromHost.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.HTTP != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.HTTP.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Postgres != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Postgres.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Mysql != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Mysql.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Redis != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Redis.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Collectd != nil {
|
||||
// TODO: see if redaction breaks these
|
||||
isExcludedResult, err := isExcluded(c.Collect.Collectd.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Ceph != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Ceph.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Longhorn != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Longhorn.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
} else if c.Collect.Sysctl != nil {
|
||||
isExcludedResult, err := isExcluded(c.Collect.Sysctl.Exclude)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if isExcludedResult {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Collector) RunCollectorSync(clientConfig *rest.Config, client kubernetes.Interface, globalRedactors []*troubleshootv1beta2.Redact) (result CollectorResult, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
_, file, line, _ := runtime.Caller(4)
|
||||
err = errors.Errorf("recovered from panic at \"%s:%d\": %v", file, line, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if c.IsExcluded() {
|
||||
return
|
||||
}
|
||||
func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, namespace string, clientConfig *rest.Config, client kubernetes.Interface, sinceTime *time.Time) (interface{}, bool) {
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
if c.Collect.ClusterInfo != nil {
|
||||
result, err = ClusterInfo(c)
|
||||
} else if c.Collect.ClusterResources != nil {
|
||||
result, err = ClusterResources(c, c.Collect.ClusterResources)
|
||||
} else if c.Collect.Secret != nil {
|
||||
result, err = Secret(ctx, c, c.Collect.Secret, client)
|
||||
} else if c.Collect.ConfigMap != nil {
|
||||
result, err = ConfigMap(ctx, c, c.Collect.ConfigMap, client)
|
||||
} else if c.Collect.Logs != nil {
|
||||
result, err = Logs(c, c.Collect.Logs)
|
||||
} else if c.Collect.Run != nil {
|
||||
result, err = Run(c, c.Collect.Run)
|
||||
} else if c.Collect.RunPod != nil {
|
||||
result, err = RunPod(c, c.Collect.RunPod)
|
||||
} else if c.Collect.Exec != nil {
|
||||
result, err = Exec(c, c.Collect.Exec)
|
||||
} else if c.Collect.Data != nil {
|
||||
result, err = Data(c, c.Collect.Data)
|
||||
} else if c.Collect.Copy != nil {
|
||||
result, err = Copy(c, c.Collect.Copy)
|
||||
} else if c.Collect.CopyFromHost != nil {
|
||||
namespace := c.Collect.CopyFromHost.Namespace
|
||||
if namespace == "" && c.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
} else if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
}
|
||||
result, err = CopyFromHost(ctx, c, c.Collect.CopyFromHost, namespace, clientConfig, client)
|
||||
} else if c.Collect.HTTP != nil {
|
||||
result, err = HTTP(c, c.Collect.HTTP)
|
||||
} else if c.Collect.Postgres != nil {
|
||||
result, err = Postgres(c, c.Collect.Postgres)
|
||||
} else if c.Collect.Mysql != nil {
|
||||
result, err = Mysql(c, c.Collect.Mysql)
|
||||
} else if c.Collect.Redis != nil {
|
||||
result, err = Redis(c, c.Collect.Redis)
|
||||
} else if c.Collect.Collectd != nil {
|
||||
// TODO: see if redaction breaks these
|
||||
namespace := c.Collect.Collectd.Namespace
|
||||
if namespace == "" && c.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
} else if namespace == "" {
|
||||
namespace = c.Namespace
|
||||
}
|
||||
result, err = Collectd(ctx, c, c.Collect.Collectd, namespace, clientConfig, client)
|
||||
} else if c.Collect.Ceph != nil {
|
||||
result, err = Ceph(c, c.Collect.Ceph)
|
||||
} else if c.Collect.Longhorn != nil {
|
||||
result, err = Longhorn(c, c.Collect.Longhorn)
|
||||
} else if c.Collect.RegistryImages != nil {
|
||||
result, err = Registry(c, c.Collect.RegistryImages)
|
||||
} else if c.Collect.Sysctl != nil {
|
||||
if c.Collect.Sysctl.Namespace == "" {
|
||||
c.Collect.Sysctl.Namespace = c.Namespace
|
||||
}
|
||||
if c.Collect.Sysctl.Namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ := kubeconfig.Namespace()
|
||||
c.Collect.Sysctl.Namespace = namespace
|
||||
}
|
||||
result, err = Sysctl(ctx, c, client, c.Collect.Sysctl)
|
||||
} else {
|
||||
err = errors.New("no spec found to run")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var RBACErrors []error
|
||||
|
||||
if c.Redact {
|
||||
err = RedactResult(c.BundlePath, result, globalRedactors)
|
||||
err = errors.Wrap(err, "failed to redact")
|
||||
switch {
|
||||
case collector.ClusterInfo != nil:
|
||||
return &CollectClusterInfo{collector.ClusterInfo, bundlePath, namespace, clientConfig, RBACErrors}, true
|
||||
case collector.ClusterResources != nil:
|
||||
return &CollectClusterResources{collector.ClusterResources, bundlePath, namespace, clientConfig, RBACErrors}, true
|
||||
case collector.Secret != nil:
|
||||
return &CollectSecret{collector.Secret, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.ConfigMap != nil:
|
||||
return &CollectConfigMap{collector.ConfigMap, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Logs != nil:
|
||||
return &CollectLogs{collector.Logs, bundlePath, namespace, clientConfig, client, ctx, sinceTime, RBACErrors}, true
|
||||
case collector.Run != nil:
|
||||
return &CollectRun{collector.Run, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.RunPod != nil:
|
||||
return &CollectRunPod{collector.RunPod, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Exec != nil:
|
||||
return &CollectExec{collector.Exec, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Data != nil:
|
||||
return &CollectData{collector.Data, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Copy != nil:
|
||||
return &CollectCopy{collector.Copy, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.CopyFromHost != nil:
|
||||
return &CollectCopyFromHost{collector.CopyFromHost, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.HTTP != nil:
|
||||
return &CollectHTTP{collector.HTTP, bundlePath, namespace, clientConfig, client, RBACErrors}, true
|
||||
case collector.Postgres != nil:
|
||||
return &CollectPostgres{collector.Postgres, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Mysql != nil:
|
||||
return &CollectMysql{collector.Mysql, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Redis != nil:
|
||||
return &CollectRedis{collector.Redis, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Collectd != nil:
|
||||
return &CollectCollectd{collector.Collectd, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Ceph != nil:
|
||||
return &CollectCeph{collector.Ceph, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Longhorn != nil:
|
||||
return &CollectLonghorn{collector.Longhorn, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.RegistryImages != nil:
|
||||
return &CollectRegistry{collector.RegistryImages, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
case collector.Sysctl != nil:
|
||||
return &CollectSysctl{collector.Sysctl, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Collector) GetDisplayName() string {
|
||||
return c.Collect.GetName()
|
||||
func collectorTitleOrDefault(meta troubleshootv1beta2.CollectorMeta, defaultTitle string) string {
|
||||
if meta.CollectorName != "" {
|
||||
return meta.CollectorName
|
||||
}
|
||||
return defaultTitle
|
||||
}
|
||||
|
||||
func (c *Collector) CheckRBAC(ctx context.Context) error {
|
||||
if c.IsExcluded() {
|
||||
return nil // excluded collectors require no permissions
|
||||
func getCollectorName(c interface{}) string {
|
||||
var collector, name, selector string
|
||||
|
||||
switch v := c.(type) {
|
||||
case *CollectClusterInfo:
|
||||
collector = "cluster-info"
|
||||
case *CollectClusterResources:
|
||||
collector = "cluster-resources"
|
||||
case *CollectSecret:
|
||||
collector = "secret"
|
||||
name = v.Collector.CollectorName
|
||||
selector = strings.Join(v.Collector.Selector, ",")
|
||||
case *CollectConfigMap:
|
||||
collector = "configmap"
|
||||
name = v.Collector.CollectorName
|
||||
selector = strings.Join(v.Collector.Selector, ",")
|
||||
case *CollectLogs:
|
||||
collector = "logs"
|
||||
name = v.Collector.CollectorName
|
||||
selector = strings.Join(v.Collector.Selector, ",")
|
||||
case *CollectRun:
|
||||
collector = "run"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectRunPod:
|
||||
collector = "run-pod"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectExec:
|
||||
collector = "exec"
|
||||
name = v.Collector.CollectorName
|
||||
selector = strings.Join(v.Collector.Selector, ",")
|
||||
case *CollectData:
|
||||
collector = "data"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectCopy:
|
||||
collector = "copy"
|
||||
name = v.Collector.CollectorName
|
||||
selector = strings.Join(v.Collector.Selector, ",")
|
||||
case *CollectCopyFromHost:
|
||||
collector = "copy-from-host"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectHTTP:
|
||||
collector = "http"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectPostgres:
|
||||
collector = "postgres"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectMysql:
|
||||
collector = "mysql"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectRedis:
|
||||
collector = "redis"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectCollectd:
|
||||
collector = "collectd"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectCeph:
|
||||
collector = "ceph"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectLonghorn:
|
||||
collector = "longhorn"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectRegistry:
|
||||
collector = "registry-images"
|
||||
name = v.Collector.CollectorName
|
||||
case *CollectSysctl:
|
||||
collector = "sysctl"
|
||||
name = v.Collector.Name
|
||||
default:
|
||||
collector = "<none>"
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create client from config")
|
||||
if name != "" {
|
||||
return fmt.Sprintf("%s/%s", collector, name)
|
||||
}
|
||||
|
||||
forbidden := make([]error, 0)
|
||||
|
||||
specs := c.Collect.AccessReviewSpecs(c.Namespace)
|
||||
for _, spec := range specs {
|
||||
sar := &authorizationv1.SelfSubjectAccessReview{
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
resp, err := client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to run subject review")
|
||||
}
|
||||
|
||||
if !resp.Status.Allowed { // all other fields of Status are empty...
|
||||
forbidden = append(forbidden, RBACError{
|
||||
DisplayName: c.GetDisplayName(),
|
||||
Namespace: spec.ResourceAttributes.Namespace,
|
||||
Resource: spec.ResourceAttributes.Resource,
|
||||
Verb: spec.ResourceAttributes.Verb,
|
||||
})
|
||||
}
|
||||
if selector != "" {
|
||||
return fmt.Sprintf("%s/%s", collector, selector)
|
||||
}
|
||||
c.RBACErrors = forbidden
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs Collectors) CheckRBAC(ctx context.Context) error {
|
||||
for _, c := range cs {
|
||||
if err := c.CheckRBAC(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to check RBAC")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return collector
|
||||
}
|
||||
|
||||
@@ -273,16 +273,26 @@ pwd=somethinggoeshere;`,
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
c := &Collector{
|
||||
Collect: tt.Collect,
|
||||
Redact: true,
|
||||
|
||||
var result CollectorResult
|
||||
|
||||
collector, _ := GetCollector(tt.Collect, "", "", nil, nil, nil)
|
||||
regCollector, _ := collector.(Collector)
|
||||
|
||||
if excluded, err := regCollector.IsExcluded(); !excluded {
|
||||
req.NoError(err)
|
||||
|
||||
result, err = regCollector.Collect(nil)
|
||||
req.NoError(err)
|
||||
|
||||
err = RedactResult("", result, tt.Redactors)
|
||||
|
||||
req.NoError(err)
|
||||
}
|
||||
got, err := c.RunCollectorSync(nil, nil, tt.Redactors)
|
||||
req.NoError(err)
|
||||
|
||||
// convert to string to make differences easier to see
|
||||
toString := map[string]string{}
|
||||
for k, v := range got {
|
||||
for k, v := range result {
|
||||
toString[k] = string(v)
|
||||
}
|
||||
req.EqualValues(tt.want, toString)
|
||||
@@ -332,16 +342,22 @@ pwd=somethinggoeshere;`,
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
c := &Collector{
|
||||
Collect: tt.Collect,
|
||||
Redact: false,
|
||||
|
||||
var result CollectorResult
|
||||
|
||||
collector, _ := GetCollector(tt.Collect, "", "", nil, nil, nil)
|
||||
regCollector, _ := collector.(Collector)
|
||||
|
||||
if excluded, err := regCollector.IsExcluded(); !excluded {
|
||||
req.NoError(err)
|
||||
|
||||
result, err = regCollector.Collect(nil)
|
||||
req.NoError(err)
|
||||
}
|
||||
got, err := c.RunCollectorSync(nil, nil, tt.Redactors)
|
||||
req.NoError(err)
|
||||
|
||||
// convert to string to make differences easier to see
|
||||
toString := map[string]string{}
|
||||
for k, v := range got {
|
||||
for k, v := range result {
|
||||
toString[k] = string(v)
|
||||
}
|
||||
req.EqualValues(tt.want, toString)
|
||||
|
||||
+29
-10
@@ -14,6 +14,7 @@ import (
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ConfigMapOutput struct {
|
||||
@@ -26,28 +27,46 @@ type ConfigMapOutput struct {
|
||||
Data map[string]string `json:"data,omitonempty"`
|
||||
}
|
||||
|
||||
func ConfigMap(ctx context.Context, c *Collector, configMapCollector *troubleshootv1beta2.ConfigMap, client kubernetes.Interface) (CollectorResult, error) {
|
||||
type CollectConfigMap struct {
|
||||
Collector *troubleshootv1beta2.ConfigMap
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
output := NewResult()
|
||||
|
||||
configMaps := []corev1.ConfigMap{}
|
||||
if configMapCollector.Name != "" {
|
||||
configMap, err := client.CoreV1().ConfigMaps(configMapCollector.Namespace).Get(ctx, configMapCollector.Name, metav1.GetOptions{})
|
||||
if c.Collector.Name != "" {
|
||||
configMap, err := c.Client.CoreV1().ConfigMaps(c.Collector.Namespace).Get(c.Context, c.Collector.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
filePath, encoded, err := configMapToOutput(configMapCollector, nil, configMapCollector.Name)
|
||||
filePath, encoded, err := configMapToOutput(c.Collector, nil, c.Collector.Name)
|
||||
if err != nil {
|
||||
return output, errors.Wrapf(err, "collect secret %s", configMapCollector.Name)
|
||||
return output, errors.Wrapf(err, "collect secret %s", c.Collector.Name)
|
||||
}
|
||||
output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(encoded))
|
||||
}
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(configMapCollector), marshalErrors([]string{err.Error()}))
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
}
|
||||
configMaps = append(configMaps, *configMap)
|
||||
} else if len(configMapCollector.Selector) > 0 {
|
||||
cms, err := listConfigMapsForSelector(ctx, client, configMapCollector.Namespace, configMapCollector.Selector)
|
||||
} else if len(c.Collector.Selector) > 0 {
|
||||
cms, err := listConfigMapsForSelector(c.Context, c.Client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if err != nil {
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(configMapCollector), marshalErrors([]string{err.Error()}))
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
}
|
||||
configMaps = append(configMaps, cms...)
|
||||
@@ -56,7 +75,7 @@ func ConfigMap(ctx context.Context, c *Collector, configMapCollector *troublesho
|
||||
}
|
||||
|
||||
for _, configMap := range configMaps {
|
||||
filePath, encoded, err := configMapToOutput(configMapCollector, &configMap, configMap.Name)
|
||||
filePath, encoded, err := configMapToOutput(c.Collector, &configMap, configMap.Name)
|
||||
if err != nil {
|
||||
return output, errors.Wrapf(err, "collect configMap %s", configMap.Name)
|
||||
}
|
||||
|
||||
@@ -328,8 +328,8 @@ func TestConfigMap(t *testing.T) {
|
||||
_, err := client.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, &configMap, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
c := &Collector{}
|
||||
got, err := ConfigMap(ctx, c, tt.configMapCollector, client)
|
||||
configMapCollector := &CollectConfigMap{tt.configMapCollector, "", "", nil, client, ctx, nil}
|
||||
got, err := configMapCollector.Collect(nil)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
||||
+33
-14
@@ -15,12 +15,31 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
//Copy function gets a file or folder from a container specified in the specs.
|
||||
func Copy(c *Collector, copyCollector *troubleshootv1beta2.Copy) (CollectorResult, error) {
|
||||
type CollectCopy struct {
|
||||
Collector *troubleshootv1beta2.Copy
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCopy) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCopy) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
// Copy function gets a file or folder from a container specified in the specs.
|
||||
func (c *CollectCopy) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -30,40 +49,40 @@ func Copy(c *Collector, copyCollector *troubleshootv1beta2.Copy) (CollectorResul
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, copyCollector.Namespace, copyCollector.Selector)
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if len(podsErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, getCopyErrosFileName(copyCollector), marshalErrors(podsErrors))
|
||||
output.SaveResult(c.BundlePath, getCopyErrosFileName(c.Collector), marshalErrors(podsErrors))
|
||||
}
|
||||
|
||||
if len(pods) > 0 {
|
||||
for _, pod := range pods {
|
||||
|
||||
containerName := pod.Spec.Containers[0].Name
|
||||
if copyCollector.ContainerName != "" {
|
||||
containerName = copyCollector.ContainerName
|
||||
if c.Collector.ContainerName != "" {
|
||||
containerName = c.Collector.ContainerName
|
||||
}
|
||||
|
||||
subPath := filepath.Join(copyCollector.Name, pod.Namespace, pod.Name, copyCollector.ContainerName)
|
||||
subPath := filepath.Join(c.Collector.Name, pod.Namespace, pod.Name, c.Collector.ContainerName)
|
||||
|
||||
copyCollector.ExtractArchive = true // TODO: existing regression. this flag is always ignored and this matches current behaviour
|
||||
c.Collector.ExtractArchive = true // TODO: existing regression. this flag is always ignored and this matches current behaviour
|
||||
|
||||
copyErrors := map[string]string{}
|
||||
|
||||
dstPath := filepath.Join(c.BundlePath, subPath, filepath.Dir(copyCollector.ContainerPath))
|
||||
files, stderr, err := copyFilesFromPod(ctx, dstPath, c.ClientConfig, client, pod.Name, containerName, pod.Namespace, copyCollector.ContainerPath, copyCollector.ExtractArchive)
|
||||
dstPath := filepath.Join(c.BundlePath, subPath, filepath.Dir(c.Collector.ContainerPath))
|
||||
files, stderr, err := copyFilesFromPod(ctx, dstPath, c.ClientConfig, client, pod.Name, containerName, pod.Namespace, c.Collector.ContainerPath, c.Collector.ExtractArchive)
|
||||
if err != nil {
|
||||
copyErrors[filepath.Join(copyCollector.ContainerPath, "error")] = err.Error()
|
||||
copyErrors[filepath.Join(c.Collector.ContainerPath, "error")] = err.Error()
|
||||
if len(stderr) > 0 {
|
||||
copyErrors[filepath.Join(copyCollector.ContainerPath, "stderr")] = string(stderr)
|
||||
copyErrors[filepath.Join(c.Collector.ContainerPath, "stderr")] = string(stderr)
|
||||
}
|
||||
|
||||
key := filepath.Join(subPath, copyCollector.ContainerPath+"-errors.json")
|
||||
key := filepath.Join(subPath, c.Collector.ContainerPath+"-errors.json")
|
||||
output.SaveResult(c.BundlePath, key, marshalErrors(copyErrors))
|
||||
continue
|
||||
}
|
||||
|
||||
for k, v := range files {
|
||||
output[filepath.Join(subPath, filepath.Dir(copyCollector.ContainerPath), k)] = v
|
||||
output[filepath.Join(subPath, filepath.Dir(c.Collector.ContainerPath), k)] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/segmentio/ksuid"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -20,19 +21,40 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// CopyFromHost is a function that copies a file or directory from a host or hosts to include in the bundle.
|
||||
func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1beta2.CopyFromHost, namespace string, clientConfig *restclient.Config, client kubernetes.Interface) (CollectorResult, error) {
|
||||
type CollectCopyFromHost struct {
|
||||
Collector *troubleshootv1beta2.CopyFromHost
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCopyFromHost) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCopyFromHost) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
// copies a file or directory from a host or hosts to include in the bundle.
|
||||
func (c *CollectCopyFromHost) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
var namespace string
|
||||
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/managed-by": "troubleshoot.sh",
|
||||
"troubleshoot.sh/collector": "copyfromhost",
|
||||
"troubleshoot.sh/copyfromhost-id": ksuid.New().String(),
|
||||
}
|
||||
|
||||
hostPath := filepath.Clean(collector.HostPath) // strip trailing slash
|
||||
hostPath := filepath.Clean(c.Collector.HostPath) // strip trailing slash
|
||||
|
||||
hostDir := filepath.Dir(hostPath)
|
||||
fileName := filepath.Base(hostPath)
|
||||
@@ -41,18 +63,24 @@ func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1be
|
||||
fileName = "."
|
||||
}
|
||||
|
||||
_, cleanup, err := copyFromHostCreateDaemonSet(ctx, client, collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
|
||||
namespace = c.Namespace
|
||||
if namespace == "" {
|
||||
kubeconfig := k8sutil.GetKubeconfig()
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
}
|
||||
|
||||
_, cleanup, err := copyFromHostCreateDaemonSet(c.Context, c.Client, c.Collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
|
||||
defer cleanup()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create daemonset")
|
||||
}
|
||||
|
||||
childCtx, cancel := context.WithCancel(ctx)
|
||||
childCtx, cancel := context.WithCancel(c.Context)
|
||||
defer cancel()
|
||||
|
||||
timeoutCtx := context.Background()
|
||||
if collector.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(collector.Timeout)
|
||||
if c.Collector.Timeout != "" {
|
||||
timeout, err := time.ParseDuration(c.Collector.Timeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse timeout")
|
||||
}
|
||||
@@ -67,12 +95,12 @@ func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1be
|
||||
resultCh := make(chan CollectorResult, 1)
|
||||
go func() {
|
||||
var outputFilename string
|
||||
if collector.Name != "" {
|
||||
outputFilename = collector.Name
|
||||
if c.Collector.Name != "" {
|
||||
outputFilename = c.Collector.Name
|
||||
} else {
|
||||
outputFilename = hostPath
|
||||
}
|
||||
b, err := copyFromHostGetFilesFromPods(childCtx, c, collector, clientConfig, client, fileName, outputFilename, labels, namespace)
|
||||
b, err := copyFromHostGetFilesFromPods(childCtx, c.BundlePath, c.Collector, c.ClientConfig, c.Client, fileName, outputFilename, labels, namespace)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
@@ -220,7 +248,7 @@ func copyFromHostCreateDaemonSet(ctx context.Context, client kubernetes.Interfac
|
||||
return createdDS.Name, cleanup, nil
|
||||
}
|
||||
|
||||
func copyFromHostGetFilesFromPods(ctx context.Context, c *Collector, collector *troubleshootv1beta2.CopyFromHost, clientConfig *restclient.Config, client kubernetes.Interface, fileName string, outputFilename string, labelSelector map[string]string, namespace string) (CollectorResult, error) {
|
||||
func copyFromHostGetFilesFromPods(ctx context.Context, bundlePath string, collector *troubleshootv1beta2.CopyFromHost, clientConfig *restclient.Config, client kubernetes.Interface, fileName string, outputFilename string, labelSelector map[string]string, namespace string) (CollectorResult, error) {
|
||||
opts := metav1.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(labelSelector).String(),
|
||||
}
|
||||
@@ -233,16 +261,16 @@ func copyFromHostGetFilesFromPods(ctx context.Context, c *Collector, collector *
|
||||
output := NewResult()
|
||||
for _, pod := range pods.Items {
|
||||
outputNodeFilename := filepath.Join(outputFilename, pod.Spec.NodeName)
|
||||
files, stderr, err := copyFilesFromHost(ctx, filepath.Join(c.BundlePath, outputNodeFilename), clientConfig, client, pod.Name, "collector", namespace, filepath.Join("/host", fileName), collector.ExtractArchive)
|
||||
files, stderr, err := copyFilesFromHost(ctx, filepath.Join(bundlePath, outputNodeFilename), clientConfig, client, pod.Name, "collector", namespace, filepath.Join("/host", fileName), collector.ExtractArchive)
|
||||
if err != nil {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(outputNodeFilename, "error.txt"), bytes.NewBuffer([]byte(err.Error())))
|
||||
output.SaveResult(bundlePath, filepath.Join(outputNodeFilename, "error.txt"), bytes.NewBuffer([]byte(err.Error())))
|
||||
if len(stderr) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(outputNodeFilename, "stderr.txt"), bytes.NewBuffer(stderr))
|
||||
output.SaveResult(bundlePath, filepath.Join(outputNodeFilename, "stderr.txt"), bytes.NewBuffer(stderr))
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range files {
|
||||
relPath, err := filepath.Rel(c.BundlePath, filepath.Join(c.BundlePath, filepath.Join(outputNodeFilename, k)))
|
||||
relPath, err := filepath.Rel(bundlePath, filepath.Join(bundlePath, filepath.Join(outputNodeFilename, k)))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "relative path")
|
||||
}
|
||||
|
||||
+24
-3
@@ -2,16 +2,37 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Data(c *Collector, dataCollector *troubleshootv1beta2.Data) (CollectorResult, error) {
|
||||
bundlePath := filepath.Join(dataCollector.Name, dataCollector.CollectorName)
|
||||
type CollectData struct {
|
||||
Collector *troubleshootv1beta2.Data
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectData) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectData) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectData) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
bundlePath := filepath.Join(c.Collector.Name, c.Collector.CollectorName)
|
||||
|
||||
output := NewResult()
|
||||
output.SaveResult(c.BundlePath, bundlePath, bytes.NewBuffer([]byte(dataCollector.Data)))
|
||||
output.SaveResult(c.BundlePath, bundlePath, bytes.NewBuffer([]byte(c.Collector.Data)))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
+35
-16
@@ -3,24 +3,43 @@ package collect
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
|
||||
if execCollector.Timeout == "" {
|
||||
return execWithoutTimeout(c, execCollector)
|
||||
type CollectExec struct {
|
||||
Collector *troubleshootv1beta2.Exec
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectExec) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectExec) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectExec) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
if c.Collector.Timeout == "" {
|
||||
return execWithoutTimeout(c.ClientConfig, c.BundlePath, c.Collector)
|
||||
}
|
||||
|
||||
timeout, err := time.ParseDuration(execCollector.Timeout)
|
||||
timeout, err := time.ParseDuration(c.Collector.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,7 +48,7 @@ func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResul
|
||||
resultCh := make(chan CollectorResult, 1)
|
||||
|
||||
go func() {
|
||||
b, err := execWithoutTimeout(c, execCollector)
|
||||
b, err := execWithoutTimeout(c.ClientConfig, c.BundlePath, c.Collector)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
@@ -47,8 +66,8 @@ func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResul
|
||||
}
|
||||
}
|
||||
|
||||
func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
func execWithoutTimeout(clientConfig *rest.Config, bundlePath string, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,23 +78,23 @@ func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, execCollector.Namespace, execCollector.Selector)
|
||||
if len(podsErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, getExecErrosFileName(execCollector), marshalErrors(podsErrors))
|
||||
output.SaveResult(bundlePath, getExecErrosFileName(execCollector), marshalErrors(podsErrors))
|
||||
}
|
||||
|
||||
if len(pods) > 0 {
|
||||
for _, pod := range pods {
|
||||
stdout, stderr, execErrors := getExecOutputs(c, client, pod, execCollector)
|
||||
stdout, stderr, execErrors := getExecOutputs(clientConfig, client, pod, execCollector)
|
||||
|
||||
bundlePath := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
|
||||
path := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
|
||||
if len(stdout) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
|
||||
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
|
||||
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
|
||||
}
|
||||
|
||||
if len(execErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
|
||||
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -84,7 +103,7 @@ func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func getExecOutputs(c *Collector, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta2.Exec) ([]byte, []byte, []string) {
|
||||
func getExecOutputs(clientConfig *rest.Config, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta2.Exec) ([]byte, []byte, []string) {
|
||||
container := pod.Spec.Containers[0].Name
|
||||
if execCollector.ContainerName != "" {
|
||||
container = execCollector.ContainerName
|
||||
@@ -106,7 +125,7 @@ func getExecOutputs(c *Collector, client *kubernetes.Clientset, pod corev1.Pod,
|
||||
TTY: false,
|
||||
}, parameterCodec)
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(c.ClientConfig, "POST", req.URL())
|
||||
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
|
||||
if err != nil {
|
||||
return nil, nil, []string{err.Error()}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (c *CollectHostHTTP) Collect(progressChan chan<- interface{}) (map[string][
|
||||
return nil, errors.New("no supported http request type")
|
||||
}
|
||||
|
||||
responseOutput, err := responseToOutput(response, err, false)
|
||||
responseOutput, err := responseToOutput(response, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -60,17 +60,17 @@ func (c *CollectHostKernelModules) IsExcluded() (bool, error) {
|
||||
// Collect the kernel module status from the host. Modules are returned as a
|
||||
// map keyed on the module name used by the kernel, e.g:
|
||||
//
|
||||
// {
|
||||
// "system/kernel_modules.json": {
|
||||
// ...
|
||||
// "dm_snapshot": {
|
||||
// "instances": 8,
|
||||
// "size": 45056,
|
||||
// "status": "loaded"
|
||||
// },
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
// {
|
||||
// "system/kernel_modules.json": {
|
||||
// ...
|
||||
// "dm_snapshot": {
|
||||
// "instances": 8,
|
||||
// "size": 45056,
|
||||
// "status": "loaded"
|
||||
// },
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Module status may be: loaded, loadable, loading, unloading or unknown. When
|
||||
// a module is loaded, it may have one or more instances. The size represents
|
||||
|
||||
+32
-13
@@ -4,13 +4,15 @@ import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type HTTPResponse struct {
|
||||
@@ -33,32 +35,49 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func HTTP(c *Collector, httpCollector *troubleshootv1beta2.HTTP) (CollectorResult, error) {
|
||||
type CollectHTTP struct {
|
||||
Collector *troubleshootv1beta2.HTTP
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
var response *http.Response
|
||||
var err error
|
||||
|
||||
if httpCollector.Get != nil {
|
||||
response, err = doGet(httpCollector.Get)
|
||||
} else if httpCollector.Post != nil {
|
||||
response, err = doPost(httpCollector.Post)
|
||||
} else if httpCollector.Put != nil {
|
||||
response, err = doPut(httpCollector.Put)
|
||||
if c.Collector.Get != nil {
|
||||
response, err = doGet(c.Collector.Get)
|
||||
} else if c.Collector.Post != nil {
|
||||
response, err = doPost(c.Collector.Post)
|
||||
} else if c.Collector.Put != nil {
|
||||
response, err = doPut(c.Collector.Put)
|
||||
} else {
|
||||
return nil, errors.New("no supported http request type")
|
||||
}
|
||||
|
||||
o, err := responseToOutput(response, err, c.Redact)
|
||||
o, err := responseToOutput(response, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileName := "result.json"
|
||||
if httpCollector.CollectorName != "" {
|
||||
fileName = httpCollector.CollectorName + ".json"
|
||||
if c.Collector.CollectorName != "" {
|
||||
fileName = c.Collector.CollectorName + ".json"
|
||||
}
|
||||
|
||||
output := NewResult()
|
||||
output.SaveResult(c.BundlePath, filepath.Join(httpCollector.Name, fileName), bytes.NewBuffer(o))
|
||||
output.SaveResult(c.BundlePath, filepath.Join(c.Collector.Name, fileName), bytes.NewBuffer(o))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
@@ -117,7 +136,7 @@ func doPut(put *troubleshootv1beta2.Put) (*http.Response, error) {
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
func responseToOutput(response *http.Response, err error, doRedact bool) ([]byte, error) {
|
||||
func responseToOutput(response *http.Response, err error) ([]byte, error) {
|
||||
output := make(map[string]interface{})
|
||||
if err != nil {
|
||||
output["error"] = HTTPError{
|
||||
|
||||
+86
-23
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,9 +14,29 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResult, error) {
|
||||
type CollectLogs struct {
|
||||
Collector *troubleshootv1beta2.Logs
|
||||
BundlePath string
|
||||
Namespace string // There is a Namespace parameter in troubleshootv1beta2.Logs. Should we remove this?
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
SinceTime *time.Time
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectLogs) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectLogs) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectLogs) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
client, err := kubernetes.NewForConfig(c.ClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -25,14 +46,21 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, logsCollector.Namespace, logsCollector.Selector)
|
||||
if c.SinceTime != nil {
|
||||
if c.Collector.Limits == nil {
|
||||
c.Collector.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
c.Collector.Limits.SinceTime = metav1.NewTime(*c.SinceTime)
|
||||
}
|
||||
|
||||
pods, podsErrors := listPodsInSelectors(ctx, client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if len(podsErrors) > 0 {
|
||||
output.SaveResult(c.BundlePath, getLogsErrorsFileName(logsCollector), marshalErrors(podsErrors))
|
||||
output.SaveResult(c.BundlePath, getLogsErrorsFileName(c.Collector), marshalErrors(podsErrors))
|
||||
}
|
||||
|
||||
if len(pods) > 0 {
|
||||
for _, pod := range pods {
|
||||
if len(logsCollector.ContainerNames) == 0 {
|
||||
if len(c.Collector.ContainerNames) == 0 {
|
||||
// make a list of all the containers in the pod, so that we can get logs from all of them
|
||||
containerNames := []string{}
|
||||
for _, container := range pod.Spec.Containers {
|
||||
@@ -43,14 +71,11 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
}
|
||||
|
||||
for _, containerName := range containerNames {
|
||||
if len(containerNames) == 1 {
|
||||
containerName = "" // if there was only one container, use the old behavior of not including the container name in the path
|
||||
}
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, logsCollector.Name, containerName, logsCollector.Limits, false)
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, c.Collector.Name, containerName, c.Collector.Limits, false)
|
||||
if err != nil {
|
||||
key := fmt.Sprintf("%s/%s-errors.json", logsCollector.Name, pod.Name)
|
||||
key := fmt.Sprintf("%s/%s-errors.json", c.Collector.Name, pod.Name)
|
||||
if containerName != "" {
|
||||
key = fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, containerName)
|
||||
key = fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, containerName)
|
||||
}
|
||||
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
|
||||
if err != nil {
|
||||
@@ -63,10 +88,10 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, container := range logsCollector.ContainerNames {
|
||||
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, logsCollector.Name, container, logsCollector.Limits, false)
|
||||
for _, container := range c.Collector.ContainerNames {
|
||||
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, c.Collector.Name, container, c.Collector.Limits, false)
|
||||
if err != nil {
|
||||
key := fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, container)
|
||||
key := fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, container)
|
||||
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -84,7 +109,7 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func listPodsInSelectors(ctx context.Context, client *kubernetes.Clientset, namespace string, selector []string) ([]corev1.Pod, []string) {
|
||||
func listPodsInSelectors(ctx context.Context, client kubernetes.Interface, namespace string, selector []string) ([]corev1.Pod, []string) {
|
||||
serializedLabelSelector := strings.Join(selector, ",")
|
||||
|
||||
listOptions := metav1.ListOptions{
|
||||
@@ -99,20 +124,54 @@ func listPodsInSelectors(ctx context.Context, client *kubernetes.Clientset, name
|
||||
return pods.Items, nil
|
||||
}
|
||||
|
||||
func savePodLogs(ctx context.Context, bundlePath string, client *kubernetes.Clientset, pod corev1.Pod, name, container string, limits *troubleshootv1beta2.LogLimits, follow bool) (CollectorResult, error) {
|
||||
func savePodLogs(
|
||||
ctx context.Context,
|
||||
bundlePath string,
|
||||
client *kubernetes.Clientset,
|
||||
pod *corev1.Pod,
|
||||
collectorName, container string,
|
||||
limits *troubleshootv1beta2.LogLimits,
|
||||
follow bool,
|
||||
) (CollectorResult, error) {
|
||||
return savePodLogsWithInterface(ctx, bundlePath, client, pod, collectorName, container, limits, follow)
|
||||
}
|
||||
|
||||
func savePodLogsWithInterface(
|
||||
ctx context.Context,
|
||||
bundlePath string,
|
||||
client kubernetes.Interface,
|
||||
pod *corev1.Pod,
|
||||
collectorName, container string,
|
||||
limits *troubleshootv1beta2.LogLimits,
|
||||
follow bool,
|
||||
) (CollectorResult, error) {
|
||||
podLogOpts := corev1.PodLogOptions{
|
||||
Follow: follow,
|
||||
Container: container,
|
||||
}
|
||||
|
||||
setLogLimits(&podLogOpts, limits, convertMaxAgeToTime)
|
||||
result := NewResult()
|
||||
|
||||
fileKey := fmt.Sprintf("%s/%s", name, pod.Name)
|
||||
// TODO: Abstract away hard coded directory structure paths
|
||||
// Maybe create a FS provider or something similar
|
||||
filePathPrefix := filepath.Join(
|
||||
"cluster-resources", "pods", "logs", pod.Namespace, pod.Name, pod.Spec.Containers[0].Name,
|
||||
)
|
||||
|
||||
// TODO: If collectorName is empty, the path is stored with a leading slash
|
||||
// Retain this behavior otherwise analysers in the wild may break
|
||||
// Analysers that need to find a file in the root of the bundle should
|
||||
// prefix the path with a slash e.g /file.txt. This behavior should be
|
||||
// properly deprecated in the future.
|
||||
linkRelPathPrefix := fmt.Sprintf("%s/%s", collectorName, pod.Name)
|
||||
if container != "" {
|
||||
fileKey = fmt.Sprintf("%s/%s/%s", name, pod.Name, container)
|
||||
linkRelPathPrefix = fmt.Sprintf("%s/%s/%s", collectorName, pod.Name, container)
|
||||
filePathPrefix = filepath.Join(
|
||||
"cluster-resources", "pods", "logs", pod.Namespace, pod.Name, container,
|
||||
)
|
||||
}
|
||||
|
||||
result := NewResult()
|
||||
setLogLimits(&podLogOpts, limits, convertMaxAgeToTime)
|
||||
|
||||
req := client.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts)
|
||||
podLogs, err := req.Stream(ctx)
|
||||
@@ -121,11 +180,13 @@ func savePodLogs(ctx context.Context, bundlePath string, client *kubernetes.Clie
|
||||
}
|
||||
defer podLogs.Close()
|
||||
|
||||
logWriter, err := result.GetWriter(bundlePath, fileKey+".log")
|
||||
logWriter, err := result.GetWriter(bundlePath, filePathPrefix+".log")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get log writer")
|
||||
}
|
||||
defer result.CloseWriter(bundlePath, fileKey+".log", logWriter)
|
||||
// NOTE: deferred calls are executed in LIFO order i.e called in reverse order
|
||||
defer result.SymLinkResult(bundlePath, linkRelPathPrefix+".log", filePathPrefix+".log")
|
||||
defer result.CloseWriter(bundlePath, filePathPrefix+".log", logWriter)
|
||||
|
||||
_, err = io.Copy(logWriter, podLogs)
|
||||
if err != nil {
|
||||
@@ -141,11 +202,13 @@ func savePodLogs(ctx context.Context, bundlePath string, client *kubernetes.Clie
|
||||
}
|
||||
defer podLogs.Close()
|
||||
|
||||
prevLogWriter, err := result.GetWriter(bundlePath, fileKey+"-previous.log")
|
||||
prevLogWriter, err := result.GetWriter(bundlePath, filePathPrefix+"-previous.log")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get previous log writer")
|
||||
}
|
||||
defer result.CloseWriter(bundlePath, fileKey+"-previous.log", logWriter)
|
||||
// NOTE: deferred calls are executed in LIFO order i.e called in reverse order
|
||||
defer result.SymLinkResult(bundlePath, linkRelPathPrefix+"-previous.log", filePathPrefix+"-previous.log")
|
||||
defer result.CloseWriter(bundlePath, filePathPrefix+"-previous.log", logWriter)
|
||||
|
||||
_, err = io.Copy(prevLogWriter, podLogs)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_setLogLimits(t *testing.T) {
|
||||
@@ -77,3 +79,74 @@ func Test_setLogLimits(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_savePodLogs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
withContainerName bool
|
||||
collectorName string
|
||||
want CollectorResult
|
||||
}{
|
||||
{
|
||||
name: "with container name",
|
||||
withContainerName: true,
|
||||
collectorName: "all-logs",
|
||||
want: CollectorResult{
|
||||
"all-logs/test-pod/nginx.log": []byte("fake logs"),
|
||||
"all-logs/test-pod/nginx-previous.log": []byte("fake logs"),
|
||||
"cluster-resources/pods/logs/my-namespace/test-pod/nginx.log": []byte("fake logs"),
|
||||
"cluster-resources/pods/logs/my-namespace/test-pod/nginx-previous.log": []byte("fake logs"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "without container name",
|
||||
withContainerName: false,
|
||||
collectorName: "all-logs",
|
||||
want: CollectorResult{
|
||||
"all-logs/test-pod.log": []byte("fake logs"),
|
||||
"all-logs/test-pod-previous.log": []byte("fake logs"),
|
||||
"cluster-resources/pods/logs/my-namespace/test-pod/nginx.log": []byte("fake logs"),
|
||||
"cluster-resources/pods/logs/my-namespace/test-pod/nginx-previous.log": []byte("fake logs"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "without container or collector names",
|
||||
withContainerName: false,
|
||||
want: CollectorResult{
|
||||
"/test-pod.log": []byte("fake logs"),
|
||||
"/test-pod-previous.log": []byte("fake logs"),
|
||||
"cluster-resources/pods/logs/my-namespace/test-pod/nginx.log": []byte("fake logs"),
|
||||
"cluster-resources/pods/logs/my-namespace/test-pod/nginx-previous.log": []byte("fake logs"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
containerName := "nginx"
|
||||
client := testclient.NewSimpleClientset()
|
||||
limits := &troubleshootv1beta2.LogLimits{
|
||||
MaxLines: 500,
|
||||
}
|
||||
pod, err := client.CoreV1().Pods("my-namespace").Create(ctx, &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-pod",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
if !tt.withContainerName {
|
||||
containerName = ""
|
||||
}
|
||||
got, err := savePodLogsWithInterface(ctx, "", client, pod, tt.collectorName, containerName, limits, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+45
-18
@@ -5,7 +5,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
@@ -29,12 +28,30 @@ const (
|
||||
|
||||
var checksumRX = regexp.MustCompile(`(\S+)\s+(\S+)`)
|
||||
|
||||
func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (CollectorResult, error) {
|
||||
type CollectLonghorn struct {
|
||||
Collector *troubleshootv1beta2.Longhorn
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
ns := DefaultLonghornNamespace
|
||||
if longhornCollector.Namespace != "" {
|
||||
ns = longhornCollector.Namespace
|
||||
if c.Collector.Namespace != "" {
|
||||
ns = c.Collector.Namespace
|
||||
}
|
||||
|
||||
client, err := longhornv1beta1.NewForConfig(c.ClientConfig)
|
||||
@@ -196,23 +213,10 @@ func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (Co
|
||||
}
|
||||
output.SaveResult(c.BundlePath, settingsKey, bytes.NewBuffer(settingsB))
|
||||
|
||||
// logs of all pods in namespace
|
||||
logsCollector := &troubleshootv1beta2.Logs{
|
||||
Selector: []string{""},
|
||||
Namespace: ns,
|
||||
}
|
||||
logs, err := Logs(c, logsCollector)
|
||||
err = c.collectLonghornLogs(ns, output, progressChan)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "collect longhorn logs")
|
||||
}
|
||||
logsDir := GetLonghornLogsDirectory(ns)
|
||||
for srcFilename, _ := range logs {
|
||||
dstFileName := path.Join(logsDir, srcFilename)
|
||||
err := copyResult(logs, output, c.BundlePath, srcFilename, dstFileName)
|
||||
if err != nil {
|
||||
logger.Printf("Failed to copy file %s; %v", srcFilename, err)
|
||||
}
|
||||
}
|
||||
|
||||
// https://longhorn.io/docs/1.1.1/advanced-resources/data-recovery/corrupted-replica/
|
||||
|
||||
@@ -285,6 +289,29 @@ func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (Co
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) collectLonghornLogs(namespace string, results CollectorResult, progressChan chan<- interface{}) error {
|
||||
// logs of all pods in namespace
|
||||
logsCollectorSpec := &troubleshootv1beta2.Logs{
|
||||
Selector: []string{""},
|
||||
Name: GetLonghornLogsDirectory(namespace), // Logs (symlinks) will be stored in this directory
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
logsCollector := &CollectLogs{logsCollectorSpec, c.BundlePath, namespace, c.ClientConfig, c.Client, c.Context, nil, rbacErrors}
|
||||
|
||||
logs, err := logsCollector.Collect(progressChan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add logs collector results to the rest of
|
||||
// the longhorn collector results for later consumption
|
||||
results.AddResult(logs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLonghornNodesDirectory(namespace string) string {
|
||||
return fmt.Sprintf("longhorn/%s/nodes", namespace)
|
||||
}
|
||||
|
||||
+25
-4
@@ -2,6 +2,7 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -9,12 +10,32 @@ import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
|
||||
type CollectMysql struct {
|
||||
Collector *troubleshootv1beta2.Database
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectMysql) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectMysql) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectMysql) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
databaseConnection := DatabaseConnection{}
|
||||
|
||||
db, err := sql.Open("mysql", databaseCollector.URI)
|
||||
db, err := sql.Open("mysql", c.Collector.URI)
|
||||
if err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
} else {
|
||||
@@ -30,7 +51,7 @@ func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
|
||||
databaseConnection.Version = version
|
||||
}
|
||||
|
||||
requestedParameters := databaseCollector.Parameters
|
||||
requestedParameters := c.Collector.Parameters
|
||||
if len(requestedParameters) > 0 {
|
||||
rows, err := db.Query("SHOW VARIABLES")
|
||||
|
||||
@@ -68,7 +89,7 @@ func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := databaseCollector.CollectorName
|
||||
collectorName := c.Collector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "mysql"
|
||||
}
|
||||
|
||||
+65
-6
@@ -2,25 +2,84 @@ package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func Postgres(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
|
||||
type CollectPostgres struct {
|
||||
Collector *troubleshootv1beta2.Database
|
||||
BundlePath string
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) Title() string {
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) createConnectConfig() (*pgx.ConnConfig, error) {
|
||||
if c.Collector.URI == "" {
|
||||
return nil, errors.New("postgres uri cannot be empty")
|
||||
}
|
||||
|
||||
cfg, err := pgx.ParseConfig(c.Collector.URI)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse postgres config")
|
||||
}
|
||||
|
||||
if c.Collector.TLS != nil {
|
||||
tlsCfg, err := createTLSConfig(c.Context, c.Client, c.Collector.TLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsCfg.ServerName = cfg.Host
|
||||
cfg.TLSConfig = tlsCfg
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) connect() (*pgx.Conn, error) {
|
||||
connCfg, err := c.createConnectConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := pgx.ConnectConfig(c.Context, connCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
databaseConnection := DatabaseConnection{}
|
||||
|
||||
db, err := sql.Open("postgres", databaseCollector.URI)
|
||||
conn, err := c.connect()
|
||||
if err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
} else {
|
||||
defer conn.Close(c.Context)
|
||||
|
||||
query := `select version()`
|
||||
row := db.QueryRow(query)
|
||||
row := conn.QueryRow(c.Context, query)
|
||||
version := ""
|
||||
if err := row.Scan(&version); err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
@@ -42,7 +101,7 @@ func Postgres(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Co
|
||||
return nil, errors.Wrap(err, "failed to marshal database connection")
|
||||
}
|
||||
|
||||
collectorName := databaseCollector.CollectorName
|
||||
collectorName := c.Collector.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "postgres"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user