mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2be6f5829 | ||
|
|
cd1511a8fb | ||
|
|
9c77a0e3da | ||
|
|
c8820b3d8e | ||
|
|
cf3a292877 | ||
|
|
a4c4b24056 | ||
|
|
e9f386a840 | ||
|
|
d96b2ded78 | ||
|
|
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 |
@@ -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,13 +107,13 @@ 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/
|
||||
@@ -109,7 +123,7 @@ jobs:
|
||||
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/
|
||||
@@ -147,13 +161,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- 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/
|
||||
@@ -163,7 +177,7 @@ jobs:
|
||||
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
|
||||
@@ -171,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
|
||||
@@ -183,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
|
||||
@@ -207,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:
|
||||
@@ -217,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"
|
||||
|
||||
@@ -239,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
|
||||
@@ -248,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'
|
||||
@@ -1,19 +1,18 @@
|
||||
name: PR license scan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
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
|
||||
- name: "Run FOSSA Analyze"
|
||||
uses: fossas/fossa-action@main
|
||||
with:
|
||||
api-key: cf2d0196f5b5bb2fd245c559af8766d8 # push-only token, safe to expose
|
||||
- name: "Run FOSSA Test"
|
||||
uses: fossas/fossa-action@main
|
||||
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: cf2d0196f5b5bb2fd245c559af8766d8 # push-only token, safe to expose
|
||||
run-tests: true
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
|
||||
@@ -38,3 +38,6 @@ sbom/
|
||||
|
||||
# Ignore local pre-commit config
|
||||
.pre-commit-config.yaml
|
||||
|
||||
# Ignore generated support bundles
|
||||
*.tar.gz
|
||||
|
||||
+20
-3
@@ -1,6 +1,12 @@
|
||||
# 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
|
||||
|
||||
@@ -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,7 +30,7 @@ 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 code linters.
|
||||
6. Install [golangci-lint] linter and run `make lint` to execute additional code linters.
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -69,3 +75,14 @@ This is a rough outline of how to prepare a contribution:
|
||||
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
|
||||
@@ -95,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)
|
||||
|
||||
@@ -12,12 +12,14 @@ To run a sample preflight check from a sample application, install the preflight
|
||||
```
|
||||
curl https://krew.sh/preflight | bash
|
||||
```
|
||||
and run:
|
||||
and run, where https://preflight.replicated.com provides an **example** preflight spec:
|
||||
|
||||
```
|
||||
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/).
|
||||
|
||||
|
||||
@@ -29,13 +31,18 @@ To collect a sample support bundle, install the troubleshoot kubectl plugin:
|
||||
```
|
||||
curl https://krew.sh/support-bundle | bash
|
||||
```
|
||||
and run:
|
||||
and run, where https://support-bundle.replicated.com provides an **example** support bundle spec:
|
||||
|
||||
```
|
||||
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).
|
||||
@@ -51,4 +58,4 @@ not been tampered with.
|
||||
```
|
||||
$ 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()
|
||||
}
|
||||
@@ -30,7 +30,7 @@ that a cluster meets the requirements to run an application.`,
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args[0])
|
||||
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -48,7 +47,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
|
||||
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 support bundle specs found in secrets within the cluster. required when no specs are provided on the command line")
|
||||
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. 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")
|
||||
@@ -82,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
|
||||
|
||||
+60
-29
@@ -34,10 +34,6 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
SupportBundleSecretKey = "support-bundle-spec"
|
||||
)
|
||||
|
||||
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")
|
||||
@@ -108,7 +104,7 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, supportBundle)
|
||||
}
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromSpec(multidocs)
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse redactors from doc")
|
||||
}
|
||||
@@ -138,34 +134,69 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
|
||||
return errors.Wrap(err, "failed to convert create k8s client")
|
||||
}
|
||||
|
||||
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), namespace, SupportBundleSecretKey)
|
||||
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...)
|
||||
|
||||
if bundlesFromSecrets != nil {
|
||||
for _, bundle := range bundlesFromSecrets {
|
||||
multidocs := strings.Split(string(bundle), "\n---\n")
|
||||
parsedBundlesFromSecrets, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
logger.Printf("failed to parse support bundle spec: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if mainBundle == nil {
|
||||
mainBundle = parsedBundlesFromSecrets
|
||||
} else {
|
||||
mainBundle = supportbundle.ConcatSpec(mainBundle, parsedBundlesFromSecrets)
|
||||
}
|
||||
|
||||
parsedRedactors, err := supportbundle.ParseRedactorsFromSpec(multidocs)
|
||||
if err != nil {
|
||||
logger.Printf("failed to parse redactors from doc: %s", err)
|
||||
continue
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
|
||||
}
|
||||
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")
|
||||
}
|
||||
@@ -302,7 +333,7 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s\n", response.ArchivePath)
|
||||
fmt.Printf("%s\n", response.ArchivePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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,68 @@
|
||||
# Run collectors concurrently
|
||||
|
||||
## Goals
|
||||
|
||||
Increase the speed at which collectors complete.
|
||||
|
||||
## Non Goals
|
||||
|
||||
|
||||
## Background
|
||||
|
||||
Currently all collectors run sequentially, in the order in which they are specified in the spec. This is implied, and has not been guaranteed. With the addition of the ability to supply multiple specs, the order is no longer predicatble.
|
||||
|
||||
Although sequence is not guaranteed for any collectors, #768 ensures that the `clusterResources` collector runs first, so that the list of resources does not include those that are started during the Troubleshoot collection sequence.
|
||||
|
||||
The performance for support-bundle and preflight runs is important to the end user experience of the product. There are examples where a support bundle takes multiple minutes to complete, and folks are left wondering if the process has hung or crashed. Preflights that take too long will simply be skipped, which leads to problems with installation being difficult to identify.
|
||||
|
||||
## High-Level Design
|
||||
|
||||
Switch collectors and host collectors to use goroutines to collect (concurrently). This should run the list of collectors of `clusterResources` type first, then the remainder concurrently when `clusterResources` is complete.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
Tasks that can be split to separate merges (in order):
|
||||
|
||||
* Switch all collectors to use goroutines to collect (concurrently).
|
||||
* In `supportbundle.runCollectors()`, refactor to use a `go func()` pattern to run all the collectors that are not `clusterResources` concurrently. Check that the first in the list is `clusterResources` and run that separately, prior to the others.
|
||||
* This task should introduce an optional concurrency limit field, default to, say, 10, which limits the number of goroutines/collectors that can run at any given time.
|
||||
* This task should improve the collection speed of in-cluster collectors and preflights
|
||||
|
||||
* Switch Host Collectors to use goroutines for concurrent collection.
|
||||
* In `supportbundle.runHostCollectors()`, refactor to use a `go func()` pattern for each collector.
|
||||
* No need to honor the same concurrency limit as host collectors do not load the Kubernetes API
|
||||
|
||||
* Switch Host Preflights to use goroutines - `preflight.CollectHost()` runs `go func()` for each collector.
|
||||
|
||||
* Switch in-cluster Preflights to use goroutines - `preflight.Collect()` runs `go func()` for each collector.
|
||||
* Ensure `clusterResources` runs in advance of the others so that pods started by collectors do not produce an error in the results.
|
||||
|
||||
## Limitations
|
||||
|
||||
Relies on splitting up the list of collectors into `clusterResources` and others.
|
||||
|
||||
Collisions between specs (e.g. if `runPod` has multiple specs with the same name) are not handled. At the current stage with collectors running sequentially, if two collectors write to the same file the second overwrites the data from the first. If collectors run concurrently, there can be issues creating multiple pods with the same name, and issues with concurrent access to the target file. This issue is tracked in [#895](https://github.com/replicatedhq/troubleshoot/issues/895)
|
||||
|
||||
This proposal excludes Remote Collectors.
|
||||
## Assumptions
|
||||
|
||||
* The Kubernetes API can handle the load of collectors running concurrently, including when the pod logs collector runs.
|
||||
|
||||
## Testing
|
||||
|
||||
Any new function will need unit tests.
|
||||
|
||||
The existing Collect functions will need unit tests altered (or added).
|
||||
|
||||
We will need to ensure that `clusterResources` is collected prior to others.
|
||||
|
||||
## Documentation
|
||||
|
||||
There should be a note in https://troubleshoot.sh/docs/collect/ ensuring that folks do not expect the collectors to run in any particular order.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
None.
|
||||
## Security Considerations
|
||||
|
||||
None identified.
|
||||
@@ -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,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,120 @@
|
||||
# Integrate SBCTL with Troubleshoot
|
||||
|
||||
## Goals
|
||||
|
||||
Make sbctl easier to maintain by consolidating code bases.
|
||||
|
||||
Reduce code duplication.
|
||||
|
||||
Improve end-user experience and improve discoverability of sbctl.
|
||||
|
||||
## Non Goals
|
||||
|
||||
## Background
|
||||
sbctl is our command line tool for examining K8s resources inside of Support Bundles generated by our troubleshoot project. With both projects having separate code bases we see the following problems:
|
||||
|
||||
* Most users are familiar with the binaries from troubleshoot (support-bundle, preflight, etc.), but are unaware of the existince of sbctl because it's a separate project
|
||||
* sbctl and troubleshoot are naturally coupled together due to sbctl's reliance on support bundles to operate. Them being in separate repos leads to duplication of code in both projects increasing maintenance burden.
|
||||
|
||||
## High-Level Design
|
||||
Because the functionality of sbctl and Troubleshoot are so tightly coupled together, they should live in the same repository. Moreover, as opposed to maintaing a separate binary for sbctl, all sbctl commands should become subcommands of `support-bundle`
|
||||
|
||||
## Detailed Design
|
||||
The `serve` and `shell` commands become subcommands of the `support-bundle` CLI.
|
||||
|
||||
```
|
||||
./bin/support-bundle --help
|
||||
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.
|
||||
|
||||
Usage:
|
||||
support-bundle [url] [flags]
|
||||
support-bundle [command]
|
||||
|
||||
Available Commands:
|
||||
analyze analyze a support bundle
|
||||
serve Start API server
|
||||
shell Start interractive shell
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
...
|
||||
```
|
||||
|
||||
Similiar to the [Analyze](https://github.com/replicatedhq/troubleshoot/blob/main/cmd/troubleshoot/cli/analyze.go) subcommand, we add `serve.go` and `shell.go` to `cmd/troubleshoot/cli`
|
||||
|
||||
### Standardize Cluster Resources Collector
|
||||
* In `pkg/collect` standardize the root directory location of the Cluster Resources collector with a variable instead of repeatedly specifying it as a string - https://github.com/replicatedhq/troubleshoot/blob/main/pkg/collect/cluster_resources.go#L121.
|
||||
* Import the directory the Cluster Resources collector uses from `pkg/collect` for usage in the APIs - https://github.com/replicatedhq/sbctl/blob/main/pkg/api/server.go#L220. This is an example of the way we can start making the sbctl code more efficient with this change.
|
||||
|
||||
cluster_resources.go
|
||||
```go
|
||||
const ClusterResourcesPath = `cluster-resources`
|
||||
...
|
||||
|
||||
path := filepath.Join(ClusterResourcesPath, "namespaces.json")
|
||||
output.SaveResult(c.BundlePath, path, bytes.NewBuffer(namespaces))
|
||||
```
|
||||
|
||||
sbctl-server.go
|
||||
```go
|
||||
import (
|
||||
...
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
...
|
||||
)
|
||||
...
|
||||
|
||||
dirName := filepath.Join(collect.ClusterResourcesPath, fmt.Sprintf("%s", resource))
|
||||
```
|
||||
|
||||
### Package Structure
|
||||
```
|
||||
cmd
|
||||
serve/
|
||||
shell/
|
||||
pkg
|
||||
serve/
|
||||
shell/
|
||||
tests # API tests, integration tests etc reside here.
|
||||
serve/ # sbctl BDD tests.
|
||||
# all tests and test fixtures moved here
|
||||
```
|
||||
|
||||
### Makefile Changes
|
||||
|
||||
We'll need to add make targets specific to these commands similar to the current ones
|
||||
```
|
||||
make serve run-serve
|
||||
make shell run-shell
|
||||
make ginkgo
|
||||
```
|
||||
|
||||
### Docs
|
||||
In troubleshoot.sh docs, add a new sub section under "Support Bundle" describing then new serve & shell subcommands.
|
||||
|
||||
### Other Changes
|
||||
We would need to merge some of the support bundle functionality we see in both projects like so
|
||||
|
||||
- Create a new sbutil module to store reusable support bundle functionality. At least `analyze` & `serve` modules will depend on this module
|
||||
|
||||
```
|
||||
pkg
|
||||
sbutil/
|
||||
```
|
||||
|
||||
- Merge `troubleshoot/pkg/analyze/download.ExtractTroubleshootBundle` & `sbctl/pkg/sbctl/support-bundle.ExtractBundle` that duplicate support bundle archive extraction
|
||||
- Build on `sbctl/pkg/sbctl/support-bundle.ClusterData` to have it expose well known paths (ClusterResourcesDir, Version files, analysis results etc) and unmarshalled resources (this bit can be built on a need to have basis) in a support bundle especially once [Add generic kubernetes resource analyzer #780 PR](https://github.com/replicatedhq/troubleshoot/pull/780) is merged.
|
||||
- Build on `sbctl/pkg/sbctl/support-bundle.FindClusterData` to perform pre-validation of a support bundle to ensure it is valid before running any applications.
|
||||
|
||||
## Limitations
|
||||
|
||||
## Assumptions
|
||||
|
||||
## Testing
|
||||
We will need to have a separate directory for sbctl (ginkgo BDD) tests. The test harness used is ginkgo cli instead of go test and there is no straight forward way of separating these tests. Also, from a directory structure point of view, such integration tests usually reside in modules in a root level tests directory. Changes to the test code need to be minimal, ideally no change, cause they will be used to ensure any refactored code does not break. We could now easily introduce tests which ensure that changes to collectors don't break APIs in sbctl.
|
||||
|
||||
## Alternatives Considered
|
||||
* Have troubleshoot as a library dependency in sbctl keeping it as a separate binary and repo
|
||||
* Move sbctl to the Troubleshoot library "as is" and continue maintaing a separate binary
|
||||
* Leave things as they are
|
||||
|
||||
## Security Considerations
|
||||
@@ -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
|
||||
@@ -11,10 +11,10 @@ spec:
|
||||
data: "5"
|
||||
- runPod:
|
||||
collectorName: "static-hi"
|
||||
podSpec:
|
||||
podSpec:
|
||||
containers:
|
||||
- name: static-hi
|
||||
image: alpine:3.5
|
||||
image: alpine:3
|
||||
command: ["echo", "hi static!"]
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
|
||||
@@ -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:
|
||||
@@ -100,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,12 +5,12 @@ 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
|
||||
github.com/go-redis/redis/v7 v7.4.1
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/godbus/dbus v4.1.0+incompatible
|
||||
github.com/google/gofuzz v1.2.0
|
||||
@@ -18,45 +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.7
|
||||
github.com/jackc/pgx/v5 v5.2.0
|
||||
github.com/longhorn/go-iscsi-helper v0.0.0-20210330030558-49a327fb024e
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/mattn/go-isatty v0.0.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.13.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.2
|
||||
k8s.io/api v0.25.4
|
||||
k8s.io/apiextensions-apiserver v0.25.0
|
||||
k8s.io/apimachinery v0.25.2
|
||||
k8s.io/apiserver v0.25.2
|
||||
k8s.io/cli-runtime v0.25.2
|
||||
k8s.io/client-go v0.25.2
|
||||
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.0
|
||||
sigs.k8s.io/controller-runtime v0.13.0
|
||||
oras.land/oras-go v1.2.1
|
||||
sigs.k8s.io/controller-runtime v0.13.1
|
||||
)
|
||||
|
||||
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
|
||||
@@ -64,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.5 // 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
|
||||
@@ -75,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/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/chzyer/readline v1.5.1 // indirect
|
||||
github.com/containerd/cgroups v1.0.4 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // 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
|
||||
@@ -109,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
|
||||
@@ -120,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
|
||||
@@ -133,15 +141,14 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
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/matttproud/golang_protobuf_extensions v1.0.4 // 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
|
||||
@@ -151,10 +158,10 @@ 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.5 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
@@ -162,14 +169,13 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // 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.4.1 // indirect
|
||||
github.com/sylabs/sif/v2 v2.3.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
|
||||
@@ -181,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-20221002022538-bcab6841153b // 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/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-20220517211312-f3a8303e98df // indirect
|
||||
google.golang.org/api v0.81.0 // 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.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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+4
-4
@@ -112,12 +112,12 @@ type CollectCeph struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCeph) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Info")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCeph) IsExcluded() (bool, error) {
|
||||
@@ -164,7 +164,7 @@ func cephCommandExec(ctx context.Context, progressChan chan<- interface{}, c *Co
|
||||
}
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
execCollector := &CollectExec{execSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, rbacErrors}
|
||||
execCollector := &CollectExec{execSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
|
||||
|
||||
results, err := execCollector.Collect(progressChan)
|
||||
if err != nil {
|
||||
@@ -172,7 +172,7 @@ func cephCommandExec(ctx context.Context, progressChan chan<- interface{}, c *Co
|
||||
}
|
||||
|
||||
pathPrefix := GetCephCollectorFilepath(cephCollector.CollectorName, cephCollector.Namespace)
|
||||
for srcFilename, _ := range results {
|
||||
for srcFilename := range results {
|
||||
var dstFileName string
|
||||
switch {
|
||||
case strings.HasSuffix(srcFilename, "-stdout.txt"):
|
||||
|
||||
@@ -26,7 +26,7 @@ type CollectClusterInfo struct {
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Info")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectClusterInfo) IsExcluded() (bool, error) {
|
||||
|
||||
@@ -43,7 +43,7 @@ type CollectClusterResources struct {
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Resources")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) IsExcluded() (bool, error) {
|
||||
@@ -51,7 +51,50 @@ func (c *CollectClusterResources) IsExcluded() (bool, error) {
|
||||
}
|
||||
|
||||
func (c *CollectClusterResources) Merge(allCollectors []Collector) ([]Collector, error) {
|
||||
result := append(allCollectors, c)
|
||||
var result []Collector
|
||||
uniqueNamespaces := make(map[string]bool)
|
||||
hasEmptyNameSpaceCollector := false
|
||||
|
||||
EMPTY_NAMESPACE_FOUND:
|
||||
for _, collectorInterface := range allCollectors {
|
||||
if collector, ok := collectorInterface.(*CollectClusterResources); ok {
|
||||
if collector.Collector.Namespaces == nil {
|
||||
hasEmptyNameSpaceCollector = true
|
||||
break
|
||||
} else {
|
||||
for _, namespace := range collector.Collector.Namespaces {
|
||||
if namespace == "" {
|
||||
hasEmptyNameSpaceCollector = true
|
||||
break EMPTY_NAMESPACE_FOUND
|
||||
} else {
|
||||
uniqueNamespaces[namespace] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clusterResourcesCollector := c
|
||||
|
||||
if hasEmptyNameSpaceCollector {
|
||||
clusterResourcesCollector.Collector.Namespaces = nil
|
||||
result = append(result, clusterResourcesCollector)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var allNamespaces []string
|
||||
for k, v := range uniqueNamespaces {
|
||||
if v {
|
||||
allNamespaces = append(allNamespaces, k)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(allNamespaces)
|
||||
|
||||
clusterResourcesCollector.Collector.Namespaces = allNamespaces
|
||||
|
||||
result = append(result, clusterResourcesCollector)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -124,25 +167,20 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
|
||||
for _, pod := range unhealthyPods {
|
||||
allContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
|
||||
for _, container := range allContainers {
|
||||
logsRoot := ""
|
||||
if c.BundlePath != "" {
|
||||
logsRoot = path.Join(c.BundlePath, "cluster-resources", "pods", "logs", pod.Namespace)
|
||||
}
|
||||
limits := &troubleshootv1beta2.LogLimits{
|
||||
MaxLines: 500,
|
||||
}
|
||||
podLogs, err := savePodLogs(ctx, logsRoot, client, pod, "", container.Name, limits, false)
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, "", container.Name, limits, false, 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())))
|
||||
}
|
||||
for k, v := range podLogs {
|
||||
output[filepath.Join("cluster-resources", "pods", "logs", pod.Namespace, k)] = v
|
||||
}
|
||||
// Add logs collector results to the rest of the output
|
||||
output.AddResult(podLogs)
|
||||
}
|
||||
}
|
||||
|
||||
// pod disruption budgets (for all existing namespaces)
|
||||
// pod disruption budgets
|
||||
|
||||
PodDisruptionBudgets, pdbError := getPodDisruptionBudgets(ctx, client, namespaceNames)
|
||||
for k, v := range PodDisruptionBudgets {
|
||||
@@ -273,6 +311,30 @@ func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (Coll
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -386,6 +448,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)
|
||||
|
||||
@@ -401,6 +512,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()
|
||||
@@ -589,29 +707,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
|
||||
@@ -1486,3 +1651,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
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_SelectCRDVersionByPriority(t *testing.T) {
|
||||
@@ -12,3 +15,199 @@ func Test_SelectCRDVersionByPriority(t *testing.T) {
|
||||
assert.Equal(t, "v1", selectCRDVersionByPriority([]string{"v1alpha2", "v1alpha3", "v1"}))
|
||||
assert.Equal(t, "v1", selectCRDVersionByPriority([]string{"v1", "v1alpha2", "v1alpha3"}))
|
||||
}
|
||||
|
||||
func TestClusterResources_Merge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
Collectors []troubleshootv1beta2.Collect
|
||||
want *CollectClusterResources
|
||||
}{
|
||||
{
|
||||
name: "single cluster resources collector with multiple unique namespaces",
|
||||
Collectors: []troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello", "hello2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &CollectClusterResources{
|
||||
Collector: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello", "hello2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple cluster resources collectors with unique namespaces",
|
||||
Collectors: []troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &CollectClusterResources{
|
||||
Collector: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello", "hello2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple cluster resources collectors with duplicate namespaces",
|
||||
Collectors: []troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &CollectClusterResources{
|
||||
Collector: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello", "hello2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple cluster resource collectors with a empty string namespace provided",
|
||||
Collectors: []troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{""},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &CollectClusterResources{
|
||||
Collector: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple cluster resource collectors with a nil namespace provided",
|
||||
Collectors: []troubleshootv1beta2.Collect{
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: []string{"hello2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterResources: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &CollectClusterResources{
|
||||
Collector: &troubleshootv1beta2.ClusterResources{
|
||||
CollectorMeta: troubleshootv1beta2.CollectorMeta{
|
||||
CollectorName: "collectorname",
|
||||
},
|
||||
Namespaces: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
var mergedCollectors []Collector
|
||||
allCollectors := make(map[reflect.Type][]Collector)
|
||||
collectorType := reflect.TypeOf(CollectClusterResources{})
|
||||
|
||||
for _, collector := range tt.Collectors {
|
||||
collectorInterface, _ := GetCollector(&collector, "", "", nil, nil, nil)
|
||||
if mergeCollector, ok := collectorInterface.(MergeableCollector); ok {
|
||||
allCollectors[collectorType] = append(allCollectors[collectorType], mergeCollector)
|
||||
}
|
||||
}
|
||||
|
||||
for _, collectors := range allCollectors {
|
||||
if mergeCollector, ok := collectors[0].(MergeableCollector); ok {
|
||||
mergedCollectors, _ = mergeCollector.Merge(collectors)
|
||||
}
|
||||
}
|
||||
|
||||
clusterResourceCollector, _ := mergedCollectors[0].(*CollectClusterResources)
|
||||
|
||||
req.EqualValues(tt.want, clusterResourceCollector)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,12 @@ type CollectCollectd struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "CollectD")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCollectd) IsExcluded() (bool, error) {
|
||||
@@ -39,7 +39,7 @@ func (c *CollectCollectd) Collect(progressChan chan<- interface{}) (CollectorRes
|
||||
}
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
copyFromHostCollector := &CollectCopyFromHost{copyFromHost, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, rbacErrors}
|
||||
copyFromHostCollector := &CollectCopyFromHost{copyFromHost, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
|
||||
|
||||
return copyFromHostCollector.Collect(progressChan)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -107,3 +109,83 @@ func collectorTitleOrDefault(meta troubleshootv1beta2.CollectorMeta, defaultTitl
|
||||
}
|
||||
return defaultTitle
|
||||
}
|
||||
|
||||
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>"
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
return fmt.Sprintf("%s/%s", collector, name)
|
||||
}
|
||||
if selector != "" {
|
||||
return fmt.Sprintf("%s/%s", collector, selector)
|
||||
}
|
||||
return collector
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ type CollectConfigMap struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "ConfigMap")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectConfigMap) IsExcluded() (bool, error) {
|
||||
@@ -50,7 +50,7 @@ func (c *CollectConfigMap) Collect(progressChan chan<- interface{}) (CollectorRe
|
||||
|
||||
configMaps := []corev1.ConfigMap{}
|
||||
if c.Collector.Name != "" {
|
||||
configMap, err := c.Client.CoreV1().ConfigMaps(c.Collector.Namespace).Get(c.ctx, c.Collector.Name, metav1.GetOptions{})
|
||||
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(c.Collector, nil, c.Collector.Name)
|
||||
@@ -64,7 +64,7 @@ func (c *CollectConfigMap) Collect(progressChan chan<- interface{}) (CollectorRe
|
||||
}
|
||||
configMaps = append(configMaps, *configMap)
|
||||
} else if len(c.Collector.Selector) > 0 {
|
||||
cms, err := listConfigMapsForSelector(c.ctx, c.Client, c.Collector.Namespace, c.Collector.Selector)
|
||||
cms, err := listConfigMapsForSelector(c.Context, c.Client, c.Collector.Namespace, c.Collector.Selector)
|
||||
if err != nil {
|
||||
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
|
||||
return output, nil
|
||||
|
||||
+2
-2
@@ -26,12 +26,12 @@ type CollectCopy struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCopy) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Copy")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCopy) IsExcluded() (bool, error) {
|
||||
|
||||
@@ -32,12 +32,12 @@ type CollectCopyFromHost struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectCopyFromHost) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Copy from Host")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectCopyFromHost) IsExcluded() (bool, error) {
|
||||
@@ -69,13 +69,13 @@ func (c *CollectCopyFromHost) Collect(progressChan chan<- interface{}) (Collecto
|
||||
namespace, _, _ = kubeconfig.Namespace()
|
||||
}
|
||||
|
||||
_, cleanup, err := copyFromHostCreateDaemonSet(c.ctx, c.Client, c.Collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
|
||||
_, 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(c.ctx)
|
||||
childCtx, cancel := context.WithCancel(c.Context)
|
||||
defer cancel()
|
||||
|
||||
timeoutCtx := context.Background()
|
||||
|
||||
+2
-2
@@ -16,12 +16,12 @@ type CollectData struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectData) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Data")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectData) IsExcluded() (bool, error) {
|
||||
|
||||
+2
-2
@@ -22,12 +22,12 @@ type CollectExec struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectExec) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Exec")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectExec) IsExcluded() (bool, error) {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ type CollectHTTP struct {
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "HTTP")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectHTTP) IsExcluded() (bool, error) {
|
||||
|
||||
+60
-18
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -19,16 +20,16 @@ import (
|
||||
type CollectLogs struct {
|
||||
Collector *troubleshootv1beta2.Logs
|
||||
BundlePath string
|
||||
Namespace string
|
||||
Namespace string // There is a Namespace parameter in troubleshootv1beta2.Logs. Should we remove this?
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
SinceTime *time.Time
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectLogs) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Logs")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectLogs) IsExcluded() (bool, error) {
|
||||
@@ -70,10 +71,7 @@ func (c *CollectLogs) Collect(progressChan chan<- interface{}) (CollectorResult,
|
||||
}
|
||||
|
||||
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, c.Collector.Name, containerName, c.Collector.Limits, false)
|
||||
podLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, c.Collector.Name, containerName, c.Collector.Limits, false, true)
|
||||
if err != nil {
|
||||
key := fmt.Sprintf("%s/%s-errors.json", c.Collector.Name, pod.Name)
|
||||
if containerName != "" {
|
||||
@@ -91,7 +89,7 @@ func (c *CollectLogs) Collect(progressChan chan<- interface{}) (CollectorResult,
|
||||
}
|
||||
} else {
|
||||
for _, container := range c.Collector.ContainerNames {
|
||||
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, c.Collector.Name, container, c.Collector.Limits, false)
|
||||
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, c.Collector.Name, container, c.Collector.Limits, false, true)
|
||||
if err != nil {
|
||||
key := fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, container)
|
||||
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
|
||||
@@ -111,7 +109,7 @@ func (c *CollectLogs) Collect(progressChan chan<- interface{}) (CollectorResult,
|
||||
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{
|
||||
@@ -126,20 +124,56 @@ 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,
|
||||
createSymLinks bool,
|
||||
) (CollectorResult, error) {
|
||||
return savePodLogsWithInterface(ctx, bundlePath, client, pod, collectorName, container, limits, follow, createSymLinks)
|
||||
}
|
||||
|
||||
func savePodLogsWithInterface(
|
||||
ctx context.Context,
|
||||
bundlePath string,
|
||||
client kubernetes.Interface,
|
||||
pod *corev1.Pod,
|
||||
collectorName, container string,
|
||||
limits *troubleshootv1beta2.LogLimits,
|
||||
follow bool,
|
||||
createSymLinks 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)
|
||||
@@ -148,11 +182,15 @@ 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
|
||||
if createSymLinks {
|
||||
defer result.SymLinkResult(bundlePath, linkRelPathPrefix+".log", filePathPrefix+".log")
|
||||
}
|
||||
defer result.CloseWriter(bundlePath, filePathPrefix+".log", logWriter)
|
||||
|
||||
_, err = io.Copy(logWriter, podLogs)
|
||||
if err != nil {
|
||||
@@ -168,11 +206,15 @@ 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
|
||||
if createSymLinks {
|
||||
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,88 @@ func Test_setLogLimits(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_savePodLogs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
withContainerName bool
|
||||
collectorName string
|
||||
createSymLinks bool
|
||||
want CollectorResult
|
||||
}{
|
||||
{
|
||||
name: "with container name",
|
||||
withContainerName: true,
|
||||
collectorName: "all-logs",
|
||||
createSymLinks: true,
|
||||
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",
|
||||
createSymLinks: true,
|
||||
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,
|
||||
createSymLinks: true,
|
||||
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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "without sym links",
|
||||
withContainerName: true,
|
||||
collectorName: "all-logs",
|
||||
createSymLinks: false,
|
||||
want: CollectorResult{
|
||||
"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, tt.createSymLinks)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+26
-21
@@ -5,7 +5,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
@@ -35,12 +34,12 @@ type CollectLonghorn struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Longhorn")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectLonghorn) IsExcluded() (bool, error) {
|
||||
@@ -214,27 +213,10 @@ func (c *CollectLonghorn) Collect(progressChan chan<- interface{}) (CollectorRes
|
||||
}
|
||||
output.SaveResult(c.BundlePath, settingsKey, bytes.NewBuffer(settingsB))
|
||||
|
||||
// logs of all pods in namespace
|
||||
logsCollectorSpec := &troubleshootv1beta2.Logs{
|
||||
Selector: []string{""},
|
||||
Namespace: ns,
|
||||
}
|
||||
|
||||
rbacErrors := c.GetRBACErrors()
|
||||
logsCollector := &CollectLogs{logsCollectorSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.ctx, nil, rbacErrors}
|
||||
|
||||
logs, err := logsCollector.Collect(progressChan)
|
||||
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/
|
||||
|
||||
@@ -307,6 +289,29 @@ func (c *CollectLonghorn) Collect(progressChan chan<- interface{}) (CollectorRes
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ type CollectMysql struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectMysql) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Mysql")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectMysql) IsExcluded() (bool, error) {
|
||||
|
||||
+44
-6
@@ -3,12 +3,11 @@ package collect
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"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"
|
||||
@@ -21,27 +20,66 @@ type CollectPostgres struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectPostgres) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Postgres")
|
||||
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", c.Collector.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()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_parsePostgresVersion(t *testing.T) {
|
||||
@@ -44,3 +47,72 @@ func Test_parsePostgresVersion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectPostgres_createConnectConfigPlainText(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
uri string
|
||||
hasError bool
|
||||
}{
|
||||
{
|
||||
name: "valid uri creates postgres connection config successfully",
|
||||
uri: "postgresql://user:password@my-pghost:5432/defaultdb?sslmode=require",
|
||||
},
|
||||
{
|
||||
name: "empty uri fails to create postgres connection config with error",
|
||||
uri: "",
|
||||
hasError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid redis protocol fails to create postgres connection config with error",
|
||||
uri: "http://somehost:5432",
|
||||
hasError: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CollectPostgres{
|
||||
Context: context.Background(),
|
||||
Collector: &v1beta2.Database{
|
||||
URI: tt.uri,
|
||||
},
|
||||
}
|
||||
|
||||
connCfg, err := c.createConnectConfig()
|
||||
assert.Equal(t, err != nil, tt.hasError)
|
||||
if err == nil {
|
||||
require.NotNil(t, connCfg)
|
||||
assert.Equal(t, connCfg.Host, "my-pghost")
|
||||
assert.Equal(t, connCfg.Database, "defaultdb")
|
||||
} else {
|
||||
t.Log(err)
|
||||
assert.Nil(t, connCfg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectPostgres_createConnectConfigTLS(t *testing.T) {
|
||||
k8sClient := testclient.NewSimpleClientset()
|
||||
|
||||
c := &CollectPostgres{
|
||||
Client: k8sClient,
|
||||
Context: context.Background(),
|
||||
Collector: &v1beta2.Database{
|
||||
URI: "postgresql://user:password@my-pghost:5432/defaultdb?sslmode=require",
|
||||
TLS: &v1beta2.TLSParams{
|
||||
CACert: getTestFixture(t, "db/ca.pem"),
|
||||
ClientCert: getTestFixture(t, "db/client.pem"),
|
||||
ClientKey: getTestFixture(t, "db/client-key.pem"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
connCfg, err := c.createConnectConfig()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, connCfg)
|
||||
assert.Equal(t, connCfg.Host, "my-pghost")
|
||||
assert.NotNil(t, connCfg.TLSConfig.Certificates)
|
||||
assert.NotNil(t, connCfg.TLSConfig.RootCAs)
|
||||
assert.False(t, connCfg.TLSConfig.InsecureSkipVerify)
|
||||
}
|
||||
|
||||
+27
-7
@@ -17,9 +17,28 @@ import (
|
||||
|
||||
func RedactResult(bundlePath string, input CollectorResult, additionalRedactors []*troubleshootv1beta2.Redact) error {
|
||||
for k, v := range input {
|
||||
file := k
|
||||
|
||||
var reader io.Reader
|
||||
if v == nil {
|
||||
r, err := input.GetReader(bundlePath, k)
|
||||
// Collected contents are in a file. Get a reader to the file.
|
||||
info, err := os.Lstat(filepath.Join(bundlePath, file))
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
// File not found, moving on.
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "failed to stat file")
|
||||
}
|
||||
|
||||
// Redact the target file of a symlink
|
||||
if info.Mode().Type() == os.ModeSymlink {
|
||||
file, err = os.Readlink(filepath.Join(bundlePath, file))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read symlink")
|
||||
}
|
||||
}
|
||||
r, err := input.GetReader(bundlePath, file)
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
continue
|
||||
@@ -30,19 +49,20 @@ func RedactResult(bundlePath string, input CollectorResult, additionalRedactors
|
||||
|
||||
reader = r
|
||||
} else {
|
||||
// Collected contents are in memory. Get a reader to the memory buffer.
|
||||
reader = bytes.NewBuffer(v)
|
||||
}
|
||||
|
||||
//If the file is .tar, .tgz or .tar.gz, it must not be redacted. Instead it is decompressed and each file inside the
|
||||
//tar is decompressed, redacted and compressed back into the tar.
|
||||
if filepath.Ext(k) == ".tar" || filepath.Ext(k) == ".tgz" || strings.HasSuffix(k, ".tar.gz") {
|
||||
if filepath.Ext(file) == ".tar" || filepath.Ext(file) == ".tgz" || strings.HasSuffix(file, ".tar.gz") {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot-subresult-")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
subResult, tarHeaders, err := decompressFile(tmpDir, reader, k)
|
||||
subResult, tarHeaders, err := decompressFile(tmpDir, reader, file)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to decompress file")
|
||||
}
|
||||
@@ -51,7 +71,7 @@ func RedactResult(bundlePath string, input CollectorResult, additionalRedactors
|
||||
return errors.Wrap(err, "failed to redact file")
|
||||
}
|
||||
|
||||
dstFilename := filepath.Join(bundlePath, k)
|
||||
dstFilename := filepath.Join(bundlePath, file)
|
||||
err = compressFiles(tmpDir, subResult, tarHeaders, dstFilename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to re-compress file")
|
||||
@@ -63,12 +83,12 @@ func RedactResult(bundlePath string, input CollectorResult, additionalRedactors
|
||||
continue
|
||||
}
|
||||
|
||||
redacted, err := redact.Redact(reader, k, additionalRedactors)
|
||||
redacted, err := redact.Redact(reader, file, additionalRedactors)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to redact")
|
||||
return errors.Wrap(err, "failed to redact io stream")
|
||||
}
|
||||
|
||||
err = input.ReplaceResult(bundlePath, k, redacted)
|
||||
err = input.ReplaceResult(bundlePath, file, redacted)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create redacted result")
|
||||
}
|
||||
|
||||
+47
-13
@@ -12,6 +12,7 @@ import (
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type CollectRedis struct {
|
||||
@@ -20,26 +21,67 @@ type CollectRedis struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectRedis) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Cluster Info")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectRedis) IsExcluded() (bool, error) {
|
||||
return isExcluded(c.Collector.Exclude)
|
||||
}
|
||||
|
||||
func (c *CollectRedis) createClient() (*redis.Client, error) {
|
||||
opt, err := redis.ParseURL(c.Collector.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Collector.TLS != nil {
|
||||
klog.V(2).Infof("Connecting to redis in mutual TLS")
|
||||
return c.createMTLSClient(opt)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("Connecting to redis in plain text")
|
||||
return redis.NewClient(opt), nil
|
||||
}
|
||||
|
||||
func (c *CollectRedis) createMTLSClient(opt *redis.Options) (*redis.Client, error) {
|
||||
tlsCfg, err := createTLSConfig(c.Context, c.Client, c.Collector.TLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt.TLSConfig = tlsCfg
|
||||
|
||||
return redis.NewClient(opt), nil
|
||||
}
|
||||
|
||||
func extractServerVersion(info string) string {
|
||||
lines := strings.Split(info, "\n")
|
||||
for _, line := range lines {
|
||||
lineParts := strings.Split(strings.TrimSpace(line), ":")
|
||||
if len(lineParts) == 2 {
|
||||
if lineParts[0] == "redis_version" {
|
||||
return strings.TrimSpace(lineParts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *CollectRedis) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
||||
databaseConnection := DatabaseConnection{}
|
||||
|
||||
opt, err := redis.ParseURL(c.Collector.URI)
|
||||
client, err := c.createClient()
|
||||
if err != nil {
|
||||
databaseConnection.Error = err.Error()
|
||||
} else {
|
||||
client := redis.NewClient(opt)
|
||||
defer client.Close()
|
||||
|
||||
stringResult := client.Info("server")
|
||||
|
||||
if stringResult.Err() != nil {
|
||||
@@ -49,15 +91,7 @@ func (c *CollectRedis) Collect(progressChan chan<- interface{}) (CollectorResult
|
||||
databaseConnection.IsConnected = stringResult.Err() == nil
|
||||
|
||||
if databaseConnection.Error == "" {
|
||||
lines := strings.Split(stringResult.Val(), "\n")
|
||||
for _, line := range lines {
|
||||
lineParts := strings.Split(line, ":")
|
||||
if len(lineParts) == 2 {
|
||||
if lineParts[0] == "redis_version" {
|
||||
databaseConnection.Version = strings.TrimSpace(lineParts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
databaseConnection.Version = extractServerVersion(stringResult.Val())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func Test_extractServerVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
info string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "extracts version successfully",
|
||||
info: `
|
||||
# Server
|
||||
redis_version:7.0.5
|
||||
redis_git_sha1:00000000
|
||||
redis_git_dirty:0
|
||||
redis_build_id:eb3578384289228a
|
||||
`,
|
||||
want: "7.0.5",
|
||||
},
|
||||
{
|
||||
name: "extracts version but fails",
|
||||
info: "",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := extractServerVersion(tt.info)
|
||||
assert.Equalf(t, tt.want, got, "extractServerVersion() = %v, want %v", got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectRedis_createPlainTextClient(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
uri string
|
||||
hasError bool
|
||||
}{
|
||||
{
|
||||
name: "valid uri creates redis client successfully",
|
||||
uri: "redis://localhost:6379",
|
||||
},
|
||||
{
|
||||
name: "empty uri fails to create client with error",
|
||||
uri: "",
|
||||
hasError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid redis protocol fails to create client with error",
|
||||
uri: "http://localhost:6379",
|
||||
hasError: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CollectRedis{
|
||||
Collector: &v1beta2.Database{
|
||||
URI: tt.uri,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := c.createClient()
|
||||
assert.Equal(t, err != nil, tt.hasError)
|
||||
if err == nil {
|
||||
require.NotNil(t, client)
|
||||
assert.Equal(t, client.Options().Addr, "localhost:6379")
|
||||
} else {
|
||||
t.Log(err)
|
||||
assert.Nil(t, client)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectRedis_createTLSClient(t *testing.T) {
|
||||
k8sClient := testclient.NewSimpleClientset()
|
||||
|
||||
c := &CollectRedis{
|
||||
Client: k8sClient,
|
||||
Collector: &v1beta2.Database{
|
||||
URI: "redis://localhost:6379",
|
||||
TLS: &v1beta2.TLSParams{
|
||||
CACert: getTestFixture(t, "db/ca.pem"),
|
||||
ClientCert: getTestFixture(t, "db/client.pem"),
|
||||
ClientKey: getTestFixture(t, "db/client-key.pem"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
client, err := c.createClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
opt := client.Options()
|
||||
assert.Equal(t, opt.Addr, "localhost:6379")
|
||||
assert.NotNil(t, opt.TLSConfig.Certificates)
|
||||
assert.NotNil(t, opt.TLSConfig.RootCAs)
|
||||
assert.False(t, opt.TLSConfig.InsecureSkipVerify)
|
||||
}
|
||||
@@ -43,12 +43,12 @@ type CollectRegistry struct {
|
||||
Namespace string
|
||||
ClientConfig *rest.Config
|
||||
Client kubernetes.Interface
|
||||
ctx context.Context
|
||||
Context context.Context
|
||||
RBACErrors
|
||||
}
|
||||
|
||||
func (c *CollectRegistry) Title() string {
|
||||
return collectorTitleOrDefault(c.Collector.CollectorMeta, "Registry Images")
|
||||
return getCollectorName(c)
|
||||
}
|
||||
|
||||
func (c *CollectRegistry) IsExcluded() (bool, error) {
|
||||
|
||||
@@ -109,7 +109,7 @@ func (c *RemoteCollector) RunCollectorSync(globalRedactors []*troubleshootv1beta
|
||||
|
||||
if err = RedactResult("", result, globalRedactors); err != nil {
|
||||
// Returning result on error to be consistent with local collector.
|
||||
return result, errors.Wrap(err, "failed to redact")
|
||||
return result, errors.Wrap(err, "failed to redact remote collector results")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user