Compare commits

...
120 Commits
Author SHA1 Message Date
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
Xav Paice e30d279cad update CODEOWNERS file (#770)
Adds a new group to CODEOWNERS as global owner, and CREs for .md files.
2022-10-11 09:02:34 -05:00
stefanrepl 9c986a74a6 make runPreflight and preflight cli flags public (#769) 2022-10-10 16:34:54 -06:00
Evans Mungai e02074941e Add golangci-lint linter make target (#752)
Add line, and lint-and-fix make targets
2022-10-10 09:52:02 +13:00
Edgar Lanting 56a68a4fef Issue #695 - Add --no-uri flag to support-bundle to ignore the uri field and revert back to the default spec… (#717)
Add --follow-uri flag to support-bundle to ignore the uri field when set in a spec

Implement the new CLI flag in `root.go` so we can pass it if we need to ignore the `uri` field in a spec. This also serves as a minimal documentation effort when running `support-bundle --help`.

Fixes: #695
2022-10-10 07:30:21 +13:00
Xav Paice b3e662c988 minor lint update for README.md (#727) 2022-10-07 13:15:01 -04:00
Diamon Wiggins a0fb06f0b9 E2E Tests for Support Bundle CLI (#761)
* adding e2e tests for support bundle cli

* update e2e.yaml
2022-10-07 14:43:16 +13:00
Evans Mungai 906fa88119 Fit wrapped long url in more information analyser message (#758) 2022-10-06 10:21:52 +13:00
ada mancini eb40b9422f implement uri: field (#702) 2022-10-05 15:35:55 +13:00
Xav Paice f6531b8434 Update golang.org/x/net to current version (#757)
Fixes: #756 (CVE resolution)
2022-10-05 13:13:02 +13:00
danj-replicated e80235f0a8 Collect resourcequotas (#729)
Signed-off-by: Dan Jones <danj@replicated.com>

Signed-off-by: Dan Jones <danj@replicated.com>
2022-10-05 12:58:54 +13:00
Diamon Wiggins c7b84ad1e5 Refactor in-clusters collectors to use struct per collector (#670)
refactor in-clusters collectors to use struct per collector
2022-10-03 13:53:05 -04:00
Diamon Wiggins 44ae409081 fix concat spec for cluster secrets (#750) 2022-09-30 08:24:33 +13:00
dependabot[bot]andlnx01 d1318ff4fd Bump k8s.io/cli-runtime from 0.25.0 to 0.25.2 (#737)
Bumps [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) from 0.25.0 to 0.25.2.
- [Release notes](https://github.com/kubernetes/cli-runtime/releases)
- [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.25.0...v0.25.2)

---
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-09-29 17:22:19 +13:00
dependabot[bot]andlnx01 169f3c6ea7 Bump k8s.io/apiserver from 0.25.1 to 0.25.2 (#740)
Bumps [k8s.io/apiserver](https://github.com/kubernetes/apiserver) from 0.25.1 to 0.25.2.
- [Release notes](https://github.com/kubernetes/apiserver/releases)
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.25.1...v0.25.2)

---
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-09-29 17:05:19 +13:00
dependabot[bot]andlnx01 7a7507b5e4 Bump k8s.io/client-go from 0.25.1 to 0.25.2 (#741)
Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.25.1 to 0.25.2.
- [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.1...v0.25.2)

---
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-09-29 16:42:59 +13:00
Nick Meyer d5c7522be5 use push-only token (#730) 2022-09-22 07:28:34 +12:00
f5b6a108a1 Bump k8s.io/apiserver from 0.25.0 to 0.25.1 (#722)
Bumps [k8s.io/apiserver](https://github.com/kubernetes/apiserver) from 0.25.0 to 0.25.1.
- [Release notes](https://github.com/kubernetes/apiserver/releases)
- [Commits](https://github.com/kubernetes/apiserver/compare/v0.25.0...v0.25.1)

---
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>
Co-authored-by: Xav Paice <xavpaice@users.noreply.github.com>
2022-09-20 12:09:59 +12:00
Nick Meyer c4746e766b Add gate to prevent attempt to run on fork (#721) 2022-09-20 11:41:21 +12:00
ada mancini 4fadd471e6 Update CONTRIBUTING.md (#718)
add notes about squashing
2022-09-16 17:48:38 -05:00
Xav Paice c397171b79 Merge pull request #709 from replicatedhq/diamonwiggins/supportbundle-specs-in-secrets-discovery
Load support bundle specs from secrets matching label selector
2022-09-16 13:06:31 +12:00
Nick Meyer a20f9d9408 Merge pull request #714 from replicatedhq/nickmeyer/sc-53201
add FOSSA PR scan; update label requirements [sc-53201][sc-53409]
2022-09-15 10:41:35 -05:00
Nick Meyer ebd49e70df add FOSSA PR scan; update label requirements [sc-53201][sc-53409] 2022-09-15 10:17:34 -05:00
Diamon Wiggins b66145e514 fixing logger statements 2022-09-14 11:56:16 -04:00
Diamon Wiggins be070c98e1 add handling for when no collectors or host collectors are specified 2022-09-14 11:19:44 -04:00
Diamon Wiggins 7eecf6c526 improving error handling 2022-09-14 10:58:08 -04:00
Diamon Wiggins 126346e1fe update run.go 2022-09-14 00:57:33 -04:00
Diamon Wiggins e7fe012f2f handling when no args are provided 2022-09-14 00:36:04 -04:00
Diamon Wiggins ec6ec59303 fixing tests 2022-09-13 23:27:49 -04:00
Diamon Wiggins e53871b4dc adding tests 2022-09-13 23:00:57 -04:00
Diamon Wiggins 93da8b6ac7 change name of flag 2022-09-13 18:42:33 -04:00
Evans Mungai ad4cfc6d9e Merge pull request #704 from replicatedhq/update-issue-template
Update issue template
2022-09-12 10:05:25 +01:00
Diamon Wiggins ad23763d6b remove comment 2022-09-11 18:46:31 -04:00
Diamon Wiggins 7120516b13 fixing description for cli flags 2022-09-11 18:39:55 -04:00
Diamon Wiggins ed34afc51b adding back default collectors 2022-09-11 18:33:35 -04:00
Diamon Wiggins d4e5c0dba4 discovery support bundle specs from secrets matching label selector 2022-09-11 18:28:24 -04:00
dependabot[bot]andlnx01 a50263c725 Bump github.com/lib/pq from 1.10.6 to 1.10.7 (#708)
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.6 to 1.10.7.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.6...v1.10.7)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  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-09-12 09:49:54 +12:00
dependabot[bot]andlnx01 818a9bc9a2 Bump github.com/spf13/viper from 1.12.0 to 1.13.0 (#707)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.12.0...v1.13.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-09-12 09:46:23 +12:00
dependabot[bot]andlnx01 e98497b78b Bump k8s.io/klog/v2 from 2.70.1 to 2.80.1 (#706)
Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.70.1 to 2.80.1.
- [Release notes](https://github.com/kubernetes/klog/releases)
- [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes/klog/compare/v2.70.1...v2.80.1)

---
updated-dependencies:
- dependency-name: k8s.io/klog/v2
  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-09-12 09:35:48 +12:00
dependabot[bot]andlnx01 60c2547cc0 Bump sigs.k8s.io/controller-runtime from 0.12.3 to 0.13.0 (#700)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.12.3 to 0.13.0.
- [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.12.3...v0.13.0)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  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-09-12 09:21:49 +12:00
Evans Mungai 934bf8ed87 Update issue template 2022-09-09 18:03:10 +01:00
Xav Paice 05d3e66de9 Merge pull request #683 from replicatedhq/danj-multiple-inputs
Danj multiple inputs
2022-09-09 15:09:25 +12:00
Dan Jones d70ecf7e5f Add a rough test for bundle concatenation
Signed-off-by: Dan Jones <danj@replicated.com>
2022-09-08 17:16:38 +01:00
Chris Sanders 7403e39210 Merge pull request #703 from replicatedhq/xav/gofmt
run make fmt across repo
2022-09-08 11:12:24 -05:00
Xav Paice 92382e600f run make fmt across repo 2022-09-08 15:10:53 +12:00
Dan Jones b5a56e5239 Remove redundant loops
Signed-off-by: Dan Jones <danj@replicated.com>
2022-09-07 09:14:09 +01:00
Dan Jones b6f8ac211e Format
Signed-off-by: Dan Jones <danj@replicated.com>
2022-09-05 15:54:54 +01:00
Dan Jones 63510fc89d Moved ConcatSpec to supportbundle package and clean up dead code
Signed-off-by: Dan Jones <danj@replicated.com>
2022-09-05 14:12:29 +01:00
Dan Jones 8bdb09ff7d Only accept one clusterResources collector
Signed-off-by: Dan Jones <danj@replicated.com>
2022-08-26 11:36:44 +01:00
Dan Jones b20a8e5321 Add example breakout point for collector merging
Signed-off-by: Dan Jones <danj@replicated.com>
2022-08-26 11:16:07 +01:00
Dan Jones f5652df9da remove old method definition
Signed-off-by: Dan Jones <danj@replicated.com>
2022-08-23 16:26:19 +01:00
Dan Jones b716374070 Had append targets inverted
Signed-off-by: Dan Jones <danj@replicated.com>
2022-08-23 13:13:30 +01:00
Dan Jones 1a637f3c4f Add concatspec method
Signed-off-by: Dan Jones <danj@replicated.com>
2022-08-23 13:03:04 +01:00
Dan Jones 3b3ae4e7bf initial attempt
Signed-off-by: Dan Jones <danj@replicated.com>
2022-08-22 16:57:54 +01:00
160 changed files with 12960 additions and 1941 deletions
+4
View File
@@ -0,0 +1,4 @@
version: 3
project:
id: github.com/replicatedhq/troubleshoot
+17 -1
View File
@@ -1 +1,17 @@
* @replicatedhq/day2
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
# More details are here: https://help.github.com/articles/about-codeowners/
# The '*' pattern is global owners.
# Order is important. The last matching pattern has the most precedence.
# The folders are ordered as follows:
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
## RULES
* @replicatedhq/troubleshoot
*.md @replicatedhq/cre
+3 -3
View File
@@ -7,11 +7,11 @@ labels: 'feature'
**Describe the rationale for the suggested feature.**
Insert a description of the rationale for the new feature here. For example, you could describe a problem that Troubleshoot doesn't address, a limitation, or an idea to improve Troubleshoot.
Insert a description of the rationale for the new feature here. For example, you could describe a problem that Troubleshoot doesn't address, a limitation, or an idea to improve Troubleshoot.
**Describe the feature**
Insert a description of the feature here. Be specific about how it addresses the any problems, limitations, or suggested improvements outlined in the rationale for the feature.
Insert a description of the feature here. Be specific about how it addresses the problem. Mention any limitations, or suggested improvements outlined in the rationale for the feature.
**Describe alternatives you've considered**
@@ -19,4 +19,4 @@ Describe alternative solutions here. Include any workarounds you've considered.
**Additional context**
Add additional context about the feature request. If the change is substantial, consider attaching files to the issue outlining architectural changes, data flows, file formats etc., anything that helps describe the requested change.
Add additional context about the feature request. If the change is substantial, consider attaching files to the issue outlining architectural changes, data flows, file formats etc., anything that helps describe the requested change.
+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"
+58 -27
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,23 +107,23 @@ jobs:
runs-on: ubuntu-latest
needs: compile-preflight
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v3
- uses: replicatedhq/action-k3s@main
id: k3s
with:
version: v1.23.6-k3s1
- name: Download preflight binary
uses: actions/download-artifact@v1
uses: actions/download-artifact@v3
with:
name: preflight
path: bin/
- run: chmod +x bin/preflight
- run: make e2e-test
- run: make preflight-e2e-test
compile-supportbundle:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v1
- uses: actions/setup-go@v3
with:
go-version: '1.19'
- name: setup env
@@ -117,9 +131,9 @@ jobs:
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
shell: bash
- uses: actions/checkout@master
- uses: actions/checkout@v3
- run: make generate support-bundle
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v3
with:
name: support-bundle
path: bin/support-bundle
@@ -128,13 +142,13 @@ jobs:
runs-on: ubuntu-latest
needs: compile-supportbundle
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- uses: replicatedhq/action-k3s@main
id: k3s
with:
version: v1.23.6-k3s1
- name: Download support-bundle binary
uses: actions/download-artifact@v1
uses: actions/download-artifact@v3
with:
name: support-bundle
path: bin/
@@ -143,10 +157,27 @@ jobs:
- run: ./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
- run: ./bin/support-bundle https://kots.io
validate-supportbundle-e2e:
runs-on: ubuntu-latest
needs: compile-supportbundle
steps:
- uses: actions/checkout@v3
- uses: replicatedhq/action-k3s@main
id: k3s
with:
version: v1.23.6-k3s1
- name: Download support bundle binary
uses: actions/download-artifact@v3
with:
name: support-bundle
path: bin/
- run: chmod +x bin/support-bundle
- run: make support-bundle-e2e-test
compile-collect:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v1
- uses: actions/setup-go@v3
with:
go-version: '1.19'
- name: setup env
@@ -154,9 +185,9 @@ jobs:
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
shell: bash
- uses: actions/checkout@master
- uses: actions/checkout@v3
- run: make generate collect
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v3
with:
name: collect
path: bin/collect
@@ -166,17 +197,17 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v') != true
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Unshallow
run: git fetch --prune --unshallow
- uses: actions/setup-go@v1
- uses: actions/setup-go@v3
with:
go-version: "1.19"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v3
with:
version: "v0.183.0"
args: build --rm-dist --snapshot --config deploy/.goreleaser.yaml
@@ -190,7 +221,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- uses: azure/docker-login@v1
with:
@@ -200,7 +231,7 @@ jobs:
- name: Unshallow
run: git fetch --prune --unshallow
- uses: actions/setup-go@v1
- uses: actions/setup-go@v3
with:
go-version: "1.19"
@@ -222,7 +253,7 @@ jobs:
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v3
with:
version: "v0.183.0"
args: release --rm-dist --config deploy/.goreleaser.yaml
@@ -231,10 +262,10 @@ jobs:
- name: Update new preflight version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.38
uses: rajatjindal/krew-release-bot@v0.0.43
with:
krew_template_file: deploy/krew/preflight.yaml
- name: Update new support-bundle version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.38
uses: rajatjindal/krew-release-bot@v0.0.43
with:
krew_template_file: deploy/krew/support-bundle.yaml
+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'
+18
View File
@@ -0,0 +1,18 @@
name: PR license scan
on:
pull_request_target: # this is safe as these scans do not execute provided code
jobs:
fossa-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: refs/pull/${{ github.event.number }}/merge
- name: "Install FOSSA"
uses: replicatedhq/action-fossa/install@main
- name: "Run FOSSA Scan"
uses: replicatedhq/action-fossa/scan@main
with:
api-key: ${{ secrets.FOSSA_API_KEY }}
+3 -7
View File
@@ -12,15 +12,11 @@ on:
- synchronize
jobs:
require_pr_labels:
require-pr-labels:
uses: replicatedhq/reusable-workflows/.github/workflows/pr-enforce-labels.yaml@main
require-bug-labels:
runs-on: ubuntu-latest
steps:
- uses: mheap/github-action-required-labels@v2
with:
mode: exactly
count: 1
labels: "type::feature, type::bug, type::chore, type::tests, type::security, type::docs"
- uses: mheap/github-action-required-labels@v2
if: ${{ github.event.label.name == 'type::bug' }}
with:
+7
View File
@@ -6,6 +6,7 @@
*.so
*.dylib
bin
.DS_Store
# Test binary, build with `go test -c`
*.test
@@ -34,3 +35,9 @@ workspace.*
cosign.key
sbom/
# Ignore local pre-commit config
.pre-commit-config.yaml
# Ignore generated support bundles
*.tar.gz
+12
View File
@@ -0,0 +1,12 @@
# https://golangci-lint.run/usage/configuration/#config-file
run:
allow-parallel-runners: true
timeout: 30s
linters:
enable:
- gocritic
- gocyclo
- gofmt
- gosec
+34 -4
View File
@@ -1,10 +1,16 @@
# Contributing to Troubleshoot
Thank you for your interest in Troubleshoot, we welcome your participation. Please familiarize yourself with our [Code of Conduct](https://github.com/replicatedhq/troubleshoot/blob/master/CODE_OF_CONDUCT.md) prior to contributing. There are a number of ways to participate in Troubleshoot as outlined below:
Thank you for your interest in Troubleshoot, we welcome your participation. Please familiarize yourself with our [Code of Conduct](https://github.com/replicatedhq/troubleshoot/blob/main/CODE_OF_CONDUCT.md) prior to contributing. There are a number of ways to participate in Troubleshoot as outlined below:
# Community
For discussions about developing Troubleshoot, there's an [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot), plus IRC using [Libera](ircs://irc.libera.chat:6697/#troubleshoot) (#troubleshoot).
There are [community meetings](https://calendar.google.com/calendar/u/0?cid=Y19mMGx1aGhiZGtscGllOGo5dWpicXMwNnN1a0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t) on a regular basis, with a shared calendar and [public notes](https://hackmd.io/yZbotEHdTg6TfRZBzb8Tcg)
## Issues
- [Request a New Feature](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=feature&template=feature_enhancement.md) Create an issue to add functionality that addresses a problem or adds an enhancement.
- [Request a New Feature](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=feature&template=feature_enhancement.md) Create an issue to add functionality that addresses a problem or adds an enhancement.
- [Report a Bug](https://github.com/replicatedhq/troubleshoot/issues/new?assignees=&labels=bug&template=bug_report.md) Report a problem or unexpected behaviour with Troubleshoot.
## Design Principles
@@ -15,7 +21,7 @@ When implementing a new feature please review the [design principles](./design/d
To get started we recommend:
1. Go (v1.17 or later)
1. Go (v1.19 or later)
2. A Kubernetes cluster (we recommend <https://k3d.io/>. This requires Docker v20.10.5 or later)
3. Fork and clone the repo to $GOPATH/src/github.com/replicatedhq/
4. Run `make support-bundle preflight` to generate binaries
@@ -24,6 +30,8 @@ To get started we recommend:
> Note: recent versions of Go support easy cross-compilation. For example, to cross-compile a Linux binary from MacOS:
> `GOOS=linux GOARCH=amd64 make support-bundle preflight`
6. Install [golangci-lint] linter and run `make lint` to execute additional code linters.
### Testing
To run the tests locally run the following:
@@ -47,12 +55,34 @@ This is a rough outline of how to prepare a contribution:
- Create a fork of this repo.
- Create a topic branch from where you want to base your work (branched from `main` is a safe choice).
- Make commits of logical units.
- When your changes are ready to merge, squash your history to 1 commit.
- For example, if you want to squash your last 3 commits and write a new commit message:
```
git reset --soft HEAD~3 &&
git commit
```
- If you want to keep the previous commit messages and concatenate them all into a new commit, you can do something like this instead:
```
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
```
- Push your changes to a topic branch in your fork of the repository.
- Submit a pull request to the original repository. It will be reviewed in a timely manner.
### Pull Requests
A pull request should address a single issue, feature or bug. For example, lets say you've written code that fixes two issues. That's great! However, you should submit two small pull requests, one for each issue as opposed to combining them into a single larger pull request. In general the size of the pull request should be kept small in order to make it easy for a reviewer to understand, and to minimize risks from integrating many changes at the same time. For example, if you are working on a large feature you should break it into several smaller PRs by implementing the feature as changes to several packages and submitting a separate pull request for each one.
A pull request should address a single issue, feature or bug. For example, lets say you've written code that fixes two issues. That's great! However, you should submit two small pull requests, one for each issue as opposed to combining them into a single larger pull request. In general the size of the pull request should be kept small in order to make it easy for a reviewer to understand, and to minimize risks from integrating many changes at the same time. For example, if you are working on a large feature you should break it into several smaller PRs by implementing the feature as changes to several packages and submitting a separate pull request for each one. Squash commit history when preparing your PR so it merges as 1 commit.
Code submitted in pull requests must be properly documented, formatted and tested in order to be approved and merged. The following guidelines describe the things a reviewer will look for when they evaluate your pull request. Here's a tip. If your reviewer doesn't understand what the code is doing, they won't approve the pull request. Strive to make code clear and well documented. If possible, request a reviewer that has some context on the PR.
### Commit messages
Commit messages should follow the general guidelines:
- Breaking changes should be highlighted in the heading of the commit message.
- Commits should be clear about their purpose (and a single commit per thing that changed)
- Messages should be descriptive:
- First line, 50 chars or less, as a heading/title that people can find
- Then a paragraph explaining things
- Consider a footer with links to which bugs they fix etc, bearing in mind that Github does some of this magic already
+30 -6
View File
@@ -45,10 +45,14 @@ ffi: fmt vet
test: generate fmt vet
go test ${BUILDFLAGS} ./pkg/... ./cmd/... -coverprofile cover.out
.PHONY: e2e-test
e2e-test:
.PHONY: preflight-e2e-test
preflight-e2e-test:
./test/validate-preflight-e2e.sh
.PHONY: support-bundle-e2e-test
support-bundle-e2e-test:
./test/validate-support-bundle-e2e.sh
.PHONY: support-bundle
support-bundle:
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
@@ -60,10 +64,10 @@ preflight:
.PHONY: analyze
analyze:
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
.PHONY: collect
collect:
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
go build ${BUILDFLAGS} ${LDFLAGS} -o bin/collect github.com/replicatedhq/troubleshoot/cmd/collect
.PHONY: fmt
fmt:
@@ -91,11 +95,23 @@ openapischema: controller-gen
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta1
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta2
check-schemas: generate schemas
@if [ -n "$(shell git status --short)" ]; then \
echo -e "\033[31mThe git repo is dirty :( Ensure all generated files are committed e.g CRD schema files\033[0;m"; \
git status --short; \
exit 1; \
fi
.PHONY: schemas
schemas: fmt vet openapischema
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
./bin/schemagen --output-dir ./schemas
.PHONY: docs
docs: fmt vet
go build ${LDFLAGS} -o bin/docsgen github.com/replicatedhq/troubleshoot/cmd/docsgen
./bin/docsgen
controller-gen:
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0
CONTROLLER_GEN=$(shell which controller-gen)
@@ -155,7 +171,7 @@ generate-sbom: install-spdx-sbom-generator
$(SPDX_GENERATOR) -o ./sbom/spdx
sbom/assets/troubleshoot-sbom.tgz: generate-sbom
tar -czf sbom/assets/troubleshoot-sbom.tgz sbom/spdx/*.spdx
tar -czf sbom/assets/troubleshoot-sbom.tgz sbom/spdx/*.spdx
sbom: sbom/assets/troubleshoot-sbom.tgz
cosign sign-blob -key cosign.key sbom/assets/troubleshoot-sbom.tgz > sbom/assets/troubleshoot-sbom.tgz.sig
@@ -182,4 +198,12 @@ scan:
--exit-code=1 \
--severity="HIGH,CRITICAL" \
--ignore-unfixed \
./
./
.PHONY: lint
lint:
golangci-lint run -c .golangci.yaml
.PHONY: lint-and-fix
lint-and-fix:
golangci-lint run --fix -c .golangci.yaml
+15 -8
View File
@@ -9,15 +9,17 @@ Preflight checks are an easy-to-run set of conformance tests that can be written
To run a sample preflight check from a sample application, install the preflight kubectl plugin:
```shell
```
curl https://krew.sh/preflight | bash
```
and run:
and run, where https://preflight.replicated.com provides an **example** preflight spec:
```shell
```
kubectl preflight https://preflight.replicated.com
```
**NOTE** this is an example. Do **not** use to validate real scenarios.
For more details on creating the custom resource files that drive preflight checks, visit [creating preflight checks](https://troubleshoot.sh/docs/preflight/introduction/).
@@ -26,16 +28,21 @@ A support bundle is an archive that's created in-cluster, by collecting logs and
To collect a sample support bundle, install the troubleshoot kubectl plugin:
```shell
```
curl https://krew.sh/support-bundle | bash
```
and run:
and run, where https://support-bundle.replicated.com provides an **example** support bundle spec:
```shell
```
kubectl support-bundle https://support-bundle.replicated.com
```
**NOTE** this is an example. Do **not** use to validate real scenarios.
For more details on creating the custom resource files that drive support-bundle collection, visit [creating collectors](https://troubleshoot.sh/docs/collect/) and [creating analyzers](https://troubleshoot.sh/docs/analyze/).
And see our other tool [sbctl](https://github.com/replicatedhq/sbctl) that makes it easier to interact with support bundles using `kubectl` commands you already know
# Community
For questions about using Troubleshoot, there's a [Replicated Community](https://help.replicated.com/community) forum, and a [#app-troubleshoot channel in Kubernetes Slack](https://kubernetes.slack.com/channels/app-troubleshoot).
@@ -48,7 +55,7 @@ A signed SBOM that includes Troubleshoot dependencies is included in each relea
The following example illustrates using [cosign](https://github.com/sigstore/cosign) to verify that **troubleshoot-sbom.tgz** has
not been tampered with.
```shell
```
$ cosign verify-blob -key key.pub -signature troubleshoot-sbom.tgz.sig troubleshoot-sbom.tgz
Verified OK
```
```
+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()
}
+3 -12
View File
@@ -6,6 +6,7 @@ import (
"github.com/go-logr/logr"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
@@ -29,24 +30,14 @@ that a cluster meets the requirements to run an application.`,
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
return runPreflights(v, args[0])
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args[0])
},
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(VersionCmd())
cmd.Flags().Bool("interactive", true, "interactive preflights")
cmd.Flags().String("format", "human", "output format, one of human, json, yaml. only used when interactive is set to false")
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
cmd.Flags().Bool("collect-without-permissions", true, "always run preflight checks even if some require permissions that preflight does not have")
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
cmd.Flags().StringP("output", "o", "", "specify the output file path for the preflight checks")
cmd.Flags().Bool("debug", false, "enable debug logging")
preflight.AddFlags(cmd.PersistentFlags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
+3 -2
View File
@@ -204,10 +204,11 @@ func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
uri := widgets.NewParagraph()
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
uri.Border = false
height = estimateNumberOfLines(uri.Text, termWidth/2)
// For long urls that lead to wrapping text, make the rectangle bigger by
// increasing the calculated height by 2
height = estimateNumberOfLines(uri.Text, termWidth/2) + 2
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(uri)
currentTop = currentTop + height + 1
}
}
+8 -16
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"
@@ -17,7 +16,7 @@ import (
func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "support-bundle [url]",
Args: cobra.MinimumNArgs(1),
Args: cobra.MinimumNArgs(0),
Short: "Generate a support bundle",
Long: `A support bundle is an archive of files, output, metrics and state
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
@@ -34,7 +33,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
v := viper.GetViper()
logger.SetQuiet(v.GetBool("quiet"))
return runTroubleshoot(v, args[0])
return runTroubleshoot(v, args)
},
}
@@ -47,6 +46,8 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
cmd.Flags().Bool("collect-without-permissions", true, "always generate a support bundle, even if it some require additional permissions")
cmd.Flags().StringSliceP("selector", "l", []string{"troubleshoot.io/kind=supportbundle-spec"}, "selector to filter on for loading additional support bundle specs found in secrets within the cluster")
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. required when no specs are provided on the command line")
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
cmd.Flags().StringP("output", "o", "", "specify the output file path for the support bundle")
@@ -56,6 +57,10 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
cmd.Flags().MarkHidden("allow-insecure-connections")
// `no-uri` references the `followURI` functionality where we can use an upstream spec when creating a support bundle
// This flag makes sure we can also disable this and fall back to the default spec.
cmd.Flags().Bool("no-uri", false, "When this flag is used, Troubleshoot does not attempt to retrieve the bundle referenced by the uri: field in the spec.`")
viper.BindPFlags(cmd.Flags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
@@ -76,19 +81,6 @@ func initConfig() {
viper.AutomaticEnv()
}
func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
for _, inList := range list {
if collector.ClusterResources != nil && inList.ClusterResources != nil {
return list
}
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
return list
}
}
return append(list, &collector)
}
func writeFile(filename string, contents []byte) error {
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
return err
+142 -38
View File
@@ -22,16 +22,23 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/convert"
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
"github.com/replicatedhq/troubleshoot/pkg/httputil"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/replicatedhq/troubleshoot/pkg/specs"
"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
"github.com/spf13/viper"
spin "github.com/tj/go-spin"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func runTroubleshoot(v *viper.Viper, arg string) error {
func runTroubleshoot(v *viper.Viper, arg []string) error {
if v.GetBool("load-cluster-specs") == false && len(arg) < 1 {
return errors.New("flag load-cluster-specs must be set if no specs are provided on the command line")
}
interactive := v.GetBool("interactive") && isatty.IsTerminal(os.Stdout.Fd())
if interactive {
@@ -68,23 +75,139 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
})
}
collectorContent, err := supportbundle.LoadSupportBundleSpec(arg)
if err != nil {
return errors.Wrap(err, "failed to load collector spec")
}
multidocs := strings.Split(string(collectorContent), "\n---\n")
// we support both raw collector kinds and supportbundle kinds here
supportBundle, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
if err != nil {
return errors.Wrap(err, "failed to parse collector")
}
var mainBundle *troubleshootv1beta2.SupportBundle
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
decode := scheme.Codecs.UniversalDeserializer().Decode
additionalRedactors := &troubleshootv1beta2.Redactor{}
// Defining `v` below will render using `v` in reference to Viper unusable.
// Therefore refactoring `v` to `val` will make sure we can still use it.
for i, val := range arg {
collectorContent, err := supportbundle.LoadSupportBundleSpec(val)
if err != nil {
return errors.Wrap(err, "failed to load support bundle spec")
}
multidocs := strings.Split(string(collectorContent), "\n---\n")
// Referencing `ParseSupportBundle with a secondary arg of `no-uri`
// Will make sure we can enable or disable the use of the `Spec.uri` field for an upstream spec.
// This change will not have an impact on KOTS' usage of `ParseSupportBundle`
// As Kots uses `load.go` directly.
supportBundle, err := supportbundle.ParseSupportBundle([]byte(multidocs[0]), !v.GetBool("no-uri"))
if err != nil {
return errors.Wrap(err, "failed to parse support bundle spec")
}
if i == 0 {
mainBundle = supportBundle
} else {
mainBundle = supportbundle.ConcatSpec(mainBundle, supportBundle)
}
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
if err != nil {
return errors.Wrap(err, "failed to parse redactors from doc")
}
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
}
if v.GetBool("load-cluster-specs") {
labelSelector := strings.Join(v.GetStringSlice("selector"), ",")
parsedSelector, err := labels.Parse(labelSelector)
if err != nil {
return errors.Wrap(err, "unable to parse selector")
}
namespace := ""
if v.GetString("namespace") != "" {
namespace = v.GetString("namespace")
}
config, err := k8sutil.GetRESTConfig()
if err != nil {
return errors.Wrap(err, "failed to convert kube flags to rest config")
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
return errors.Wrap(err, "failed to convert create k8s client")
}
var bundlesFromCluster []string
// Search cluster for Troubleshoot objects in cluster
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), namespace, specs.SupportBundleKey)
if err != nil {
logger.Printf("failed to load support bundle spec from secrets: %s", err)
}
bundlesFromCluster = append(bundlesFromCluster, bundlesFromSecrets...)
bundlesFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), namespace, specs.SupportBundleKey)
if err != nil {
logger.Printf("failed to load support bundle spec from secrets: %s", err)
}
bundlesFromCluster = append(bundlesFromCluster, bundlesFromConfigMaps...)
for _, bundle := range bundlesFromCluster {
multidocs := strings.Split(string(bundle), "\n---\n")
parsedBundleFromSecret, err := supportbundle.ParseSupportBundleFromDoc([]byte(multidocs[0]))
if err != nil {
logger.Printf("failed to parse support bundle spec: %s", err)
continue
}
if mainBundle == nil {
mainBundle = parsedBundleFromSecret
} else {
mainBundle = supportbundle.ConcatSpec(mainBundle, parsedBundleFromSecret)
}
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
if err != nil {
logger.Printf("failed to parse redactors from doc: %s", err)
continue
}
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
}
var redactorsFromCluster []string
// Search cluster for Troubleshoot objects in ConfigMaps
redactorsFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), namespace, specs.RedactorKey)
if err != nil {
logger.Printf("failed to load redactor specs from config maps: %s", err)
}
redactorsFromCluster = append(redactorsFromCluster, redactorsFromSecrets...)
redactorsFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), namespace, specs.RedactorKey)
if err != nil {
logger.Printf("failed to load redactor specs from config maps: %s", err)
}
redactorsFromCluster = append(redactorsFromCluster, redactorsFromConfigMaps...)
for _, redactor := range redactorsFromCluster {
multidocs := strings.Split(string(redactor), "\n---\n")
parsedRedactors, err := supportbundle.ParseRedactorsFromDocs(multidocs)
if err != nil {
logger.Printf("failed to parse redactors from doc: %s", err)
}
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, parsedRedactors...)
}
if mainBundle == nil {
return errors.New("no specs found in cluster")
}
}
if mainBundle == nil {
return errors.New("no support bundle specs provided to run")
} else if mainBundle.Spec.Collectors == nil && mainBundle.Spec.HostCollectors == nil {
return errors.New("no collectors specified in support bundle")
}
for idx, redactor := range v.GetStringSlice("redactors") {
redactorObj, err := supportbundle.GetRedactorFromURI(redactor)
if err != nil {
@@ -96,25 +219,6 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
}
}
for i, additionalDoc := range multidocs {
if i == 0 {
continue
}
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
if err != nil {
return errors.Wrap(err, "failed to convert to v1beta2")
}
obj, _, err := decode(additionalDoc, nil, nil)
if err != nil {
return errors.Wrapf(err, "failed to parse additional doc %d", i)
}
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
if !ok {
continue
}
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
}
var collectorCB func(chan interface{}, string)
progressChan := make(chan interface{}) // non-zero buffer can result in missed messages
finishedCh := make(chan bool, 1)
@@ -193,7 +297,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
c.Println(fmt.Sprintf("\r%s\r", cursor.ClearEntireLine()))
}
response, err := supportbundle.CollectSupportBundleFromSpec(&supportBundle.Spec, additionalRedactors, createOpts)
response, err := supportbundle.CollectSupportBundleFromSpec(&mainBundle.Spec, additionalRedactors, createOpts)
if err != nil {
return errors.Wrap(err, "failed to run collect and analyze process")
}
@@ -202,7 +306,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
close(finishedCh) // this removes the spinner
isFinishedChClosed = true
if err := showInteractiveResults(supportBundle.Name, response.AnalyzerResults); err != nil {
if err := showInteractiveResults(mainBundle.Name, response.AnalyzerResults); err != nil {
interactive = false
}
} else {
@@ -211,7 +315,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
}
if !response.FileUploaded {
if appName := supportBundle.Labels["applicationName"]; appName != "" {
if appName := mainBundle.Labels["applicationName"]; appName != "" {
f := `A support bundle for %s has been created in this directory
named %s. Please upload it on the Troubleshoot page of
the %s Admin Console to begin analysis.`
-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
+66
View File
@@ -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
+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)
+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
+103
View File
@@ -0,0 +1,103 @@
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: example
spec:
collectors:
- clusterInfo: {}
- clusterResources: {}
- data:
name: config/replicas.txt
data: "5"
- runPod:
collectorName: "static-hi"
podSpec:
containers:
- name: static-hi
image: alpine:3
command: ["echo", "hi static!"]
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< 1.20.0"
message: This application requires at least Kubernetes 1.13.0 or later, and recommends 1.15.0.
uri: https://www.kubernetes.io
- warn:
when: "< 1.21.0"
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.
uri: https://kubernetes.io
- pass:
when: ">= 1.22.0"
message: Your cluster meets the recommended and required versions of Kubernetes.
- distribution:
outcomes:
- pass:
when: "== k3s"
message: K3S is a supported distribution
- warn:
message: Unable to determine the distribution of Kubernetes
- textAnalyze:
checkName: Replica Count
fileName: config/replicas.txt
regexGroups: '(?P<Replicas>\d+)'
outcomes:
- fail:
when: "Replicas < 5"
message: That's not enough replicas!
- pass:
message: You have at least 5 replicas
- textAnalyze:
checkName: Said hi!
fileName: /static-hi.log
regex: 'hi static'
outcomes:
- fail:
message: Didn't say hi.
- pass:
message: Said hi!
- nodeResources:
checkName: Must be exactly 1 node in the cluster
outcomes:
- pass:
when: "= 1"
message: This cluster has exactly 1 node
- fail:
message: This application requires exactly 1 node
- nodeResources:
checkName: Must have 1 node with at least 2 cores
filters:
cpuCapacity: "2"
outcomes:
- pass:
when: "= 1"
message: This cluster has exactly 1 node with at least 2 cores
- fail:
message: This application requires exactly 1 node with at least 2 cores
- nodeResources:
checkName: Must have 1 node with 2Gi (available) memory and at least 2 cores (on a single node)
filters:
allocatableMemory: 2Gi
cpuCapacity: "2"
outcomes:
- pass:
when: "= 1"
message: This cluster has exactly 1 node with at least 2Gi available memory and 2 cores
- fail:
message: This application requires exactly 1 node with at least 2Gi available memory and 2 cores
- nodeResources:
checkName: There must be exactly 1 node in the cluster
outcomes:
- pass:
when: "count() = 1"
message: This has exactly 1 node in the cluster
- fail:
message: This application requires exactly 1 node in the cluster
- nodeResources:
checkName: There must be a total of at least 2Gi of memory on all nodes
outcomes:
- fail:
when: "sum(memoryCapacity) < 2Gi"
message: This application requires that 2Gi or more memory be available to the cluster
- pass:
message: This cluster has sufficient memory
@@ -12,6 +12,16 @@ spec:
limits:
maxAge: 720h # 30*24
maxLines: 10000
- logs:
collectorName: all-logs
name: all-logs
- runPod:
collectorName: "static-hi"
podSpec:
containers:
- name: static-hi
image: alpine:3
command: ["echo", "hi static!"]
analyzers:
- clusterVersion:
outcomes:
@@ -30,6 +40,7 @@ spec:
outcomes:
- fail:
message: The Rook CRD was not found in the cluster.
uri: https://kurl.sh/docs/add-ons/rook/some/path/that/does/not/exist/for/the/sake/of/having/a/very/long/url.md
- pass:
message: Rook is installed and available.
- containerRuntime:
@@ -99,3 +110,12 @@ spec:
message: The API deployment has only a single ready replica.
- pass:
message: There are multiple replicas of the API deployment ready.
- textAnalyze:
checkName: Said hi!
fileName: /static-hi.log
regex: 'hi static'
outcomes:
- fail:
message: Didn't say hi.
- pass:
message: Said hi!
+74 -65
View File
@@ -5,7 +5,7 @@ go 1.19
require (
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
github.com/blang/semver v3.5.1+incompatible
github.com/containers/image/v5 v5.19.3
github.com/containers/image/v5 v5.23.1
github.com/docker/distribution v2.8.1+incompatible
github.com/fatih/color v1.13.0
github.com/go-logr/logr v1.2.3
@@ -18,42 +18,54 @@ require (
github.com/gorilla/handlers v1.5.1
github.com/hashicorp/go-getter v1.6.2
github.com/hashicorp/go-multierror v1.1.1
github.com/lib/pq v1.10.6
github.com/jackc/pgx/v5 v5.1.1
github.com/longhorn/go-iscsi-helper v0.0.0-20210330030558-49a327fb024e
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-isatty v0.0.16
github.com/mholt/archiver/v3 v3.5.1
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/pkg/errors v0.9.1
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
github.com/segmentio/ksuid v1.0.4
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.8.0
github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.1
github.com/tj/go-spin v1.1.0
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/sync v0.1.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.25.0
k8s.io/apiextensions-apiserver v0.24.2
k8s.io/apimachinery v0.25.0
k8s.io/apiserver v0.25.0
k8s.io/cli-runtime v0.25.0
k8s.io/client-go v0.25.0
k8s.io/klog/v2 v2.70.1
oras.land/oras-go v1.2.0
sigs.k8s.io/controller-runtime v0.12.3
k8s.io/api v0.25.4
k8s.io/apiextensions-apiserver v0.25.0
k8s.io/apimachinery v0.25.4
k8s.io/apiserver v0.25.4
k8s.io/cli-runtime v0.25.4
k8s.io/client-go v0.25.4
k8s.io/klog/v2 v2.80.1
oras.land/oras-go v1.2.1
sigs.k8s.io/controller-runtime v0.13.1
)
require github.com/emicklei/go-restful/v3 v3.8.0 // indirect
require (
cloud.google.com/go/compute/metadata v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/go-type-adapters v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/mistifyio/go-zfs/v3 v3.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sylabs/sif/v2 v2.8.1 // indirect
)
require (
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/compute v1.6.1 // indirect
cloud.google.com/go v0.104.0 // indirect
cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.14.0 // indirect
cloud.google.com/go/storage v1.23.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
@@ -61,9 +73,9 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/Microsoft/hcsshim v0.9.3 // indirect
github.com/BurntSushi/toml v1.2.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Microsoft/hcsshim v0.9.4 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/andybalholm/brotli v1.0.1 // indirect
@@ -72,27 +84,26 @@ require (
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/containerd/cgroups v1.0.3 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/containerd/containerd v1.6.6 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.11.0 // indirect
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b // indirect
github.com/containers/ocicrypt v1.1.4 // indirect
github.com/containers/storage v1.38.3 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a // indirect
github.com/containers/ocicrypt v1.1.5 // indirect
github.com/containers/storage v1.43.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/docker v20.10.18+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
@@ -106,10 +117,10 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -117,12 +128,12 @@ require (
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/magiconair/properties v1.8.6 // indirect
@@ -131,14 +142,13 @@ require (
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/sys/mountinfo v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -148,25 +158,24 @@ require (
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
github.com/nwaples/rardecode v1.1.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.3 // indirect
github.com/opencontainers/runc v1.1.4 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
github.com/opencontainers/selinux v1.10.1 // indirect
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 // indirect
github.com/opencontainers/selinux v1.10.2 // indirect
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/sylabs/sif/v2 v2.3.1 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
@@ -178,25 +187,25 @@ require (
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/api v0.81.0 // indirect
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.102.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
periph.io/x/host/v3 v3.7.2
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
periph.io/x/host/v3 v3.8.0
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
+181 -381
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")
}
+3 -3
View File
@@ -1,11 +1,11 @@
package analyzer
import (
"strconv"
"strings"
"fmt"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"strconv"
"strings"
)
func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey string, iconURI string, readyReplicas int, exists bool, resourceType string) (*AnalyzeResult, error) {
@@ -36,7 +36,7 @@ func commonStatus(outcomes []*troubleshootv1beta2.Outcome, name string, iconKey
return result, nil
}
if outcome.Fail.When == "absent" {
if outcome.Fail.When == "absent" {
if exists == false {
result.IsFail = true
result.Message = outcome.Fail.Message
+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")
}
+10 -10
View File
@@ -21,7 +21,7 @@ func Test_deploymentStatus(t *testing.T) {
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "absent",
When: "absent",
Message: "fail",
},
},
@@ -33,14 +33,14 @@ func Test_deploymentStatus(t *testing.T) {
},
},
Namespace: "default",
Name: "nonexistant-deployment",
Name: "nonexistant-deployment",
},
expectResult: []*AnalyzeResult{
{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "nonexistant-deployment Status",
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "nonexistant-deployment Status",
Message: "fail",
IconKey: "kubernetes_deployment_status",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
@@ -58,7 +58,7 @@ func Test_deploymentStatus(t *testing.T) {
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "absent",
When: "absent",
Message: "fail",
},
},
@@ -100,7 +100,7 @@ func Test_deploymentStatus(t *testing.T) {
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "absent",
When: "absent",
Message: "fail",
},
},
@@ -142,7 +142,7 @@ func Test_deploymentStatus(t *testing.T) {
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "absent",
When: "absent",
Message: "fail",
},
},
@@ -221,7 +221,7 @@ func Test_deploymentStatus(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
getFiles := func(n string) (map[string][]byte, error) {
getFiles := func(n string, _ []string) (map[string][]byte, error) {
if file, ok := test.files[n]; ok {
return map[string][]byte{n: file}, nil
}
+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
-1
View File
@@ -40,7 +40,6 @@ func (a *AnalyzeHostKernelModules) IsExcluded() (bool, error) {
// - a default fail will only trigger if there are no matching non-default pass outcomes.
// - a default warn will only trigger if there are no matching non-default pass or fail outcomes.
// - a default pass will only trigger if there are no matching non-default fail outcomes.
//
func (a *AnalyzeHostKernelModules) Analyze(getCollectedFileContents func(string) ([]byte, error)) ([]*AnalyzeResult, error) {
hostAnalyzer := a.hostAnalyzer
contents, err := getCollectedFileContents(collect.HostKernelModulesPath)
+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
}
+9 -8
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)
}
@@ -133,8 +134,8 @@ func longhorn(analyzer *troubleshootv1beta2.LonghornAnalyze, getCollectedFileCon
// Check Volume replicas
if volume.Spec.NumberOfReplicas < 2 {
result := &AnalyzeResult{
Title: "Longhorn volume with low replicas",
IsWarn: true,
Title: "Longhorn volume with low replicas",
IsWarn: true,
Message: fmt.Sprintf("Longhorn volume %s has less than two replicas, this could lead to issues with volume availability", volume.Name),
}
results = append(results, result)
+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")
}
+8 -8
View File
@@ -21,7 +21,7 @@ func Test_analyzeStatefulsetStatus(t *testing.T) {
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "absent",
When: "absent",
Message: "fail",
},
},
@@ -33,21 +33,21 @@ func Test_analyzeStatefulsetStatus(t *testing.T) {
},
},
Namespace: "default",
Name: "nonexistant",
Name: "nonexistant",
},
expectResult: []*AnalyzeResult{
{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "nonexistant Status",
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "nonexistant Status",
Message: "fail",
IconKey: "kubernetes_statefulset_status",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/statefulset-status.svg?w=23&h=14",
},
},
files: map[string][]byte{
"cluster-resources/statefulsets/default.json": []byte(defaultStatefulSets),
"cluster-resources/statefulsets/default.json": []byte(defaultStatefulSets),
},
},
{
@@ -105,7 +105,7 @@ func Test_analyzeStatefulsetStatus(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
getFiles := func(n string) (map[string][]byte, error) {
getFiles := func(n string, _ []string) (map[string][]byte, error) {
if file, ok := test.files[n]; ok {
return map[string][]byte{n: file}, nil
}
+94 -3
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.
@@ -38,14 +38,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
// of clientsets, like in:
//
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
//
// kclientset, _ := kubernetes.NewForConfig(c)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
// kclientset, _ := kubernetes.NewForConfig(c)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
//
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
@@ -38,14 +38,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
// of clientsets, like in:
//
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
// import (
// "k8s.io/client-go/kubernetes"
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
// )
//
// kclientset, _ := kubernetes.NewForConfig(c)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
// kclientset, _ := kubernetes.NewForConfig(c)
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
//
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
+34 -11
View File
@@ -11,6 +11,7 @@ import (
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
const (
@@ -105,23 +106,41 @@ var CephCommands = []CephCommand{
},
}
func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (CollectorResult, error) {
type CollectCeph struct {
Collector *troubleshootv1beta2.Ceph
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectCeph) Title() string {
return getCollectorName(c)
}
func (c *CollectCeph) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectCeph) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
ctx := context.TODO()
if cephCollector.Namespace == "" {
cephCollector.Namespace = DefaultCephNamespace
if c.Namespace == "" {
c.Namespace = DefaultCephNamespace
}
pod, err := findRookCephToolsPod(ctx, c, cephCollector.Namespace)
pod, err := findRookCephToolsPod(ctx, c, c.Namespace)
if err != nil {
return nil, err
}
output := NewResult()
for _, command := range CephCommands {
err := cephCommandExec(ctx, c, cephCollector, pod, command, output)
err := cephCommandExec(ctx, progressChan, c, c.Collector, pod, command, output)
if err != nil {
pathPrefix := GetCephCollectorFilepath(cephCollector.CollectorName, cephCollector.Namespace)
pathPrefix := GetCephCollectorFilepath(c.Collector.CollectorName, c.Namespace)
dstFileName := path.Join(pathPrefix, fmt.Sprintf("%s.%s-error", command.ID, command.Format))
output.SaveResult(c.BundlePath, dstFileName, strings.NewReader(err.Error()))
}
@@ -130,26 +149,30 @@ func Ceph(c *Collector, cephCollector *troubleshootv1beta2.Ceph) (CollectorResul
return output, nil
}
func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand, output CollectorResult) error {
func cephCommandExec(ctx context.Context, progressChan chan<- interface{}, c *CollectCeph, cephCollector *troubleshootv1beta2.Ceph, pod *corev1.Pod, command CephCommand, output CollectorResult) error {
timeout := cephCollector.Timeout
if timeout == "" {
timeout = command.DefaultTimeout
}
execCollector := &troubleshootv1beta2.Exec{
execSpec := &troubleshootv1beta2.Exec{
Selector: labelsToSelector(pod.Labels),
Namespace: pod.Namespace,
Command: command.Command,
Args: command.Args,
Timeout: timeout,
}
results, err := Exec(c, execCollector)
rbacErrors := c.GetRBACErrors()
execCollector := &CollectExec{execSpec, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
results, err := execCollector.Collect(progressChan)
if err != nil {
return errors.Wrap(err, "failed to exec command")
}
pathPrefix := GetCephCollectorFilepath(cephCollector.CollectorName, cephCollector.Namespace)
for srcFilename, _ := range results {
for srcFilename := range results {
var dstFileName string
switch {
case strings.HasSuffix(srcFilename, "-stdout.txt"):
@@ -171,7 +194,7 @@ func cephCommandExec(ctx context.Context, c *Collector, cephCollector *troublesh
return nil
}
func findRookCephToolsPod(ctx context.Context, c *Collector, namespace string) (*corev1.Pod, error) {
func findRookCephToolsPod(ctx context.Context, c *CollectCeph, namespace string) (*corev1.Pod, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to create kubernetes client")
+19 -1
View File
@@ -6,8 +6,10 @@ import (
"path/filepath"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type ClusterVersion struct {
@@ -15,7 +17,23 @@ type ClusterVersion struct {
String string `json:"string"`
}
func ClusterInfo(c *Collector) (CollectorResult, error) {
type CollectClusterInfo struct {
Collector *troubleshootv1beta2.ClusterInfo
BundlePath string
Namespace string
ClientConfig *rest.Config
RBACErrors
}
func (c *CollectClusterInfo) Title() string {
return getCollectorName(c)
}
func (c *CollectClusterInfo) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectClusterInfo) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return nil, errors.Wrap(err, "Failed to create kubernetes clientset")
+322 -13
View File
@@ -34,7 +34,28 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/k8sutil/discovery"
)
func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1beta2.ClusterResources) (CollectorResult, error) {
type CollectClusterResources struct {
Collector *troubleshootv1beta2.ClusterResources
BundlePath string
Namespace string
ClientConfig *rest.Config
RBACErrors
}
func (c *CollectClusterResources) Title() string {
return getCollectorName(c)
}
func (c *CollectClusterResources) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectClusterResources) Merge(allCollectors []Collector) ([]Collector, error) {
result := append(allCollectors, c)
return result, nil
}
func (c *CollectClusterResources) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return nil, err
@@ -51,9 +72,9 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
// namespaces
nsListedFromCluster := false
var namespaceNames []string
if len(clusterResourcesCollector.Namespaces) > 0 {
namespaces, namespaceErrors := getNamespaces(ctx, client, clusterResourcesCollector.Namespaces)
namespaceNames = clusterResourcesCollector.Namespaces
if len(c.Collector.Namespaces) > 0 {
namespaces, namespaceErrors := getNamespaces(ctx, client, c.Collector.Namespaces)
namespaceNames = c.Collector.Namespaces
output.SaveResult(c.BundlePath, "cluster-resources/namespaces.json", bytes.NewBuffer(namespaces))
output.SaveResult(c.BundlePath, "cluster-resources/namespaces-errors.json", marshalErrors(namespaceErrors))
} else if c.Namespace != "" {
@@ -82,7 +103,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
}
output.SaveResult(c.BundlePath, "cluster-resources/auth-cani-list-errors.json", marshalErrors(reviewStatusErrors))
if nsListedFromCluster && !clusterResourcesCollector.IgnoreRBAC {
if nsListedFromCluster && !c.Collector.IgnoreRBAC {
filteredNamespaces := []string{}
for _, ns := range namespaceNames {
status := reviewStatuses[ns]
@@ -110,7 +131,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
limits := &troubleshootv1beta2.LogLimits{
MaxLines: 500,
}
podLogs, err := savePodLogs(ctx, logsRoot, client, pod, "", container.Name, limits, false)
podLogs, err := savePodLogs(ctx, logsRoot, client, &pod, "", container.Name, limits, false)
if err != nil {
errPath := filepath.Join("cluster-resources", "pods", "logs", pod.Namespace, pod.Name, fmt.Sprintf("%s-logs-errors.log", container.Name))
output.SaveResult(c.BundlePath, errPath, bytes.NewBuffer([]byte(err.Error())))
@@ -121,7 +142,7 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
}
}
// pod disruption budgets (for all existing namespaces)
// pod disruption budgets
PodDisruptionBudgets, pdbError := getPodDisruptionBudgets(ctx, client, namespaceNames)
for k, v := range PodDisruptionBudgets {
@@ -185,6 +206,12 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
}
output.SaveResult(c.BundlePath, "cluster-resources/network-policy-errors.json", marshalErrors(networkPolicyErrors))
// resource quotas
resourceQuota, resourceQuotaErrors := resourceQuota(ctx, client, namespaceNames)
for k, v := range resourceQuota {
output.SaveResult(c.BundlePath, path.Join("cluster-resources/resource-quotas", k), bytes.NewBuffer(v))
}
output.SaveResult(c.BundlePath, "cluster-resources/resource-quota-errors.json", marshalErrors(resourceQuotaErrors))
// storage classes
storageClasses, storageErrors := storageClasses(ctx, client)
@@ -246,6 +273,30 @@ func ClusterResources(c *Collector, clusterResourcesCollector *troubleshootv1bet
}
output.SaveResult(c.BundlePath, "cluster-resources/pvcs-errors.json", marshalErrors(pvcsErrors))
//Roles
roles, rolesErrors := roles(ctx, client, namespaceNames)
for k, v := range roles {
output.SaveResult(c.BundlePath, path.Join("cluster-resources/roles", k), bytes.NewBuffer(v))
}
output.SaveResult(c.BundlePath, "cluster-resources/roles-errors.json", marshalErrors(rolesErrors))
//Role Bindings
roleBindings, roleBindingsErrors := roleBindings(ctx, client, namespaceNames)
for k, v := range roleBindings {
output.SaveResult(c.BundlePath, path.Join("cluster-resources/rolebindings", k), bytes.NewBuffer(v))
}
output.SaveResult(c.BundlePath, "cluster-resources/rolebindings-errors.json", marshalErrors(roleBindingsErrors))
//Cluster Roles
clusterRoles, clusterRolesErrors := clusterRoles(ctx, client)
output.SaveResult(c.BundlePath, "cluster-resources/clusterroles.json", bytes.NewBuffer(clusterRoles))
output.SaveResult(c.BundlePath, "cluster-resources/clusterroles-errors.json", marshalErrors(clusterRolesErrors))
//Cluster Role Bindings
clusterRoleBindings, clusterRoleBindingsErrors := clusterRoleBindings(ctx, client)
output.SaveResult(c.BundlePath, "cluster-resources/clusterRoleBindings.json", bytes.NewBuffer(clusterRoleBindings))
output.SaveResult(c.BundlePath, "cluster-resources/clusterRoleBindings-errors.json", marshalErrors(clusterRoleBindingsErrors))
return output, nil
}
@@ -359,6 +410,55 @@ func pods(ctx context.Context, client *kubernetes.Clientset, namespaces []string
}
func getPodDisruptionBudgets(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
ok, err := discovery.HasResource(client, "policy.k8s.io/v1", "PodDisruptionBudgets")
if err != nil {
return nil, map[string]string{"": err.Error()}
}
if ok {
return pdbV1(ctx, client, namespaces)
}
return pdbV1beta(ctx, client, namespaces)
}
// TODO: The below function (`pdbV1`) needs to be DRY'd and moved into the main `getPodDisruptionBudgets` function.
func pdbV1(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
pdbByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
for _, namespace := range namespaces {
PodDisruptionBudgets, err := client.PolicyV1().PodDisruptionBudgets(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
gvk, err := apiutil.GVKForObject(PodDisruptionBudgets, scheme.Scheme)
if err == nil {
PodDisruptionBudgets.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range PodDisruptionBudgets.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
PodDisruptionBudgets.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(PodDisruptionBudgets, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
pdbByNamespace[namespace+".json"] = b
}
return pdbByNamespace, errorsByNamespace
}
// This block/function can remain as is
func pdbV1beta(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
pdbByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
@@ -374,6 +474,13 @@ func getPodDisruptionBudgets(ctx context.Context, client *kubernetes.Clientset,
PodDisruptionBudgets.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range PodDisruptionBudgets.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
PodDisruptionBudgets.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(PodDisruptionBudgets, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
@@ -562,29 +669,76 @@ func jobs(ctx context.Context, client *kubernetes.Clientset, namespaces []string
}
func cronJobs(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
ok, err := discovery.HasResource(client, "batch.k8s.io/v1", "CronJobs")
if err != nil {
return nil, map[string]string{"": err.Error()}
}
if ok {
return cronJobsV1(ctx, client, namespaces)
}
return cronJobsV1beta(ctx, client, namespaces)
}
func cronJobsV1(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
cronJobsByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
for _, namespace := range namespaces {
nsCronJobs, err := client.BatchV1beta1().CronJobs(namespace).List(ctx, metav1.ListOptions{})
cronJobs, err := client.BatchV1().CronJobs(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
gvk, err := apiutil.GVKForObject(nsCronJobs, scheme.Scheme)
gvk, err := apiutil.GVKForObject(cronJobs, scheme.Scheme)
if err == nil {
nsCronJobs.GetObjectKind().SetGroupVersionKind(gvk)
cronJobs.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range nsCronJobs.Items {
for i, o := range cronJobs.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
nsCronJobs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
cronJobs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(nsCronJobs, "", " ")
b, err := json.MarshalIndent(cronJobs, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
cronJobsByNamespace[namespace+".json"] = b
}
return cronJobsByNamespace, errorsByNamespace
}
func cronJobsV1beta(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
cronJobsByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
for _, namespace := range namespaces {
cronJobs, err := client.BatchV1beta1().CronJobs(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
gvk, err := apiutil.GVKForObject(cronJobs, scheme.Scheme)
if err == nil {
cronJobs.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range cronJobs.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
cronJobs.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(cronJobs, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
@@ -713,6 +867,41 @@ func networkPolicy(ctx context.Context, client *kubernetes.Clientset, namespaces
return networkPolicyByNamespace, errorsByNamespace
}
func resourceQuota(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
resourceQuotaByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
for _, namespace := range namespaces {
resourceQuota, err := client.CoreV1().ResourceQuotas(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
gvk, err := apiutil.GVKForObject(resourceQuota, scheme.Scheme)
if err == nil {
resourceQuota.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range resourceQuota.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
resourceQuota.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(resourceQuota, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
resourceQuotaByNamespace[namespace+".json"] = b
}
return resourceQuotaByNamespace, errorsByNamespace
}
func storageClasses(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
ok, err := discovery.HasResource(client, "storage.k8s.io/v1", "StorageClass")
if err != nil {
@@ -1424,3 +1613,123 @@ func pvcs(ctx context.Context, client *kubernetes.Clientset, namespaces []string
return pvcsByNamespace, errorsByNamespace
}
func roles(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
rolesByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
for _, namespace := range namespaces {
roles, err := client.RbacV1().Roles(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
gvk, err := apiutil.GVKForObject(roles, scheme.Scheme)
if err == nil {
roles.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range roles.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
roles.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(roles, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
rolesByNamespace[namespace+".json"] = b
}
return rolesByNamespace, errorsByNamespace
}
func roleBindings(ctx context.Context, client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
roleBindingsByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
for _, namespace := range namespaces {
roleBindings, err := client.RbacV1().RoleBindings(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
gvk, err := apiutil.GVKForObject(roleBindings, scheme.Scheme)
if err == nil {
roleBindings.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range roleBindings.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
roleBindings.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(roleBindings, "", " ")
if err != nil {
errorsByNamespace[namespace] = err.Error()
continue
}
roleBindingsByNamespace[namespace+".json"] = b
}
return roleBindingsByNamespace, errorsByNamespace
}
func clusterRoles(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
clusterRoles, err := client.RbacV1().ClusterRoles().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
gvk, err := apiutil.GVKForObject(clusterRoles, scheme.Scheme)
if err == nil {
clusterRoles.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range clusterRoles.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
clusterRoles.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(clusterRoles, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
func clusterRoleBindings(ctx context.Context, client *kubernetes.Clientset) ([]byte, []string) {
clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
gvk, err := apiutil.GVKForObject(clusterRoleBindings, scheme.Scheme)
if err == nil {
clusterRoleBindings.GetObjectKind().SetGroupVersionKind(gvk)
}
for i, o := range clusterRoleBindings.Items {
gvk, err := apiutil.GVKForObject(&o, scheme.Scheme)
if err == nil {
clusterRoleBindings.Items[i].GetObjectKind().SetGroupVersionKind(gvk)
}
}
b, err := json.MarshalIndent(clusterRoleBindings, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
+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)
})
}
}
+32 -10
View File
@@ -5,19 +5,41 @@ import (
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest"
)
func Collectd(ctx context.Context, c *Collector, collector *troubleshootv1beta2.Collectd, namespace string, clientConfig *restclient.Config, client kubernetes.Interface) (CollectorResult, error) {
type CollectCollectd struct {
Collector *troubleshootv1beta2.Collectd
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectCollectd) Title() string {
return getCollectorName(c)
}
func (c *CollectCollectd) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectCollectd) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
copyFromHost := &troubleshootv1beta2.CopyFromHost{
CollectorMeta: collector.CollectorMeta,
CollectorMeta: c.Collector.CollectorMeta,
Name: "collectd/rrd",
Namespace: collector.Namespace,
Image: collector.Image,
ImagePullPolicy: collector.ImagePullPolicy,
ImagePullSecret: collector.ImagePullSecret,
Timeout: collector.Timeout,
HostPath: collector.HostPath,
Namespace: c.Collector.Namespace,
Image: c.Collector.Image,
ImagePullPolicy: c.Collector.ImagePullPolicy,
ImagePullSecret: c.Collector.ImagePullSecret,
Timeout: c.Collector.Timeout,
HostPath: c.Collector.HostPath,
}
return CopyFromHost(ctx, c, copyFromHost, namespace, clientConfig, client)
rbacErrors := c.GetRBACErrors()
copyFromHostCollector := &CollectCopyFromHost{copyFromHost, c.BundlePath, c.Namespace, c.ClientConfig, c.Client, c.Context, rbacErrors}
return copyFromHostCollector.Collect(progressChan)
}
+140 -300
View File
@@ -2,29 +2,33 @@ package collect
import (
"context"
"runtime"
"fmt"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/multitype"
authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type Collector struct {
Collect *troubleshootv1beta2.Collect
Redact bool
RBACErrors []error
ClientConfig *rest.Config
Namespace string
BundlePath string
type Collector interface {
Title() string
IsExcluded() (bool, error)
GetRBACErrors() []error
HasRBACErrors() bool
CheckRBAC(ctx context.Context, c Collector, collector *troubleshootv1beta2.Collect, clientConfig *rest.Config, namespace string) error
Collect(progressChan chan<- interface{}) (CollectorResult, error)
}
type Collectors []*Collector
type MergeableCollector interface {
Collector
Merge(allCollectors []Collector) ([]Collector, error)
}
//type Collectors []*Collector
func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
if excludeVal == nil {
@@ -47,305 +51,141 @@ func isExcluded(excludeVal *multitype.BoolOrString) (bool, error) {
return parsed, nil
}
// checks if a given collector has a spec with 'exclude' that evaluates to true.
func (c *Collector) IsExcluded() bool {
if c.Collect.ClusterInfo != nil {
isExcludedResult, err := isExcluded(c.Collect.ClusterInfo.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.ClusterResources != nil {
isExcludedResult, err := isExcluded(c.Collect.ClusterResources.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Secret != nil {
isExcludedResult, err := isExcluded(c.Collect.Secret.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.ConfigMap != nil {
isExcludedResult, err := isExcluded(c.Collect.ConfigMap.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Logs != nil {
isExcludedResult, err := isExcluded(c.Collect.Logs.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Run != nil {
isExcludedResult, err := isExcluded(c.Collect.Run.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.RunPod != nil {
isExcludedResult, err := isExcluded(c.Collect.RunPod.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Exec != nil {
isExcludedResult, err := isExcluded(c.Collect.Exec.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Data != nil {
isExcludedResult, err := isExcluded(c.Collect.Data.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Copy != nil {
isExcludedResult, err := isExcluded(c.Collect.Copy.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.CopyFromHost != nil {
isExcludedResult, err := isExcluded(c.Collect.CopyFromHost.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.HTTP != nil {
isExcludedResult, err := isExcluded(c.Collect.HTTP.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Postgres != nil {
isExcludedResult, err := isExcluded(c.Collect.Postgres.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Mysql != nil {
isExcludedResult, err := isExcluded(c.Collect.Mysql.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Redis != nil {
isExcludedResult, err := isExcluded(c.Collect.Redis.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Collectd != nil {
// TODO: see if redaction breaks these
isExcludedResult, err := isExcluded(c.Collect.Collectd.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Ceph != nil {
isExcludedResult, err := isExcluded(c.Collect.Ceph.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Longhorn != nil {
isExcludedResult, err := isExcluded(c.Collect.Longhorn.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
} else if c.Collect.Sysctl != nil {
isExcludedResult, err := isExcluded(c.Collect.Sysctl.Exclude)
if err != nil {
return true
}
if isExcludedResult {
return true
}
}
return false
}
func (c *Collector) RunCollectorSync(clientConfig *rest.Config, client kubernetes.Interface, globalRedactors []*troubleshootv1beta2.Redact) (result CollectorResult, err error) {
defer func() {
if r := recover(); r != nil {
_, file, line, _ := runtime.Caller(4)
err = errors.Errorf("recovered from panic at \"%s:%d\": %v", file, line, r)
}
}()
if c.IsExcluded() {
return
}
func GetCollector(collector *troubleshootv1beta2.Collect, bundlePath string, namespace string, clientConfig *rest.Config, client kubernetes.Interface, sinceTime *time.Time) (interface{}, bool) {
ctx := context.TODO()
if c.Collect.ClusterInfo != nil {
result, err = ClusterInfo(c)
} else if c.Collect.ClusterResources != nil {
result, err = ClusterResources(c, c.Collect.ClusterResources)
} else if c.Collect.Secret != nil {
result, err = Secret(ctx, c, c.Collect.Secret, client)
} else if c.Collect.ConfigMap != nil {
result, err = ConfigMap(ctx, c, c.Collect.ConfigMap, client)
} else if c.Collect.Logs != nil {
result, err = Logs(c, c.Collect.Logs)
} else if c.Collect.Run != nil {
result, err = Run(c, c.Collect.Run)
} else if c.Collect.RunPod != nil {
result, err = RunPod(c, c.Collect.RunPod)
} else if c.Collect.Exec != nil {
result, err = Exec(c, c.Collect.Exec)
} else if c.Collect.Data != nil {
result, err = Data(c, c.Collect.Data)
} else if c.Collect.Copy != nil {
result, err = Copy(c, c.Collect.Copy)
} else if c.Collect.CopyFromHost != nil {
namespace := c.Collect.CopyFromHost.Namespace
if namespace == "" && c.Namespace == "" {
kubeconfig := k8sutil.GetKubeconfig()
namespace, _, _ = kubeconfig.Namespace()
} else if namespace == "" {
namespace = c.Namespace
}
result, err = CopyFromHost(ctx, c, c.Collect.CopyFromHost, namespace, clientConfig, client)
} else if c.Collect.HTTP != nil {
result, err = HTTP(c, c.Collect.HTTP)
} else if c.Collect.Postgres != nil {
result, err = Postgres(c, c.Collect.Postgres)
} else if c.Collect.Mysql != nil {
result, err = Mysql(c, c.Collect.Mysql)
} else if c.Collect.Redis != nil {
result, err = Redis(c, c.Collect.Redis)
} else if c.Collect.Collectd != nil {
// TODO: see if redaction breaks these
namespace := c.Collect.Collectd.Namespace
if namespace == "" && c.Namespace == "" {
kubeconfig := k8sutil.GetKubeconfig()
namespace, _, _ = kubeconfig.Namespace()
} else if namespace == "" {
namespace = c.Namespace
}
result, err = Collectd(ctx, c, c.Collect.Collectd, namespace, clientConfig, client)
} else if c.Collect.Ceph != nil {
result, err = Ceph(c, c.Collect.Ceph)
} else if c.Collect.Longhorn != nil {
result, err = Longhorn(c, c.Collect.Longhorn)
} else if c.Collect.RegistryImages != nil {
result, err = Registry(c, c.Collect.RegistryImages)
} else if c.Collect.Sysctl != nil {
if c.Collect.Sysctl.Namespace == "" {
c.Collect.Sysctl.Namespace = c.Namespace
}
if c.Collect.Sysctl.Namespace == "" {
kubeconfig := k8sutil.GetKubeconfig()
namespace, _, _ := kubeconfig.Namespace()
c.Collect.Sysctl.Namespace = namespace
}
result, err = Sysctl(ctx, c, client, c.Collect.Sysctl)
} else {
err = errors.New("no spec found to run")
return
}
if err != nil {
return
}
var RBACErrors []error
if c.Redact {
err = RedactResult(c.BundlePath, result, globalRedactors)
err = errors.Wrap(err, "failed to redact")
switch {
case collector.ClusterInfo != nil:
return &CollectClusterInfo{collector.ClusterInfo, bundlePath, namespace, clientConfig, RBACErrors}, true
case collector.ClusterResources != nil:
return &CollectClusterResources{collector.ClusterResources, bundlePath, namespace, clientConfig, RBACErrors}, true
case collector.Secret != nil:
return &CollectSecret{collector.Secret, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.ConfigMap != nil:
return &CollectConfigMap{collector.ConfigMap, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Logs != nil:
return &CollectLogs{collector.Logs, bundlePath, namespace, clientConfig, client, ctx, sinceTime, RBACErrors}, true
case collector.Run != nil:
return &CollectRun{collector.Run, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.RunPod != nil:
return &CollectRunPod{collector.RunPod, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Exec != nil:
return &CollectExec{collector.Exec, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Data != nil:
return &CollectData{collector.Data, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Copy != nil:
return &CollectCopy{collector.Copy, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.CopyFromHost != nil:
return &CollectCopyFromHost{collector.CopyFromHost, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.HTTP != nil:
return &CollectHTTP{collector.HTTP, bundlePath, namespace, clientConfig, client, RBACErrors}, true
case collector.Postgres != nil:
return &CollectPostgres{collector.Postgres, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Mysql != nil:
return &CollectMysql{collector.Mysql, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Redis != nil:
return &CollectRedis{collector.Redis, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Collectd != nil:
return &CollectCollectd{collector.Collectd, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Ceph != nil:
return &CollectCeph{collector.Ceph, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Longhorn != nil:
return &CollectLonghorn{collector.Longhorn, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.RegistryImages != nil:
return &CollectRegistry{collector.RegistryImages, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
case collector.Sysctl != nil:
return &CollectSysctl{collector.Sysctl, bundlePath, namespace, clientConfig, client, ctx, RBACErrors}, true
default:
return nil, false
}
return
}
func (c *Collector) GetDisplayName() string {
return c.Collect.GetName()
func collectorTitleOrDefault(meta troubleshootv1beta2.CollectorMeta, defaultTitle string) string {
if meta.CollectorName != "" {
return meta.CollectorName
}
return defaultTitle
}
func (c *Collector) CheckRBAC(ctx context.Context) error {
if c.IsExcluded() {
return nil // excluded collectors require no permissions
func getCollectorName(c interface{}) string {
var collector, name, selector string
switch v := c.(type) {
case *CollectClusterInfo:
collector = "cluster-info"
case *CollectClusterResources:
collector = "cluster-resources"
case *CollectSecret:
collector = "secret"
name = v.Collector.CollectorName
selector = strings.Join(v.Collector.Selector, ",")
case *CollectConfigMap:
collector = "configmap"
name = v.Collector.CollectorName
selector = strings.Join(v.Collector.Selector, ",")
case *CollectLogs:
collector = "logs"
name = v.Collector.CollectorName
selector = strings.Join(v.Collector.Selector, ",")
case *CollectRun:
collector = "run"
name = v.Collector.CollectorName
case *CollectRunPod:
collector = "run-pod"
name = v.Collector.CollectorName
case *CollectExec:
collector = "exec"
name = v.Collector.CollectorName
selector = strings.Join(v.Collector.Selector, ",")
case *CollectData:
collector = "data"
name = v.Collector.CollectorName
case *CollectCopy:
collector = "copy"
name = v.Collector.CollectorName
selector = strings.Join(v.Collector.Selector, ",")
case *CollectCopyFromHost:
collector = "copy-from-host"
name = v.Collector.CollectorName
case *CollectHTTP:
collector = "http"
name = v.Collector.CollectorName
case *CollectPostgres:
collector = "postgres"
name = v.Collector.CollectorName
case *CollectMysql:
collector = "mysql"
name = v.Collector.CollectorName
case *CollectRedis:
collector = "redis"
name = v.Collector.CollectorName
case *CollectCollectd:
collector = "collectd"
name = v.Collector.CollectorName
case *CollectCeph:
collector = "ceph"
name = v.Collector.CollectorName
case *CollectLonghorn:
collector = "longhorn"
name = v.Collector.CollectorName
case *CollectRegistry:
collector = "registry-images"
name = v.Collector.CollectorName
case *CollectSysctl:
collector = "sysctl"
name = v.Collector.Name
default:
collector = "<none>"
}
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return errors.Wrap(err, "failed to create client from config")
if name != "" {
return fmt.Sprintf("%s/%s", collector, name)
}
forbidden := make([]error, 0)
specs := c.Collect.AccessReviewSpecs(c.Namespace)
for _, spec := range specs {
sar := &authorizationv1.SelfSubjectAccessReview{
Spec: spec,
}
resp, err := client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{})
if err != nil {
return errors.Wrap(err, "failed to run subject review")
}
if !resp.Status.Allowed { // all other fields of Status are empty...
forbidden = append(forbidden, RBACError{
DisplayName: c.GetDisplayName(),
Namespace: spec.ResourceAttributes.Namespace,
Resource: spec.ResourceAttributes.Resource,
Verb: spec.ResourceAttributes.Verb,
})
}
if selector != "" {
return fmt.Sprintf("%s/%s", collector, selector)
}
c.RBACErrors = forbidden
return nil
}
func (cs Collectors) CheckRBAC(ctx context.Context) error {
for _, c := range cs {
if err := c.CheckRBAC(ctx); err != nil {
return errors.Wrap(err, "failed to check RBAC")
}
}
return nil
return collector
}
+28 -12
View File
@@ -273,16 +273,26 @@ pwd=somethinggoeshere;`,
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := require.New(t)
c := &Collector{
Collect: tt.Collect,
Redact: true,
var result CollectorResult
collector, _ := GetCollector(tt.Collect, "", "", nil, nil, nil)
regCollector, _ := collector.(Collector)
if excluded, err := regCollector.IsExcluded(); !excluded {
req.NoError(err)
result, err = regCollector.Collect(nil)
req.NoError(err)
err = RedactResult("", result, tt.Redactors)
req.NoError(err)
}
got, err := c.RunCollectorSync(nil, nil, tt.Redactors)
req.NoError(err)
// convert to string to make differences easier to see
toString := map[string]string{}
for k, v := range got {
for k, v := range result {
toString[k] = string(v)
}
req.EqualValues(tt.want, toString)
@@ -332,16 +342,22 @@ pwd=somethinggoeshere;`,
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := require.New(t)
c := &Collector{
Collect: tt.Collect,
Redact: false,
var result CollectorResult
collector, _ := GetCollector(tt.Collect, "", "", nil, nil, nil)
regCollector, _ := collector.(Collector)
if excluded, err := regCollector.IsExcluded(); !excluded {
req.NoError(err)
result, err = regCollector.Collect(nil)
req.NoError(err)
}
got, err := c.RunCollectorSync(nil, nil, tt.Redactors)
req.NoError(err)
// convert to string to make differences easier to see
toString := map[string]string{}
for k, v := range got {
for k, v := range result {
toString[k] = string(v)
}
req.EqualValues(tt.want, toString)
+29 -10
View File
@@ -14,6 +14,7 @@ import (
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type ConfigMapOutput struct {
@@ -26,28 +27,46 @@ type ConfigMapOutput struct {
Data map[string]string `json:"data,omitonempty"`
}
func ConfigMap(ctx context.Context, c *Collector, configMapCollector *troubleshootv1beta2.ConfigMap, client kubernetes.Interface) (CollectorResult, error) {
type CollectConfigMap struct {
Collector *troubleshootv1beta2.ConfigMap
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectConfigMap) Title() string {
return getCollectorName(c)
}
func (c *CollectConfigMap) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectConfigMap) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
output := NewResult()
configMaps := []corev1.ConfigMap{}
if configMapCollector.Name != "" {
configMap, err := client.CoreV1().ConfigMaps(configMapCollector.Namespace).Get(ctx, configMapCollector.Name, metav1.GetOptions{})
if c.Collector.Name != "" {
configMap, err := c.Client.CoreV1().ConfigMaps(c.Collector.Namespace).Get(c.Context, c.Collector.Name, metav1.GetOptions{})
if err != nil {
if kuberneteserrors.IsNotFound(err) {
filePath, encoded, err := configMapToOutput(configMapCollector, nil, configMapCollector.Name)
filePath, encoded, err := configMapToOutput(c.Collector, nil, c.Collector.Name)
if err != nil {
return output, errors.Wrapf(err, "collect secret %s", configMapCollector.Name)
return output, errors.Wrapf(err, "collect secret %s", c.Collector.Name)
}
output.SaveResult(c.BundlePath, filePath, bytes.NewBuffer(encoded))
}
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(configMapCollector), marshalErrors([]string{err.Error()}))
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
return output, nil
}
configMaps = append(configMaps, *configMap)
} else if len(configMapCollector.Selector) > 0 {
cms, err := listConfigMapsForSelector(ctx, client, configMapCollector.Namespace, configMapCollector.Selector)
} else if len(c.Collector.Selector) > 0 {
cms, err := listConfigMapsForSelector(c.Context, c.Client, c.Collector.Namespace, c.Collector.Selector)
if err != nil {
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(configMapCollector), marshalErrors([]string{err.Error()}))
output.SaveResult(c.BundlePath, GetConfigMapErrorsFileName(c.Collector), marshalErrors([]string{err.Error()}))
return output, nil
}
configMaps = append(configMaps, cms...)
@@ -56,7 +75,7 @@ func ConfigMap(ctx context.Context, c *Collector, configMapCollector *troublesho
}
for _, configMap := range configMaps {
filePath, encoded, err := configMapToOutput(configMapCollector, &configMap, configMap.Name)
filePath, encoded, err := configMapToOutput(c.Collector, &configMap, configMap.Name)
if err != nil {
return output, errors.Wrapf(err, "collect configMap %s", configMap.Name)
}
+2 -2
View File
@@ -328,8 +328,8 @@ func TestConfigMap(t *testing.T) {
_, err := client.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, &configMap, metav1.CreateOptions{})
require.NoError(t, err)
}
c := &Collector{}
got, err := ConfigMap(ctx, c, tt.configMapCollector, client)
configMapCollector := &CollectConfigMap{tt.configMapCollector, "", "", nil, client, ctx, nil}
got, err := configMapCollector.Collect(nil)
if tt.wantErr {
assert.Error(t, err)
} else {
+33 -14
View File
@@ -15,12 +15,31 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
//Copy function gets a file or folder from a container specified in the specs.
func Copy(c *Collector, copyCollector *troubleshootv1beta2.Copy) (CollectorResult, error) {
type CollectCopy struct {
Collector *troubleshootv1beta2.Copy
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectCopy) Title() string {
return getCollectorName(c)
}
func (c *CollectCopy) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
// Copy function gets a file or folder from a container specified in the specs.
func (c *CollectCopy) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return nil, err
@@ -30,40 +49,40 @@ func Copy(c *Collector, copyCollector *troubleshootv1beta2.Copy) (CollectorResul
ctx := context.Background()
pods, podsErrors := listPodsInSelectors(ctx, client, copyCollector.Namespace, copyCollector.Selector)
pods, podsErrors := listPodsInSelectors(ctx, client, c.Collector.Namespace, c.Collector.Selector)
if len(podsErrors) > 0 {
output.SaveResult(c.BundlePath, getCopyErrosFileName(copyCollector), marshalErrors(podsErrors))
output.SaveResult(c.BundlePath, getCopyErrosFileName(c.Collector), marshalErrors(podsErrors))
}
if len(pods) > 0 {
for _, pod := range pods {
containerName := pod.Spec.Containers[0].Name
if copyCollector.ContainerName != "" {
containerName = copyCollector.ContainerName
if c.Collector.ContainerName != "" {
containerName = c.Collector.ContainerName
}
subPath := filepath.Join(copyCollector.Name, pod.Namespace, pod.Name, copyCollector.ContainerName)
subPath := filepath.Join(c.Collector.Name, pod.Namespace, pod.Name, c.Collector.ContainerName)
copyCollector.ExtractArchive = true // TODO: existing regression. this flag is always ignored and this matches current behaviour
c.Collector.ExtractArchive = true // TODO: existing regression. this flag is always ignored and this matches current behaviour
copyErrors := map[string]string{}
dstPath := filepath.Join(c.BundlePath, subPath, filepath.Dir(copyCollector.ContainerPath))
files, stderr, err := copyFilesFromPod(ctx, dstPath, c.ClientConfig, client, pod.Name, containerName, pod.Namespace, copyCollector.ContainerPath, copyCollector.ExtractArchive)
dstPath := filepath.Join(c.BundlePath, subPath, filepath.Dir(c.Collector.ContainerPath))
files, stderr, err := copyFilesFromPod(ctx, dstPath, c.ClientConfig, client, pod.Name, containerName, pod.Namespace, c.Collector.ContainerPath, c.Collector.ExtractArchive)
if err != nil {
copyErrors[filepath.Join(copyCollector.ContainerPath, "error")] = err.Error()
copyErrors[filepath.Join(c.Collector.ContainerPath, "error")] = err.Error()
if len(stderr) > 0 {
copyErrors[filepath.Join(copyCollector.ContainerPath, "stderr")] = string(stderr)
copyErrors[filepath.Join(c.Collector.ContainerPath, "stderr")] = string(stderr)
}
key := filepath.Join(subPath, copyCollector.ContainerPath+"-errors.json")
key := filepath.Join(subPath, c.Collector.ContainerPath+"-errors.json")
output.SaveResult(c.BundlePath, key, marshalErrors(copyErrors))
continue
}
for k, v := range files {
output[filepath.Join(subPath, filepath.Dir(copyCollector.ContainerPath), k)] = v
output[filepath.Join(subPath, filepath.Dir(c.Collector.ContainerPath), k)] = v
}
}
}
+43 -15
View File
@@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/segmentio/ksuid"
appsv1 "k8s.io/api/apps/v1"
@@ -20,19 +21,40 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
// CopyFromHost is a function that copies a file or directory from a host or hosts to include in the bundle.
func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1beta2.CopyFromHost, namespace string, clientConfig *restclient.Config, client kubernetes.Interface) (CollectorResult, error) {
type CollectCopyFromHost struct {
Collector *troubleshootv1beta2.CopyFromHost
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectCopyFromHost) Title() string {
return getCollectorName(c)
}
func (c *CollectCopyFromHost) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
// copies a file or directory from a host or hosts to include in the bundle.
func (c *CollectCopyFromHost) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
var namespace string
labels := map[string]string{
"app.kubernetes.io/managed-by": "troubleshoot.sh",
"troubleshoot.sh/collector": "copyfromhost",
"troubleshoot.sh/copyfromhost-id": ksuid.New().String(),
}
hostPath := filepath.Clean(collector.HostPath) // strip trailing slash
hostPath := filepath.Clean(c.Collector.HostPath) // strip trailing slash
hostDir := filepath.Dir(hostPath)
fileName := filepath.Base(hostPath)
@@ -41,18 +63,24 @@ func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1be
fileName = "."
}
_, cleanup, err := copyFromHostCreateDaemonSet(ctx, client, collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
namespace = c.Namespace
if namespace == "" {
kubeconfig := k8sutil.GetKubeconfig()
namespace, _, _ = kubeconfig.Namespace()
}
_, cleanup, err := copyFromHostCreateDaemonSet(c.Context, c.Client, c.Collector, hostDir, namespace, "troubleshoot-copyfromhost-", labels)
defer cleanup()
if err != nil {
return nil, errors.Wrap(err, "create daemonset")
}
childCtx, cancel := context.WithCancel(ctx)
childCtx, cancel := context.WithCancel(c.Context)
defer cancel()
timeoutCtx := context.Background()
if collector.Timeout != "" {
timeout, err := time.ParseDuration(collector.Timeout)
if c.Collector.Timeout != "" {
timeout, err := time.ParseDuration(c.Collector.Timeout)
if err != nil {
return nil, errors.Wrap(err, "parse timeout")
}
@@ -67,12 +95,12 @@ func CopyFromHost(ctx context.Context, c *Collector, collector *troubleshootv1be
resultCh := make(chan CollectorResult, 1)
go func() {
var outputFilename string
if collector.Name != "" {
outputFilename = collector.Name
if c.Collector.Name != "" {
outputFilename = c.Collector.Name
} else {
outputFilename = hostPath
}
b, err := copyFromHostGetFilesFromPods(childCtx, c, collector, clientConfig, client, fileName, outputFilename, labels, namespace)
b, err := copyFromHostGetFilesFromPods(childCtx, c.BundlePath, c.Collector, c.ClientConfig, c.Client, fileName, outputFilename, labels, namespace)
if err != nil {
errCh <- err
} else {
@@ -220,7 +248,7 @@ func copyFromHostCreateDaemonSet(ctx context.Context, client kubernetes.Interfac
return createdDS.Name, cleanup, nil
}
func copyFromHostGetFilesFromPods(ctx context.Context, c *Collector, collector *troubleshootv1beta2.CopyFromHost, clientConfig *restclient.Config, client kubernetes.Interface, fileName string, outputFilename string, labelSelector map[string]string, namespace string) (CollectorResult, error) {
func copyFromHostGetFilesFromPods(ctx context.Context, bundlePath string, collector *troubleshootv1beta2.CopyFromHost, clientConfig *restclient.Config, client kubernetes.Interface, fileName string, outputFilename string, labelSelector map[string]string, namespace string) (CollectorResult, error) {
opts := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(labelSelector).String(),
}
@@ -233,16 +261,16 @@ func copyFromHostGetFilesFromPods(ctx context.Context, c *Collector, collector *
output := NewResult()
for _, pod := range pods.Items {
outputNodeFilename := filepath.Join(outputFilename, pod.Spec.NodeName)
files, stderr, err := copyFilesFromHost(ctx, filepath.Join(c.BundlePath, outputNodeFilename), clientConfig, client, pod.Name, "collector", namespace, filepath.Join("/host", fileName), collector.ExtractArchive)
files, stderr, err := copyFilesFromHost(ctx, filepath.Join(bundlePath, outputNodeFilename), clientConfig, client, pod.Name, "collector", namespace, filepath.Join("/host", fileName), collector.ExtractArchive)
if err != nil {
output.SaveResult(c.BundlePath, filepath.Join(outputNodeFilename, "error.txt"), bytes.NewBuffer([]byte(err.Error())))
output.SaveResult(bundlePath, filepath.Join(outputNodeFilename, "error.txt"), bytes.NewBuffer([]byte(err.Error())))
if len(stderr) > 0 {
output.SaveResult(c.BundlePath, filepath.Join(outputNodeFilename, "stderr.txt"), bytes.NewBuffer(stderr))
output.SaveResult(bundlePath, filepath.Join(outputNodeFilename, "stderr.txt"), bytes.NewBuffer(stderr))
}
}
for k, v := range files {
relPath, err := filepath.Rel(c.BundlePath, filepath.Join(c.BundlePath, filepath.Join(outputNodeFilename, k)))
relPath, err := filepath.Rel(bundlePath, filepath.Join(bundlePath, filepath.Join(outputNodeFilename, k)))
if err != nil {
return nil, errors.Wrap(err, "relative path")
}
+24 -3
View File
@@ -2,16 +2,37 @@ package collect
import (
"bytes"
"context"
"path/filepath"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func Data(c *Collector, dataCollector *troubleshootv1beta2.Data) (CollectorResult, error) {
bundlePath := filepath.Join(dataCollector.Name, dataCollector.CollectorName)
type CollectData struct {
Collector *troubleshootv1beta2.Data
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectData) Title() string {
return getCollectorName(c)
}
func (c *CollectData) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectData) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
bundlePath := filepath.Join(c.Collector.Name, c.Collector.CollectorName)
output := NewResult()
output.SaveResult(c.BundlePath, bundlePath, bytes.NewBuffer([]byte(dataCollector.Data)))
output.SaveResult(c.BundlePath, bundlePath, bytes.NewBuffer([]byte(c.Collector.Data)))
return output, nil
}
+35 -16
View File
@@ -3,24 +3,43 @@ package collect
import (
"bytes"
"context"
"errors"
"fmt"
"path/filepath"
"time"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
if execCollector.Timeout == "" {
return execWithoutTimeout(c, execCollector)
type CollectExec struct {
Collector *troubleshootv1beta2.Exec
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectExec) Title() string {
return getCollectorName(c)
}
func (c *CollectExec) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectExec) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
if c.Collector.Timeout == "" {
return execWithoutTimeout(c.ClientConfig, c.BundlePath, c.Collector)
}
timeout, err := time.ParseDuration(execCollector.Timeout)
timeout, err := time.ParseDuration(c.Collector.Timeout)
if err != nil {
return nil, err
}
@@ -29,7 +48,7 @@ func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResul
resultCh := make(chan CollectorResult, 1)
go func() {
b, err := execWithoutTimeout(c, execCollector)
b, err := execWithoutTimeout(c.ClientConfig, c.BundlePath, c.Collector)
if err != nil {
errCh <- err
} else {
@@ -47,8 +66,8 @@ func Exec(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResul
}
}
func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
func execWithoutTimeout(clientConfig *rest.Config, bundlePath string, execCollector *troubleshootv1beta2.Exec) (CollectorResult, error) {
client, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
@@ -59,23 +78,23 @@ func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (
pods, podsErrors := listPodsInSelectors(ctx, client, execCollector.Namespace, execCollector.Selector)
if len(podsErrors) > 0 {
output.SaveResult(c.BundlePath, getExecErrosFileName(execCollector), marshalErrors(podsErrors))
output.SaveResult(bundlePath, getExecErrosFileName(execCollector), marshalErrors(podsErrors))
}
if len(pods) > 0 {
for _, pod := range pods {
stdout, stderr, execErrors := getExecOutputs(c, client, pod, execCollector)
stdout, stderr, execErrors := getExecOutputs(clientConfig, client, pod, execCollector)
bundlePath := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
path := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
if len(stdout) > 0 {
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stdout.txt"), bytes.NewBuffer(stdout))
}
if len(stderr) > 0 {
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-stderr.txt"), bytes.NewBuffer(stderr))
}
if len(execErrors) > 0 {
output.SaveResult(c.BundlePath, filepath.Join(bundlePath, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
output.SaveResult(bundlePath, filepath.Join(path, execCollector.CollectorName+"-errors.json"), marshalErrors(execErrors))
continue
}
}
@@ -84,7 +103,7 @@ func execWithoutTimeout(c *Collector, execCollector *troubleshootv1beta2.Exec) (
return output, nil
}
func getExecOutputs(c *Collector, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta2.Exec) ([]byte, []byte, []string) {
func getExecOutputs(clientConfig *rest.Config, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta2.Exec) ([]byte, []byte, []string) {
container := pod.Spec.Containers[0].Name
if execCollector.ContainerName != "" {
container = execCollector.ContainerName
@@ -106,7 +125,7 @@ func getExecOutputs(c *Collector, client *kubernetes.Clientset, pod corev1.Pod,
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(c.ClientConfig, "POST", req.URL())
exec, err := remotecommand.NewSPDYExecutor(clientConfig, "POST", req.URL())
if err != nil {
return nil, nil, []string{err.Error()}
}
+1 -1
View File
@@ -38,7 +38,7 @@ func (c *CollectHostHTTP) Collect(progressChan chan<- interface{}) (map[string][
return nil, errors.New("no supported http request type")
}
responseOutput, err := responseToOutput(response, err, false)
responseOutput, err := responseToOutput(response, err)
if err != nil {
return nil, err
}
+11 -11
View File
@@ -60,17 +60,17 @@ func (c *CollectHostKernelModules) IsExcluded() (bool, error) {
// Collect the kernel module status from the host. Modules are returned as a
// map keyed on the module name used by the kernel, e.g:
//
// {
// "system/kernel_modules.json": {
// ...
// "dm_snapshot": {
// "instances": 8,
// "size": 45056,
// "status": "loaded"
// },
// ...
// },
// }
// {
// "system/kernel_modules.json": {
// ...
// "dm_snapshot": {
// "instances": 8,
// "size": 45056,
// "status": "loaded"
// },
// ...
// },
// }
//
// Module status may be: loaded, loadable, loading, unloading or unknown. When
// a module is loaded, it may have one or more instances. The size represents
+32 -13
View File
@@ -4,13 +4,15 @@ import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"path/filepath"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type HTTPResponse struct {
@@ -33,32 +35,49 @@ var (
}
)
func HTTP(c *Collector, httpCollector *troubleshootv1beta2.HTTP) (CollectorResult, error) {
type CollectHTTP struct {
Collector *troubleshootv1beta2.HTTP
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
RBACErrors
}
func (c *CollectHTTP) Title() string {
return getCollectorName(c)
}
func (c *CollectHTTP) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectHTTP) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
var response *http.Response
var err error
if httpCollector.Get != nil {
response, err = doGet(httpCollector.Get)
} else if httpCollector.Post != nil {
response, err = doPost(httpCollector.Post)
} else if httpCollector.Put != nil {
response, err = doPut(httpCollector.Put)
if c.Collector.Get != nil {
response, err = doGet(c.Collector.Get)
} else if c.Collector.Post != nil {
response, err = doPost(c.Collector.Post)
} else if c.Collector.Put != nil {
response, err = doPut(c.Collector.Put)
} else {
return nil, errors.New("no supported http request type")
}
o, err := responseToOutput(response, err, c.Redact)
o, err := responseToOutput(response, err)
if err != nil {
return nil, err
}
fileName := "result.json"
if httpCollector.CollectorName != "" {
fileName = httpCollector.CollectorName + ".json"
if c.Collector.CollectorName != "" {
fileName = c.Collector.CollectorName + ".json"
}
output := NewResult()
output.SaveResult(c.BundlePath, filepath.Join(httpCollector.Name, fileName), bytes.NewBuffer(o))
output.SaveResult(c.BundlePath, filepath.Join(c.Collector.Name, fileName), bytes.NewBuffer(o))
return output, nil
}
@@ -117,7 +136,7 @@ func doPut(put *troubleshootv1beta2.Put) (*http.Response, error) {
return httpClient.Do(req)
}
func responseToOutput(response *http.Response, err error, doRedact bool) ([]byte, error) {
func responseToOutput(response *http.Response, err error) ([]byte, error) {
output := make(map[string]interface{})
if err != nil {
output["error"] = HTTPError{
+86 -23
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
"time"
@@ -13,9 +14,29 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResult, error) {
type CollectLogs struct {
Collector *troubleshootv1beta2.Logs
BundlePath string
Namespace string // There is a Namespace parameter in troubleshootv1beta2.Logs. Should we remove this?
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
SinceTime *time.Time
RBACErrors
}
func (c *CollectLogs) Title() string {
return getCollectorName(c)
}
func (c *CollectLogs) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectLogs) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
client, err := kubernetes.NewForConfig(c.ClientConfig)
if err != nil {
return nil, err
@@ -25,14 +46,21 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
ctx := context.Background()
pods, podsErrors := listPodsInSelectors(ctx, client, logsCollector.Namespace, logsCollector.Selector)
if c.SinceTime != nil {
if c.Collector.Limits == nil {
c.Collector.Limits = new(troubleshootv1beta2.LogLimits)
}
c.Collector.Limits.SinceTime = metav1.NewTime(*c.SinceTime)
}
pods, podsErrors := listPodsInSelectors(ctx, client, c.Collector.Namespace, c.Collector.Selector)
if len(podsErrors) > 0 {
output.SaveResult(c.BundlePath, getLogsErrorsFileName(logsCollector), marshalErrors(podsErrors))
output.SaveResult(c.BundlePath, getLogsErrorsFileName(c.Collector), marshalErrors(podsErrors))
}
if len(pods) > 0 {
for _, pod := range pods {
if len(logsCollector.ContainerNames) == 0 {
if len(c.Collector.ContainerNames) == 0 {
// make a list of all the containers in the pod, so that we can get logs from all of them
containerNames := []string{}
for _, container := range pod.Spec.Containers {
@@ -43,14 +71,11 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
}
for _, containerName := range containerNames {
if len(containerNames) == 1 {
containerName = "" // if there was only one container, use the old behavior of not including the container name in the path
}
podLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, logsCollector.Name, containerName, logsCollector.Limits, false)
podLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, c.Collector.Name, containerName, c.Collector.Limits, false)
if err != nil {
key := fmt.Sprintf("%s/%s-errors.json", logsCollector.Name, pod.Name)
key := fmt.Sprintf("%s/%s-errors.json", c.Collector.Name, pod.Name)
if containerName != "" {
key = fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, containerName)
key = fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, containerName)
}
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
if err != nil {
@@ -63,10 +88,10 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
}
}
} else {
for _, container := range logsCollector.ContainerNames {
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, pod, logsCollector.Name, container, logsCollector.Limits, false)
for _, container := range c.Collector.ContainerNames {
containerLogs, err := savePodLogs(ctx, c.BundlePath, client, &pod, c.Collector.Name, container, c.Collector.Limits, false)
if err != nil {
key := fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, container)
key := fmt.Sprintf("%s/%s/%s-errors.json", c.Collector.Name, pod.Name, container)
err := output.SaveResult(c.BundlePath, key, marshalErrors([]string{err.Error()}))
if err != nil {
return nil, err
@@ -84,7 +109,7 @@ func Logs(c *Collector, logsCollector *troubleshootv1beta2.Logs) (CollectorResul
return output, nil
}
func listPodsInSelectors(ctx context.Context, client *kubernetes.Clientset, namespace string, selector []string) ([]corev1.Pod, []string) {
func listPodsInSelectors(ctx context.Context, client kubernetes.Interface, namespace string, selector []string) ([]corev1.Pod, []string) {
serializedLabelSelector := strings.Join(selector, ",")
listOptions := metav1.ListOptions{
@@ -99,20 +124,54 @@ func listPodsInSelectors(ctx context.Context, client *kubernetes.Clientset, name
return pods.Items, nil
}
func savePodLogs(ctx context.Context, bundlePath string, client *kubernetes.Clientset, pod corev1.Pod, name, container string, limits *troubleshootv1beta2.LogLimits, follow bool) (CollectorResult, error) {
func savePodLogs(
ctx context.Context,
bundlePath string,
client *kubernetes.Clientset,
pod *corev1.Pod,
collectorName, container string,
limits *troubleshootv1beta2.LogLimits,
follow bool,
) (CollectorResult, error) {
return savePodLogsWithInterface(ctx, bundlePath, client, pod, collectorName, container, limits, follow)
}
func savePodLogsWithInterface(
ctx context.Context,
bundlePath string,
client kubernetes.Interface,
pod *corev1.Pod,
collectorName, container string,
limits *troubleshootv1beta2.LogLimits,
follow bool,
) (CollectorResult, error) {
podLogOpts := corev1.PodLogOptions{
Follow: follow,
Container: container,
}
setLogLimits(&podLogOpts, limits, convertMaxAgeToTime)
result := NewResult()
fileKey := fmt.Sprintf("%s/%s", name, pod.Name)
// TODO: Abstract away hard coded directory structure paths
// Maybe create a FS provider or something similar
filePathPrefix := filepath.Join(
"cluster-resources", "pods", "logs", pod.Namespace, pod.Name, pod.Spec.Containers[0].Name,
)
// TODO: If collectorName is empty, the path is stored with a leading slash
// Retain this behavior otherwise analysers in the wild may break
// Analysers that need to find a file in the root of the bundle should
// prefix the path with a slash e.g /file.txt. This behavior should be
// properly deprecated in the future.
linkRelPathPrefix := fmt.Sprintf("%s/%s", collectorName, pod.Name)
if container != "" {
fileKey = fmt.Sprintf("%s/%s/%s", name, pod.Name, container)
linkRelPathPrefix = fmt.Sprintf("%s/%s/%s", collectorName, pod.Name, container)
filePathPrefix = filepath.Join(
"cluster-resources", "pods", "logs", pod.Namespace, pod.Name, container,
)
}
result := NewResult()
setLogLimits(&podLogOpts, limits, convertMaxAgeToTime)
req := client.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts)
podLogs, err := req.Stream(ctx)
@@ -121,11 +180,13 @@ func savePodLogs(ctx context.Context, bundlePath string, client *kubernetes.Clie
}
defer podLogs.Close()
logWriter, err := result.GetWriter(bundlePath, fileKey+".log")
logWriter, err := result.GetWriter(bundlePath, filePathPrefix+".log")
if err != nil {
return nil, errors.Wrap(err, "failed to get log writer")
}
defer result.CloseWriter(bundlePath, fileKey+".log", logWriter)
// NOTE: deferred calls are executed in LIFO order i.e called in reverse order
defer result.SymLinkResult(bundlePath, linkRelPathPrefix+".log", filePathPrefix+".log")
defer result.CloseWriter(bundlePath, filePathPrefix+".log", logWriter)
_, err = io.Copy(logWriter, podLogs)
if err != nil {
@@ -141,11 +202,13 @@ func savePodLogs(ctx context.Context, bundlePath string, client *kubernetes.Clie
}
defer podLogs.Close()
prevLogWriter, err := result.GetWriter(bundlePath, fileKey+"-previous.log")
prevLogWriter, err := result.GetWriter(bundlePath, filePathPrefix+"-previous.log")
if err != nil {
return nil, errors.Wrap(err, "failed to get previous log writer")
}
defer result.CloseWriter(bundlePath, fileKey+"-previous.log", logWriter)
// NOTE: deferred calls are executed in LIFO order i.e called in reverse order
defer result.SymLinkResult(bundlePath, linkRelPathPrefix+"-previous.log", filePathPrefix+"-previous.log")
defer result.CloseWriter(bundlePath, filePathPrefix+"-previous.log", logWriter)
_, err = io.Copy(prevLogWriter, podLogs)
if err != nil {
+73
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,74 @@ func Test_setLogLimits(t *testing.T) {
})
}
}
func Test_savePodLogs(t *testing.T) {
tests := []struct {
name string
withContainerName bool
collectorName string
want CollectorResult
}{
{
name: "with container name",
withContainerName: true,
collectorName: "all-logs",
want: CollectorResult{
"all-logs/test-pod/nginx.log": []byte("fake logs"),
"all-logs/test-pod/nginx-previous.log": []byte("fake logs"),
"cluster-resources/pods/logs/my-namespace/test-pod/nginx.log": []byte("fake logs"),
"cluster-resources/pods/logs/my-namespace/test-pod/nginx-previous.log": []byte("fake logs"),
},
},
{
name: "without container name",
withContainerName: false,
collectorName: "all-logs",
want: CollectorResult{
"all-logs/test-pod.log": []byte("fake logs"),
"all-logs/test-pod-previous.log": []byte("fake logs"),
"cluster-resources/pods/logs/my-namespace/test-pod/nginx.log": []byte("fake logs"),
"cluster-resources/pods/logs/my-namespace/test-pod/nginx-previous.log": []byte("fake logs"),
},
},
{
name: "without container or collector names",
withContainerName: false,
want: CollectorResult{
"/test-pod.log": []byte("fake logs"),
"/test-pod-previous.log": []byte("fake logs"),
"cluster-resources/pods/logs/my-namespace/test-pod/nginx.log": []byte("fake logs"),
"cluster-resources/pods/logs/my-namespace/test-pod/nginx-previous.log": []byte("fake logs"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.TODO()
containerName := "nginx"
client := testclient.NewSimpleClientset()
limits := &troubleshootv1beta2.LogLimits{
MaxLines: 500,
}
pod, err := client.CoreV1().Pods("my-namespace").Create(ctx, &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: containerName,
},
},
},
}, metav1.CreateOptions{})
assert.NoError(t, err)
if !tt.withContainerName {
containerName = ""
}
got, err := savePodLogsWithInterface(ctx, "", client, pod, tt.collectorName, containerName, limits, false)
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
+45 -18
View File
@@ -5,7 +5,6 @@ import (
"bytes"
"context"
"fmt"
"path"
"path/filepath"
"regexp"
"sync"
@@ -29,12 +28,30 @@ const (
var checksumRX = regexp.MustCompile(`(\S+)\s+(\S+)`)
func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (CollectorResult, error) {
type CollectLonghorn struct {
Collector *troubleshootv1beta2.Longhorn
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectLonghorn) Title() string {
return getCollectorName(c)
}
func (c *CollectLonghorn) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectLonghorn) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
ctx := context.TODO()
ns := DefaultLonghornNamespace
if longhornCollector.Namespace != "" {
ns = longhornCollector.Namespace
if c.Collector.Namespace != "" {
ns = c.Collector.Namespace
}
client, err := longhornv1beta1.NewForConfig(c.ClientConfig)
@@ -196,23 +213,10 @@ func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (Co
}
output.SaveResult(c.BundlePath, settingsKey, bytes.NewBuffer(settingsB))
// logs of all pods in namespace
logsCollector := &troubleshootv1beta2.Logs{
Selector: []string{""},
Namespace: ns,
}
logs, err := Logs(c, logsCollector)
err = c.collectLonghornLogs(ns, output, progressChan)
if err != nil {
return nil, errors.Wrap(err, "collect longhorn logs")
}
logsDir := GetLonghornLogsDirectory(ns)
for srcFilename, _ := range logs {
dstFileName := path.Join(logsDir, srcFilename)
err := copyResult(logs, output, c.BundlePath, srcFilename, dstFileName)
if err != nil {
logger.Printf("Failed to copy file %s; %v", srcFilename, err)
}
}
// https://longhorn.io/docs/1.1.1/advanced-resources/data-recovery/corrupted-replica/
@@ -285,6 +289,29 @@ func Longhorn(c *Collector, longhornCollector *troubleshootv1beta2.Longhorn) (Co
return output, nil
}
func (c *CollectLonghorn) collectLonghornLogs(namespace string, results CollectorResult, progressChan chan<- interface{}) error {
// logs of all pods in namespace
logsCollectorSpec := &troubleshootv1beta2.Logs{
Selector: []string{""},
Name: GetLonghornLogsDirectory(namespace), // Logs (symlinks) will be stored in this directory
Namespace: namespace,
}
rbacErrors := c.GetRBACErrors()
logsCollector := &CollectLogs{logsCollectorSpec, c.BundlePath, namespace, c.ClientConfig, c.Client, c.Context, nil, rbacErrors}
logs, err := logsCollector.Collect(progressChan)
if err != nil {
return err
}
// Add logs collector results to the rest of
// the longhorn collector results for later consumption
results.AddResult(logs)
return nil
}
func GetLonghornNodesDirectory(namespace string) string {
return fmt.Sprintf("longhorn/%s/nodes", namespace)
}
+25 -4
View File
@@ -2,6 +2,7 @@ package collect
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
@@ -9,12 +10,32 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
type CollectMysql struct {
Collector *troubleshootv1beta2.Database
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectMysql) Title() string {
return getCollectorName(c)
}
func (c *CollectMysql) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectMysql) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
databaseConnection := DatabaseConnection{}
db, err := sql.Open("mysql", databaseCollector.URI)
db, err := sql.Open("mysql", c.Collector.URI)
if err != nil {
databaseConnection.Error = err.Error()
} else {
@@ -30,7 +51,7 @@ func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
databaseConnection.Version = version
}
requestedParameters := databaseCollector.Parameters
requestedParameters := c.Collector.Parameters
if len(requestedParameters) > 0 {
rows, err := db.Query("SHOW VARIABLES")
@@ -68,7 +89,7 @@ func Mysql(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Colle
return nil, errors.Wrap(err, "failed to marshal database connection")
}
collectorName := databaseCollector.CollectorName
collectorName := c.Collector.CollectorName
if collectorName == "" {
collectorName = "mysql"
}
+65 -6
View File
@@ -2,25 +2,84 @@ package collect
import (
"bytes"
"database/sql"
"context"
"encoding/json"
"fmt"
"regexp"
_ "github.com/lib/pq"
"github.com/jackc/pgx/v5"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func Postgres(c *Collector, databaseCollector *troubleshootv1beta2.Database) (CollectorResult, error) {
type CollectPostgres struct {
Collector *troubleshootv1beta2.Database
BundlePath string
Namespace string
ClientConfig *rest.Config
Client kubernetes.Interface
Context context.Context
RBACErrors
}
func (c *CollectPostgres) Title() string {
return getCollectorName(c)
}
func (c *CollectPostgres) IsExcluded() (bool, error) {
return isExcluded(c.Collector.Exclude)
}
func (c *CollectPostgres) createConnectConfig() (*pgx.ConnConfig, error) {
if c.Collector.URI == "" {
return nil, errors.New("postgres uri cannot be empty")
}
cfg, err := pgx.ParseConfig(c.Collector.URI)
if err != nil {
return nil, errors.Wrap(err, "failed to parse postgres config")
}
if c.Collector.TLS != nil {
tlsCfg, err := createTLSConfig(c.Context, c.Client, c.Collector.TLS)
if err != nil {
return nil, err
}
tlsCfg.ServerName = cfg.Host
cfg.TLSConfig = tlsCfg
}
return cfg, nil
}
func (c *CollectPostgres) connect() (*pgx.Conn, error) {
connCfg, err := c.createConnectConfig()
if err != nil {
return nil, err
}
conn, err := pgx.ConnectConfig(c.Context, connCfg)
if err != nil {
return nil, err
}
return conn, nil
}
func (c *CollectPostgres) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
databaseConnection := DatabaseConnection{}
db, err := sql.Open("postgres", databaseCollector.URI)
conn, err := c.connect()
if err != nil {
databaseConnection.Error = err.Error()
} else {
defer conn.Close(c.Context)
query := `select version()`
row := db.QueryRow(query)
row := conn.QueryRow(c.Context, query)
version := ""
if err := row.Scan(&version); err != nil {
databaseConnection.Error = err.Error()
@@ -42,7 +101,7 @@ func Postgres(c *Collector, databaseCollector *troubleshootv1beta2.Database) (Co
return nil, errors.Wrap(err, "failed to marshal database connection")
}
collectorName := databaseCollector.CollectorName
collectorName := c.Collector.CollectorName
if collectorName == "" {
collectorName = "postgres"
}

Some files were not shown because too many files have changed in this diff Show More