Compare commits

...
75 Commits
Author SHA1 Message Date
Diamon Wiggins f2be6f5829 Allow Preflight CLI to consume multiple specs as input (#894)
To keep both the Support Bundle and Preflight CLIs similar, this PR adds the ability for the Preflight binary to allow multiple specs be provided as CLI args and for them all to be run.
2022-12-14 14:50:01 -04:00
Evans Mungai cd1511a8fb fix(collectors): store unhealthy pod logs correctly (#909)
The symlinking logs feature led to a regression where symlinks of
unhealthy pods were overwritting logs in the support bundle. This
fix allows the cluster resources collector to instruct the logs
collector not to symlink logs, which in turn ensures logs are not
overwritten.

Fixes: #908
2022-12-14 14:47:20 -04:00
Diamon WigginsandEvans Mungai 9c77a0e3da Add sbctl integration proposal and move design directory into docs (#893)
* add sbctl integration proposal and move design directory into docs

Co-authored-by: Evans Mungai <evans@replicated.com>
2022-12-14 14:52:40 +13:00
Xav Paice c8820b3d8e chore: update containerd to v1.6.12 (#896)
Fixes: CVE-2022-23471
2022-12-09 16:48:37 -04:00
Xav Paice cf3a292877 Add proposal for concurrent collection (#809)
* Add proposal for concurrent collection

Design proposal for running collectors concurrently instead of sequentially.

* Update concurrent collector proposal with PR feedback
2022-12-09 16:48:09 -04:00
Diamon Wiggins a4c4b24056 Deduplication for Cluster Resources Collector (#832)
* add dedup for cluster resources collector
* restructure both collect.go in both pkg/supportbundle and pkg/preflight to be more similar for eventual refactor
2022-12-07 15:10:31 -04:00
dependabot[bot]andlnx01 e9f386a840 Bump github.com/jackc/pgx/v5 from 5.1.1 to 5.2.0 (#890)
Bumps [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) from 5.1.1 to 5.2.0.
- [Release notes](https://github.com/jackc/pgx/releases)
- [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v5.1.1...v5.2.0)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgx/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 12:37:53 +00:00
dependabot[bot]andlnx01 d96b2ded78 Bump github.com/go-sql-driver/mysql from 1.6.0 to 1.7.0 (#889)
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/go-sql-driver/mysql/releases)
- [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-sql-driver/mysql/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/go-sql-driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 11:39:12 +00:00
Evans Mungai 8c31e61367 fix(collectors): Fix logs collection in longhorn collector (#886)
* fix(collectors): Fix logs collection in longhorn collector

* Small typo

* Run go fmt on added changes
2022-12-02 12:51:07 -05:00
Evans Mungai 8e174fb46c chore: Update PR template (#880) 2022-12-01 11:26:17 +00:00
Evans Mungai 2a61a8686a feat(collectors): Add TLS parameters to the postgres collector (#875)
For a postgres collector spec targeting a server configured to accept
(m)TLS connections we need to pass in the necessary parameters in order
to successfully connect to the server. Both preflight and support bundle
specs use this collector.

This change allows us to pass in the necessary TLS parameters via inlined
TLS configuration or via a secret reference.

Fixes #747
2022-11-30 15:52:08 +13:00
Xav Paice c85bf9a9a6 BREAKING: remove IP address redaction (#734)
This change removes the IPv4 address redaction which previously ran by default on all
support bundle collections.

Folks that want to redact IPv4 addresses will need to add that redactor manually to their redactor specs.
2022-11-30 08:42:42 +13:00
Evans Mungai bfb77ad601 feat(collectors): Add TLS parameters to the redis collector (#870)
feat(collectors): Add mTLS parameters to the redis collector

For a redis collector spec targeting a redis server configured to accept
(m)TLS connections we need to pass in the necessary TLS parameters in order
to successfully connect to the server. Both preflight and support bundle
specs use this collector.

This change allows us to pass in the necessary TLS parameters via inlined
TLS configuration or via a secret reference.

Fixes #746
2022-11-29 17:47:52 +00:00
Chuck D'Antonio c4c66633e5 Includes virtual memory parameters in Sysctl (#874)
TL;DR
-----

Updates Sysctl collector and analyzer for virtual memory parameters

Details
-------

Adds supoort for virtual memory parameters to the Sysctl collector and
analyzers. I uncovered this writing a pre-flight for a Helm chart that
includes ECK as a subchart. Since ECK requires a specific minimum value
for `vm.max_map_count` I wanted to use the Sysctl analyzer to check for
the expected value, but wasn't able to because of the limited values it
collected. I also learned that Sonarqube expects the same parameter to
be increased, so it seemed like a general enough requirement to add it
in.

The code updates the collector to collect values under `/proc/sys/vm`
and adds tests to the analyzer to based on the ECK requirements. Making
the tests pass required adding operators to the when expression, since
the existing code only allowed for `=`, `==`, and `===`. The when
expression now supports `>`, `<`, `>=`, and `<=`.

All tests pass.
2022-11-29 12:32:59 -05:00
f13a9598b2 Bump github/codeql-action from 1 to 2 (#865)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diamon Wiggins <38189728+diamonwiggins@users.noreply.github.com>
2022-11-28 11:01:12 +13:00
4fa8411259 Bump rajatjindal/krew-release-bot from 0.0.38 to 0.0.43 (#866)
Bumps [rajatjindal/krew-release-bot](https://github.com/rajatjindal/krew-release-bot) from 0.0.38 to 0.0.43.
- [Release notes](https://github.com/rajatjindal/krew-release-bot/releases)
- [Changelog](https://github.com/rajatjindal/krew-release-bot/blob/master/.goreleaser.yml)
- [Commits](https://github.com/rajatjindal/krew-release-bot/compare/v0.0.38...v0.0.43)

---
updated-dependencies:
- dependency-name: rajatjindal/krew-release-bot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Diamon Wiggins <38189728+diamonwiggins@users.noreply.github.com>
2022-11-28 10:59:37 +13:00
Evans Mungai b693e6650d chore: Add GH workflow to ensure schemas are generated in a PR (#872)
* Make file target to check generated schemas

* Add missing yaml tag to Logs struct

* chore: Add GH workflow to ensure schemas are generated in a PR
2022-11-28 10:58:09 +13:00
Dexter Yan 7e3a59cfc0 feat(analyze): add ExcludeFiles field to textAnazlye (#867)
* feat(analyze): add ExcludeFiles field to textAnazlye

* feat(analyze): fix test for getFiles

* feat(analyze): change function name to  excludeFilePaths

* feat(analyze): fix preflight test fail

* feat(analyze): add tests for excludeFiles

* feat(schemas): run make schemas

* feat(analyze): use getChildCollectedFileContents function prototype

* feat(analyze): reduce time complexity

* feat(longhorn): add getFileContents as getCollectedFileContents
2022-11-28 10:45:10 +13:00
Xav Paice 48faf0c144 Revert "Add ghcr to package releases (#848)" (#868)
This reverts commit 233966d4be.
2022-11-22 17:14:04 +13:00
Evans Mungai fbbcf87405 feat(collectors): Store all pod logs in cluster-resources directory (#821)
* feat(collectors): Store all pod logs in cluster-resources directory

All pod logs collected by the logs collector will now be stored in
/cluster-resources/pods/logs/[namespace]/[pod]/[container].log. This
will provide consistency and allow sbctl to find the logs when we run
`kubectl logs <pod>`. To allow backwards compatibility, symlinks of the
log files will be created in the current expected locations.

Closes: #744
2022-11-22 07:10:34 +13:00
Xav Paice 6530cb364f feat: Add docsgen command (#862)
* feat: Add docsgen command

* docs: add CLI docs for support-bundle and preflight
2022-11-21 11:31:08 -05:00
Xav Paice c9b48aa716 docs: add ADR for mergable preflight specs (#858)
Adds a proposed Architecture Decision Records for the ability to specify
multiple specs per preflight test.
2022-11-21 11:29:51 -05:00
dependabot[bot]andlnx01 511671f699 Bump actions/setup-go from 1 to 3 (#853)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 1 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 18:19:42 +13:00
dependabot[bot]andlnx01 7a3bcfc74a Bump actions/download-artifact from 1 to 3 (#854)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 1 to 3.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 18:19:16 +13:00
dependabot[bot]andlnx01 e88f309391 Bump actions/upload-artifact from 1 to 3 (#855)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 1 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 18:18:46 +13:00
dependabot[bot]andlnx01 da48621eff Bump goreleaser/goreleaser-action from 2 to 3 (#852)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 17:44:56 +13:00
dependabot[bot]andlnx01 e51a25dc2f Bump actions/checkout from 2 to 3 (#856)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 17:35:55 +13:00
Camila Macedo 7a20aa1934 Add job to dependabot to update github actions (#851)
* Add job to dependabot to update github actions
2022-11-17 17:09:36 -05:00
Dexter Yan 78bcafe489 fix(flag): fix wrong output filename (#834)
* fix(flag): fix wrong output filename

* fix(flag): add reset flag function

* fix(flag): add output flag test cases

* fix(flag): move resetFlags function into private go test

* fix(flag): restructure flag tests with testify

* fix(flag): remove resetFlags function

* fix(flag): remove duplicated test and rewrite test names
2022-11-17 14:38:01 +13:00
Chris Sanders 233966d4be Add ghcr to package releases (#848) 2022-11-17 11:12:54 +13:00
Camila Macedo ebc569fcdb doc/readme : make clear that the links are just examples (#845)
* doc/readme : make clear that the links are just examples
2022-11-16 19:52:48 +13:00
dependabot[bot]andlnx01 0521b0e82b Bump k8s.io/apiserver from 0.25.3 to 0.25.4 (#837)
Bumps [k8s.io/apiserver](https://github.com/kubernetes/apiserver) from 0.25.3 to 0.25.4.
- [Release notes](https://github.com/kubernetes/apiserver/releases)
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: k8s.io/apiserver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 15:55:12 +13:00
dependabot[bot]andlnx01 34f29f2914 Bump k8s.io/cli-runtime from 0.25.3 to 0.25.4 (#847)
Bumps [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) from 0.25.3 to 0.25.4.
- [Release notes](https://github.com/kubernetes/cli-runtime/releases)
- [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: k8s.io/cli-runtime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 15:40:51 +13:00
dependabot[bot]andlnx01 1de1d00680 Bump github.com/containers/image/v5 from 5.23.0 to 5.23.1 (#836)
Bumps [github.com/containers/image/v5](https://github.com/containers/image) from 5.23.0 to 5.23.1.
- [Release notes](https://github.com/containers/image/releases)
- [Commits](https://github.com/containers/image/compare/v5.23.0...v5.23.1)

---
updated-dependencies:
- dependency-name: github.com/containers/image/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 14:59:57 +13:00
dependabot[bot]andlnx01 92a5f3569f Bump k8s.io/api from 0.25.3 to 0.25.4 (#838)
Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.25.3 to 0.25.4.
- [Release notes](https://github.com/kubernetes/api/releases)
- [Commits](https://github.com/kubernetes/api/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: k8s.io/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 14:59:10 +13:00
dependabot[bot]andlnx01 495b1a760d Bump k8s.io/apimachinery from 0.25.3 to 0.25.4 (#839)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.25.3 to 0.25.4.
- [Release notes](https://github.com/kubernetes/apimachinery/releases)
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 14:58:43 +13:00
dependabot[bot]andlnx01 7bcfd01d43 Bump k8s.io/client-go from 0.25.3 to 0.25.4 (#835)
Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.25.3 to 0.25.4.
- [Release notes](https://github.com/kubernetes/client-go/releases)
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: k8s.io/client-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 14:58:18 +13:00
Camila Macedo f2ffef80af Revert "Remove ingress check from https://preflight.replicated.com (#840)" (#846)
This reverts commit a5244a262c.
2022-11-16 14:57:47 +13:00
Camila Macedo a5244a262c Remove ingress check from https://preflight.replicated.com (#840)
## Description:

Currently, when we install an k8s with kurl and we run kubectl preflight https://preflight.replicated.com it will fail:

> ------------
> Check FAIL
> Title: Ingress
> Message: Contour ingress not found!

Therefore,  Contour ingress does not seems a pre-requirement to kURL. So, should we have this check in the default example/test?
2022-11-15 08:55:04 -06:00
Camila Macedo 18d9a16ceb Minimal memory requirement for kURL is 8GI (#843) 2022-11-15 08:54:29 -06:00
Xav Paice 473738d709 Add proposal to consolidate collection code (#810)
Proposal to reduce duplication of code, and implement a more stable, public,
collection API.
2022-11-15 15:50:37 +13:00
Diamon Wiggins c34d80c300 Discover Redactors in Cluster (#827)
Adds the ability to search for support bundle specs and redactors, in both configmaps and secrets
2022-11-10 17:36:51 +13:00
Nick Meyer 38c1193ace correct checked out ref for FOSSA tests (#833) 2022-11-09 14:34:14 -05:00
Xav Paice 6b87cdb267 docs: ADR-001 remove default redaction of IP addresses (#823)
* Add ADR-001 Remove IP address redaction

Add design record (proposed) for removing IP address redaction by default.

Fixes: #735
2022-11-09 16:37:12 +13:00
Xav Paice 80cca8a487 Add omitempty to StorageClassName in schema (#814)
* Add omitempty to StorageClassName in schema

Allow a StorageClass spec to not require specify storageClassName.

Fixes: #813
2022-11-09 11:34:37 +13:00
dependabot[bot]andlnx01 6e79c169e0 Bump github.com/spf13/viper from 1.13.0 to 1.14.0 (#829)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-09 09:22:44 +13:00
dependabot[bot]andlnx01 7edbec3e45 Bump sigs.k8s.io/controller-runtime from 0.13.0 to 0.13.1 (#828)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.13.0 to 0.13.1.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/master/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.13.0...v0.13.1)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 18:16:52 +13:00
Xav Paice 8f8e0dc6e3 chore: Update PULL_REQUEST_TEMPLATE.md (#831)
Fix a minor grammatical error in the pull request template
2022-11-08 18:14:16 +13:00
Dexter Yan e2c20cfda4 feat(github): add pull request template (#830)
* feat(github): add pull request template

* feat(github): rephrase pr template
2022-11-08 17:41:42 +13:00
Nick Meyer ccb7b1619b Update fossa (#826)
* Transition FOSSA to locally-managed action

* correct ref
2022-11-07 11:46:27 -06:00
Xav Paice d5c0dd4857 Add ADR directory and readme (#822)
Adds a directory and readme.md for storing Architecture Design Records
2022-11-07 13:41:32 +00:00
Xav Paice 8c5cd5a460 Update CONTRINBUTING.md with community info (#819)
Add to CONTRIBUTING.md:
* community meeting calendar and notes
* IRC
* commit messages info
2022-11-02 15:22:51 -04:00
dependabot[bot]andlnx01 9073a14c02 Bump periph.io/x/host/v3 from 3.7.2 to 3.8.0 (#815)
Bumps [periph.io/x/host/v3](https://github.com/periph/host) from 3.7.2 to 3.8.0.
- [Release notes](https://github.com/periph/host/releases)
- [Commits](https://github.com/periph/host/compare/v3.7.2...v3.8.0)

---
updated-dependencies:
- dependency-name: periph.io/x/host/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-02 15:04:03 +13:00
Xav Paice 3513eeca19 Ensure clusterResources is added prior to other collectors (#768)
This change ensures that the clusterResources collector runs prior to any others
in order to not collect info on pods that collectors run during collection.

Additionally centralizes functions that are common to all collection to make future
maintenance simpler.

Fixes: #767
2022-11-01 12:16:01 +13:00
Edgar Lanting 34817b67d0 Update cluster_resources.go (#804)
Due to deprecation of the API at `policy/v1beta1` for `PodDisruptionBudgets` and `batch/v1beta1` for `CronJobs`, updated cluster_resources.go to accommodate using either apiVersion v1 & v1beta1
2022-10-28 14:56:57 +13:00
Nick Meyer 388e7ca77d Update FOSSA API key (#803) 2022-10-25 15:17:12 -05:00
dependabot[bot]andlnx01 d8091909b3 Bump k8s.io/cli-runtime from 0.25.2 to 0.25.3 (#800)
Bumps [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) from 0.25.2 to 0.25.3.
- [Release notes](https://github.com/kubernetes/cli-runtime/releases)
- [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.25.2...v0.25.3)

---
updated-dependencies:
- dependency-name: k8s.io/cli-runtime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 14:58:37 +13:00
dependabot[bot]andlnx01 9ae416ab23 Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#801)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 14:58:03 +13:00
dependabot[bot]andlnx01 e373c71874 Bump github.com/spf13/cobra from 1.6.0 to 1.6.1 (#802)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 14:57:26 +13:00
Diamon Wiggins e2ac7bf715 fix ceph title (#799) 2022-10-24 12:35:35 -05:00
Diamon Wiggins 3d4bd4b601 trim whitespace from collected contents (#796) 2022-10-21 16:41:07 +13:00
Diamon Wiggins 3b2aae1866 Remove unused Preflight spec (#782)
* remove unused preflight-spec

* removing other unused files
2022-10-21 11:38:43 +13:00
dependabot[bot]andlnx01 8a4f539173 Bump oras.land/oras-go from 1.2.0 to 1.2.1 (#786)
Bumps [oras.land/oras-go](https://github.com/oras-project/oras-go) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/oras-project/oras-go/releases)
- [Commits](https://github.com/oras-project/oras-go/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: oras.land/oras-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-20 16:24:36 +13:00
dependabot[bot]andlnx01 89b98c197e Bump k8s.io/apiserver from 0.25.2 to 0.25.3 (#783)
Bumps [k8s.io/apiserver](https://github.com/kubernetes/apiserver) from 0.25.2 to 0.25.3.
- [Release notes](https://github.com/kubernetes/apiserver/releases)
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.25.2...v0.25.3)

---
updated-dependencies:
- dependency-name: k8s.io/apiserver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-20 16:02:24 +13:00
ada mancini 000abae68b plug sbctl (#790) in README.md 2022-10-19 10:22:47 +13:00
Ahmed MousaandEdgar Lanting 764f0ac8b6 'added collection of roles, cluster roles and their respective bindings' (#779)
Co-authored-by: Edgar Lanting <edgarlanting@users.noreply.github.com>
2022-10-17 11:02:07 -05:00
Diamon Wiggins 04c7a18da3 Fix Progress Callback for Support Bundle Collection (#781)
fix progress callback for support bundle and revert collector title changes
2022-10-14 12:29:59 -04:00
dependabot[bot]andlnx01 9b37b98a93 Bump github.com/opencontainers/image-spec from 1.1.0-rc1 to 1.1.0-rc2 (#777)
Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc1 to 1.1.0-rc2.
- [Release notes](https://github.com/opencontainers/image-spec/releases)
- [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md)
- [Commits](https://github.com/opencontainers/image-spec/compare/v1.1.0-rc1...v1.1.0-rc2)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/image-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 15:23:34 +13:00
dependabot[bot]andlnx01 32f622a714 Bump github.com/containers/image/v5 from 5.19.3 to 5.23.0 (#755)
Bumps [github.com/containers/image/v5](https://github.com/containers/image) from 5.19.3 to 5.23.0.
- [Release notes](https://github.com/containers/image/releases)
- [Commits](https://github.com/containers/image/compare/v5.19.3...v5.23.0)

---
updated-dependencies:
- dependency-name: github.com/containers/image/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 13:28:30 +13:00
Diamon Wiggins bcaaa9e59a Fix Preflight CheckRBAC (#776)
* return collect result instead of nil
2022-10-13 12:54:40 +13:00
Chuck D'Antonio 2298ec3030 Supports the Kubernetes distribution analyzer identifying VMware Tanzu (#766)
Adds a check to the Kubernetes distribution analyzer to identify VMware Tanzu using the same approach as identifying OpenShift.
2022-10-13 12:52:22 +13:00
dependabot[bot]andlnx01 afa2eb552a Bump github.com/spf13/cobra from 1.5.0 to 1.6.0 (#775)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 12:48:07 +13:00
Xav Paice 771058980a bump golang.org/x/text to 0.3.8 (#773)
Fixes CVE-2022-32149
2022-10-12 10:53:42 -05:00
Xav Paice d789119634 Update github.com/sylabs/sif/v2 to 2.8.1 (#772)
References
CVE-2004-2761
CVE-2005-4900

Fixes: Dependabot alert #19
2022-10-12 10:52:10 -05:00
Diamon Wiggins 48beb303be export context field from collector structs (#771) 2022-10-11 14:54:54 -04:00
139 changed files with 11381 additions and 1082 deletions
+21
View File
@@ -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. -->
+12
View File
@@ -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"
+42 -28
View File
@@ -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
+2 -2
View File
@@ -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'
+7 -8
View File
@@ -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 }}
+3
View File
@@ -38,3 +38,6 @@ sbom/
# Ignore local pre-commit config
.pre-commit-config.yaml
# Ignore generated support bundles
*.tar.gz
+20 -3
View File
@@ -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
+12
View File
@@ -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)
+10 -3
View File
@@ -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
```
```
+37
View File
@@ -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)
}
}
+10
View File
@@ -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()
}
+1 -1
View File
@@ -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)
},
}
+1 -15
View File
@@ -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
View File
@@ -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
}
-9
View File
@@ -1,9 +0,0 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostCollector
metadata:
name: modules
spec:
collectors:
- cpu: {}
- memory: {}
# - kernelModules: {}
-9
View File
@@ -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
+148 -35
View File
@@ -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:
+358 -36
View File
@@ -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
+68
View File
@@ -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.
+76
View File
@@ -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.
+37
View File
@@ -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.
+81
View File
@@ -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
+120
View File
@@ -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
+51
View File
@@ -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
+38
View File
@@ -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
+53
View File
@@ -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
+26
View File
@@ -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
+23
View File
@@ -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
+1 -1
View File
@@ -9,7 +9,7 @@ spec:
data: "5"
- run:
collectorName: "static-hi"
image: 'alpine:3.5'
image: 'alpine:3'
command: ["echo", "hi static!"]
analyzers:
- clusterVersion:
-1
View File
@@ -20,4 +20,3 @@ spec:
message: The postgres server must be at least version 10
- pass:
message: The postgres connection checks out
+4 -3
View File
@@ -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
+3 -3
View File
@@ -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"
@@ -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
+2 -2
View File
@@ -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!
+66 -60
View File
@@ -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
+168 -272
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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 {
+3 -2
View File
@@ -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")
}
+7 -5
View File
@@ -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")
}
+1 -1
View 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
}
+11 -2
View File
@@ -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
View File
@@ -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)
+6 -3
View File
@@ -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
+3 -2
View File
@@ -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")
}
+7 -5
View File
@@ -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")
}
+1 -1
View 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
}
+7 -6
View File
@@ -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)
}
+7 -5
View File
@@ -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")
}
+1 -1
View 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
}
+7 -5
View File
@@ -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")
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+7 -2
View File
@@ -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)
}
+120 -1
View File
@@ -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)
}
+3 -2
View File
@@ -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)
}
+1 -1
View File
@@ -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
View File
@@ -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"):
+1 -1
View File
@@ -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) {
+302 -17
View File
@@ -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
}
+199
View File
@@ -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)
})
}
}
+28
View File
@@ -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...)
}
+43
View File
@@ -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)
})
}
}
+3 -3
View File
@@ -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)
}
+82
View File
@@ -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
}
+4 -4
View File
@@ -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
View File
@@ -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) {
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+87
View File
@@ -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
View File
@@ -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)
}
+2 -2
View File
@@ -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
View File
@@ -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()
+72
View File
@@ -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
View File
@@ -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
View File
@@ -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())
}
}
+108
View File
@@ -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)
}
+2 -2
View File
@@ -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) {
+1 -1
View File
@@ -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