Compare commits

...
153 Commits
Author SHA1 Message Date
divolgin 529fe8cdbf Merge pull request #319 from replicatedhq/divolgin/cani
log not logs is the subresource
2021-02-12 11:40:36 -08:00
divolgin 105a718bbb log not logs is the subresource 2021-02-12 18:38:46 +00:00
divolgin c2b53036f9 Merge pull request #318 from replicatedhq/divolgin/cani
Fix can-i checks
2021-02-12 09:42:18 -08:00
divolgin ba22fe9f22 Fix can-i checks 2021-02-12 17:27:48 +00:00
Andrew Reed 8df8a5c792 Merge pull request #316 from areed/host-block-device
Analyze block devices
2021-02-11 14:50:22 -05:00
Andrew Reed 0bcd5183f5 Analyze block devices 2021-02-11 19:19:45 +00:00
Andrew Reed e2c0c722ae Merge pull request #315 from areed/host-time
Get time info from systemd
2021-02-10 15:30:49 -05:00
Andrew Reed 9984fe2caa Get time info from timedated 2021-02-10 20:01:15 +00:00
Andrew Reed f2e4127111 Merge pull request #314 from areed/host-preflight-urls
Host HTTP request analyzer
2021-02-10 10:56:54 -05:00
Andrew Reed f25149f45c Host HTTP request analyzer 2021-02-09 20:31:28 +00:00
Andrew Reed 2a06cb25e4 Merge pull request #313 from areed/fix-darwin-build
Fix build for darwin amd64
2021-02-08 18:00:49 -05:00
Andrew Reed 86bc366ec3 Fix build for darwin amd64 2021-02-08 22:47:06 +00:00
Andrew ReedandMarc Campbell 10a34c2e58 Host preflight (#311)
* Add HostPreflight v1beta2

* Work on TCP Load Balancer

* Host disk usage collector and analyzer

* Host memory analyzer

* TCP port status

* TCP load balancer

* Review changes

Co-authored-by: Marc Campbell <marc.e.campbell@gmail.com>
2021-02-08 16:09:01 -05:00
Marc Campbell df68f7445f Merge pull request #310 from replicatedhq/remove-scope
Removing Scopeagent
2021-01-28 10:31:24 -08:00
Marc Campbell c7fdec0291 Removing Scopeagent 2021-01-28 18:22:48 +00:00
divolgin e8dba3cad9 Merge pull request #309 from replicatedhq/divolgin/secrets
Create pull secrets if they don't have names
2021-01-08 12:21:04 -08:00
divolgin 3e2d90ee9b Create pull secrets if they don't have names 2021-01-08 20:01:47 +00:00
Salah Aldeen Al Saleh b05eb6bf75 fix subdirectory missing in support bundle issue (#308) 2020-12-08 12:52:29 -08:00
Andrew Lavery 2e16424b44 Merge pull request #307 from replicatedhq/laverya/fix-node-resources-label-filtering
fix node resources label filtering
2020-12-04 10:14:23 -05:00
Andrew Lavery f19ac98055 add tests for analyzeNodeResources, including label filtering 2020-12-03 19:19:57 -05:00
Andrew Lavery 55fbd92673 not finding a label is not an error 2020-12-03 19:17:47 -05:00
Andrew Lavery 2f2edb5356 Merge pull request #302 from replicatedhq/laverya/dont-rbac-check-excluded-collectors
check whether a collector is excluded before checking RBAC for it
2020-11-19 19:44:41 -05:00
Andrew Lavery 0ec1c43afd update setup-kind action 2020-11-19 18:57:52 -05:00
Andrew Lavery b43ef17b8f remve set-env and add-path from CI 2020-11-19 18:37:57 -05:00
Andrew Lavery 858d00ce41 check whether a collector is excluded before checking RBAC for it 2020-11-19 18:31:56 -05:00
emosbaugh c81576f25f Merge pull request #299 from replicatedhq/ceph-no-name
Ceph collector does not need a name property
2020-11-13 13:03:05 -08:00
Ethan Mosbaugh 1e8e20a295 Ceph collector does not need a name property 2020-11-13 21:01:02 +00:00
emosbaugh 4cdfa5b112 Merge pull request #298 from replicatedhq/ceph-format
Fix Ceph collector file extension
2020-11-13 12:17:48 -08:00
Ethan Mosbaugh 8ef720ba9e Fix Ceph collector file extension 2020-11-13 20:02:15 +00:00
divolgin 7d2e0d0aa3 Merge pull request #297 from replicatedhq/divolgin/fix
fixing build
2020-11-13 09:54:03 -08:00
divolgin 88685ae5f7 fixing build 2020-11-13 17:49:43 +00:00
divolgin 364a63533a Merge pull request #296 from replicatedhq/divolgin/releaser
Revert changes for static linking
2020-11-13 09:37:31 -08:00
divolgin 6749f339e8 Revert changes for static linking 2020-11-13 17:34:08 +00:00
emosbaugh 2bf19eaddf Ceph collectors and analyzers (#295)
* Ceph collectors and analyzers

* updating based on prior pr

* fixes

* fixes
2020-11-13 09:12:42 -08:00
divolgin fd6af65f7e Merge pull request #294 from replicatedhq/divolgin/no-rrd
Remove RRD dependency and switch to dynamic linking
2020-11-12 11:28:07 -08:00
divolgin 9e669b3f13 Remove RRD dependency and switch to dynamic linking 2020-11-12 19:13:25 +00:00
divolgin 4e74fd209e Merge pull request #293 from replicatedhq/divolgin/fix
fix build
2020-11-10 22:00:21 -08:00
divolgin a00a1892d9 include darwin builds in archives 2020-11-11 02:10:30 +00:00
divolgin 474a874f82 Merge pull request #292 from replicatedhq/divolgin/fix
fixing build
2020-11-10 16:38:49 -08:00
divolgin 463ba62ffa fixing build 2020-11-11 00:26:53 +00:00
divolgin 46194fccb7 Merge pull request #291 from replicatedhq/divolgin/fix
fixing build
2020-11-10 14:59:59 -08:00
divolgin badedf34ee fixing build 2020-11-10 22:55:21 +00:00
divolgin af2f49cb07 Merge pull request #271 from manavellamnimble/fixImagePullSecretAndTextAnalyzer
ImagePullSecret and TextAnalyzer with regex fixed
2020-11-10 09:40:10 -08:00
divolgin 88a6d3f715 Merge pull request #290 from replicatedhq/divolgin/rrd
Collector for RRD data
2020-11-10 09:19:46 -08:00
divolgin 5a1321da02 Collector and analyzer for RRD data 2020-11-10 17:19:17 +00:00
divolgin 014a716949 Merge pull request #287 from manavellamnimble/addLogFlags
added flag --since-time for Logs Collectors
2020-10-29 15:20:10 -07:00
Matias Manavella ad4893bec5 default message 2020-10-29 18:19:32 -03:00
Matias Manavella 989093394c test fixed 2020-10-29 17:47:37 -03:00
Matias Manavella f1868d0ba8 return messages modified 2020-10-22 14:42:05 -03:00
Matias Manavella 3cbdb41c8f return error when both flags are used 2020-10-22 14:35:08 -03:00
Matias Manavella d2f6594c0c return error when both flags are used 2020-10-22 14:31:34 -03:00
Matias Manavella 56408fab01 return error when both flags are used 2020-10-22 14:28:35 -03:00
Matias Manavella f0d9418e21 parseTimeFlag function and error handling added 2020-10-22 12:26:29 -03:00
Matias Manavella 1ba3840ad9 parseTimeFlag function and error handling added 2020-10-22 11:37:52 -03:00
Matias ManavellaandMark Pundsack a3d667298e Update cmd/preflight/cli/root.go
Co-authored-by: Mark Pundsack <markpundsack@users.noreply.github.com>
2020-10-22 10:13:45 -03:00
Matias ManavellaandMark Pundsack 5cf4ae2157 Update cmd/preflight/cli/root.go
Co-authored-by: Mark Pundsack <markpundsack@users.noreply.github.com>
2020-10-22 10:13:32 -03:00
Matias ManavellaandMark Pundsack dede187841 Update cmd/troubleshoot/cli/root.go
Co-authored-by: Mark Pundsack <markpundsack@users.noreply.github.com>
2020-10-22 09:26:29 -03:00
Matias ManavellaandMark Pundsack 7dc6e22d8d Update cmd/troubleshoot/cli/root.go
Co-authored-by: Mark Pundsack <markpundsack@users.noreply.github.com>
2020-10-22 09:26:11 -03:00
Matias Manavella f3af73b6e3 nil and error handling 2020-10-21 14:49:46 -03:00
Matias Manavella 4e047b2372 nil and error handling 2020-10-21 14:47:25 -03:00
Matias ManavellaandSalah Aldeen Al Saleh 7f190e3cce Update cmd/troubleshoot/cli/run.go
Co-authored-by: Salah Aldeen Al Saleh <salahalsaleh1993@gmail.com>
2020-10-21 13:59:51 -03:00
Matias ManavellaandSalah Aldeen Al Saleh 2436a0c163 Update cmd/preflight/cli/root.go
Co-authored-by: Salah Aldeen Al Saleh <salahalsaleh1993@gmail.com>
2020-10-21 13:59:24 -03:00
Matias Manavella 883abacaf8 --since flag added 2020-10-21 09:59:10 -03:00
Matias Manavella 7186b75f7e --since flag added 2020-10-21 09:51:52 -03:00
Matias Manavella e16eabd531 added flag --since-time 2020-10-19 16:53:13 -03:00
Salah Aldeen Al Saleh 3ff91a3b92 support bundle subdirectory (#285) 2020-10-19 11:11:56 -07:00
Marc Campbell 2020049cb7 Merge pull request #283 from manavellamnimble/fixFilePath
Filepath fixed
2020-10-14 19:02:41 -07:00
Salah Aldeen Al Saleh d94954e6dd use '_' instead of ':' in support bundle name format (#284) 2020-10-14 15:57:26 -07:00
Matias Manavella 06817c1682 Filepath fixed 2020-10-14 15:01:00 -03:00
divolgin 0554c2ac0f Merge pull request #278 from replicatedhq/divolgin/insecure
Retry http requests on x509 errors if allowed
2020-10-06 14:44:36 -07:00
divolgin 1087eb65d2 Retry http requests on x509 errors if allowed 2020-10-06 21:42:04 +00:00
divolgin 09dc6b5862 Merge pull request #276 from replicatedhq/divolgin/panic
Don't panic when pod is in a waiting state
2020-10-05 17:27:04 -07:00
divolgin 3d301f4307 Don't panic when pod is in a waiting state 2020-10-06 00:24:46 +00:00
divolgin 446b1d5552 Merge pull request #274 from manavellamnimble/removeImagePullSecretPathSupport
Remove image pull secret path support
2020-10-05 16:55:46 -07:00
divolgin 63c8422e63 Merge pull request #270 from manavellamnimble/secretAnalyzerFixed
Secret analyzer fixed
2020-10-05 16:51:28 -07:00
divolgin 616f6e73a5 Merge pull request #273 from replicatedhq/divolgin/preflight
Allow preflight spec to be loaded from a secret
2020-10-01 12:12:29 -07:00
Matias Manavella 213b52477f Run collectors docs modified 2020-10-01 13:53:54 -03:00
Matias Manavella e9ba73d87f Support for path to config.json file removed 2020-10-01 13:42:29 -03:00
divolgin 6e86cdc803 Allow preflight spec to be loaded from a secret 2020-10-01 01:37:37 +00:00
divolgin c023a81966 Merge pull request #272 from replicatedhq/divolgin/secrets
Support loading spec from a secret
2020-09-30 16:21:10 -07:00
divolgin 7d0bb1a5d7 Support loading spec from a secret 2020-09-30 23:06:42 +00:00
Matias Manavella 3cf679260e ImagePullSecret and TextAnalyzer with regex fixed 2020-09-30 18:06:52 -03:00
Matias Manavella 53bd30755d secret analyzer fixed 2020-09-29 11:42:21 -03:00
divolgin 7fe7216b29 Merge pull request #268 from manavellamnimble/postgressVersion
Error Parsing Postgres Version
2020-09-24 15:43:28 -07:00
Matias Manavella 2da1f518d3 parse tolerant 2020-09-23 15:10:54 -03:00
divolgin 8a30f2ddce Merge pull request #269 from manavellamnimble/distributionEnhacement
Distribution enhacement
2020-09-23 08:53:19 -07:00
Matias Manavella 0ea4e1d52f distribution_test fixed 2020-09-22 18:10:28 -03:00
Matias Manavella 74c9ff2432 add ibm and minikube to distribution analyzer 2020-09-22 17:36:27 -03:00
Matias Manavella bfa50c9643 add ibm and minikube to distribution analyzer 2020-09-22 17:32:41 -03:00
Matias Manavella 74a7e5f19c add ibm and minikube to distribution analyzer 2020-09-22 16:31:52 -03:00
Matias Manavella e3c954f460 add ibm and minikube to distribution analyzer 2020-09-22 16:17:47 -03:00
Matias Manavella 16d3ff372f add ibm and minikube to distribution analyzer 2020-09-22 16:08:03 -03:00
Matias Manavella 437357f099 Filename fixed 2020-09-21 11:06:15 -03:00
Matias Manavella 955e08c35a fixed varsion formats 2020-09-17 17:38:42 -03:00
divolgin 58261cdaf2 Merge pull request #267 from manavellamnimble/fixIngress
ingres analyzer fixed
2020-09-17 08:55:14 -07:00
divolgin 545313aa06 Merge pull request #260 from manavellamnimble/abortImagePullBackoff
Abort run collector on ImagePullBackoff
2020-09-17 08:54:39 -07:00
divolgin 1ceab64b6f Merge pull request #259 from manavellamnimble/emptyKeySecret
fixEmptyKeySecret
2020-09-17 08:50:39 -07:00
Matias Manavella eb0f692aa0 using filepath instead of path 2020-09-16 18:25:15 -03:00
Matias Manavella 2ae1b9b678 abort run collector on ImagePullBackOff 2020-09-16 18:16:32 -03:00
Matias Manavella 2f2501ac3b fix empty key in secret 2020-09-16 18:12:19 -03:00
Matias Manavella a57894962d ingres analyzer fixed 2020-09-16 18:04:46 -03:00
Bryant Hagadorn 3f48574071 Add all auth providers (#266)
* Update main.go

* Adding generic auth for OIDC
2020-09-15 15:34:10 -07:00
Matias Manavella 00bbfc0414 Abort run collector on ImagePullBackoff 2020-09-11 10:20:39 -03:00
Matias Manavella c4d4af6890 fixEmptyKeySecret 2020-09-10 13:01:31 -03:00
Salah Aldeen Al Saleh 4149e4d6ed this is an empty commit (#256) 2020-09-09 08:53:29 -07:00
Salah Aldeen Al Saleh b2cb3bf43b handle nil ImagePullSecret in collector (#255) 2020-09-09 07:41:34 -07:00
divolgin e232cc0f34 Merge pull request #253 from manavellamnimble/runImagePullSecret
Add or create ImagePullSecret  in a run collector
2020-09-08 13:59:25 -07:00
Andrew Lavery f68e0dbae0 Merge pull request #174 from replicatedhq/laverya/allow-analyzing-all-files-in-dir
allow textAnalyze to run on all files in a dir or matching a prefix
2020-09-08 14:43:58 -04:00
Andrew Lavery 0126fb7d7a Merge remote-tracking branch 'origin/master' into laverya/allow-analyzing-all-files-in-dir 2020-09-08 14:27:26 -04:00
Andrew Lavery d2bc571a18 Merge pull request #250 from manavellamnimble/defaultStorageClass
Check for Default Storage Class when storageClassName is empty
2020-09-08 14:25:36 -04:00
Matias Manavella 1f0842484c error-handling modified 2020-09-07 11:33:06 -03:00
Matias Manavella 8c31054331 error-handling modified 2020-09-07 11:21:23 -03:00
Matias Manavella 4b2e7e153e add or create ImagePullSecret 2020-09-04 12:41:47 -03:00
Marc Campbell 2289e00558 Merge pull request #252 from replicatedhq/fix-statefulset-status
Fix statefulset status
2020-09-03 14:00:16 -07:00
Marc Campbell 949f0f2213 Fix statefulset status 2020-09-03 13:48:25 -07:00
divolgin 703219119e Merge pull request #251 from replicatedhq/divolgin/v1beta2
Adding troubleshoot.sh/v1beta2
2020-09-01 15:19:19 -07:00
Matias Manavella 5522b0623c check Default SC 2020-09-01 19:07:13 -03:00
Matias Manavella 8b1ae0194e check Default SC 2020-09-01 18:36:55 -03:00
divolgin a0ce85ae1e Adding troubleshoot.sh/v1beta2 2020-09-01 19:57:11 +00:00
Matias Manavella b1e251ed03 selector:MatchLabel (#249)
* selector:MatchLabel

* selector:MatchLabel
2020-08-31 09:46:04 -07:00
Matias Manavella f7e4103d9b check Default SC 2020-08-31 11:54:49 -03:00
Matias Manavella 442c97f138 check Default SC 2020-08-31 11:52:45 -03:00
Matias Manavella 6862f0f6c1 K8s events (#248)
* K8s events collected

* collect events

* collect events
2020-08-24 14:41:38 -07:00
Matias Manavella 6ad9103f86 Copy collector enhanced (#237)
Copy collector enhanced
2020-08-24 10:57:40 -07:00
divolgin 016d6e4f18 Merge pull request #246 from manavellamnimble/add_checkNames
checkName in Distribution and Runtime
2020-08-18 15:55:31 -07:00
Matias Manavella 97db83b270 checkName in Distribution and Runtime 2020-08-18 13:57:53 -03:00
GraysonNull 20e82be101 Merge pull request #244 from GraysonNull/grayson-termui-scrolling
Add scrolling to table, use table widget for support bundle
2020-08-13 12:49:29 -05:00
GraysonNull 4e08ce4d93 remove commented code blocks, move appName func to shared package 2020-08-13 16:38:51 +00:00
GraysonNull e8dc5ae1b8 use table widget for bundle analysis so scrolling works 2020-08-13 16:02:29 +00:00
GraysonNull 0e667e9685 scrolling on analyzers table 2020-08-11 15:16:23 +00:00
Andrew Lavery 99f5e2d896 Merge pull request #238 from replicatedhq/laverya/latest-controller-gen
update controller-gen to v0.3.0
2020-08-10 17:55:45 -04:00
Andrew Lavery 407b80daf5 update controller-gen to v0.3.0
the first version to be k8s 1.18 compatible
2020-08-10 17:51:53 -04:00
Marc Campbell c84dc6de45 Merge pull request #233 from replicatedhq/support-bundle-command
Update help text to match the command
2020-08-08 06:47:45 -07:00
Matias Manavella a686e47fa2 Issue#228 (#230)
* Added timestamp to support-bundle file

* Added timestamp to support-bundle file

* Added timestamp to support-bundle file

* Added timestamp with second-level precision
2020-08-03 15:09:21 -07:00
Marc Campbell 56e58c0849 Update help text to match the command 2020-08-03 11:55:19 -07:00
GraysonNull 3b8e53bf90 Merge pull request #225 from GraysonNull/grayson/update-samples
fix invalid analyzer in sample support-bundle
2020-07-13 17:36:00 -05:00
GraysonNull cc9d3aedec move logs to collectors array 2020-07-13 22:26:17 +00:00
GraysonNull a70843cd69 add space 2020-07-13 22:07:37 +00:00
GraysonNull 08c52770a9 fix invalid analyzer in sample support-bundle 2020-07-13 22:06:37 +00:00
Mark Pundsack 52e385ffce Merge pull request #224 from replicatedhq/markpundsack-patch-1
Update URL
2020-07-13 16:24:51 -05:00
Mark Pundsack d63321bc43 Update URL 2020-07-13 16:19:01 -05:00
GraysonNull 37ae457aa1 Merge pull request #223 from GraysonNull/grayson/update-samples
update sample preflight and support bundles, update directory name
2020-07-13 14:14:30 -05:00
GraysonNull 4b1852c3e1 updates from feedback 2020-07-13 16:45:46 +00:00
GraysonNull dc80733397 update sample preflight and support bundles, update diretory name 2020-07-13 15:43:10 +00:00
GraysonNull 78e328be86 Merge pull request #219 from replicatedhq/msp-update-readme
Update readme
2020-07-07 10:50:57 -05:00
Mark Pundsack ea43d4d9b6 Simplify language 2020-07-06 12:23:16 -05:00
Mark Pundsack 8ce751f989 Update readme 2020-07-06 12:14:08 -05:00
Salah Aldeen Al Saleh f55b9b6ae4 update schemas + add redactor and supportbundle schemas (#218) 2020-06-23 10:08:59 -07:00
Andrew Lavery 079765b558 add 'no matching files' analyze result 2020-04-15 19:17:01 -04:00
Andrew Lavery 45ccd7be5b don't conglomerate results
doing so sort of breaks the 'invalid analyzer' message if no files match
2020-04-15 19:13:37 -04:00
Andrew Lavery 2bc9938a2a use filepath.Glob and filepath.Match 2020-04-15 18:59:17 -04:00
Andrew Lavery 5050f46b1e use gobwas/glob to match globs 2020-04-15 18:47:16 -04:00
Andrew Lavery 222a93162d allow textAnalyze to run on all files in a dir or matching a prefix 2020-04-15 18:08:08 -04:00
197 changed files with 28345 additions and 1252 deletions
+10 -10
View File
@@ -18,8 +18,8 @@ jobs:
- name: setup env
run: |
echo "::set-env name=GOPATH::$(go env GOPATH)"
echo "::add-path::$(go env GOPATH)/bin"
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
shell: bash
- uses: actions/checkout@v2
@@ -35,8 +35,8 @@ jobs:
go-version: '1.14'
- name: setup env
run: |
echo "::set-env name=GOPATH::$(go env GOPATH)"
echo "::add-path::$(go env GOPATH)/bin"
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
shell: bash
- uses: actions/checkout@master
- run: make preflight
@@ -54,7 +54,7 @@ jobs:
with:
name: preflight
path: bin/
- uses: engineerd/setup-kind@v0.2.0
- uses: engineerd/setup-kind@v0.5.0
- run: chmod +x bin/preflight
- run: ./bin/preflight --interactive=false --format=json https://preflight.replicated.com
@@ -67,8 +67,8 @@ jobs:
go-version: '1.14'
- name: setup env
run: |
echo "::set-env name=GOPATH::$(go env GOPATH)"
echo "::add-path::$(go env GOPATH)/bin"
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
shell: bash
- uses: actions/checkout@master
- run: make support-bundle
@@ -87,10 +87,10 @@ jobs:
with:
name: support-bundle
path: bin/
- uses: engineerd/setup-kind@v0.2.0
- uses: engineerd/setup-kind@v0.5.0
- run: chmod +x bin/support-bundle
- run: ./bin/support-bundle ./examples/troubleshoot/sample-collectors.yaml
- run: ./bin/support-bundle ./examples/troubleshoot/sample-supportbundle.yaml
- run: ./bin/support-bundle ./examples/support-bundle/sample-collectors.yaml
- run: ./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
- run: ./bin/support-bundle https://kots.io
goreleaser:
-3
View File
@@ -26,7 +26,4 @@ vendor
*.swo
*~
# scope test logs
scope-go-*.log
dist
+10 -8
View File
@@ -3,7 +3,6 @@
IMG ?= controller:latest
export GO111MODULE=on
export GOPROXY=https://proxy.golang.org
export SCOPE_LOG_ROOT_PATH=/dev/null
SHELL := /bin/bash -o pipefail
VERSION_PACKAGE = github.com/replicatedhq/troubleshoot/pkg/version
@@ -29,6 +28,7 @@ endif
define LDFLAGS
-ldflags "\
-s -w \
-X ${VERSION_PACKAGE}.version=${VERSION} \
-X ${VERSION_PACKAGE}.gitSHA=${GIT_SHA} \
-X ${VERSION_PACKAGE}.buildTime=${DATE} \
@@ -47,15 +47,15 @@ test: generate fmt vet
.PHONY: support-bundle
support-bundle: generate fmt vet
go build ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
go build -tags netgo ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
.PHONY: preflight
preflight: generate fmt vet
go build ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
go build -tags netgo ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
.PHONY: analyze
analyze: generate fmt vet
go build ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
go build -tags netgo ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
.PHONY: fmt
fmt:
@@ -74,11 +74,13 @@ generate: controller-gen client-gen
--clientset-name troubleshootclientset \
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
--input troubleshoot/v1beta1 \
--input troubleshoot/v1beta2 \
-h ./hack/boilerplate.go.txt
.PHONY: openapischema
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
.PHONY: schemas
schemas: fmt vet openapischema
@@ -88,7 +90,7 @@ schemas: fmt vet openapischema
.PHONY: contoller-gen
controller-gen:
ifeq (, $(shell which controller-gen))
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.8
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0
CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
@@ -116,7 +118,7 @@ snapshot-release:
.PHONY: local-release
local-release:
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.local.yml
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.yaml
docker tag replicated/troubleshoot:alpha localhost:32000/troubleshoot:alpha
docker tag replicated/preflight:alpha localhost:32000/preflight:alpha
docker push localhost:32000/troubleshoot:alpha
@@ -128,8 +130,8 @@ run-preflight: preflight
.PHONY: run-troubleshoot
run-troubleshoot: support-bundle
./bin/support-bundle ./examples/troubleshoot/sample-supportbundle.yaml
./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
.PHONY: run-analyze
run-analyze: analyze
./bin/analyze --analyzers ./examples/troubleshoot/sample-analyzers.yaml ./support-bundle.tar.gz
./bin/analyze --analyzers ./examples/support-bundle/sample-analyzers.yaml ./support-bundle.tar.gz
+6 -7
View File
@@ -1,8 +1,8 @@
# Replicated Troubleshoot
Replicated Troubleshoot is a framework for collecting, redacting and analyzing highly customizable diagnostic information about a Kubernetes cluster. Troubleshoot specs are created by 3rd-party application developers/maintainers and run by cluster operators in the initial and ongoing operation of those applications.
Replicated Troubleshoot is a framework for collecting, redacting, and analyzing highly customizable diagnostic information about a Kubernetes cluster. Troubleshoot specs are created by 3rd-party application developers/maintainers and run by cluster operators in the initial and ongoing operation of those applications.
Troubleshoot provides two CLI tools as kubectl plugins (using [Krew](https://krew.dev)) `kubectl preflight` and `kubectl support-bundle`. Preflight provides pre-installation cluster conformance testing and validation (preflight checks) and support-bundle provides post-installation troubleshooting and diagnostics (support bundles).
Troubleshoot provides two CLI tools as kubectl plugins (using [Krew](https://krew.dev)): `kubectl preflight` and `kubectl support-bundle`. Preflight provides pre-installation cluster conformance testing and validation (preflight checks) and support-bundle provides post-installation troubleshooting and diagnostics (support bundles).
## Preflight Checks
Preflight checks are an easy-to-run set of conformance tests that can be written to verify that specific requirements in a cluster are met.
@@ -18,11 +18,11 @@ curl https://krew.sh/preflight | bash
kubectl preflight https://preflight.replicated.com
```
For a details on creating the custom resource files that drive preflight checks, visit [creating preflight checks](https://troubleshoot.sh/docs/preflight/creating/).
For a details on creating the custom resource files that drive preflight checks, visit [creating preflight checks](https://troubleshoot.sh/docs/preflight/introduction/).
## Support Bundle
A support bundle is an archive that's created in-cluster, by collecting logs, cluster information and executing specified commands (including redaction of sensitive information). After creating a support bundle, the cluster operator will normally deliver it to the 3rd-party application vendor for analysis and disconnected debugging. Another Replicated project, [Kotsadm](https://github.com/replicatedhq/kotsadm), provides cluster operators with in-cluster UI for processing support bundles and viewing analyzers (as well as support bundle collection).
A support bundle is an archive that's created in-cluster, by collecting logs and cluster information, and executing specified commands (including redaction of sensitive information). After creating a support bundle, the cluster operator will normally deliver it to the 3rd-party application vendor for analysis and disconnected debugging. Another Replicated project, [Kotsadm](https://github.com/replicatedhq/kotsadm), provides cluster operators with an in-cluster UI for processing support bundles and viewing analyzers (as well as support bundle collection).
To collect a sample support bundle, install the troubleshoot kubectl plugin:
@@ -32,11 +32,10 @@ curl https://krew.sh/support-bundle | bash
and run:
```shell
kubectl support-bundle https://troubleshoot.replicated.com
kubectl support-bundle https://support-bundle.replicated.com
```
For details on creating the custom resource files that drive support-bundle collection [creating collectors](https://troubleshoot.sh/reference/collectors/overview/) and for analyzers [creating analyzers](https://troubleshoot.sh/reference/analyzers/overview/).
For 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/).
# 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).
+2 -7
View File
@@ -4,14 +4,10 @@ import (
"os"
"strings"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var (
KubernetesConfigFlags *genericclioptions.ConfigFlags
)
func RootCmd() *cobra.Command {
@@ -41,8 +37,7 @@ func RootCmd() *cobra.Command {
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
KubernetesConfigFlags.AddFlags(cmd.Flags())
k8sutil.AddFlags(cmd.Flags())
return cmd
}
+1 -1
View File
@@ -2,7 +2,7 @@ package main
import (
"github.com/replicatedhq/troubleshoot/cmd/analyze/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
func main() {
+9 -22
View File
@@ -5,18 +5,18 @@ import (
"io/ioutil"
"os"
"path"
"strings"
"time"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
"github.com/pkg/errors"
ui "github.com/replicatedhq/termui/v3"
"github.com/replicatedhq/termui/v3/widgets"
"github.com/replicatedhq/troubleshoot/cmd/util"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
)
var (
selectedResult = 0
table = widgets.NewTable()
isShowingSaved = false
)
@@ -64,7 +64,9 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
selectedResult++
} else {
selectedResult = 0
table.SelectedRow = 0
}
table.ScrollDown()
ui.Clear()
drawUI(preflightName, analyzeResults)
case "<Up>":
@@ -72,7 +74,9 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
selectedResult--
} else {
selectedResult = len(analyzeResults) - 1
table.SelectedRow = len(analyzeResults)
}
table.ScrollUp()
ui.Clear()
drawUI(preflightName, analyzeResults)
}
@@ -95,7 +99,7 @@ func drawHeader(preflightName string) {
termWidth, _ := ui.TerminalDimensions()
title := widgets.NewParagraph()
title.Text = fmt.Sprintf("%s Preflight Checks", appName(preflightName))
title.Text = fmt.Sprintf("%s Preflight Checks", util.AppName(preflightName))
title.TextStyle.Fg = ui.ColorWhite
title.TextStyle.Bg = ui.ColorClear
title.TextStyle.Modifier = ui.ModifierBold
@@ -127,7 +131,6 @@ func drawFooter() {
func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
termWidth, termHeight := ui.TerminalDimensions()
table := widgets.NewTable()
table.SetRect(0, 3, termWidth/2, termHeight-6)
table.FillRow = true
table.Border = true
@@ -221,7 +224,7 @@ func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (
os.Remove(filename)
}
results := fmt.Sprintf("%s Preflight Checks\n\n", appName(preflightName))
results := fmt.Sprintf("%s Preflight Checks\n\n", util.AppName(preflightName))
for _, analyzeResult := range analyzeResults {
result := ""
@@ -270,19 +273,3 @@ func showSaved(filename string) {
isShowingSaved = true
}
func appName(preflightName string) string {
words := strings.Split(strings.Title(strings.Replace(preflightName, "-", " ", -1)), " ")
casedWords := []string{}
for i, word := range words {
if strings.ToLower(word) == "ai" {
casedWords = append(casedWords, "AI")
} else if strings.ToLower(word) == "io" && i > 0 {
casedWords[i-1] += ".io"
} else {
casedWords = append(casedWords, word)
}
}
return strings.Join(casedWords, " ")
}
+4 -7
View File
@@ -4,13 +4,9 @@ import (
"os"
"strings"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var (
KubernetesConfigFlags *genericclioptions.ConfigFlags
)
func RootCmd() *cobra.Command {
@@ -39,11 +35,12 @@ that a cluster meets the requirements to run an application.`,
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", false, "always run preflight checks even if some require permissions that preflight does not have")
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.")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
KubernetesConfigFlags.AddFlags(cmd.Flags())
k8sutil.AddFlags(cmd.Flags())
return cmd
}
+146 -38
View File
@@ -5,17 +5,22 @@ import (
"io/ioutil"
"net/http"
"os"
"strings"
"time"
cursor "github.com/ahmetalpbalkan/go-cursor"
"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/cmd/util"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
"github.com/replicatedhq/troubleshoot/pkg/specs"
"github.com/spf13/viper"
spin "github.com/tj/go-spin"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
)
@@ -23,15 +28,28 @@ func runPreflights(v *viper.Viper, arg string) error {
fmt.Print(cursor.Hide())
defer fmt.Print(cursor.Show())
preflightContent := ""
var preflightContent []byte
var err error
if _, err = os.Stat(arg); err == nil {
if strings.HasPrefix(arg, "secret/") {
// format secret/namespace-name/secret-name
pathParts := strings.Split(arg, "/")
if len(pathParts) != 3 {
return errors.Errorf("path %s must have 3 components", arg)
}
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "preflight-spec")
if err != nil {
return errors.Wrap(err, "failed to get spec from secret")
}
preflightContent = spec
} else if _, err = os.Stat(arg); err == nil {
b, err := ioutil.ReadFile(arg)
if err != nil {
return err
}
preflightContent = string(b)
preflightContent = b
} else {
if !util.IsURL(arg) {
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
@@ -41,7 +59,7 @@ func runPreflights(v *viper.Viper, arg string) error {
if err != nil {
return err
}
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta1")
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
@@ -53,7 +71,12 @@ func runPreflights(v *viper.Viper, arg string) error {
return err
}
preflightContent = string(body)
preflightContent = body
}
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
if err != nil {
return errors.Wrap(err, "failed to convert to v1beta2")
}
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
@@ -63,15 +86,16 @@ func runPreflights(v *viper.Viper, arg string) error {
return errors.Wrapf(err, "failed to parse %s", arg)
}
preflightSpec := obj.(*troubleshootv1beta1.Preflight)
var collectResults preflight.CollectResult
preflightSpecName := ""
finishedCh := make(chan bool, 1)
progressCh := make(chan interface{}, 0) // non-zero buffer will result in missed messages
s := spin.New()
finishedCh := make(chan bool, 1)
progressChan := make(chan interface{}, 0) // non-zero buffer will result in missed messages
go func() {
for {
select {
case msg, ok := <-progressChan:
case msg, ok := <-progressCh:
if !ok {
continue
}
@@ -91,40 +115,40 @@ func runPreflights(v *viper.Viper, arg string) error {
}
}
}()
defer func() {
close(finishedCh)
close(progressCh)
}()
restConfig, err := KubernetesConfigFlags.ToRESTConfig()
if err != nil {
return errors.Wrap(err, "failed to convert kube flags to rest config")
}
collectOpts := preflight.CollectOpts{
Namespace: v.GetString("namespace"),
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
ProgressChan: progressChan,
KubernetesRestConfig: restConfig,
}
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
if err != nil {
if !collectResults.IsRBACAllowed {
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, collectResults.Collectors)
if err != nil {
progressChan <- err
}
}
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
r, err := collectInCluster(preflightSpec, finishedCh, progressCh)
if err != nil {
return errors.Wrap(err, "failed to collect in cluster")
}
return err
collectResults = *r
preflightSpecName = preflightSpec.Name
} else if hostPreflightSpec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
r, err := collectHost(hostPreflightSpec, finishedCh, progressCh)
if err != nil {
return errors.Wrap(err, "failed to collect from host")
}
collectResults = *r
preflightSpecName = hostPreflightSpec.Name
}
if collectResults == nil {
return errors.New("no results")
}
analyzeResults := collectResults.Analyze()
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
if err != nil {
progressChan <- err
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
if preflightSpec.Spec.UploadResultsTo != "" {
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
if err != nil {
progressCh <- err
}
}
}
@@ -134,8 +158,92 @@ func runPreflights(v *viper.Viper, arg string) error {
if len(analyzeResults) == 0 {
return errors.New("no data has been collected")
}
return showInteractiveResults(preflightSpec.Name, analyzeResults)
return showInteractiveResults(preflightSpecName, analyzeResults)
}
return showStdoutResults(v.GetString("format"), preflightSpec.Name, analyzeResults)
return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults)
}
func collectInCluster(preflightSpec *troubleshootv1beta2.Preflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
v := viper.GetViper()
restConfig, err := k8sutil.GetRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
collectOpts := preflight.CollectOpts{
Namespace: v.GetString("namespace"),
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
ProgressChan: progressCh,
KubernetesRestConfig: restConfig,
}
if v.GetString("since") != "" || v.GetString("since-time") != "" {
err := parseTimeFlags(v, progressCh, preflightSpec.Spec.Collectors)
if err != nil {
return nil, err
}
}
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
if err != nil {
if !collectResults.IsRBACAllowed() {
if preflightSpec.Spec.UploadResultsTo != "" {
clusterCollectResults := collectResults.(preflight.ClusterCollectResult)
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, clusterCollectResults.Collectors)
if err != nil {
progressCh <- err
}
}
}
return nil, err
}
return &collectResults, nil
}
func collectHost(hostPreflightSpec *troubleshootv1beta2.HostPreflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
collectOpts := preflight.CollectOpts{
ProgressChan: progressCh,
}
collectResults, err := preflight.CollectHost(collectOpts, hostPreflightSpec)
if err != nil {
return nil, errors.Wrap(err, "failed to collect from host")
}
return &collectResults, nil
}
func parseTimeFlags(v *viper.Viper, progressChan chan interface{}, collectors []*troubleshootv1beta2.Collect) error {
var (
sinceTime time.Time
err error
)
if v.GetString("since-time") != "" {
if v.GetString("since") != "" {
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
}
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
if err != nil {
return errors.Wrap(err, "unable to parse --since-time flag")
}
} else {
parsedDuration, err := time.ParseDuration(v.GetString("since"))
if err != nil {
return errors.Wrap(err, "unable to parse --since flag")
}
now := time.Now()
sinceTime = now.Add(0 - parsedDuration)
}
for _, collector := range collectors {
if collector.Logs != nil {
if collector.Logs.Limits == nil {
collector.Logs.Limits = new(troubleshootv1beta2.LogLimits)
}
collector.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
}
}
return nil
}
-21
View File
@@ -1,21 +0,0 @@
package cli
import (
"github.com/pkg/errors"
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func createTroubleshootK8sClient(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
config, err := configFlags.ToRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "failed to create troubleshoot client")
}
return troubleshootClient, nil
}
+1 -1
View File
@@ -2,7 +2,7 @@ package main
import (
"github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
func main() {
+52 -20
View File
@@ -62,28 +62,60 @@ func generateSchemas(v *viper.Viper) error {
return errors.Wrap(err, "failed to get workdir")
}
preflightContents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", "troubleshoot.replicated.com_preflights.yaml"))
if err != nil {
return errors.Wrap(err, "failed to read preflight crd")
}
if err := generateSchemaFromCRD(preflightContents, filepath.Join(workdir, v.GetString("output-dir"), "preflight-troubleshoot-v1beta1.json")); err != nil {
return errors.Wrap(err, "failed to write preflight schema")
files := []struct {
inFilename string
outFilename string
}{
{
"troubleshoot.replicated.com_preflights.yaml",
"preflight-troubleshoot-v1beta1.json",
},
{
"troubleshoot.replicated.com_analyzers.yaml",
"analyzer-troubleshoot-v1beta1.json",
},
{
"troubleshoot.replicated.com_collectors.yaml",
"collector-troubleshoot-v1beta1.json",
},
{
"troubleshoot.replicated.com_redactors.yaml",
"redactor-troubleshoot-v1beta1.json",
},
{
"troubleshoot.replicated.com_supportbundles.yaml",
"supportbundle-troubleshoot-v1beta1.json",
},
{
"troubleshoot.sh_analyzers.yaml",
"analyzer-troubleshoot-v1beta2.json",
},
{
"troubleshoot.sh_collectors.yaml",
"collector-troubleshoot-v1beta2.json",
},
{
"troubleshoot.sh_preflights.yaml",
"preflight-troubleshoot-v1beta2.json",
},
{
"troubleshoot.sh_redactors.yaml",
"redactor-troubleshoot-v1beta2.json",
},
{
"troubleshoot.sh_supportbundles.yaml",
"supportbundle-troubleshoot-v1beta2.json",
},
}
analyzersContents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", "troubleshoot.replicated.com_analyzers.yaml"))
if err != nil {
return errors.Wrap(err, "failed to read analyzers crd")
}
if err := generateSchemaFromCRD(analyzersContents, filepath.Join(workdir, v.GetString("output-dir"), "analyzer-troubleshoot-v1beta1.json")); err != nil {
return errors.Wrap(err, "failed to write analyzers schema")
}
collectorsContents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", "troubleshoot.replicated.com_collectors.yaml"))
if err != nil {
return errors.Wrap(err, "failed to read collectors crd")
}
if err := generateSchemaFromCRD(collectorsContents, filepath.Join(workdir, v.GetString("output-dir"), "collector-troubleshoot-v1beta1.json")); err != nil {
return errors.Wrap(err, "failed to write collectors schema")
for _, file := range files {
contents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", file.inFilename))
if err != nil {
return errors.Wrapf(err, "failed to read crd from %s", file.inFilename)
}
if err := generateSchemaFromCRD(contents, filepath.Join(workdir, v.GetString("output-dir"), file.outFilename)); err != nil {
return errors.Wrapf(err, "failed to write crd schema to %s", file.outFilename)
}
}
return nil
+121 -47
View File
@@ -7,24 +7,25 @@ import (
"path"
"time"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
"github.com/pkg/errors"
ui "github.com/replicatedhq/termui/v3"
"github.com/replicatedhq/termui/v3/widgets"
"github.com/replicatedhq/troubleshoot/cmd/util"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
)
var (
selectedResult = 0
table = widgets.NewTable()
isShowingSaved = false
)
func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error {
func showInteractiveResults(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
if err := ui.Init(); err != nil {
return errors.Wrap(err, "failed to create terminal ui")
}
defer ui.Close()
drawUI(analyzeResults)
drawUI(supportBundleName, analyzeResults)
uiEvents := ui.PollEvents()
for {
@@ -37,7 +38,7 @@ func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error
if isShowingSaved == true {
isShowingSaved = false
ui.Clear()
drawUI(analyzeResults)
drawUI(supportBundleName, analyzeResults)
} else {
return nil
}
@@ -51,79 +52,63 @@ func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error
time.Sleep(time.Second * 5)
isShowingSaved = false
ui.Clear()
drawUI(analyzeResults)
drawUI(supportBundleName, analyzeResults)
}()
}
case "<Resize>":
ui.Clear()
drawUI(analyzeResults)
drawUI(supportBundleName, analyzeResults)
case "<Down>":
if selectedResult < len(analyzeResults)-1 {
selectedResult++
} else {
selectedResult = 0
table.SelectedRow = 0
}
table.ScrollDown()
ui.Clear()
drawUI(analyzeResults)
drawUI(supportBundleName, analyzeResults)
case "<Up>":
if selectedResult > 0 {
selectedResult--
} else {
selectedResult = len(analyzeResults) - 1
table.SelectedRow = len(analyzeResults)
}
table.ScrollUp()
ui.Clear()
drawUI(analyzeResults)
drawUI(supportBundleName, analyzeResults)
}
}
}
}
func drawUI(analyzeResults []*analyzerunner.AnalyzeResult) {
func drawUI(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) {
drawGrid(analyzeResults)
drawHeader(supportBundleName)
drawFooter()
}
func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
drawAnalyzersTable(analyzeResults)
drawDetails(analyzeResults[selectedResult])
}
func drawHeader(supportBundleName string) {
termWidth, _ := ui.TerminalDimensions()
tileWidth := 40
tileHeight := 10
title := widgets.NewParagraph()
title.Text = fmt.Sprintf("%s Support Bundle Analysis", util.AppName(supportBundleName))
title.TextStyle.Fg = ui.ColorWhite
title.TextStyle.Bg = ui.ColorClear
title.TextStyle.Modifier = ui.ModifierBold
title.Border = false
columnCount := termWidth / tileWidth
left := termWidth/2 - 2*len(title.Text)/3
right := termWidth/2 + (termWidth/2 - left)
row := 0
col := 0
for _, analyzeResult := range analyzeResults {
// draw this file
tile := widgets.NewParagraph()
tile.Title = analyzeResult.Title
tile.Text = analyzeResult.Message
tile.PaddingLeft = 1
tile.PaddingBottom = 1
tile.PaddingRight = 1
tile.PaddingTop = 1
tile.SetRect(col*tileWidth, row*tileHeight, col*tileWidth+tileWidth, row*tileHeight+tileHeight)
if analyzeResult.IsFail {
tile.BorderStyle.Fg = ui.ColorRed
} else if analyzeResult.IsWarn {
tile.BorderStyle.Fg = ui.ColorYellow
} else {
tile.BorderStyle.Fg = ui.ColorGreen
}
ui.Render(tile)
col++
if col >= columnCount {
col = 0
row++
}
}
title.SetRect(left, 0, right, 1)
ui.Render(title)
}
func drawFooter() {
@@ -142,11 +127,100 @@ func drawFooter() {
ui.Render(instructions)
}
func drawAnalyzersTable(analyzeResults []*analyzerunner.AnalyzeResult) {
termWidth, termHeight := ui.TerminalDimensions()
table.SetRect(0, 3, termWidth/2, termHeight-6)
table.FillRow = true
table.Border = true
table.Rows = [][]string{}
table.ColumnWidths = []int{termWidth}
for i, analyzeResult := range analyzeResults {
title := analyzeResult.Title
if analyzeResult.IsPass {
title = fmt.Sprintf("✔ %s", title)
} else if analyzeResult.IsWarn {
title = fmt.Sprintf("⚠️ %s", title)
} else if analyzeResult.IsFail {
title = fmt.Sprintf("✘ %s", title)
}
table.Rows = append(table.Rows, []string{
title,
})
if analyzeResult.IsPass {
if i == selectedResult {
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierReverse)
} else {
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear)
}
} else if analyzeResult.IsWarn {
if i == selectedResult {
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierReverse)
} else {
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear)
}
} else if analyzeResult.IsFail {
if i == selectedResult {
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierReverse)
} else {
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear)
}
}
}
ui.Render(table)
}
func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
termWidth, _ := ui.TerminalDimensions()
currentTop := 4
title := widgets.NewParagraph()
title.Text = analysisResult.Title
title.Border = false
if analysisResult.IsPass {
title.TextStyle = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierBold)
} else if analysisResult.IsWarn {
title.TextStyle = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierBold)
} else if analysisResult.IsFail {
title.TextStyle = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierBold)
}
height := estimateNumberOfLines(title.Text, termWidth/2)
title.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(title)
currentTop = currentTop + height + 1
message := widgets.NewParagraph()
message.Text = analysisResult.Message
message.Border = false
height = estimateNumberOfLines(message.Text, termWidth/2) + 2
message.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(message)
currentTop = currentTop + height + 1
if analysisResult.URI != "" {
uri := widgets.NewParagraph()
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
uri.Border = false
height = estimateNumberOfLines(uri.Text, termWidth/2)
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(uri)
currentTop = currentTop + height + 1
}
}
func estimateNumberOfLines(text string, width int) int {
lines := len(text)/width + 1
return lines
}
func showSaved(filename string) {
termWidth, termHeight := ui.TerminalDimensions()
savedMessage := widgets.NewParagraph()
savedMessage.Text = fmt.Sprintf("Preflight results saved to\n\n%s", filename)
savedMessage.Text = fmt.Sprintf("Support Bundle analysis results saved to\n\n%s", filename)
savedMessage.WrapText = true
savedMessage.Border = true
+11 -14
View File
@@ -5,24 +5,20 @@ import (
"os"
"strings"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
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"
"github.com/spf13/viper"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var (
KubernetesConfigFlags *genericclioptions.ConfigFlags
)
func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "troubleshoot [url]",
Use: "support-bundle [url]",
Args: cobra.MinimumNArgs(1),
Short: "Generate and manage support bundles",
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 server.`,
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
SilenceUsage: true,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
@@ -42,18 +38,19 @@ from a server that can be used to assist when troubleshooting a server.`,
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
cmd.Flags().Bool("collect-without-permissions", false, "always run troubleshoot collectors even if some require permissions that troubleshoot does not have")
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
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.")
// hidden in favor of the `insecure-skip-tls-verify` flag
cmd.Flags().Bool("allow-insecure-connections", false, "don't verify TLS certs when retrieving spec and reporting results")
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")
viper.BindPFlags(cmd.Flags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
KubernetesConfigFlags.AddFlags(cmd.Flags())
k8sutil.AddFlags(cmd.Flags())
return cmd
}
@@ -69,7 +66,7 @@ func initConfig() {
viper.AutomaticEnv()
}
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
for _, inList := range list {
if collector.ClusterResources != nil && inList.ClusterResources != nil {
return list
+324 -105
View File
@@ -1,16 +1,20 @@
package cli
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"time"
@@ -18,16 +22,18 @@ import (
"github.com/fatih/color"
"github.com/manifoldco/promptui"
"github.com/mattn/go-isatty"
"github.com/mholt/archiver"
"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/cmd/util"
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/convert"
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/redact"
"github.com/replicatedhq/troubleshoot/pkg/specs"
"github.com/spf13/viper"
spin "github.com/tj/go-spin"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -54,7 +60,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
return errors.Wrap(err, "failed to load collector spec")
}
multidocs := strings.Split(string(collectorContent), "---")
multidocs := strings.Split(string(collectorContent), "\n---\n")
// we suppory both raw collector kinds and supportbundle kinds here
supportBundleSpec, err := parseSupportBundleFromDoc([]byte(multidocs[0]))
@@ -65,31 +71,42 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
decode := scheme.Codecs.UniversalDeserializer().Decode
additionalRedactors := &troubleshootv1beta1.Redactor{}
additionalRedactors := &troubleshootv1beta2.Redactor{}
for idx, redactor := range v.GetStringSlice("redactors") {
redactorContent, err := loadSpec(v, redactor)
if err != nil {
return errors.Wrapf(err, "failed to load redactor spec #%d", idx)
}
redactorContent, err = docrewrite.ConvertToV1Beta2(redactorContent)
if err != nil {
return errors.Wrap(err, "failed to convert to v1beta2")
}
obj, _, err := decode([]byte(redactorContent), nil, nil)
if err != nil {
return errors.Wrapf(err, "failed to parse redactors %s", redactor)
}
loopRedactors, ok := obj.(*troubleshootv1beta1.Redactor)
loopRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
if !ok {
return fmt.Errorf("%s is not a troubleshootv1beta1 redactor type", redactor)
return fmt.Errorf("%s is not a troubleshootv1beta2 redactor type", redactor)
}
if loopRedactors != nil {
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, loopRedactors.Spec.Redactors...)
}
}
for i, additionalDoc := range multidocs[1:] {
obj, _, err := decode([]byte(additionalDoc), nil, nil)
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.(*troubleshootv1beta1.Redactor)
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
if !ok {
continue
}
@@ -189,7 +206,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
close(finishedCh) // this removes the spinner
isFinishedChClosed = true
if err := showInteractiveResults(analyzeResults); err != nil {
if err := showInteractiveResults(supportBundleSpec.Name, analyzeResults); err != nil {
interactive = false
}
}
@@ -232,6 +249,21 @@ the %s Admin Console to begin analysis.`
func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
var err error
if strings.HasPrefix(arg, "secret/") {
// format secret/namespace-name/secret-name
pathParts := strings.Split(arg, "/")
if len(pathParts) != 3 {
return nil, errors.Errorf("path %s must have 3 components", arg)
}
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "support-bundle-spec")
if err != nil {
return nil, errors.Wrap(err, "failed to get spec from secret")
}
return spec, nil
}
if _, err = os.Stat(arg); err == nil {
b, err := ioutil.ReadFile(arg)
if err != nil {
@@ -243,6 +275,14 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
return nil, fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
}
spec, err := loadSpecFromURL(v, arg)
if err != nil {
return nil, errors.Wrap(err, "failed to get spec from URL")
}
return spec, nil
}
func loadSpecFromURL(v *viper.Viper, arg string) ([]byte, error) {
for {
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
@@ -252,10 +292,7 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
req.Header.Set("Bundle-Upload-Host", fmt.Sprintf("%s://%s", req.URL.Scheme, req.URL.Host))
resp, err := httpClient.Do(req)
if err != nil {
if strings.Contains(err.Error(), "x509") && httpClient == http.DefaultClient && canTryInsecure(v) {
httpClient = &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
if shouldRetryRequest(err) {
continue
}
return nil, errors.Wrap(err, "execute request")
@@ -271,7 +308,12 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
}
}
func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle, error) {
func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta2.SupportBundle, error) {
doc, err := docrewrite.ConvertToV1Beta2(doc)
if err != nil {
return nil, errors.Wrap(err, "failed to convert to v1beta2")
}
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
decode := scheme.Codecs.UniversalDeserializer().Decode
@@ -280,17 +322,17 @@ func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle,
return nil, errors.Wrap(err, "failed to parse document")
}
collector, ok := obj.(*troubleshootv1beta1.Collector)
collector, ok := obj.(*troubleshootv1beta2.Collector)
if ok {
supportBundle := troubleshootv1beta1.SupportBundle{
supportBundle := troubleshootv1beta2.SupportBundle{
TypeMeta: metav1.TypeMeta{
APIVersion: "troubleshoot.replicated.com/v1beta1",
APIVersion: "troubleshoot.sh/v1beta2",
Kind: "SupportBundle",
},
ObjectMeta: collector.ObjectMeta,
Spec: troubleshootv1beta1.SupportBundleSpec{
Spec: troubleshootv1beta2.SupportBundleSpec{
Collectors: collector.Spec.Collectors,
Analyzers: []*troubleshootv1beta1.Analyze{},
Analyzers: []*troubleshootv1beta2.Analyze{},
AfterCollection: collector.Spec.AfterCollection,
},
}
@@ -298,7 +340,7 @@ func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle,
return &supportBundle, nil
}
supportBundle, ok := obj.(*troubleshootv1beta1.SupportBundle)
supportBundle, ok := obj.(*troubleshootv1beta2.SupportBundle)
if ok {
return supportBundle, nil
}
@@ -306,7 +348,17 @@ func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle,
return nil, errors.New("spec was not parseable as a troubleshoot kind")
}
func canTryInsecure(v *viper.Viper) bool {
func shouldRetryRequest(err error) bool {
if strings.Contains(err.Error(), "x509") && httpClient == http.DefaultClient && canTryInsecure() {
httpClient = &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
return true
}
return false
}
func canTryInsecure() bool {
if !isatty.IsTerminal(os.Stdout.Fd()) {
return false
}
@@ -323,23 +375,33 @@ func canTryInsecure(v *viper.Viper) bool {
return true
}
func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, additionalRedactors *troubleshootv1beta1.Redactor, progressChan chan interface{}) (string, error) {
bundlePath, err := ioutil.TempDir("", "troubleshoot")
func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta2.Collect, additionalRedactors *troubleshootv1beta2.Redactor, progressChan chan interface{}) (string, error) {
tmpDir, err := ioutil.TempDir("", "troubleshoot")
if err != nil {
return "", errors.Wrap(err, "create temp dir")
}
defer os.RemoveAll(bundlePath)
defer os.RemoveAll(tmpDir)
filename, err := findFileName("support-bundle-"+time.Now().Format("2006-01-02T15_04_05"), "tar.gz")
if err != nil {
return "", errors.Wrap(err, "find file name")
}
bundlePath := filepath.Join(tmpDir, strings.TrimSuffix(filename, ".tar.gz"))
if err := os.MkdirAll(bundlePath, 0777); err != nil {
return "", errors.Wrap(err, "create bundle dir")
}
if err = writeVersionFile(bundlePath); err != nil {
return "", errors.Wrap(err, "write version file")
}
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
collectSpecs := make([]*troubleshootv1beta2.Collect, 0, 0)
collectSpecs = append(collectSpecs, collectors...)
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}})
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}})
config, err := KubernetesConfigFlags.ToRESTConfig()
config, err := k8sutil.GetRESTConfig()
if err != nil {
return "", errors.Wrap(err, "failed to convert kube flags to rest config")
}
@@ -351,6 +413,7 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
Collect: desiredCollector,
ClientConfig: config,
Namespace: v.GetString("namespace"),
PathPrefix: filepath.Base(bundlePath),
}
cleanedCollectors = append(cleanedCollectors, &collector)
}
@@ -371,10 +434,16 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
return "", errors.New("insufficient permissions to run all collectors")
}
globalRedactors := []*troubleshootv1beta1.Redact{}
globalRedactors := []*troubleshootv1beta2.Redact{}
if additionalRedactors != nil {
globalRedactors = additionalRedactors.Spec.Redactors
}
if v.GetString("since-time") != "" || v.GetString("since") != "" {
err := parseTimeFlags(v, progressChan, &cleanedCollectors)
if err != nil {
return "", err
}
}
// Run preflights collectors synchronously
for _, collector := range cleanedCollectors {
@@ -395,7 +464,8 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
}
if result != nil {
err = saveCollectorOutput(result, bundlePath)
// results already contain the bundle dir name in their paths
err = saveCollectorOutput(result, filepath.Dir(bundlePath), collector)
if err != nil {
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
continue
@@ -403,11 +473,6 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
}
}
filename, err := findFileName("support-bundle", "tar.gz")
if err != nil {
return "", errors.Wrap(err, "find file name")
}
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
return "", errors.Wrap(err, "create bundle file")
}
@@ -415,8 +480,15 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
return filename, nil
}
func saveCollectorOutput(output map[string][]byte, bundlePath string) error {
func saveCollectorOutput(output map[string][]byte, bundlePath string, c *collect.Collector) error {
for filename, maybeContents := range output {
if c.Collect.Copy != nil {
err := untarAndSave(maybeContents, filepath.Join(bundlePath, filepath.Dir(filename)))
if err != nil {
return errors.Wrap(err, "extract copied files")
}
continue
}
fileDir, fileName := filepath.Split(filename)
outPath := filepath.Join(bundlePath, fileDir)
@@ -432,39 +504,99 @@ func saveCollectorOutput(output map[string][]byte, bundlePath string) error {
return nil
}
func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
func untarAndSave(tarFile []byte, bundlePath string) error {
keys := make([]string, 0)
dirs := make(map[string]*tar.Header)
files := make(map[string][]byte)
fileHeaders := make(map[string]*tar.Header)
tarReader := tar.NewReader(bytes.NewBuffer(tarFile))
//Extract and separate tar contentes in file and folders, keeping header info from each one.
for {
header, err := tarReader.Next()
if err != nil {
if err != io.EOF {
return err
}
break
}
switch header.Typeflag {
case tar.TypeDir:
dirs[header.Name] = header
case tar.TypeReg:
file := new(bytes.Buffer)
_, err = io.Copy(file, tarReader)
if err != nil {
return err
}
files[header.Name] = file.Bytes()
fileHeaders[header.Name] = header
default:
return fmt.Errorf("Tar file entry %s contained unsupported file type %v", header.Name, header.FileInfo().Mode())
}
}
//Create directories from base path: <namespace>/<pod name>/containerPath
if err := os.MkdirAll(filepath.Join(bundlePath), 0777); err != nil {
return errors.Wrap(err, "create output file")
}
//Order folders stored in variable keys to start always by parent folder. That way folder info is preserved.
for k := range dirs {
keys = append(keys, k)
}
sort.Strings(keys)
//Orderly create folders.
for _, k := range keys {
if err := os.Mkdir(filepath.Join(bundlePath, k), dirs[k].FileInfo().Mode().Perm()); err != nil {
return errors.Wrap(err, "create output file")
}
}
//Populate folders with respective files and its permissions stored in the header.
for k, v := range files {
if err := ioutil.WriteFile(filepath.Join(bundlePath, k), v, fileHeaders[k].FileInfo().Mode().Perm()); err != nil {
return err
}
}
return nil
}
func uploadSupportBundle(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
contentType := getExpectedContentType(r.URI)
if contentType != "" && contentType != "application/tar+gzip" {
return fmt.Errorf("cannot upload content type %s", contentType)
}
f, err := os.Open(archivePath)
if err != nil {
return errors.Wrap(err, "open file")
}
defer f.Close()
for {
f, err := os.Open(archivePath)
if err != nil {
return errors.Wrap(err, "open file")
}
defer f.Close()
fileStat, err := f.Stat()
if err != nil {
return errors.Wrap(err, "stat file")
}
fileStat, err := f.Stat()
if err != nil {
return errors.Wrap(err, "stat file")
}
req, err := http.NewRequest(r.Method, r.URI, f)
if err != nil {
return errors.Wrap(err, "create request")
}
req.ContentLength = fileStat.Size()
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
req, err := http.NewRequest(r.Method, r.URI, f)
if err != nil {
return errors.Wrap(err, "create request")
}
req.ContentLength = fileStat.Size()
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
resp, err := httpClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
resp, err := httpClient.Do(req)
if err != nil {
if shouldRetryRequest(err) {
continue
}
return errors.Wrap(err, "execute request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
break
}
// send redaction report
@@ -478,19 +610,26 @@ func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath strin
return errors.Wrap(err, "get redaction report")
}
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
if err != nil {
return errors.Wrap(err, "create redaction report request")
}
req.ContentLength = int64(len(redactBytes))
for {
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
if err != nil {
return errors.Wrap(err, "create redaction report request")
}
req.ContentLength = int64(len(redactBytes))
resp, err := httpClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute redaction request")
}
resp, err := httpClient.Do(req)
if err != nil {
if shouldRetryRequest(err) {
continue
}
return errors.Wrap(err, "execute redaction request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
}
break
}
}
@@ -505,54 +644,134 @@ func getExpectedContentType(uploadURL string) string {
return parsedURL.Query().Get("Content-Type")
}
func callbackSupportBundleAPI(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
req, err := http.NewRequest(r.Method, r.URI, nil)
if err != nil {
return errors.Wrap(err, "create request")
}
func callbackSupportBundleAPI(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
for {
req, err := http.NewRequest(r.Method, r.URI, nil)
if err != nil {
return errors.Wrap(err, "create request")
}
resp, err := httpClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
resp, err := httpClient.Do(req)
if err != nil {
if shouldRetryRequest(err) {
continue
}
return errors.Wrap(err, "execute request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
break
}
return nil
}
func tarSupportBundleDir(inputDir, outputFilename string) error {
tarGz := archiver.TarGz{
Tar: &archiver.Tar{
ImplicitTopLevelFolder: false,
},
}
paths := []string{
filepath.Join(inputDir, VersionFilename), // version file should be first in tar archive for quick extraction
}
topLevelFiles, err := ioutil.ReadDir(inputDir)
fileWriter, err := os.Create(outputFilename)
if err != nil {
return errors.Wrap(err, "list bundle directory contents")
}
for _, f := range topLevelFiles {
if f.Name() == VersionFilename {
continue
}
paths = append(paths, filepath.Join(inputDir, f.Name()))
return errors.Wrap(err, "failed to create output file")
}
defer fileWriter.Close()
if err := tarGz.Archive(paths, outputFilename); err != nil {
return errors.Wrap(err, "create archive")
gzipWriter := gzip.NewWriter(fileWriter)
defer gzipWriter.Close()
tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()
err = filepath.Walk(inputDir, func(filename string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fileMode := info.Mode()
if !fileMode.IsRegular() { // support bundle can have only files
return nil
}
parentDirName := filepath.Dir(inputDir) // this is to have the files inside a subdirectory
nameInArchive, err := filepath.Rel(parentDirName, filename)
if err != nil {
return errors.Wrap(err, "failed to create relative file name")
}
// tar.FileInfoHeader call causes a crash in static builds
// https://github.com/golang/go/issues/24787
hdr := &tar.Header{
Name: nameInArchive,
ModTime: info.ModTime(),
Mode: int64(fileMode.Perm()),
Typeflag: tar.TypeReg,
Size: info.Size(),
}
err = tarWriter.WriteHeader(hdr)
if err != nil {
return errors.Wrap(err, "failed to write tar header")
}
err = func() error {
fileReader, err := os.Open(filename)
if err != nil {
return errors.Wrap(err, "failed to open source file")
}
defer fileReader.Close()
_, err = io.Copy(tarWriter, fileReader)
if err != nil {
return errors.Wrap(err, "failed to copy file into archive")
}
return nil
}()
if err != nil {
return err
}
return nil
})
if err != nil {
return errors.Wrap(err, "failed to walk source dir")
}
return nil
}
type CollectorFailure struct {
Collector *troubleshootv1beta1.Collect
Collector *troubleshootv1beta2.Collect
Failure string
}
func parseTimeFlags(v *viper.Viper, progressChan chan interface{}, collectors *collect.Collectors) error {
var (
sinceTime time.Time
err error
)
if v.GetString("since-time") != "" {
if v.GetString("since") != "" {
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
}
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
if err != nil {
return errors.Wrap(err, "unable to parse --since-time flag")
}
} else {
parsedDuration, err := time.ParseDuration(v.GetString("since"))
if err != nil {
return errors.Wrap(err, "unable to parse --since flag")
}
now := time.Now()
sinceTime = now.Add(0 - parsedDuration)
}
for _, collector := range *collectors {
if collector.Collect.Logs != nil {
if collector.Collect.Logs.Limits == nil {
collector.Collect.Logs.Limits = new(troubleshootv1beta2.LogLimits)
}
collector.Collect.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
}
}
return nil
}
-16
View File
@@ -5,24 +5,8 @@ import (
"os"
"github.com/pkg/errors"
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func createTroubleshootK8sClient(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
config, err := configFlags.ToRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "failed to create troubleshoot client")
}
return troubleshootClient, nil
}
func findFileName(basename, extension string) (string, error) {
n := 1
name := basename
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"io/ioutil"
"path/filepath"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/version"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
@@ -29,10 +29,10 @@ func VersionCmd() *cobra.Command {
const VersionFilename = "version.yaml"
func writeVersionFile(path string) error {
version := troubleshootv1beta1.SupportBundleVersion{
ApiVersion: "troubleshoot.replicated.com/v1beta1",
version := troubleshootv1beta2.SupportBundleVersion{
ApiVersion: "troubleshoot.sh/v1beta2",
Kind: "SupportBundle",
Spec: troubleshootv1beta1.SupportBundleVersionSpec{
Spec: troubleshootv1beta2.SupportBundleVersionSpec{
VersionNumber: version.Version(),
},
}
+1 -1
View File
@@ -2,7 +2,7 @@ package main
import (
"github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
func main() {
+17
View File
@@ -3,6 +3,7 @@ package util
import (
"net/url"
"os"
"strings"
)
func HomeDir() string {
@@ -20,3 +21,19 @@ func IsURL(str string) bool {
return parsed.Scheme != ""
}
func AppName(name string) string {
words := strings.Split(strings.Title(strings.Replace(name, "-", " ", -1)), " ")
casedWords := []string{}
for i, word := range words {
if strings.ToLower(word) == "ai" {
casedWords = append(casedWords, "AI")
} else if strings.ToLower(word) == "io" && i > 0 {
casedWords[i-1] += ".io"
} else {
casedWords = append(casedWords, word)
}
}
return strings.Join(casedWords, " ")
}
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: analyzers.troubleshoot.replicated.com
spec:
@@ -42,7 +42,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -83,7 +88,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -126,7 +136,12 @@ spec:
customResourceDefinitionName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -168,7 +183,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -215,7 +235,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -256,7 +281,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -300,7 +330,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
ingressName:
type: string
namespace:
@@ -342,12 +377,68 @@ spec:
- namespace
- outcomes
type: object
mysql:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
nodeResources:
properties:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
filters:
properties:
cpuAllocatable:
@@ -366,6 +457,13 @@ spec:
type: string
podCapacity:
type: string
selector:
properties:
matchLabel:
additionalProperties:
type: string
type: object
type: object
type: object
outcomes:
items:
@@ -402,12 +500,119 @@ spec:
required:
- outcomes
type: object
postgres:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
redis:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
secret:
properties:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
key:
type: string
namespace:
@@ -456,7 +661,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -503,7 +713,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -549,7 +764,12 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
fileName:
type: string
outcomes:
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: collectors.troubleshoot.replicated.com
spec:
@@ -41,20 +41,26 @@ spec:
properties:
method:
type: string
redactUri:
type: string
uri:
type: string
required:
- method
- redactUri
- uri
type: object
uploadResultsTo:
properties:
method:
type: string
redactUri:
type: string
uri:
type: string
required:
- method
- redactUri
- uri
type: object
type: object
@@ -67,14 +73,24 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
type: object
clusterResources:
properties:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
type: object
copy:
properties:
@@ -85,7 +101,12 @@ spec:
containerPath:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -106,7 +127,12 @@ spec:
data:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
required:
@@ -127,7 +153,12 @@ spec:
containerName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -147,7 +178,12 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
get:
properties:
headers:
@@ -203,7 +239,12 @@ spec:
type: string
type: array
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
limits:
properties:
maxAge:
@@ -223,6 +264,54 @@ spec:
required:
- selector
type: object
mysql:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
postgres:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
redis:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
run:
properties:
args:
@@ -236,7 +325,12 @@ spec:
type: string
type: array
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
image:
type: string
imagePullPolicy:
@@ -256,7 +350,12 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
includeValue:
type: boolean
key:
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: preflights.troubleshoot.replicated.com
spec:
@@ -42,7 +42,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -83,7 +88,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -126,7 +136,12 @@ spec:
customResourceDefinitionName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -168,7 +183,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -215,7 +235,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -256,7 +281,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -300,7 +330,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
ingressName:
type: string
namespace:
@@ -342,12 +377,68 @@ spec:
- namespace
- outcomes
type: object
mysql:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
nodeResources:
properties:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
filters:
properties:
cpuAllocatable:
@@ -366,6 +457,13 @@ spec:
type: string
podCapacity:
type: string
selector:
properties:
matchLabel:
additionalProperties:
type: string
type: object
type: object
type: object
outcomes:
items:
@@ -402,12 +500,119 @@ spec:
required:
- outcomes
type: object
postgres:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
redis:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
secret:
properties:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
key:
type: string
namespace:
@@ -456,7 +661,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -503,7 +713,12 @@ spec:
checkName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
@@ -549,7 +764,12 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
fileName:
type: string
outcomes:
@@ -601,14 +821,24 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
type: object
clusterResources:
properties:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
type: object
copy:
properties:
@@ -619,7 +849,12 @@ spec:
containerPath:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -640,7 +875,12 @@ spec:
data:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
required:
@@ -661,7 +901,12 @@ spec:
containerName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
@@ -681,7 +926,12 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
get:
properties:
headers:
@@ -737,7 +987,12 @@ spec:
type: string
type: array
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
limits:
properties:
maxAge:
@@ -757,6 +1012,54 @@ spec:
required:
- selector
type: object
mysql:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
postgres:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
redis:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
run:
properties:
args:
@@ -770,7 +1073,12 @@ spec:
type: string
type: array
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
image:
type: string
imagePullPolicy:
@@ -790,7 +1098,12 @@ spec:
collectorName:
type: string
exclude:
type: boolean
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
includeValue:
type: boolean
key:
@@ -0,0 +1,88 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: redactors.troubleshoot.replicated.com
spec:
group: troubleshoot.replicated.com
names:
kind: Redactor
listKind: RedactorList
plural: redactors
singular: redactor
scope: Namespaced
validation:
openAPIV3Schema:
description: Redactor is the Schema for the redaction API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: RedactorSpec defines the desired state of Redactor
properties:
redactors:
items:
properties:
fileSelector:
properties:
file:
type: string
files:
items:
type: string
type: array
type: object
name:
type: string
removals:
properties:
regex:
items:
properties:
redactor:
type: string
selector:
type: string
type: object
type: array
values:
items:
type: string
type: array
yamlPath:
items:
type: string
type: array
type: object
type: object
type: array
type: object
status:
description: RedactorStatus defines the observed state of Redactor
type: object
type: object
version: v1beta1
versions:
- name: v1beta1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
File diff suppressed because it is too large Load Diff
+831
View File
@@ -0,0 +1,831 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: analyzers.troubleshoot.sh
spec:
group: troubleshoot.sh
names:
kind: Analyzer
listKind: AnalyzerList
plural: analyzers
singular: analyzer
scope: Namespaced
validation:
openAPIV3Schema:
description: Analyzer is the Schema for the analyzers API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: AnalyzerSpec defines the desired state of Analyzer
properties:
analyzers:
items:
properties:
clusterVersion:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- outcomes
type: object
containerRuntime:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- outcomes
type: object
customResourceDefinition:
properties:
checkName:
type: string
customResourceDefinitionName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- customResourceDefinitionName
- outcomes
type: object
deploymentStatus:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- name
- namespace
- outcomes
type: object
distribution:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- outcomes
type: object
imagePullSecret:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
registryName:
type: string
required:
- outcomes
- registryName
type: object
ingress:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
ingressName:
type: string
namespace:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- ingressName
- namespace
- outcomes
type: object
mysql:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
nodeResources:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
filters:
properties:
cpuAllocatable:
type: string
cpuCapacity:
type: string
ephemeralStorageAllocatable:
type: string
ephemeralStorageCapacity:
type: string
memoryAllocatable:
type: string
memoryCapacity:
type: string
podAllocatable:
type: string
podCapacity:
type: string
selector:
properties:
matchLabel:
additionalProperties:
type: string
type: object
type: object
type: object
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- outcomes
type: object
postgres:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
redis:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
required:
- collectorName
- outcomes
type: object
secret:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
key:
type: string
namespace:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
secretName:
type: string
required:
- namespace
- outcomes
- secretName
type: object
statefulsetStatus:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- name
- namespace
- outcomes
type: object
storageClass:
properties:
checkName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
storageClassName:
type: string
required:
- outcomes
- storageClassName
type: object
textAnalyze:
properties:
checkName:
type: string
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
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
regex:
type: string
regexGroups:
type: string
required:
- outcomes
type: object
type: object
type: array
type: object
status:
description: AnalyzerStatus defines the observed state of Analyzer
type: object
type: object
version: v1beta2
versions:
- name: v1beta2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
+387
View File
@@ -0,0 +1,387 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: collectors.troubleshoot.sh
spec:
group: troubleshoot.sh
names:
kind: Collector
listKind: CollectorList
plural: collectors
singular: collector
scope: Namespaced
validation:
openAPIV3Schema:
description: Collector is the Schema for the collectors API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: CollectorSpec defines the desired state of Collector
properties:
afterCollection:
items:
properties:
callback:
properties:
method:
type: string
redactUri:
type: string
uri:
type: string
required:
- method
- redactUri
- uri
type: object
uploadResultsTo:
properties:
method:
type: string
redactUri:
type: string
uri:
type: string
required:
- method
- redactUri
- uri
type: object
type: object
type: array
collectors:
items:
properties:
clusterInfo:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
type: object
clusterResources:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
type: object
copy:
properties:
collectorName:
type: string
containerName:
type: string
containerPath:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
required:
- containerPath
- namespace
- selector
type: object
data:
properties:
collectorName:
type: string
data:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
required:
- data
type: object
exec:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
containerName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
timeout:
type: string
required:
- namespace
- selector
type: object
http:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
get:
properties:
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: string
required:
- url
type: object
name:
type: string
post:
properties:
body:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: string
required:
- url
type: object
put:
properties:
body:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
type: string
required:
- url
type: object
type: object
logs:
properties:
collectorName:
type: string
containerNames:
items:
type: string
type: array
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
limits:
properties:
maxAge:
type: string
maxLines:
format: int64
type: integer
type: object
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
required:
- selector
type: object
mysql:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
postgres:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
redis:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
uri:
type: string
required:
- uri
type: object
run:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
image:
type: string
imagePullPolicy:
type: string
name:
type: string
namespace:
type: string
timeout:
type: string
required:
- image
- namespace
type: object
secret:
properties:
collectorName:
type: string
exclude:
description: BoolOrString is a type that can hold an bool
or a string. When used in JSON or YAML marshalling and
unmarshalling, it produces or consumes the inner type. This
allows you to have, for example, a JSON field that can accept
a booolean string or raw bool.
type: BoolString
includeValue:
type: boolean
key:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: object
type: array
type: object
status:
description: CollectorStatus defines the observed state of Collector
type: object
type: object
version: v1beta2
versions:
- name: v1beta2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,88 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.3.0
creationTimestamp: null
name: redactors.troubleshoot.sh
spec:
group: troubleshoot.sh
names:
kind: Redactor
listKind: RedactorList
plural: redactors
singular: redactor
scope: Namespaced
validation:
openAPIV3Schema:
description: Redactor is the Schema for the redaction API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: RedactorSpec defines the desired state of Redactor
properties:
redactors:
items:
properties:
fileSelector:
properties:
file:
type: string
files:
items:
type: string
type: array
type: object
name:
type: string
removals:
properties:
regex:
items:
properties:
redactor:
type: string
selector:
type: string
type: object
type: array
values:
items:
type: string
type: array
yamlPath:
items:
type: string
type: array
type: object
type: object
type: array
type: object
status:
description: RedactorStatus defines the observed state of Redactor
type: object
type: object
version: v1beta2
versions:
- name: v1beta2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Analyzer
metadata:
labels:
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Collector
metadata:
name: collector-sample
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: support-io
+1 -1
View File
@@ -110,4 +110,4 @@ dockers:
- "replicated/preflight:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
binaries:
- support-bundle
- preflight
- preflight
@@ -0,0 +1,18 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: block
spec:
collectors:
- blockDevices: {}
analyzers:
- blockDevices:
outcomes:
- pass:
when: ".* == 1"
message: One available block device
- pass:
when: ".* > 1"
message: Multiple available block devices
- fail:
message: No available block devices
+22
View File
@@ -0,0 +1,22 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: cpu
spec:
collectors:
- cpu: {}
analyzers:
- cpu:
outcomes:
- fail:
when: "physical < 4"
message: At least 4 physical CPU cores are required
- fail:
when: "logical < 8"
message: At least 8 CPU cores are required
- warn:
when: "count < 16"
message: At least 16 CPU cores preferred
- pass:
message: This server has sufficient CPU cores
+33
View File
@@ -0,0 +1,33 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: diskUsage
spec:
collectors:
- diskUsage:
collectorName: ephemeral
path: /var/lib/kubelet
analyzers:
- diskUsage:
collectorName: ephemeral
outcomes:
- fail:
when: "total < 20Gi"
message: /var/lib/kubelet has less than 20Gi of total space
- fail:
when: "available < 10Gi"
message: /var/lib/kubelet has less than 10Gi of disk space available
- fail:
when: "used/total > 70%"
message: /var/lib/kubelet is more than 70% full
- warn:
when: "total < 40Gi"
message: /var/lib/kubelet has less than 40Gi of total space
- warn:
when: "used/total > 60%"
message: /var/lib/kubelet is more than 60% full
- pass:
when: "available/total >= 90%"
message: /var/lib/kubelet has more than 90% available
- pass:
message: /var/lib/kubelet has sufficient disk space available
+22
View File
@@ -0,0 +1,22 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: http
spec:
collectors:
- http:
collectorName: registry
get:
url: https://registry.replicated.com
analyzers:
- http:
collectorName: registry
outcomes:
- fail:
when: "error"
message: Error connecting to registry
- pass:
when: "statusCode == 404"
message: Connected to registry
- fail:
message: "Unexpected response"
+19
View File
@@ -0,0 +1,19 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: memory
spec:
collectors:
- memory:
collectorName: memory
analyzers:
- memory:
outcomes:
- fail:
when: "< 8Gi"
message: At least 8Gi of memory is required
- warn:
when: "< 32Gi"
message: At least 32Gi of memory is recommended
- pass:
message: The system has as sufficient memory
+22
View File
@@ -0,0 +1,22 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: ntp
spec:
collectors:
- time: {}
analyzers:
- time:
outcomes:
- fail:
when: "ntp == unsynchronized+inactive"
message: System clock not synchronized
- warn:
when: "ntp == unsynchronized+active"
message: System clock not yet synchronized
- warn:
when: "ntp == synchronized+inactive"
message: NTP not active
- pass:
when: "ntp == synchronized+active"
message: System clock is synchronized
+30
View File
@@ -0,0 +1,30 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: port
spec:
collectors:
- tcpPortStatus:
collectorName: k8s
port: 7443
analyzers:
- tcpPortStatus:
collectorName: k8s
outcomes:
- fail:
when: "connection-refused"
message: Connection to port 7443 was refused.
- fail:
when: "address-in-use"
message: Another process was already listening on port 7443.
- fail:
when: "connection-timeout"
message: Timed out connecting to port 7443. Check your firewall.
- fail:
when: "error"
message: Unexpected port status
- pass:
when: "connected"
message: Port 7443 is open
- warn:
message: Unexpected port status
@@ -0,0 +1,31 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: loadbalancer
spec:
collectors:
- tcpLoadBalancer:
collectorName: loadbalancer
port: 7443
address: 10.128.0.29:7444
analyzers:
- tcpLoadBalancer:
collectorName: loadbalancer
outcomes:
- fail:
when: "connection-refused"
message: Connection to port 7443 via load balancer was refused.
- fail:
when: "address-in-use"
message: Another process was already listening on port 7443.
- fail:
when: "connection-timeout"
message: Timed out connecting to port 7443 via load balancer. Check your firewall.
- fail:
when: "error"
message: Unexpected port status
- pass:
when: "connected"
message: Successfully connected to port 7443 via load balancer
- warn:
message: Unexpected port status
+15
View File
@@ -0,0 +1,15 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: timezone
spec:
collectors:
- time: {}
analyzers:
- time:
outcomes:
- pass:
when: "timezone == UTC"
message: Timezone is UTC
- fail:
message: Timezone is not UTC
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: sample
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: sample
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: sample
@@ -0,0 +1,51 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: example
spec:
collectors:
- tcpLoadBalancer:
collectorName: LB1
address: 10.1.1.1
port: 6443
timeout: 5000ms
- diskUsage:
collectorName: ephemeral
path: /var/lib/kubelet
analyzers:
- cpu:
outcomes:
- fail:
when: "< 4"
message: This server has less than 4 CPU cores, and we require 8, but recommend 16
- warn:
when: "< 16"
message: This server has at least 4 CPU cores, but we recommend 16 or more
- pass:
message: This server has sufficient CPU cores
- tcpLoadBalancer:
collectorName: LB1
outcomes:
- fail:
when: "connection-timeout"
message: The TCP Load Balancer is not forwarding traffic to this server.
- fail:
when: "address-in-use"
message: The local port is not available to validate the Load Balancer configuration.
- pass:
when: "connected"
message: The specified TCP Load Balancer appears to be properly forwarding traffic to this server.
- diskUsage:
collectorName: ephemeral
outcomes:
- fail:
when: "total < 20Gi"
message: /var/lib/kubelet has less than 20Gi of total space
- fail:
when: "available < 10Gi"
message: /var/lib/kubelet has less than 10Gi of disk space available
- fail:
when: "used/total > 70%"
message: /var/lib/kubelet is more than 70% full
- pass:
message: /var/lib/kubelet has sufficient disk space available
+93 -29
View File
@@ -1,20 +1,14 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: example-preflight-checks
name: example
spec:
collectors:
- secret:
name: myapp-postgres
namespace: default
key: uri
includeValue: false
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< 1.13.0"
message: The application requires at Kubernetes 1.13.0 or later, and recommends 1.15.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.15.0"
@@ -24,32 +18,102 @@ spec:
when: ">= 1.15.0"
message: Your cluster meets the recommended and required versions of Kubernetes.
- customResourceDefinition:
customResourceDefinitionName: constrainttemplates.templates.gatekeeper.sh
checkName: Gatekeeper policy runtime
checkName: Ingress
customResourceDefinitionName: ingressroutes.contour.heptio.com
outcomes:
- fail:
message: Gatekeeper is required for the application, but not found in the cluster.
uri: https://enterprise.support.io/installing/gatekeeper
message: Contour ingress not found!
- pass:
message: Found a supported version of Gatekeeper installed and running in the cluster.
- imagePullSecret:
checkName: Registry credentials for Quay.io
registryName: quay.io
message: Contour ingress found!
- containerRuntime:
outcomes:
- pass:
when: "== docker"
message: Docker container runtime was found.
- fail:
message: Did not find Docker container runtime.
- storageClass:
checkName: Required storage classes
storageClassName: "default"
outcomes:
- fail:
message: |
Cannot pull from quay.io. An image pull secret should be deployed to the cluster that has credentials to pull the images. To obtain this secret, please contact your support rep.
uri: https://enterprise.support.io/installing/registry-credentials
message: Could not find a storage class called default.
- pass:
message: Found credentials to pull from quay.io
- secret:
checkName: Postgres connection string
secretName: myapp-postgres
namespace: default
key: uri
message: All good on storage classes
- distribution:
outcomes:
- fail:
message: A secret named "myapp-postgres" must be deployed with a "uri" key
uri: https://enterprise.support.io/installing/postgres
when: "== docker-desktop"
message: The application does not support Docker Desktop Clusters
- fail:
when: "== microk8s"
message: The application does not support Microk8s Clusters
- fail:
when: "== minikube"
message: The application does not support Minikube Clusters
- pass:
message: Found a valid postgres connection string
when: "== eks"
message: EKS is a supported distribution
- pass:
when: "== gke"
message: GKE is a supported distribution
- pass:
when: "== aks"
message: AKS is a supported distribution
# Will be supported in the future
- pass:
when: "== kurl"
message: KURL is a supported distribution
- pass:
when: "== digitalocean"
message: DigitalOcean is a supported distribution
- warn:
message: Unable to determine the distribution of Kubernetes
- nodeResources:
checkName: Must have at least 3 nodes in the cluster, with 5 recommended
outcomes:
- fail:
when: "count() < 3"
message: This application requires at least 3 nodes.
uri: https://kurl.sh/docs/install-with-kurl/adding-nodes
- warn:
when: "count() < 5"
message: This application recommends at last 5 nodes.
uri: https://kurl.sh/docs/install-with-kurl/adding-nodes
- 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
outcomes:
- fail:
when: "min(memoryCapacity) < 10Gi"
message: All nodes must have at least 10 GB of memory.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- warn:
when: "min(memoryCapacity) < 32Gi"
message: All nodes are recommended to have at least 32 GB of memory.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: All nodes have at least 32 GB of memory.
- nodeResources:
checkName: Total CPU Cores in the cluster is 4 or greater
outcomes:
- fail:
when: "sum(cpuCapacity) < 4"
message: The cluster must contain at least 4 cores
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: There are at least 4 cores in the cluster
- nodeResources:
checkName: Every node in the cluster must have at least 40 GB of ephemeral storage, with 100 GB recommended
outcomes:
- fail:
when: "min(ephemeralStorageCapacity) < 40Gi"
message: All nodes must have at least 40 GB of ephemeral storage.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- warn:
when: "min(ephemeralStorageCapacity) < 100Gi"
message: All nodes are recommended to have at least 100 GB of ephemeral storage.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: All nodes have at least 100 GB of ephemeral storage.
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
name: example-preflight-checks
@@ -0,0 +1,24 @@
apiVersion: troubleshoot.sh/v1beta2
kind: Analyzer
metadata:
name: a
spec:
analyzers:
- distribution:
outcomes:
- fail:
when: "= dockerdesktop"
message: "docker for desktop is not allowed"
# - fail:
# when: "microk8s"
# message: "mickrk8s is not prod"
- fail:
when: "!= openshift"
message: "this should fail on anything other than openshift"
- warn:
when: "!= eks"
message: "YMMV on not eks"
- pass:
message: "good work"
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Collector
metadata:
name: collector-sample
@@ -0,0 +1,101 @@
apiVersion: troubleshoot.sh/v1beta2
kind: SupportBundle
metadata:
name: example
spec:
collectors:
- logs:
selector:
- app=example
- component=nginx
namespace: repl{{ Namespace }}
limits:
maxAge: 30d
maxLines: 10000
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< 1.16.0"
message: The application requires at least Kubernetes 1.16.0 or later
uri: https://kubernetes.io
- warn:
when: "< 1.17.0"
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.17.0 or later.
uri: https://kubernetes.io
- pass:
message: Your cluster meets the recommended and required versions of Kubernetes.
- customResourceDefinition:
customResourceDefinitionName: rook
outcomes:
- fail:
message: The Rook CRD was not found in the cluster.
- pass:
message: Rook is installed and available.
- containerRuntime:
outcomes:
- fail:
when: "== docker"
message: The application does not support docker
- pass:
message: A supported container runtime was found
- storageClass:
checkName: Required storage classes
storageClassName: "microk8s-hostpath"
outcomes:
- fail:
message: The microk8s storage class thing was not found
- pass:
message: All good on storage classes
- nodeResources:
checkName: Must have at least 3 nodes in the cluster
outcomes:
- fail:
when: "count() < 3"
message: This application requires at least 3 nodes
- warn:
when: "count() < 5"
message: This application recommends at last 5 nodes.
- pass:
message: This cluster has enough nodes.
- nodeResources:
checkName: Total CPU Cores in the cluster is 4 or greater
outcomes:
- fail:
when: "sum(cpuCapacity) < 4"
message: The cluster must contain at least 4 cores
- pass:
message: There are at least 4 cores in the cluster
- nodeResources:
checkName: Each node must have at least 40 GB of ephemeral storage
outcomes:
- fail:
when: "min(ephemeralStorageCapacity) < 40Gi"
message: Noees in this cluster do not have at least 40 GB of ephemeral storage.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- warn:
when: "min(ephemeralStorageCapacity) < 100Gi"
message: Nodes in this cluster are recommended to have at least 100 GB of ephemeral storage.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: The nodes in this cluster have enough ephemeral storage.
- ingress:
namespace: default
ingressName: connect-to-me
outcomes:
- fail:
message: The ingress isn't ingressing
- pass:
message: All systems ok on ingress
- deploymentStatus:
name: api
namespace: default
outcomes:
- fail:
when: "< 1"
message: The API deployment does not have any ready replicas.
- warn:
when: "= 1"
message: The API deployment has only a single ready replica.
- pass:
message: There are multiple replicas of the API deployment ready.
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: troubleshoot.replicated.com/v1beta1
apiVersion: troubleshoot.sh/v1beta2
kind: Analyzer
metadata:
name: a
@@ -1,20 +0,0 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: SupportBundle
metadata:
name: supportbundle-sample
spec:
collectors: []
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< 1.13.0"
message: The application requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.
uri: https://www.kubernetes.io
- warn:
when: "< 1.15.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.15.0"
message: Your cluster meets the recommended and required versions of Kubernetes.
@@ -0,0 +1,6 @@
apiVersion: troubleshoot.sh/v1beta2
kind: Collector
metadata:
name: collector-sample
spec:
collectors: []
+10 -14
View File
@@ -8,17 +8,15 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b // indirect
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
github.com/emicklei/go-restful v2.9.6+incompatible // indirect
github.com/fatih/color v1.7.0
github.com/frankban/quicktest v1.7.2 // indirect
github.com/gizak/termui/v3 v3.1.0
github.com/go-openapi/spec v0.19.4 // indirect
github.com/go-redis/redis/v7 v7.2.0
github.com/go-sql-driver/mysql v1.5.0
github.com/gobwas/glob v0.2.3
github.com/golang/snappy v0.0.1 // indirect
github.com/godbus/dbus v4.1.0+incompatible
github.com/google/go-cmp v0.3.1 // indirect
github.com/google/gofuzz v1.1.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e
@@ -28,32 +26,30 @@ require (
github.com/manifoldco/promptui v0.3.2
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.9
github.com/mholt/archiver v3.1.1+incompatible
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/nicksnyder/go-i18n v1.10.1 // indirect
github.com/nwaples/rardecode v1.0.0 // indirect
github.com/onsi/gomega v1.9.0 // indirect
github.com/pierrec/lz4 v2.2.6+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/procfs v0.0.5 // indirect
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
github.com/segmentio/ksuid v1.0.3
github.com/shirou/gopsutil v3.21.1+incompatible
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.5.1
github.com/tj/go-spin v1.1.0
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/ulikunitz/xz v0.5.6 // indirect
go.opencensus.io v0.22.0 // indirect
go.undefinedlabs.com/scopeagent v0.1.7
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff // indirect
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.3
k8s.io/apiextensions-apiserver v0.18.0
k8s.io/apiextensions-apiserver v0.18.2
k8s.io/apimachinery v0.18.3
k8s.io/cli-runtime v0.18.0
k8s.io/client-go v0.18.0
k8s.io/code-generator v0.18.3-beta.0 // indirect
k8s.io/client-go v0.18.2
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
sigs.k8s.io/controller-tools v0.2.8 // indirect
)
+30 -124
View File
@@ -10,16 +10,21 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -45,8 +50,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.25.18 h1:fMEkpli4r+FS4xZRqjgjYHP+uKaSwfk2MOcDUpLYwIE=
github.com/aws/aws-sdk-go v1.25.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -84,10 +87,10 @@ github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@@ -97,12 +100,8 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 h1:pEtiCjIXx3RvGjlUJuCNxNOw0MNblyR9Wi+vJGBFh+8=
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
@@ -120,20 +119,14 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -173,7 +166,6 @@ github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nA
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
@@ -195,14 +187,12 @@ github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRf
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM=
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@@ -220,8 +210,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -231,8 +219,6 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -254,6 +240,7 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
@@ -298,7 +285,6 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
@@ -310,8 +296,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -339,11 +323,9 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
@@ -352,8 +334,6 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -368,7 +348,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -382,75 +361,65 @@ github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDa
github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw=
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851 h1:eRlNDHxGfVkPCRXbA4BfQJvt5DHjFiTtWy3R/t4djyY=
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851/go.mod h1:JDxG6+uubnk9/BZ2yUsyAJJwlptjrnmB2MPF5d2Xe/8=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/segmentio/ksuid v1.0.3 h1:FoResxvleQwYiPAVKe1tMUlEirodZqlqglIuFsdDntY=
github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.20.12+incompatible h1:6VEGkOXP/eP4o2Ilk8cSsX0PhOEfX6leqAnD+urrp9M=
github.com/shirou/gopsutil v3.20.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.1+incompatible h1:2LwXWdbjXwyDgq26Yy/OT4xozlpmssQfy/rtfhWb0bY=
github.com/shirou/gopsutil v3.21.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
@@ -500,10 +469,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -520,14 +487,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98 h1:Z/megzdoMbuZ0H/oLmfTw92PAEfyTi1DkvAr8AUzFgw=
github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98/go.mod h1:TyJZDQ/5AgyN7FSLiBJ8RO9u2c6wbtRvK827b6AVqY4=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -537,7 +498,6 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -548,8 +508,6 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.undefinedlabs.com/scopeagent v0.1.7 h1:TPALQQNkPah8NgVjPKKhaBNZL2hg+hY2GRPfRMSt48c=
go.undefinedlabs.com/scopeagent v0.1.7/go.mod h1:XCD8wnuBZ+eskhynYNP+uvBiFan9cFrWLwHnWOETXDU=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -560,20 +518,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -590,16 +543,12 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -628,21 +577,19 @@ golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -661,10 +608,8 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -676,9 +621,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+y
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -690,8 +632,6 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -700,7 +640,6 @@ google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -709,7 +648,6 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
@@ -732,93 +670,61 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0=
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/api v0.17.4 h1:HbwOhDapkguO8lTAE8OX3hdF2qp8GtpC9CW/MQATXXo=
k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0=
k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA=
k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=
k8s.io/apiextensions-apiserver v0.18.0 h1:HN4/P8vpGZFvB5SOMuPPH2Wt9Y/ryX+KRvIyAkchu1Q=
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw=
k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
k8s.io/cli-runtime v0.17.4 h1:ZIJdxpBEszZqUhydrCoiI5rLXS2J/1AF5xFok2QJ9bc=
k8s.io/cli-runtime v0.17.4/go.mod h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc=
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
k8s.io/cli-runtime v0.18.0 h1:jG8XpSqQ5TrV0N+EZ3PFz6+gqlCk71dkggWCCq9Mq34=
k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8=
k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4=
k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.18.0 h1:0xIRWzym+qMgVpGmLESDeMfz/orwgxwxFFAo1xfGNtQ=
k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/code-generator v0.18.3-beta.0 h1:OIi8tEf8j3ppkJP8pyV9whXqnmdon4okY9GjQnbU0dc=
k8s.io/code-generator v0.18.3-beta.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
k8s.io/code-generator v0.18.2 h1:C1Nn2JiMf244CvBDKVPX0W2mZFJkVBg54T8OV7/Imso=
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c=
k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120 h1:RPscN6KhmG54S33L+lr3GS+oD1jmchIU0ll519K6FA4=
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM=
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901 h1:qwHvTyQQBjATQeKPdJ0TwcXdtScjyI6GuiAQ5CSqEvM=
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901/go.mod h1:j4echH3Y/UPHRpXS65rxGXujda8iWOheMQvDh1uNgaY=
sigs.k8s.io/controller-tools v0.2.8 h1:UmYsnu89dn8/wBhjKL3lkGyaDGRnPDYUx2+iwXRnylA=
sigs.k8s.io/controller-tools v0.2.8/go.mod h1:9VKHPszmf2DHz/QmHkcfZoewO6BL7pPs9uAiBVsaJSE=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+152 -17
View File
@@ -4,7 +4,7 @@ import (
"strconv"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/multitype"
)
@@ -40,7 +40,68 @@ func isExcluded(excludeVal multitype.BoolOrString) (bool, error) {
return parsed, nil
}
func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) (*AnalyzeResult, error) {
func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
if hostAnalyzer.CPU != nil {
result, err := analyzeHostCPU(hostAnalyzer.CPU, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.TCPLoadBalancer != nil {
result, err := analyzeHostTCPLoadBalancer(hostAnalyzer.TCPLoadBalancer, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.DiskUsage != nil {
result, err := analyzeHostDiskUsage(hostAnalyzer.DiskUsage, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.Memory != nil {
result, err := analyzeHostMemory(hostAnalyzer.Memory, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.TCPPortStatus != nil {
result, err := analyzeHostTCPPortStatus(hostAnalyzer.TCPPortStatus, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.HTTP != nil {
result, err := analyzeHostHTTP(hostAnalyzer.HTTP, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.Time != nil {
result, err := analyzeHostTime(hostAnalyzer.Time, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if hostAnalyzer.BlockDevices != nil {
result, err := analyzeHostBlockDevices(hostAnalyzer.BlockDevices, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
return nil, errors.New("invalid analyzer")
}
func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
if analyzer.ClusterVersion != nil {
isExcluded, err := isExcluded(analyzer.ClusterVersion.Exclude)
if err != nil {
@@ -49,7 +110,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeClusterVersion(analyzer.ClusterVersion, getFile)
result, err := analyzeClusterVersion(analyzer.ClusterVersion, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.StorageClass != nil {
isExcluded, err := isExcluded(analyzer.StorageClass.Exclude)
@@ -59,7 +124,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeStorageClass(analyzer.StorageClass, getFile)
result, err := analyzeStorageClass(analyzer.StorageClass, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.CustomResourceDefinition != nil {
isExcluded, err := isExcluded(analyzer.CustomResourceDefinition.Exclude)
@@ -69,7 +138,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeCustomResourceDefinition(analyzer.CustomResourceDefinition, getFile)
result, err := analyzeCustomResourceDefinition(analyzer.CustomResourceDefinition, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.Ingress != nil {
isExcluded, err := isExcluded(analyzer.Ingress.Exclude)
@@ -79,7 +152,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeIngress(analyzer.Ingress, getFile)
result, err := analyzeIngress(analyzer.Ingress, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.Secret != nil {
isExcluded, err := isExcluded(analyzer.Secret.Exclude)
@@ -89,7 +166,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeSecret(analyzer.Secret, getFile)
result, err := analyzeSecret(analyzer.Secret, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.ImagePullSecret != nil {
isExcluded, err := isExcluded(analyzer.ImagePullSecret.Exclude)
@@ -99,7 +180,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeImagePullSecret(analyzer.ImagePullSecret, findFiles)
result, err := analyzeImagePullSecret(analyzer.ImagePullSecret, findFiles)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.DeploymentStatus != nil {
isExcluded, err := isExcluded(analyzer.DeploymentStatus.Exclude)
@@ -109,7 +194,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeDeploymentStatus(analyzer.DeploymentStatus, getFile)
result, err := analyzeDeploymentStatus(analyzer.DeploymentStatus, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.StatefulsetStatus != nil {
isExcluded, err := isExcluded(analyzer.StatefulsetStatus.Exclude)
@@ -119,7 +208,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeStatefulsetStatus(analyzer.StatefulsetStatus, getFile)
result, err := analyzeStatefulsetStatus(analyzer.StatefulsetStatus, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.ContainerRuntime != nil {
isExcluded, err := isExcluded(analyzer.ContainerRuntime.Exclude)
@@ -129,7 +222,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeContainerRuntime(analyzer.ContainerRuntime, getFile)
result, err := analyzeContainerRuntime(analyzer.ContainerRuntime, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.Distribution != nil {
isExcluded, err := isExcluded(analyzer.Distribution.Exclude)
@@ -139,7 +236,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeDistribution(analyzer.Distribution, getFile)
result, err := analyzeDistribution(analyzer.Distribution, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.NodeResources != nil {
isExcluded, err := isExcluded(analyzer.NodeResources.Exclude)
@@ -149,7 +250,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeNodeResources(analyzer.NodeResources, getFile)
result, err := analyzeNodeResources(analyzer.NodeResources, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.TextAnalyze != nil {
isExcluded, err := isExcluded(analyzer.TextAnalyze.Exclude)
@@ -159,7 +264,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeTextAnalyze(analyzer.TextAnalyze, getFile)
multiResult, err := analyzeTextAnalyze(analyzer.TextAnalyze, findFiles)
if err != nil {
return nil, err
}
return multiResult, nil
}
if analyzer.Postgres != nil {
isExcluded, err := isExcluded(analyzer.Postgres.Exclude)
@@ -169,7 +278,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzePostgres(analyzer.Postgres, getFile)
result, err := analyzePostgres(analyzer.Postgres, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.Mysql != nil {
isExcluded, err := isExcluded(analyzer.Mysql.Exclude)
@@ -179,7 +292,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeMysql(analyzer.Mysql, getFile)
result, err := analyzeMysql(analyzer.Mysql, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.Redis != nil {
isExcluded, err := isExcluded(analyzer.Redis.Exclude)
@@ -189,7 +306,25 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
if isExcluded {
return nil, nil
}
return analyzeRedis(analyzer.Redis, getFile)
result, err := analyzeRedis(analyzer.Redis, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
if analyzer.CephStatus != nil {
isExcluded, err := isExcluded(analyzer.CephStatus.Exclude)
if err != nil {
return nil, err
}
if isExcluded {
return nil, nil
}
result, err := cephStatus(analyzer.CephStatus, getFile)
if err != nil {
return nil, err
}
return []*AnalyzeResult{result}, nil
}
return nil, errors.New("invalid analyzer")
+175
View File
@@ -0,0 +1,175 @@
package analyzer
import (
"encoding/json"
"path"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
type CephHealth string
const (
CephHealthOK CephHealth = "HEALTH_OK"
CephHealthWarn CephHealth = "HEALTH_WARN"
CephHealthErr CephHealth = "HEALTH_ERR"
)
func (a CephHealth) Compare(b CephHealth) int {
if a == b {
return 0
}
switch a {
case CephHealthOK:
return 1
case CephHealthWarn:
switch b {
case CephHealthOK:
return -1
case CephHealthErr:
return 1
}
return 1
case CephHealthErr:
switch b {
case CephHealthOK, CephHealthWarn:
return -1
}
return 1
default:
return -1
}
}
var CephStatusDefaultOutcomes = []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "Ceph is healthy",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
Message: "Ceph status is HEALTH_WARN",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "Ceph status is HEALTH_ERR",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
},
},
}
func cephStatus(analyzer *troubleshootv1beta2.CephStatusAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
fileName := path.Join(collect.GetCephCollectorFilepath(analyzer.CollectorName, analyzer.Namespace), "status.json")
collected, err := getCollectedFileContents(fileName)
if err != nil {
return nil, errors.Wrap(err, "failed to read collected ceph status")
}
title := analyzer.CheckName
if title == "" {
title = "Ceph Status"
}
analyzeResult := &AnalyzeResult{
Title: title,
IconKey: "rook", // maybe this should be ceph?
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
}
status := struct {
Health struct {
Status string `json:"status"`
} `json:"health"`
}{}
if err := json.Unmarshal(collected, &status); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal status.json")
}
if len(analyzer.Outcomes) == 0 {
analyzer.Outcomes = CephStatusDefaultOutcomes
}
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
outcome.Fail.When = string(CephHealthErr)
}
match, err := compareCephStatus(status.Health.Status, outcome.Fail.When)
if err != nil {
return nil, errors.Wrap(err, "failed to compare ceph status")
} else if match {
analyzeResult.IsFail = true
analyzeResult.Message = outcome.Fail.Message
analyzeResult.URI = outcome.Fail.URI
return analyzeResult, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
outcome.Warn.When = string(CephHealthWarn)
}
match, err := compareCephStatus(status.Health.Status, outcome.Warn.When)
if err != nil {
return nil, errors.Wrap(err, "failed to compare ceph status")
} else if match {
analyzeResult.IsWarn = true
analyzeResult.Message = outcome.Warn.Message
analyzeResult.URI = outcome.Warn.URI
return analyzeResult, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
outcome.Pass.When = string(CephHealthOK)
}
match, err := compareCephStatus(status.Health.Status, outcome.Pass.When)
if err != nil {
return nil, errors.Wrap(err, "failed to compare ceph status")
} else if match {
analyzeResult.IsPass = true
analyzeResult.Message = outcome.Pass.Message
analyzeResult.URI = outcome.Pass.URI
return analyzeResult, nil
}
}
}
return analyzeResult, nil
}
func compareCephStatus(actual, when string) (bool, error) {
parts := strings.Split(strings.TrimSpace(when), " ")
if len(parts) == 1 {
value := strings.TrimSpace(parts[0])
return value == actual, nil
}
if len(parts) != 2 {
return false, errors.New("unable to parse when range")
}
operator := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
compareResult := CephHealth(actual).Compare(CephHealth(value))
switch operator {
case "=", "==", "===":
return compareResult == 0, nil
case "<":
return compareResult == -1, nil
case ">":
return compareResult == 1, nil
case "<=":
return compareResult <= 0, nil
case ">=":
return compareResult >= 0, nil
default:
return false, errors.New("unknown operator")
}
}
+201
View File
@@ -0,0 +1,201 @@
package analyzer
import (
"fmt"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_cephStatus(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta2.CephStatusAnalyze
expectResult AnalyzeResult
filePath, file string
}{
{
name: "pass case",
analyzer: troubleshootv1beta2.CephStatusAnalyze{},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "Ceph Status",
Message: "Ceph is healthy",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_OK"
}
}`,
},
{
name: "warn case",
analyzer: troubleshootv1beta2.CephStatusAnalyze{},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: true,
IsFail: false,
Title: "Ceph Status",
Message: "Ceph status is HEALTH_WARN",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_WARN"
}
}`,
},
{
name: "fail case",
analyzer: troubleshootv1beta2.CephStatusAnalyze{},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "Ceph Status",
Message: "Ceph status is HEALTH_ERR",
URI: "https://rook.io/docs/rook/v1.4/ceph-common-issues.html",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_ERR"
}
}`,
},
{
name: "CollectorName and Namespace",
analyzer: troubleshootv1beta2.CephStatusAnalyze{
CollectorName: "custom-namespace",
Namespace: "namespace",
},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "Ceph Status",
Message: "Ceph is healthy",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "custom-namespace/namespace/ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_OK"
}
}`,
},
{
name: "outcomes when",
analyzer: troubleshootv1beta2.CephStatusAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "== HEALTH_OK",
Message: "custom message OK",
URI: "custom uri OK",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "<= HEALTH_WARN",
Message: "custom message WARN",
URI: "custom uri WARN",
},
},
},
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "Ceph Status",
Message: "custom message WARN",
URI: "custom uri WARN",
IconKey: "rook",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/rook.svg?w=11&h=16",
},
filePath: "ceph/status.json",
file: `{
"fsid": "96a8178c-6aa2-4adf-a309-9e8869a79611",
"health": {
"status": "HEALTH_WARN"
}
}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
getFile := func(n string) ([]byte, error) {
assert.Equal(t, n, test.filePath)
return []byte(test.file), nil
}
actual, err := cephStatus(&test.analyzer, getFile)
req.NoError(err)
assert.Equal(t, test.expectResult, *actual)
})
}
}
func Test_compareCephStatus(t *testing.T) {
tests := []struct {
actual string
when string
want bool
wantErr bool
}{
{
actual: "HEALTH_OK",
when: "HEALTH_OK",
want: true,
},
{
actual: "HEALTH_OK",
when: "HEALTH_WARN",
want: false,
},
{
actual: "HEALTH_OK",
when: "<= HEALTH_WARN",
want: false,
},
{
actual: "HEALTH_OK",
when: ">= HEALTH_WARN",
want: true,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s %s", tt.actual, tt.when), func(t *testing.T) {
got, err := compareCephStatus(tt.actual, tt.when)
if (err != nil) != tt.wantErr {
t.Errorf("compareCephStatus() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("compareCephStatus() = %v, want %v", got, tt.want)
}
})
}
}
+3 -3
View File
@@ -6,11 +6,11 @@ import (
"github.com/blang/semver"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeClusterVersion(analyzer *troubleshootv1beta1.ClusterVersion, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeClusterVersion(analyzer *troubleshootv1beta2.ClusterVersion, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
clusterInfo, err := getCollectedFileContents("cluster-info/cluster_version.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get contents of cluster_version.json")
@@ -29,7 +29,7 @@ func analyzeClusterVersion(analyzer *troubleshootv1beta1.ClusterVersion, getColl
return analyzeClusterVersionResult(k8sVersion, analyzer.Outcomes, analyzer.CheckName)
}
func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubleshootv1beta1.Outcome, checkName string) (*AnalyzeResult, error) {
func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
for _, outcome := range outcomes {
when := ""
message := ""
+6 -9
View File
@@ -5,28 +5,27 @@ import (
"testing"
"github.com/blang/semver"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"go.undefinedlabs.com/scopeagent"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
func Test_analyzeClusterVersionResult(t *testing.T) {
outcomes := []*troubleshootv1beta1.Outcome{
outcomes := []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "< 1.13.0",
Message: "Sentry requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.",
URI: "https://www.kubernetes.io",
},
},
{
Warn: &troubleshootv1beta1.SingleOutcome{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "< 1.15.0",
Message: "Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.",
URI: "https://www.kubernetes.io",
},
},
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "Your cluster meets the recommended and required versions of Kubernetes.",
},
},
@@ -34,7 +33,7 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
type args struct {
k8sVersion semver.Version
outcomes []*troubleshootv1beta1.Outcome
outcomes []*troubleshootv1beta2.Outcome
checkName string
}
tests := []struct {
@@ -93,8 +92,6 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
got, err := analyzeClusterVersionResult(tt.args.k8sVersion, tt.args.outcomes, tt.args.checkName)
if (err != nil) != tt.wantErr {
t.Errorf("analyzeClusterVersionResult() error = %v, wantErr %v", err, tt.wantErr)
+2 -2
View File
@@ -5,10 +5,10 @@ import (
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
func commonStatus(outcomes []*troubleshootv1beta1.Outcome, title, iconKey string, iconURI string, readyReplicas int) (*AnalyzeResult, error) {
func commonStatus(outcomes []*troubleshootv1beta2.Outcome, title, iconKey string, iconURI string, readyReplicas int) (*AnalyzeResult, error) {
result := &AnalyzeResult{
Title: title,
IconKey: iconKey,
+7 -3
View File
@@ -6,11 +6,11 @@ import (
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
)
func analyzeContainerRuntime(analyzer *troubleshootv1beta1.ContainerRuntime, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeContainerRuntime(analyzer *troubleshootv1beta2.ContainerRuntime, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
@@ -26,8 +26,12 @@ func analyzeContainerRuntime(analyzer *troubleshootv1beta1.ContainerRuntime, get
foundRuntimes = append(foundRuntimes, node.Status.NodeInfo.ContainerRuntimeVersion)
}
title := analyzer.CheckName
if title == "" {
title = "Container Runtime"
}
result := &AnalyzeResult{
Title: "Container Runtime",
Title: title,
IconKey: "kubernetes_container_runtime",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/container-runtime.svg?w=23&h=16",
}
+6 -11
View File
@@ -3,10 +3,9 @@ package analyzer
import (
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.undefinedlabs.com/scopeagent"
)
func Test_compareRuntimeConditionalToActual(t *testing.T) {
@@ -56,8 +55,6 @@ func Test_compareRuntimeConditionalToActual(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
actual, err := compareRuntimeConditionalToActual(test.conditional, test.actual)
@@ -72,22 +69,22 @@ func Test_compareRuntimeConditionalToActual(t *testing.T) {
func Test_containerRuntime(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta1.ContainerRuntime
analyzer troubleshootv1beta2.ContainerRuntime
expectResult AnalyzeResult
files map[string][]byte
}{
{
name: "no containerd, when it's containerd",
analyzer: troubleshootv1beta1.ContainerRuntime{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.ContainerRuntime{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "!= containerd",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "containerd detected",
},
},
@@ -110,8 +107,6 @@ func Test_containerRuntime(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
getFiles := func(n string) ([]byte, error) {
+2 -2
View File
@@ -4,11 +4,11 @@ import (
"encoding/json"
"fmt"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
)
func analyzeCustomResourceDefinition(analyzer *troubleshootv1beta1.CustomResourceDefinition, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeCustomResourceDefinition(analyzer *troubleshootv1beta2.CustomResourceDefinition, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
crdData, err := getCollectedFileContents("cluster-resources/custom-resource-definitions.json")
if err != nil {
return nil, err
+8 -4
View File
@@ -35,14 +35,18 @@ func compareDatabaseConditionalToActual(conditional string, result *collect.Data
return false, errors.New("unable to parse postgres connected analyzer")
case "version":
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], parts[2]))
expected, err := semver.ParseTolerant(strings.Replace(parts[2], "x", "0", -1))
if err != nil {
return false, errors.Wrap(err, "failed to parse semver range")
return false, errors.Wrap(err, "failed to parse expected version")
}
actual, err := semver.ParseTolerant(strings.Replace(result.Version, "x", "0", -1))
if err != nil {
return false, errors.Wrap(err, "failed to parse postgres db actual version")
}
actual, err := semver.Parse(result.Version)
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], expected.String()))
if err != nil {
return false, errors.Wrap(err, "failed to parse actual psotgres version")
return false, errors.Wrap(err, "failed to parse semver range")
}
return expectedRange(actual), nil
-3
View File
@@ -6,7 +6,6 @@ import (
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.undefinedlabs.com/scopeagent"
)
func Test_compareDatabaseConditionalToActual(t *testing.T) {
@@ -62,8 +61,6 @@ func Test_compareDatabaseConditionalToActual(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
actual, err := compareDatabaseConditionalToActual(test.conditional, &test.result)
+5 -5
View File
@@ -3,15 +3,15 @@ package analyzer
import (
"encoding/json"
"fmt"
"path"
"path/filepath"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
appsv1 "k8s.io/api/apps/v1"
)
func analyzeDeploymentStatus(analyzer *troubleshootv1beta1.DeploymentStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents(path.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)))
func analyzeDeploymentStatus(analyzer *troubleshootv1beta2.DeploymentStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents(filepath.Join("cluster-resources", "deployments", fmt.Sprintf("%s.json", analyzer.Namespace)))
if err != nil {
return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
}
@@ -24,7 +24,7 @@ func analyzeDeploymentStatus(analyzer *troubleshootv1beta1.DeploymentStatus, get
var status *appsv1.DeploymentStatus
for _, deployment := range deployments {
if deployment.Name == analyzer.Name {
status = &deployment.Status
status = deployment.Status.DeepCopy()
}
}
+15 -18
View File
@@ -3,31 +3,30 @@ package analyzer
import (
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.undefinedlabs.com/scopeagent"
)
func Test_deploymentStatus(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta1.DeploymentStatus
analyzer troubleshootv1beta2.DeploymentStatus
expectResult AnalyzeResult
files map[string][]byte
}{
{
name: "1/1, pass when = 1",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.DeploymentStatus{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "= 1",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -50,16 +49,16 @@ func Test_deploymentStatus(t *testing.T) {
},
{
name: "1/1, pass when = 2",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.DeploymentStatus{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "= 2",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -82,22 +81,22 @@ func Test_deploymentStatus(t *testing.T) {
},
{
name: "1/1, pass when >= 2, warn when = 1, fail when 0",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.DeploymentStatus{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ">= 2",
Message: "pass",
},
},
{
Warn: &troubleshootv1beta1.SingleOutcome{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "= 1",
Message: "warn",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -122,8 +121,6 @@ func Test_deploymentStatus(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
getFiles := func(n string) ([]byte, error) {
+41 -8
View File
@@ -2,10 +2,11 @@ package analyzer
import (
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -19,6 +20,8 @@ type providers struct {
openShift bool
kurl bool
aks bool
ibm bool
minikube bool
}
type Provider int
@@ -33,6 +36,8 @@ const (
openShift Provider = iota
kurl Provider = iota
aks Provider = iota
ibm Provider = iota
minikube Provider = iota
)
func CheckOpenShift(foundProviders *providers, apiResources []*metav1.APIResourceList, provider string) string {
@@ -68,6 +73,10 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
foundProviders.aks = true
stringProvider = "aks"
}
if k == "minikube.k8s.io/version" {
foundProviders.minikube = true
stringProvider = "minikube"
}
}
if node.Status.NodeInfo.OSImage == "Docker Desktop" {
@@ -87,6 +96,10 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
foundProviders.gke = true
stringProvider = "gke"
}
if strings.HasPrefix(node.Spec.ProviderID, "ibm:") {
foundProviders.ibm = true
stringProvider = "ibm"
}
}
if foundMaster {
@@ -100,7 +113,8 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
return foundProviders, stringProvider
}
func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeDistribution(analyzer *troubleshootv1beta2.Distribution, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
var unknownDistribution string
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
@@ -123,9 +137,12 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
}
_ = CheckOpenShift(&foundProviders, apiResources, "")
}
title := analyzer.CheckName
if title == "" {
title = "Kubernetes Distribution"
}
result := &AnalyzeResult{
Title: "Kubernetes Distribution",
Title: title,
IconKey: "kubernetes_distribution",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/distribution.svg?w=20&h=14",
}
@@ -141,7 +158,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
return result, nil
}
isMatch, err := compareDistributionConditionalToActual(outcome.Fail.When, foundProviders)
isMatch, err := compareDistributionConditionalToActual(outcome.Fail.When, foundProviders, &unknownDistribution)
if err != nil {
return result, errors.Wrap(err, "failed to compare distribution conditional")
}
@@ -162,7 +179,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
return result, nil
}
isMatch, err := compareDistributionConditionalToActual(outcome.Warn.When, foundProviders)
isMatch, err := compareDistributionConditionalToActual(outcome.Warn.When, foundProviders, &unknownDistribution)
if err != nil {
return result, errors.Wrap(err, "failed to compare distribution conditional")
}
@@ -183,7 +200,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
return result, nil
}
isMatch, err := compareDistributionConditionalToActual(outcome.Pass.When, foundProviders)
isMatch, err := compareDistributionConditionalToActual(outcome.Pass.When, foundProviders, &unknownDistribution)
if err != nil {
return result, errors.Wrap(err, "failed to compare distribution conditional")
}
@@ -199,10 +216,17 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
}
}
result.IsWarn = true
if unknownDistribution != "" {
result.Message = unknownDistribution
} else {
result.Message = "None of the conditionals were met"
}
return result, nil
}
func compareDistributionConditionalToActual(conditional string, actual providers) (bool, error) {
func compareDistributionConditionalToActual(conditional string, actual providers, unknownDistribution *string) (bool, error) {
parts := strings.Split(strings.TrimSpace(conditional), " ")
// we can make this a lot more flexible
@@ -220,6 +244,7 @@ func compareDistributionConditionalToActual(conditional string, actual providers
normalizedName := mustNormalizeDistributionName(parts[1])
if normalizedName == unknown {
*unknownDistribution += fmt.Sprintf("- Unknown distribution: %s ", parts[1])
return false, nil
}
@@ -241,6 +266,10 @@ func compareDistributionConditionalToActual(conditional string, actual providers
isMatch = actual.kurl
case aks:
isMatch = actual.aks
case ibm:
isMatch = actual.ibm
case minikube:
isMatch = actual.minikube
}
switch parts[0] {
@@ -271,6 +300,10 @@ func mustNormalizeDistributionName(raw string) Provider {
return kurl
case "aks":
return aks
case "ibm", "ibmcloud", "ibm cloud":
return ibm
case "minikube":
return minikube
}
return unknown
+2 -6
View File
@@ -5,10 +5,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.undefinedlabs.com/scopeagent"
)
func Test_compareDistributionConditionalToActual(t *testing.T) {
var unknownDistribution string
tests := []struct {
name string
conditional string
@@ -42,11 +42,9 @@ func Test_compareDistributionConditionalToActual(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
actual, err := compareDistributionConditionalToActual(test.conditional, test.input)
actual, err := compareDistributionConditionalToActual(test.conditional, test.input, &unknownDistribution)
req.NoError(err)
assert.Equal(t, test.expected, actual)
@@ -79,8 +77,6 @@ func Test_mustNormalizeDistributionName(t *testing.T) {
for _, test := range tests {
t.Run(test.raw, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
actual := mustNormalizeDistributionName(test.raw)
assert.Equal(t, test.expected, actual)
+80 -15
View File
@@ -10,8 +10,9 @@ import (
getter "github.com/hashicorp/go-getter"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"k8s.io/client-go/kubernetes/scheme"
)
@@ -21,8 +22,13 @@ type fileContentProvider struct {
}
// Analyze local will analyze a locally available (already downloaded) bundle
func AnalyzeLocal(localBundlePath string, analyzers []*troubleshootv1beta1.Analyze) ([]*AnalyzeResult, error) {
fcp := fileContentProvider{rootDir: localBundlePath}
func AnalyzeLocal(localBundlePath string, analyzers []*troubleshootv1beta2.Analyze) ([]*AnalyzeResult, error) {
rootDir, err := FindBundleRootDir(localBundlePath)
if err != nil {
return nil, errors.Wrap(err, "failed to find root dir")
}
fcp := fileContentProvider{rootDir: rootDir}
analyzeResults := []*AnalyzeResult{}
for _, analyzer := range analyzers {
@@ -33,7 +39,7 @@ func AnalyzeLocal(localBundlePath string, analyzers []*troubleshootv1beta1.Analy
}
if analyzeResult != nil {
analyzeResults = append(analyzeResults, analyzeResult)
analyzeResults = append(analyzeResults, analyzeResult...)
}
}
@@ -51,12 +57,17 @@ func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResul
return nil, errors.Wrap(err, "failed to download bundle")
}
_, err = os.Stat(filepath.Join(tmpDir, "version.yaml"))
rootDir, err := FindBundleRootDir(tmpDir)
if err != nil {
return nil, errors.Wrap(err, "failed to find root dir")
}
_, err = os.Stat(filepath.Join(rootDir, "version.yaml"))
if err != nil {
return nil, errors.Wrap(err, "failed to read version.yaml")
}
analyzers := []*troubleshootv1beta1.Analyze{}
analyzers := []*troubleshootv1beta2.Analyze{}
if analyzersSpec == "" {
defaultAnalyzers, err := getDefaultAnalyzers()
@@ -72,7 +83,7 @@ func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResul
analyzers = parsedAnalyzers
}
return AnalyzeLocal(tmpDir, analyzers)
return AnalyzeLocal(rootDir, analyzers)
}
func downloadTroubleshootBundle(bundleURL string, destDir string) error {
@@ -139,6 +150,12 @@ func ExtractTroubleshootBundle(reader io.Reader, destDir string) error {
}
case tar.TypeReg:
name := filepath.Join(destDir, header.Name)
dirName := filepath.Dir(name)
if err := os.MkdirAll(dirName, 0755); err != nil {
return errors.Wrapf(err, "failed to mkdir for file %s", header.Name)
}
file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.FileMode(header.Mode))
if err != nil {
return errors.Wrap(err, "failed to open tar file")
@@ -154,21 +171,26 @@ func ExtractTroubleshootBundle(reader io.Reader, destDir string) error {
return nil
}
func parseAnalyzers(spec string) ([]*troubleshootv1beta1.Analyze, error) {
func parseAnalyzers(spec string) ([]*troubleshootv1beta2.Analyze, error) {
troubleshootscheme.AddToScheme(scheme.Scheme)
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(spec), nil, nil)
convertedSpec, err := docrewrite.ConvertToV1Beta2([]byte(spec))
if err != nil {
return nil, errors.Wrap(err, "failed to convert to v1beta2")
}
obj, _, err := decode(convertedSpec, nil, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to decode analyzers")
}
analyzer := obj.(*troubleshootv1beta1.Analyzer)
analyzer := obj.(*troubleshootv1beta2.Analyzer)
return analyzer.Spec.Analyzers, nil
}
func getDefaultAnalyzers() ([]*troubleshootv1beta1.Analyze, error) {
spec := `apiVersion: troubleshoot.replicated.com/v1beta1
func getDefaultAnalyzers() ([]*troubleshootv1beta2.Analyze, error) {
spec := `apiVersion: troubleshoot.sh/v1beta2
kind: Analyzer
metadata:
name: defaultAnalyzers
@@ -191,12 +213,55 @@ spec:
return parseAnalyzers(spec)
}
// FindBundleRootDir detects whether the bundle is stored inside a subdirectory or not.
// returns the subdirectory path if so, otherwise, returns the path unchanged
func FindBundleRootDir(localBundlePath string) (string, error) {
f, err := os.Open(localBundlePath)
if err != nil {
return "", errors.Wrap(err, "failed to open bundle dir")
}
defer f.Close()
names, err := f.Readdirnames(0)
if err != nil {
return "", errors.Wrap(err, "failed to read dirnames")
}
if len(names) == 0 {
return "", errors.New("bundle directory is empty")
}
isInSubDir := true
for _, name := range names {
if name == "version.yaml" {
isInSubDir = false
break
}
}
if isInSubDir {
return filepath.Join(localBundlePath, names[0]), nil
}
return localBundlePath, nil
}
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) {
// TODO: walk sub-dirs
// return nil, errors.New("not implemnted")
return map[string][]byte{}, nil
files, err := filepath.Glob(filepath.Join(f.rootDir, dirName))
if err != nil {
return nil, errors.Wrapf(err, "invalid glob %q", dirName)
}
fileArr := map[string][]byte{}
for _, filePath := range files {
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, errors.Wrapf(err, "read %q", filePath)
}
fileArr[filePath] = bytes
}
return fileArr, nil
}
+183
View File
@@ -0,0 +1,183 @@
package analyzer
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeHostBlockDevices(hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
contents, err := getCollectedFileContents("system/block_devices.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
var devices []collect.BlockDeviceInfo
if err := json.Unmarshal(contents, &devices); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal block devices info")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "Block Devices"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Fail.When, devices)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Warn.When, devices)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Pass.When, devices)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
// <regexp> <op> <count>
// example: sdb > 0
func compareHostBlockDevicesConditionalToActual(conditional string, devices []collect.BlockDeviceInfo) (res bool, err error) {
parts := strings.Split(conditional, " ")
if len(parts) != 3 {
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
}
rx, err := regexp.Compile(parts[0])
if err != nil {
return false, errors.Wrapf(err, "failed to compile regex %q", parts[0])
}
count := countEligibleBlockDevices(rx, devices)
desiredInt, err := strconv.Atoi(parts[2])
if err != nil {
return false, errors.Wrapf(err, "failed to parse desired quantity %q", parts[2])
}
switch parts[1] {
case ">":
return count > desiredInt, nil
case ">=":
return count >= desiredInt, nil
case "<":
return count < desiredInt, nil
case "<=":
return count <= desiredInt, nil
case "=", "==", "===":
return count == desiredInt, nil
}
return false, fmt.Errorf("Unexpected operator %q", parts[1])
}
func countEligibleBlockDevices(rx *regexp.Regexp, devices []collect.BlockDeviceInfo) int {
count := 0
for _, device := range devices {
if isEligibleBlockDevice(rx, device, devices) {
count++
}
}
return count
}
func isEligibleBlockDevice(rx *regexp.Regexp, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
if !rx.MatchString(device.Name) {
return false
}
if device.Type != "disk" {
return false
}
if device.Mountpoint != "" {
return false
}
if device.FilesystemType != "" {
return false
}
if device.ReadOnly {
return false
}
if device.Removable {
return false
}
for _, d := range devices {
if d.ParentKernelName == device.KernelName {
return false
}
}
return true
}
+175
View File
@@ -0,0 +1,175 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeBlockDevices(t *testing.T) {
tests := []struct {
name string
devices []collect.BlockDeviceInfo
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "sdb == 1, pass when there is an empty /dev/sdb",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sdb == 1",
Message: "Block device available",
},
},
},
},
result: &AnalyzeResult{
Title: "Block Devices",
IsPass: true,
Message: "Block device available",
},
},
{
name: "sdb == 1, fail when partitioned",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
},
{
Name: "sdb1",
KernelName: "sdb1",
ParentKernelName: "sdb",
Type: "part",
Major: 8,
Minor: 1,
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sdb == 1",
Message: "Block device available",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No block device available",
},
},
},
},
result: &AnalyzeResult{
Title: "Block Devices",
IsFail: true,
Message: "No block device available",
},
},
{
name: "sdb == 1, fail when it has a filesystem",
devices: []collect.BlockDeviceInfo{
{
Name: "sdb",
KernelName: "sdb",
Type: "disk",
Major: 8,
Serial: "disk1",
FilesystemType: "ext4",
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sdb == 1",
Message: "Block device available",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No block device available",
},
},
},
},
result: &AnalyzeResult{
Title: "Block Devices",
IsFail: true,
Message: "No block device available",
},
},
{
name: ".* > 0, fail when only loop devices are found",
devices: []collect.BlockDeviceInfo{
{
Name: "loop0",
KernelName: "loop0",
Type: "loop",
Major: 7,
},
},
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ".* > 0",
Message: "Block device available",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No block device available",
},
},
},
},
result: &AnalyzeResult{
Title: "Block Devices",
IsFail: true,
Message: "No block device available",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.devices)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostBlockDevices(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+165
View File
@@ -0,0 +1,165 @@
package analyzer
import (
"encoding/json"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeHostCPU(hostAnalyzer *troubleshootv1beta2.CPUAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
contents, err := getCollectedFileContents("system/cpu.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
cpuInfo := collect.CPUInfo{}
if err := json.Unmarshal(contents, &cpuInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal cpu info")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "Number of CPUs"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
isMatch, err := compareHostCPUConditionalToActual(outcome.Fail.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
if err != nil {
return nil, errors.Wrap(err, "failed to compare")
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
isMatch, err := compareHostCPUConditionalToActual(outcome.Warn.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
if err != nil {
return nil, errors.Wrap(err, "failed to compare")
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
isMatch, err := compareHostCPUConditionalToActual(outcome.Pass.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
if err != nil {
return nil, errors.Wrap(err, "failed to compare")
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
func compareHostCPUConditionalToActual(conditional string, logicalCount int, physicalCount int) (res bool, err error) {
compareLogical := false
comparePhysical := false
compareUnspecified := false
comparator := ""
desired := ""
parts := strings.Split(conditional, " ")
if len(parts) == 3 {
comparator = parts[1]
desired = parts[2]
if strings.ToLower(parts[0]) == "logical" {
compareLogical = true
} else if strings.ToLower(parts[0]) == "physical" {
comparePhysical = true
} else if strings.ToLower(parts[0]) == "count" {
compareUnspecified = true
}
} else if len(parts) == 2 {
compareUnspecified = true
comparator = parts[0]
desired = parts[1]
}
if !compareLogical && !comparePhysical && !compareUnspecified {
return false, errors.New("unable to parse conditional")
}
if compareLogical {
return doCompareHostCPU(comparator, desired, logicalCount)
} else if comparePhysical {
return doCompareHostCPU(comparator, desired, physicalCount)
} else {
actual := logicalCount
if physicalCount > logicalCount {
actual = physicalCount
}
return doCompareHostCPU(comparator, desired, actual)
}
}
func doCompareHostCPU(operator string, desired string, actual int) (bool, error) {
desiredInt, err := strconv.ParseInt(desired, 10, 64)
if err != nil {
return false, errors.Wrap(err, "failed to parse")
}
switch operator {
case "<":
return actual < int(desiredInt), nil
case "<=":
return actual <= int(desiredInt), nil
case ">":
return actual > int(desiredInt), nil
case ">=":
return actual >= int(desiredInt), nil
case "=", "==", "===":
return actual == int(desiredInt), nil
}
return false, errors.New("unknown operator")
}
+150
View File
@@ -0,0 +1,150 @@
package analyzer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_doCompareHostCPU(t *testing.T) {
tests := []struct {
name string
operator string
desired string
actual int
expected bool
}{
{
name: "< 16",
operator: "<",
desired: "16",
actual: 8,
expected: true,
},
{
name: "< 8 when actual is 8",
operator: "<",
desired: "8",
actual: 8,
expected: false,
},
{
name: "<= 8 when actual is 8",
operator: "<=",
desired: "8",
actual: 8,
expected: true,
},
{
name: "<= 8 when actual is 16",
operator: "<=",
desired: "8",
actual: 16,
expected: false,
},
{
name: "== 8 when actual is 16",
operator: "==",
desired: "8",
actual: 16,
expected: false,
},
{
name: "== 8 when actual is 8",
operator: "==",
desired: "8",
actual: 8,
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := doCompareHostCPU(test.operator, test.desired, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_compareHostCPUConditionalToActual(t *testing.T) {
tests := []struct {
name string
when string
logicalCount int
physicalCount int
expected bool
}{
{
name: "physical > 4, when physical is 8",
when: "physical > 4",
logicalCount: 0,
physicalCount: 8,
expected: true,
},
{
name: "physical > 4, when physical is 4",
when: "physical > 4",
logicalCount: 0,
physicalCount: 4,
expected: false,
},
{
name: "physical > 4, when physical is 3, logical is 6",
when: "physical > 4",
logicalCount: 6,
physicalCount: 3,
expected: false,
},
{
name: "logical > 4, when physical is 4, logical is 8",
when: "logical > 4",
logicalCount: 8,
physicalCount: 4,
expected: true,
},
{
name: ">= 4, when physical is 2, logical is 4",
when: ">= 4",
logicalCount: 4,
physicalCount: 2,
expected: true,
},
{
name: "count < 4, when physical is 2, logical is 4",
when: "count < 4",
logicalCount: 4,
physicalCount: 2,
expected: false,
},
{
name: "count <= 4, when physical is 2, logical is 4",
when: "count <= 4",
logicalCount: 4,
physicalCount: 2,
expected: true,
},
{
name: "== 4, physical is 4, logical is 4",
when: "== 4",
logicalCount: 4,
physicalCount: 4,
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareHostCPUConditionalToActual(test.when, test.logicalCount, test.physicalCount)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
+185
View File
@@ -0,0 +1,185 @@
package analyzer
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/apimachinery/pkg/api/resource"
)
func analyzeHostDiskUsage(hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
key := collect.HostDiskUsageKey(hostAnalyzer.CollectorName)
contents, err := getCollectedFileContents(key)
if err != nil {
return nil, errors.Wrapf(err, "failed to get collected file %s", key)
}
diskUsageInfo := collect.DiskUsageInfo{}
if err := json.Unmarshal(contents, &diskUsageInfo); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal disk usage info from %s", key)
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = fmt.Sprintf("Disk Usage %s", hostAnalyzer.CollectorName)
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
isMatch, err := compareHostDiskUsageConditionalToActual(outcome.Fail.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %q", outcome.Fail.When)
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
isMatch, err := compareHostDiskUsageConditionalToActual(outcome.Warn.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %q", outcome.Warn.When)
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
isMatch, err := compareHostDiskUsageConditionalToActual(outcome.Pass.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %q", outcome.Pass.When)
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
func compareHostDiskUsageConditionalToActual(conditional string, totalBytes uint64, usedBytes uint64) (res bool, err error) {
parts := strings.Split(conditional, " ")
if len(parts) != 3 {
return false, fmt.Errorf("conditional must have exactly 3 parts, got %d", len(parts))
}
stat := strings.ToLower(parts[0])
comparator := parts[1]
desired := parts[2]
switch stat {
case "total":
return doCompareHostDiskUsage(comparator, desired, totalBytes)
case "used":
return doCompareHostDiskUsage(comparator, desired, usedBytes)
case "available":
return doCompareHostDiskUsage(comparator, desired, totalBytes-usedBytes)
case "used/total":
used := float64(usedBytes) / float64(totalBytes)
return doCompareHostDiskUsagePercent(comparator, desired, used)
case "available/total":
available := float64(totalBytes-usedBytes) / float64(totalBytes)
return doCompareHostDiskUsagePercent(comparator, desired, available)
}
return false, fmt.Errorf("unknown disk usage statistic %q", stat)
}
func doCompareHostDiskUsage(operator string, desired string, actual uint64) (bool, error) {
quantity, err := resource.ParseQuantity(desired)
if err != nil {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
desiredInt, ok := quantity.AsInt64()
if !ok {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
switch operator {
case "<":
return actual < uint64(desiredInt), nil
case "<=":
return actual <= uint64(desiredInt), nil
case ">":
return actual > uint64(desiredInt), nil
case ">=":
return actual >= uint64(desiredInt), nil
case "=", "==", "===":
return actual == uint64(desiredInt), nil
}
return false, errors.New("unknown operator")
}
func doCompareHostDiskUsagePercent(operator string, desired string, actual float64) (bool, error) {
isPercent := false
if strings.HasSuffix(desired, "%") {
desired = strings.TrimSuffix(desired, "%")
isPercent = true
}
desiredPercent, err := strconv.ParseFloat(desired, 64)
if err != nil {
return false, errors.Wrap(err, "parsed desired quantity")
}
if isPercent {
desiredPercent = desiredPercent / 100.0
}
switch operator {
case "<":
return actual < desiredPercent, nil
case "<=":
return actual <= desiredPercent, nil
case ">":
return actual > desiredPercent, nil
case ">=":
return actual >= desiredPercent, nil
case "=", "==", "===":
return actual == desiredPercent, nil
}
return false, errors.New("unknown operator")
}
+380
View File
@@ -0,0 +1,380 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_doCompareHostDiskUsage(t *testing.T) {
tests := []struct {
name string
operator string
desired string
actual uint64
expected bool
}{
{
name: ">= 1Gi, when actual is 2Gi",
operator: ">=",
desired: "1Gi",
actual: 2147483648,
expected: true,
},
{
name: "<= 1Gi, when actual is 1GB",
operator: "<=",
desired: "1Gi",
actual: 1000000000,
expected: true,
},
{
name: "< 20Gi, when actual is 15Gi",
operator: "<",
desired: "20Gi",
actual: 15 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "< 20Gi, when actual is 20Gi",
operator: "<",
desired: "20Gi",
actual: 20 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "> 1073741824, when actual is 1024",
operator: ">",
desired: "1073741824",
actual: 1024,
expected: false,
},
{
name: "= 4096, when actual is 4096",
operator: "=",
desired: "4096",
actual: 4096,
expected: true,
},
{
name: "= 4096, when actual is 1024",
operator: "=",
desired: "4096",
actual: 1024,
expected: false,
},
{
name: "= 4096, when actual is 5000",
operator: "=",
desired: "4096",
actual: 5000,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := doCompareHostDiskUsage(test.operator, test.desired, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_doCompareHostDiskUsagePercent(t *testing.T) {
tests := []struct {
name string
operator string
desired string
actual float64
expected bool
}{
{
name: ">= .20, when actual is .30",
operator: ">=",
desired: ".20",
actual: .30,
expected: true,
},
{
name: ">= .20, when actual is .10",
operator: ">=",
desired: ".20",
actual: .10,
expected: false,
},
{
name: ">= .20, when actual is .20",
operator: ">=",
desired: ".20",
actual: .20,
expected: true,
},
{
name: "> .20, when actual is .30",
operator: ">",
desired: ".20",
actual: .30,
expected: true,
},
{
name: "> .20, when actual is .10",
operator: ">",
desired: ".20",
actual: .10,
expected: false,
},
{
name: "> .20, when actual is .20",
operator: ">",
desired: ".20",
actual: .20,
expected: false,
},
{
name: "<= .20, when actual is .30",
operator: "<=",
desired: ".20",
actual: .30,
expected: false,
},
{
name: "<= .20, when actual is .10",
operator: "<=",
desired: ".20",
actual: .10,
expected: true,
},
{
name: "<= .20, when actual is .20",
operator: "<=",
desired: ".20",
actual: .20,
expected: true,
},
{
name: "< .20, when actual is .30",
operator: "<",
desired: ".20",
actual: .30,
expected: false,
},
{
name: "< .20, when actual is .10",
operator: "<",
desired: ".20",
actual: .10,
expected: true,
},
{
name: "< .20, when actual is .20",
operator: "<",
desired: ".20",
actual: .20,
expected: false,
},
{
name: "= .20, when actual is .30",
operator: "=",
desired: ".20",
actual: .30,
expected: false,
},
{
name: "= .20, when actual is .10",
operator: "=",
desired: ".20",
actual: .10,
expected: false,
},
{
name: "= .20, when actual is .20",
operator: "=",
desired: ".20",
actual: .20,
expected: true,
},
{
name: "= 20%, when actual is .20",
operator: "=",
desired: "20%",
actual: .20,
expected: true,
},
{
name: "= 20%, when actual is 20",
operator: "=",
desired: "20%",
actual: 20,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := doCompareHostDiskUsagePercent(test.operator, test.desired, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func TestAnalyzeHostDiskUsage(t *testing.T) {
tests := []struct {
name string
diskUsageInfo *collect.DiskUsageInfo
hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "Fail on insuffient total ephemeral disk space",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 10 * 1024 * 1024 * 1024,
UsedBytes: 5 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "used/total >= 80%",
Message: "/var/lib/kubelet is more than 80% full",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "total <= 10Gi",
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsFail: true,
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
{
name: "Fail on insuffient available ephemeral disk space percentage",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 10 * 1024 * 1024 * 1024,
UsedBytes: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "total < 10Gi",
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "used/total >= 80%",
Message: "/var/lib/kubelet is more than 80% full",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsFail: true,
Message: "/var/lib/kubelet is more than 80% full",
},
},
{
name: "Warn on high ephemeral disk space usage",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 1024 * 1024 * 1024 * 1024,
UsedBytes: 100 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "total < 10Gi",
Message: "/var/lib/kubelet requires at least 10Gi",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "used/total >= 80%",
Message: "/var/lib/kubelet is more than 80% full",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "used >= 100Gi",
Message: "/var/lib/kubelet has more than 100Gi used",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsWarn: true,
Message: "/var/lib/kubelet has more than 100Gi used",
},
},
{
name: "Pass on ephemeral disk space available",
diskUsageInfo: &collect.DiskUsageInfo{
TotalBytes: 12 * 1024 * 1024 * 1024,
UsedBytes: 1 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
CollectorName: "ephemeral",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "available > 10Gi",
Message: "/var/lib/kubelet has at least 10Gi available",
},
},
},
},
result: &AnalyzeResult{
Title: "Disk Usage ephemeral",
IsPass: true,
Message: "/var/lib/kubelet has at least 10Gi available",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.diskUsageInfo)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostDiskUsage(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+140
View File
@@ -0,0 +1,140 @@
package analyzer
import (
"encoding/json"
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
type httpResult struct {
Error *collect.HTTPError
Response *collect.HTTPResponse
}
func analyzeHostHTTP(hostAnalyzer *troubleshootv1beta2.HTTPAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
name := filepath.Join("http", "result.json")
if hostAnalyzer.CollectorName != "" {
name = filepath.Join("http", hostAnalyzer.CollectorName+".json")
}
contents, err := getCollectedFileContents(name)
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
httpInfo := &httpResult{}
if err := json.Unmarshal(contents, httpInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal http result")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "HTTP Request"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
isMatch, err := compareHostHTTPConditionalToActual(outcome.Fail.When, httpInfo)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
isMatch, err := compareHostHTTPConditionalToActual(outcome.Warn.When, httpInfo)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
isMatch, err := compareHostHTTPConditionalToActual(outcome.Pass.When, httpInfo)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
func compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) {
if conditional == "error" {
return result.Error != nil, nil
}
parts := strings.Split(conditional, " ")
if len(parts) != 3 {
return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts))
}
if parts[0] != "statusCode" {
return false, errors.New(`Conditional must begin with keyword "statusCode"`)
}
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
return false, errors.New(`Only supported operator is "=="`)
}
i, err := strconv.Atoi(parts[2])
if err != nil {
return false, err
}
if result.Response == nil {
return false, err
}
return result.Response.Status == i, nil
}
+104
View File
@@ -0,0 +1,104 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeHostHTTP(t *testing.T) {
tests := []struct {
name string
httpResult *httpResult
hostAnalyzer *troubleshootv1beta2.HTTPAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "error",
httpResult: &httpResult{
Error: &collect.HTTPError{
Message: "i/o timeout",
},
},
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "registry",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "error",
Message: "Failed to reach replicated.registry.com",
},
},
},
},
result: &AnalyzeResult{
Title: "HTTP Request",
IsFail: true,
Message: "Failed to reach replicated.registry.com",
},
},
{
name: "200",
httpResult: &httpResult{
Response: &collect.HTTPResponse{
Status: 200,
},
},
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
CollectorName: "registry",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "error",
Message: "Failed to reach replicated.registry.com",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "statusCode == 204",
Message: "No content",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "statusCode == 200",
Message: "Successfully reached registry",
},
},
},
},
result: &AnalyzeResult{
Title: "HTTP Request",
IsPass: true,
Message: "Successfully reached registry",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.httpResult)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostHTTP(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+134
View File
@@ -0,0 +1,134 @@
package analyzer
import (
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"k8s.io/apimachinery/pkg/api/resource"
)
func analyzeHostMemory(hostAnalyzer *troubleshootv1beta2.MemoryAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
contents, err := getCollectedFileContents("system/memory.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
memoryInfo := collect.MemoryInfo{}
if err := json.Unmarshal(contents, &memoryInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal memory info")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "Amount of Memory"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
isMatch, err := compareHostMemoryConditionalToActual(outcome.Fail.When, memoryInfo.Total)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
isMatch, err := compareHostMemoryConditionalToActual(outcome.Warn.When, memoryInfo.Total)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
isMatch, err := compareHostMemoryConditionalToActual(outcome.Pass.When, memoryInfo.Total)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
func compareHostMemoryConditionalToActual(conditional string, total uint64) (res bool, err error) {
parts := strings.Split(conditional, " ")
if len(parts) != 2 {
return false, fmt.Errorf("Expected 2 parts in conditional, got %d", len(parts))
}
operator := parts[0]
desired := parts[1]
quantity, err := resource.ParseQuantity(desired)
if err != nil {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
desiredInt, ok := quantity.AsInt64()
if !ok {
return false, fmt.Errorf("could not parse quantity %q", desired)
}
switch operator {
case "<":
return total < uint64(desiredInt), nil
case "<=":
return total <= uint64(desiredInt), nil
case ">":
return total > uint64(desiredInt), nil
case ">=":
return total >= uint64(desiredInt), nil
case "=", "==", "===":
return total == uint64(desiredInt), nil
}
return false, errors.New("unknown operator")
}
+177
View File
@@ -0,0 +1,177 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_doCompareHostMemory(t *testing.T) {
tests := []struct {
name string
conditional string
actual uint64
expected bool
}{
{
name: "< 16Gi when actual is 8Gi",
conditional: "< 16Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "< 8Gi when actual is 8Gi",
conditional: "< 8Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "<= 8Gi when actual is 8Gi",
conditional: "<= 8Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "<= 8Gi when actual is 16Gi",
conditional: "<= 8Gi",
actual: 16 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "== 8Gi when actual is 16Gi",
conditional: "== 8Gi",
actual: 16 * 1024 * 1024 * 1024,
expected: false,
},
{
name: "== 8Gi when actual is 8Gi",
conditional: "== 8Gi",
actual: 8 * 1024 * 1024 * 1024,
expected: true,
},
{
name: "== 8000000000 when actual is 8000000000",
conditional: "== 8000000000",
actual: 8 * 1000 * 1000 * 1000,
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareHostMemoryConditionalToActual(test.conditional, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func TestAnalyzeHostMemory(t *testing.T) {
tests := []struct {
name string
memoryInfo *collect.MemoryInfo
hostAnalyzer *troubleshootv1beta2.MemoryAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "Pass on memory available",
memoryInfo: &collect.MemoryInfo{
Total: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ">= 4Gi",
Message: "System has at least 4Gi of memory",
},
},
},
},
result: &AnalyzeResult{
Title: "Amount of Memory",
IsPass: true,
Message: "System has at least 4Gi of memory",
},
},
{
name: "Fail on memory available",
memoryInfo: &collect.MemoryInfo{
Total: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "< 16Gi",
Message: "System requires at least 16Gi of memory",
},
},
},
},
result: &AnalyzeResult{
Title: "Amount of Memory",
IsFail: true,
Message: "System requires at least 16Gi of memory",
},
},
{
name: "Warn on memory available",
memoryInfo: &collect.MemoryInfo{
Total: 8 * 1024 * 1024 * 1024,
},
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "< 4Gi",
Message: "System requires at least 4Gi of memory",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "<= 8Gi",
Message: "System performs best with more than 8Gi of memory",
},
},
},
},
result: &AnalyzeResult{
Title: "Amount of Memory",
IsWarn: true,
Message: "System performs best with more than 8Gi of memory",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.memoryInfo)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostMemory(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+91
View File
@@ -0,0 +1,91 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeHostTCPLoadBalancer(hostAnalyzer *troubleshootv1beta2.TCPLoadBalancerAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := hostAnalyzer.CollectorName
if collectorName == "" {
collectorName = "tcpLoadBalancer"
}
fullPath := path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", collectorName))
collected, err := getCollectedFileContents(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
}
actual := collect.NetworkStatusResult{}
if err := json.Unmarshal(collected, &actual); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal collected")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "TCP Load Balancer"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
if string(actual.Status) == outcome.Fail.When {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
if string(actual.Status) == outcome.Warn.When {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
if string(actual.Status) == outcome.Pass.When {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
+89
View File
@@ -0,0 +1,89 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeHostTCPPortStatus(hostAnalyzer *troubleshootv1beta2.TCPPortStatusAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
fullPath := path.Join("tcpPortStatus", "tcpPortStatus.json")
if hostAnalyzer.CollectorName != "" {
fullPath = path.Join("tcpPortStatus", fmt.Sprintf("%s.json", hostAnalyzer.CollectorName))
}
collected, err := getCollectedFileContents(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
}
actual := collect.NetworkStatusResult{}
if err := json.Unmarshal(collected, &actual); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal collected")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "TCP Port Status"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
if string(actual.Status) == outcome.Fail.When {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
if string(actual.Status) == outcome.Warn.When {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
if string(actual.Status) == outcome.Pass.When {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
+144
View File
@@ -0,0 +1,144 @@
package analyzer
import (
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
const (
SynchronizedActive = "synchronized+active"
SynchronizedInactive = "synchronized+inactive"
UnsynchronizedActive = "unsynchronized+active"
UnsynchronizedInactive = "unsynchronized+inactive"
)
func analyzeHostTime(hostAnalyzer *troubleshootv1beta2.TimeAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
contents, err := getCollectedFileContents("system/time.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get collected file")
}
timeInfo := collect.TimeInfo{}
if err := json.Unmarshal(contents, &timeInfo); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal time info")
}
result := AnalyzeResult{}
title := hostAnalyzer.CheckName
if title == "" {
title = "Time"
}
result.Title = title
for _, outcome := range hostAnalyzer.Outcomes {
if outcome.Fail != nil {
if outcome.Fail.When == "" {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
isMatch, err := compareHostTimeStatusToActual(outcome.Fail.When, timeInfo)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
}
if isMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return &result, nil
}
} else if outcome.Warn != nil {
if outcome.Warn.When == "" {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
isMatch, err := compareHostTimeStatusToActual(outcome.Warn.When, timeInfo)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
}
if isMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return &result, nil
}
} else if outcome.Pass != nil {
if outcome.Pass.When == "" {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
isMatch, err := compareHostTimeStatusToActual(outcome.Pass.When, timeInfo)
if err != nil {
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return &result, nil
}
}
}
return &result, nil
}
func compareHostTimeStatusToActual(status string, timeInfo collect.TimeInfo) (res bool, err error) {
parts := strings.Split(status, " ")
if len(parts) != 3 {
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
}
if parts[0] == "timezone" {
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" && parts[1] != "!=" {
return false, errors.New(`Only supported operators are "==" and "!="`)
}
if parts[1] == "!=" {
return parts[2] != timeInfo.Timezone, nil
}
return parts[2] == timeInfo.Timezone, nil
}
if parts[0] == "ntp" {
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
return false, errors.New(`Only supported operator is "=="`)
}
switch parts[2] {
case SynchronizedActive:
return timeInfo.NTPSynchronized && timeInfo.NTPActive, nil
case SynchronizedInactive:
return timeInfo.NTPSynchronized && !timeInfo.NTPActive, nil
case UnsynchronizedActive:
return !timeInfo.NTPSynchronized && timeInfo.NTPActive, nil
case UnsynchronizedInactive:
return !timeInfo.NTPSynchronized && !timeInfo.NTPActive, nil
default:
return false, fmt.Errorf("Unknown status %q. Allowed values are %q, %q, %q, or %q", parts[2], SynchronizedActive, SynchronizedInactive, UnsynchronizedActive, UnsynchronizedInactive)
}
}
return false, fmt.Errorf("Unknown keyword: %s", parts[0])
}
+232
View File
@@ -0,0 +1,232 @@
package analyzer
import (
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeHostTime(t *testing.T) {
tests := []struct {
name string
timeInfo *collect.TimeInfo
hostAnalyzer *troubleshootv1beta2.TimeAnalyze
result *AnalyzeResult
expectErr bool
}{
{
name: "ntp == synchronized+active",
timeInfo: &collect.TimeInfo{
Timezone: "UTC",
NTPSynchronized: true,
NTPActive: true,
},
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "ntp == unsynchronized+inactive",
Message: "System clock not synchronized",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "ntp == synchronized+active",
Message: "System clock synchronized and NTP is active",
},
},
},
},
result: &AnalyzeResult{
Title: "Time",
IsPass: true,
Message: "System clock synchronized and NTP is active",
},
},
{
name: "ntp == unsynchronized+inactive",
timeInfo: &collect.TimeInfo{
Timezone: "UTC",
NTPSynchronized: false,
NTPActive: false,
},
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "ntp == unsynchronized+inactive",
Message: "System clock not synchronized",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "ntp == synchronized+active",
Message: "System clock synchronized and NTP is active",
},
},
},
},
result: &AnalyzeResult{
Title: "Time",
IsFail: true,
Message: "System clock not synchronized",
},
},
{
name: "ntp == unsynchronized+active",
timeInfo: &collect.TimeInfo{
Timezone: "UTC",
NTPSynchronized: false,
NTPActive: true,
},
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "ntp == unsynchronized+inactive",
Message: "System clock not synchronized",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "ntp == unsynchronized+active",
Message: "System clock not yet synchronized",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "ntp == synchronized+active",
Message: "System clock synchronized and NTP is active",
},
},
},
},
result: &AnalyzeResult{
Title: "Time",
IsWarn: true,
Message: "System clock not yet synchronized",
},
},
{
name: "ntp == synchronized+inactive",
timeInfo: &collect.TimeInfo{
Timezone: "UTC",
NTPSynchronized: true,
NTPActive: false,
},
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "ntp == unsynchronized+inactive",
Message: "System clock not synchronized",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "ntp == unsynchronized+active",
Message: "System clock not yet synchronized",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "ntp == synchronized+inactive",
Message: "System clock synchronized for now",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "ntp == synchronized+active",
Message: "System clock synchronized and NTP is active",
},
},
},
},
result: &AnalyzeResult{
Title: "Time",
IsWarn: true,
Message: "System clock synchronized for now",
},
},
{
name: "timezone",
timeInfo: &collect.TimeInfo{
Timezone: "UTC",
NTPSynchronized: true,
NTPActive: true,
},
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "timezone == UTC",
Message: "Timezone is set to UTC",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "timezone not set to UTC",
},
},
},
},
result: &AnalyzeResult{
Title: "Time",
IsPass: true,
Message: "Timezone is set to UTC",
},
},
{
name: "timezone is not UTC",
timeInfo: &collect.TimeInfo{
Timezone: "PST",
NTPSynchronized: true,
NTPActive: true,
},
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "timezone != UTC",
Message: "Timezone is not set to UTC",
},
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "Timezone is set to UTC",
},
},
},
},
result: &AnalyzeResult{
Title: "Time",
IsFail: true,
Message: "Timezone is not set to UTC",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
b, err := json.Marshal(test.timeInfo)
if err != nil {
t.Fatal(err)
}
getCollectedFileContents := func(filename string) ([]byte, error) {
return b, nil
}
result, err := analyzeHostTime(test.hostAnalyzer, getCollectedFileContents)
if test.expectErr {
req.Error(err)
} else {
req.NoError(err)
}
assert.Equal(t, test.result, result)
})
}
}
+27 -11
View File
@@ -2,34 +2,41 @@ package analyzer
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
func analyzeImagePullSecret(analyzer *troubleshootv1beta1.ImagePullSecret, getChildCollectedFileContents func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
func analyzeImagePullSecret(analyzer *troubleshootv1beta2.ImagePullSecret, getChildCollectedFileContents func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
imagePullSecrets, err := getChildCollectedFileContents("cluster-resources/image-pull-secrets")
if err != nil {
return nil, errors.Wrap(err, "failed to get file contents for image pull secrets")
}
var failOutcome *troubleshootv1beta1.Outcome
var passOutcome *troubleshootv1beta1.Outcome
var failOutcome *troubleshootv1beta2.SingleOutcome
var passOutcome *troubleshootv1beta2.SingleOutcome
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
failOutcome = outcome
failOutcome = outcome.Fail
} else if outcome.Pass != nil {
passOutcome = outcome
passOutcome = outcome.Pass
}
}
title := analyzer.CheckName
if title == "" {
title = "Image Pull Secrets"
}
result := AnalyzeResult{
Title: analyzer.CheckName,
Title: title,
IconKey: "kubernetes_image_pull_secret",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/image-pull-secret.svg?w=16&h=14",
IsFail: true,
Message: failOutcome.Fail.Message,
URI: failOutcome.Fail.URI,
}
if failOutcome != nil {
result.Message = failOutcome.Message
result.URI = failOutcome.URI
}
for _, v := range imagePullSecrets {
@@ -42,11 +49,20 @@ func analyzeImagePullSecret(analyzer *troubleshootv1beta1.ImagePullSecret, getCh
if registry == analyzer.RegistryName {
result.IsPass = true
result.IsFail = false
result.Message = passOutcome.Pass.Message
result.URI = passOutcome.Pass.URI
if passOutcome != nil {
result.Message = passOutcome.Message
result.URI = passOutcome.URI
}
}
}
}
if result.Message == "" {
if result.IsPass {
result.Message = fmt.Sprintf("Credentials to pull from: %s found", analyzer.RegistryName)
} else {
result.Message = fmt.Sprintf("Credentials to pull from: %s not ound", analyzer.RegistryName)
}
}
return &result, nil
}
+4 -3
View File
@@ -3,13 +3,14 @@ package analyzer
import (
"encoding/json"
"fmt"
"path/filepath"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
)
func analyzeIngress(analyzer *troubleshootv1beta1.Ingress, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
ingressData, err := getCollectedFileContents("cluster-resources/storage-classes.json")
func analyzeIngress(analyzer *troubleshootv1beta2.Ingress, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
ingressData, err := getCollectedFileContents(filepath.Join("cluster-resources", "ingress", fmt.Sprintf("%s.json", analyzer.Namespace)))
if err != nil {
return nil, err
}
-12
View File
@@ -1,12 +0,0 @@
package analyzer
import (
"os"
"testing"
"go.undefinedlabs.com/scopeagent"
)
func TestMain(m *testing.M) {
os.Exit(scopeagent.Run(m))
}
+6 -6
View File
@@ -6,11 +6,11 @@ import (
"path"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeMysql(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeMysql(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := analyzer.CollectorName
if collectorName == "" {
collectorName = "mysql"
@@ -28,13 +28,13 @@ func analyzeMysql(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFil
return nil, errors.Wrap(err, "failed to unmarshal databased connection result")
}
checkName := analyzer.CheckName
if checkName == "" {
checkName = collectorName
title := analyzer.CheckName
if title == "" {
title = collectorName
}
result := &AnalyzeResult{
Title: checkName,
Title: title,
IconKey: "kubernetes_mysql_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/mysql-analyze.svg",
}
+13 -3
View File
@@ -8,12 +8,12 @@ import (
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeNodeResources(analyzer *troubleshootv1beta2.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
@@ -313,12 +313,22 @@ func findMax(nodes []corev1.Node, property string) *resource.Quantity {
return max
}
func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta1.NodeResourceFilters) (bool, error) {
func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta2.NodeResourceFilters) (bool, error) {
if filters == nil {
return true, nil
}
// all filters must pass for this to pass
if filters.Selector != nil {
for k, v := range filters.Selector.MatchLabel {
l, found := node.Labels[k]
if !found {
return false, nil
} else if l != v {
return false, nil
}
}
}
if filters.CPUCapacity != "" {
parsed, err := resource.ParseQuantity(filters.CPUCapacity)
File diff suppressed because it is too large Load Diff
+431 -5
View File
@@ -3,7 +3,7 @@ package analyzer
import (
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
@@ -386,6 +386,11 @@ func Test_compareNodeResourceConditionalToActual(t *testing.T) {
func Test_nodeMatchesFilters(t *testing.T) {
node := corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"label": "value",
},
},
Status: corev1.NodeStatus{
Capacity: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
@@ -411,13 +416,13 @@ func Test_nodeMatchesFilters(t *testing.T) {
tests := []struct {
name string
node corev1.Node
filters *troubleshootv1beta1.NodeResourceFilters
filters *troubleshootv1beta2.NodeResourceFilters
expectResult bool
}{
{
name: "true when empty filters",
node: node,
filters: &troubleshootv1beta1.NodeResourceFilters{},
filters: &troubleshootv1beta2.NodeResourceFilters{},
expectResult: true,
},
{
@@ -428,7 +433,7 @@ func Test_nodeMatchesFilters(t *testing.T) {
{
name: "false when allocatable memory is too high",
node: node,
filters: &troubleshootv1beta1.NodeResourceFilters{
filters: &troubleshootv1beta2.NodeResourceFilters{
MemoryAllocatable: "16Gi",
},
expectResult: false,
@@ -436,11 +441,47 @@ func Test_nodeMatchesFilters(t *testing.T) {
{
name: "true when allocatable memory is available",
node: node,
filters: &troubleshootv1beta1.NodeResourceFilters{
filters: &troubleshootv1beta2.NodeResourceFilters{
MemoryAllocatable: "4Gi",
},
expectResult: true,
},
{
name: "false when the label does not exist",
node: node,
filters: &troubleshootv1beta2.NodeResourceFilters{
Selector: &troubleshootv1beta2.NodeResourceSelectors{
MatchLabel: map[string]string{
"label2": "value",
},
},
},
expectResult: false,
},
{
name: "false when the label value differs",
node: node,
filters: &troubleshootv1beta2.NodeResourceFilters{
Selector: &troubleshootv1beta2.NodeResourceSelectors{
MatchLabel: map[string]string{
"label": "value2",
},
},
},
expectResult: false,
},
{
name: "true when the label key and value match",
node: node,
filters: &troubleshootv1beta2.NodeResourceFilters{
Selector: &troubleshootv1beta2.NodeResourceSelectors{
MatchLabel: map[string]string{
"label": "value",
},
},
},
expectResult: true,
},
}
for _, test := range tests {
@@ -455,3 +496,388 @@ func Test_nodeMatchesFilters(t *testing.T) {
})
}
}
func Test_analyzeNodeResources(t *testing.T) {
tests := []struct {
name string
analyzer *troubleshootv1beta2.NodeResources
want *AnalyzeResult
wantErr bool
}{
{
name: "at least one pod per node capacity", // this is intended as a general "yes, the end-to-end test works"
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "min(podCapacity) < 1",
Message: "There exist nodes with no pod capacity",
URI: "",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "min(podCapacity) < 50",
Message: "There exist nodes with under 50 pod capacity",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "min(podCapacity) >= 50",
Message: "All nodes can host at least 50 pods",
URI: "",
},
},
},
Filters: nil,
},
want: &AnalyzeResult{
IsPass: true,
IsFail: false,
IsWarn: false,
Title: "Node Resources",
Message: "All nodes can host at least 50 pods",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "at least 16GB ram", // this is intended as a general "yes, the end-to-end fails properly"
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "min(memoryCapacity) < 16Gi",
Message: "There exist nodes with under 16Gb of RAM",
URI: "",
},
},
},
Filters: nil,
},
want: &AnalyzeResult{
IsPass: false,
IsFail: true,
IsWarn: false,
Title: "Node Resources",
Message: "There exist nodes with under 16Gb of RAM",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "at least 16GB ram in g-8vcpu-32gb nodes", // this is intended as a "does filtering work" test
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "min(memoryCapacity) < 16Gi",
Message: "There exist nodes with under 16Gb of RAM",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "min(memoryCapacity) >= 16Gi",
Message: "All nodes have at least 16Gb of RAM",
URI: "",
},
},
},
Filters: &troubleshootv1beta2.NodeResourceFilters{
Selector: &troubleshootv1beta2.NodeResourceSelectors{
MatchLabel: map[string]string{
"node.kubernetes.io/instance-type": "g-8vcpu-32gb",
},
},
},
},
want: &AnalyzeResult{
IsPass: true,
IsFail: false,
IsWarn: false,
Title: "Node Resources",
Message: "All nodes have at least 16Gb of RAM",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "at least 4 cores in all nodes", // cpu count end-to-end
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "quadcore",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "min(cpuCapacity) < 4",
Message: "There exist nodes with under 4 cores",
URI: "",
},
},
},
},
want: &AnalyzeResult{
IsPass: false,
IsFail: true,
IsWarn: false,
Title: "quadcore",
Message: "There exist nodes with under 4 cores",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "at least 4 cores in all g-8vcpu-32gb nodes", // cpu count end-to-end with filtering
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "min(cpuCapacity) < 4",
Message: "There exist nodes with under 4 cores",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "min(cpuCapacity) >= 4",
Message: "All nodes have at least 4 cores",
URI: "",
},
},
},
Filters: &troubleshootv1beta2.NodeResourceFilters{
Selector: &troubleshootv1beta2.NodeResourceSelectors{
MatchLabel: map[string]string{
"node.kubernetes.io/instance-type": "g-8vcpu-32gb",
},
},
},
},
want: &AnalyzeResult{
IsPass: true,
IsFail: false,
IsWarn: false,
Title: "Node Resources",
Message: "All nodes have at least 4 cores",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "at least 8 cores in one node", // "max" e2e test
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "bignode-exists",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "max(cpuCapacity) < 8",
Message: "There isn't a node with 8 or more cores",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "max(cpuCapacity) >= 8",
Message: "There is a node with at least 8 cores",
URI: "",
},
},
},
},
want: &AnalyzeResult{
IsPass: true,
IsFail: false,
IsWarn: false,
Title: "bignode-exists",
Message: "There is a node with at least 8 cores",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "unfiltered CPU totals",
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "total-cpu",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) < 6",
Message: "there are less than 6 total cores",
URI: "",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) > 6",
Message: "there are more than 6 total cores",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) = 6",
Message: "There are exactly 6 total cores",
URI: "",
},
},
},
},
want: &AnalyzeResult{
IsPass: false,
IsFail: false,
IsWarn: true,
Title: "total-cpu",
Message: "there are more than 6 total cores",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "6 cores in s-2vcpu-4gb nodes",
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "s-2vcpu-4gb total",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) < 6",
Message: "there are less than 3 s-2vcpu-4gb nodes",
URI: "",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) > 6",
Message: "there are more than 3 s-2vcpu-4gb nodes",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) = 6",
Message: "There are exactly 3 s-2vcpu-4gb nodes",
URI: "",
},
},
},
Filters: &troubleshootv1beta2.NodeResourceFilters{
Selector: &troubleshootv1beta2.NodeResourceSelectors{
MatchLabel: map[string]string{
"node.kubernetes.io/instance-type": "s-2vcpu-4gb",
},
},
},
},
want: &AnalyzeResult{
IsPass: true,
IsFail: false,
IsWarn: false,
Title: "s-2vcpu-4gb total",
Message: "There are exactly 3 s-2vcpu-4gb nodes",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "8 cores in nodes with at least 8gb of ram", // validate that filtering based on memory capacity works
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "memory filter",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) < 8",
Message: "less than 8 CPUs in nodes with 8Gb of ram",
URI: "",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) > 8",
Message: "more than 8 CPUs in nodes with 8Gb of ram",
URI: "",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) = 8",
Message: "exactly 8 CPUs total in nodes with 8Gb of ram",
URI: "",
},
},
},
Filters: &troubleshootv1beta2.NodeResourceFilters{
MemoryCapacity: "8Gi",
},
},
want: &AnalyzeResult{
IsPass: true,
IsFail: false,
IsWarn: false,
Title: "memory filter",
Message: "exactly 8 CPUs total in nodes with 8Gb of ram",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
{
name: "no pass or fail", // validate that the pass message is not always shown
analyzer: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "no outcome",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "sum(cpuCapacity) = 8",
Message: "exactly 8 CPUs total in nodes",
URI: "",
},
},
},
},
want: &AnalyzeResult{
IsPass: false,
IsFail: false,
IsWarn: false,
Title: "no outcome",
Message: "",
URI: "",
IconKey: "kubernetes_node_resources",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := require.New(t)
got, err := analyzeNodeResources(tt.analyzer, getExampleNodeContents)
req.NoError(err)
req.Equal(tt.want, got)
})
}
}
+6 -6
View File
@@ -6,11 +6,11 @@ import (
"path"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzePostgres(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzePostgres(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := analyzer.CollectorName
if collectorName == "" {
collectorName = "postgres"
@@ -28,13 +28,13 @@ func analyzePostgres(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollected
return nil, errors.Wrap(err, "failed to unmarshal databased connection result")
}
checkName := analyzer.CheckName
if checkName == "" {
checkName = collectorName
title := analyzer.CheckName
if title == "" {
title = collectorName
}
result := &AnalyzeResult{
Title: checkName,
Title: title,
IconKey: "kubernetes_postgres_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/postgres-analyze.svg",
}
+6 -6
View File
@@ -6,11 +6,11 @@ import (
"path"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeRedis(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeRedis(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collectorName := analyzer.CollectorName
if collectorName == "" {
collectorName = "redis"
@@ -28,13 +28,13 @@ func analyzeRedis(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFil
return nil, errors.Wrap(err, "failed to unmarshal database connection result")
}
checkName := analyzer.CheckName
if checkName == "" {
checkName = collectorName
title := analyzer.CheckName
if title == "" {
title = collectorName
}
result := &AnalyzeResult{
Title: checkName,
Title: title,
IconKey: "kubernetes_redis_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/redis-analyze.svg",
}
+3 -3
View File
@@ -4,11 +4,11 @@ import (
"encoding/json"
"fmt"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
func analyzeSecret(analyzer *troubleshootv1beta1.AnalyzeSecret, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeSecret(analyzer *troubleshootv1beta2.AnalyzeSecret, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
secretData, err := getCollectedFileContents(fmt.Sprintf("secrets/%s/%s.json", analyzer.Namespace, analyzer.SecretName))
if err != nil {
return nil, err
@@ -30,7 +30,7 @@ func analyzeSecret(analyzer *troubleshootv1beta1.AnalyzeSecret, getCollectedFile
IconURI: "https://troubleshoot.sh/images/analyzer-icons/secret.svg?w=13&h=16",
}
var failOutcome *troubleshootv1beta1.Outcome
var failOutcome *troubleshootv1beta2.Outcome
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
failOutcome = outcome
+5 -5
View File
@@ -3,15 +3,15 @@ package analyzer
import (
"encoding/json"
"fmt"
"path"
"path/filepath"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
appsv1 "k8s.io/api/apps/v1"
)
func analyzeStatefulsetStatus(analyzer *troubleshootv1beta1.StatefulsetStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents(path.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
func analyzeStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents(filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
if err != nil {
return nil, errors.Wrap(err, "failed to read collected statefulsets from namespace")
}
@@ -24,7 +24,7 @@ func analyzeStatefulsetStatus(analyzer *troubleshootv1beta1.StatefulsetStatus, g
var status *appsv1.StatefulSetStatus
for _, statefulset := range statefulsets {
if statefulset.Name == analyzer.Name {
status = &statefulset.Status
status = statefulset.Status.DeepCopy()
}
}
+15 -4
View File
@@ -4,11 +4,11 @@ import (
"encoding/json"
"fmt"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
storagev1beta1 "k8s.io/api/storage/v1beta1"
)
func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeStorageClass(analyzer *troubleshootv1beta2.StorageClass, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
storageClassesData, err := getCollectedFileContents("cluster-resources/storage-classes.json")
if err != nil {
return nil, err
@@ -21,7 +21,11 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
title := analyzer.CheckName
if title == "" {
title = fmt.Sprintf("Storage class %s", analyzer.StorageClassName)
if analyzer.StorageClassName != "" {
title = fmt.Sprintf("Storage class %s", analyzer.StorageClassName)
} else {
title = "Default Storage Class"
}
}
result := AnalyzeResult{
@@ -31,7 +35,8 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
}
for _, storageClass := range storageClasses {
if storageClass.Name == analyzer.StorageClassName {
val, _ := storageClass.Annotations["storageclass.kubernetes.io/is-default-class"]
if (storageClass.Name == analyzer.StorageClassName) || (analyzer.StorageClassName == "" && val == "true") {
result.IsPass = true
for _, outcome := range analyzer.Outcomes {
if outcome.Pass != nil {
@@ -39,6 +44,9 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
result.URI = outcome.Pass.URI
}
}
if analyzer.StorageClassName == "" && result.Message == "" {
result.Message = "Default Storage Class found"
}
return &result, nil
}
@@ -51,6 +59,9 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
result.URI = outcome.Fail.URI
}
}
if analyzer.StorageClassName == "" && result.Message == "" {
result.Message = "No Default Storage Class found"
}
return &result, nil
}
+69 -31
View File
@@ -8,10 +8,10 @@ import (
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
func analyzeTextAnalyze(analyzer *troubleshootv1beta1.TextAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
func analyzeTextAnalyze(analyzer *troubleshootv1beta2.TextAnalyze, getCollectedFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
fullPath := filepath.Join(analyzer.CollectorName, analyzer.FileName)
collected, err := getCollectedFileContents(fullPath)
if err != nil {
@@ -23,59 +23,97 @@ func analyzeTextAnalyze(analyzer *troubleshootv1beta1.TextAnalyze, getCollectedF
checkName = analyzer.CollectorName
}
if len(collected) == 0 {
return []*AnalyzeResult{
{
Title: checkName,
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
IsFail: false,
Message: "No matching files",
},
}, nil
}
results := []*AnalyzeResult{}
if analyzer.RegexPattern != "" {
return analyzeRegexPattern(analyzer.RegexPattern, collected, analyzer.Outcomes, checkName)
for _, fileContents := range collected {
result, err := analyzeRegexPattern(analyzer.RegexPattern, fileContents, analyzer.Outcomes, checkName)
if err != nil {
return nil, err
}
if result != nil {
results = append(results, result)
}
}
}
if analyzer.RegexGroups != "" {
return analyzeRegexGroups(analyzer.RegexGroups, collected, analyzer.Outcomes, checkName)
for _, fileContents := range collected {
result, err := analyzeRegexGroups(analyzer.RegexGroups, fileContents, analyzer.Outcomes, checkName)
if err != nil {
return nil, err
}
if result != nil {
results = append(results, result)
}
}
}
return &AnalyzeResult{
Title: checkName,
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
IsFail: true,
Message: "Invalid analyzer",
if len(results) > 0 {
return results, nil
}
return []*AnalyzeResult{
{
Title: checkName,
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
IsFail: true,
Message: "Invalid analyzer",
},
}, nil
}
func analyzeRegexPattern(pattern string, collected []byte, outcomes []*troubleshootv1beta1.Outcome, checkName string) (*AnalyzeResult, error) {
func analyzeRegexPattern(pattern string, collected []byte, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
re, err := regexp.Compile(pattern)
if err != nil {
return nil, errors.Wrapf(err, "failed to compile regex: %s", pattern)
}
var failOutcome *troubleshootv1beta1.Outcome
var passOutcome *troubleshootv1beta1.Outcome
var failOutcome *troubleshootv1beta2.SingleOutcome
var passOutcome *troubleshootv1beta2.SingleOutcome
for _, outcome := range outcomes {
if outcome.Fail != nil {
failOutcome = outcome
failOutcome = outcome.Fail
} else if outcome.Pass != nil {
passOutcome = outcome
passOutcome = outcome.Pass
}
}
if re.MatchString(string(collected)) {
return &AnalyzeResult{
Title: checkName,
IsPass: true,
Message: passOutcome.Pass.Message,
URI: passOutcome.Pass.URI,
}, nil
}
return &AnalyzeResult{
result := AnalyzeResult{
Title: checkName,
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
IsFail: true,
Message: failOutcome.Fail.Message,
URI: failOutcome.Fail.URI,
}, nil
}
if re.MatchString(string(collected)) {
result.IsPass = true
if passOutcome != nil {
result.Message = passOutcome.Message
result.URI = passOutcome.URI
}
return &result, nil
}
result.IsFail = true
if failOutcome != nil {
result.Message = failOutcome.Message
result.URI = failOutcome.URI
}
return &result, nil
}
func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troubleshootv1beta1.Outcome, checkName string) (*AnalyzeResult, error) {
func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
re, err := regexp.Compile(pattern)
if err != nil {
return nil, errors.Wrapf(err, "failed to compile regex: %s", pattern)
+192 -81
View File
@@ -2,32 +2,33 @@ package analyzer
import (
"fmt"
"path/filepath"
"strings"
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.undefinedlabs.com/scopeagent"
)
func Test_textAnalyze(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta1.TextAnalyze
expectResult AnalyzeResult
analyzer troubleshootv1beta2.TextAnalyze
expectResult []AnalyzeResult
files map[string][]byte
}{
{
name: "success case 1",
analyzer: troubleshootv1beta1.TextAnalyze{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -36,12 +37,16 @@ func Test_textAnalyze(t *testing.T) {
FileName: "cfile-1.txt",
RegexPattern: "succeeded",
},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "text-collector-1",
Message: "pass",
expectResult: []AnalyzeResult{
{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "text-collector-1",
Message: "pass",
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"),
@@ -49,15 +54,15 @@ func Test_textAnalyze(t *testing.T) {
},
{
name: "failure case 1",
analyzer: troubleshootv1beta1.TextAnalyze{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "success",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -66,14 +71,16 @@ func Test_textAnalyze(t *testing.T) {
FileName: "cfile-2.txt",
RegexPattern: "succeeded",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-2",
Message: "fail",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
expectResult: []AnalyzeResult{
{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-2",
Message: "fail",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
},
},
files: map[string][]byte{
"text-collector-2/cfile-2.txt": []byte(""),
@@ -81,15 +88,15 @@ func Test_textAnalyze(t *testing.T) {
},
{
name: "success case 2",
analyzer: troubleshootv1beta1.TextAnalyze{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "success",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -98,14 +105,16 @@ func Test_textAnalyze(t *testing.T) {
FileName: "cfile-3.txt",
RegexPattern: "",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-3",
Message: "Invalid analyzer",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
expectResult: []AnalyzeResult{
{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-3",
Message: "Invalid analyzer",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
},
},
files: map[string][]byte{
"text-collector-3/cfile-3.txt": []byte("Connection to service succeeded"),
@@ -113,15 +122,15 @@ func Test_textAnalyze(t *testing.T) {
},
{
name: "success case 3",
analyzer: troubleshootv1beta1.TextAnalyze{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "success",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -130,12 +139,16 @@ func Test_textAnalyze(t *testing.T) {
FileName: "cfile-5.txt",
RegexPattern: "([a-zA-Z0-9\\-_:*\\s])*succe([a-zA-Z0-9\\-_:*\\s!])*",
},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "text-collector-5",
Message: "success",
expectResult: []AnalyzeResult{
{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "text-collector-5",
Message: "success",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
},
},
files: map[string][]byte{
"text-collector-5/cfile-5.txt": []byte("Connection to service succeeded!"),
@@ -143,15 +156,15 @@ func Test_textAnalyze(t *testing.T) {
},
{
name: "failure case 3",
analyzer: troubleshootv1beta1.TextAnalyze{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "success",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -160,14 +173,16 @@ func Test_textAnalyze(t *testing.T) {
FileName: "cfile-4.txt",
RegexPattern: "succeeded",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-4",
Message: "fail",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
expectResult: []AnalyzeResult{
{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-4",
Message: "fail",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
},
},
files: map[string][]byte{
"text-collector-4/cfile-4.txt": []byte("A different message"),
@@ -175,15 +190,15 @@ func Test_textAnalyze(t *testing.T) {
},
{
name: "failure case 4",
analyzer: troubleshootv1beta1.TextAnalyze{
Outcomes: []*troubleshootv1beta1.Outcome{
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "success",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
@@ -192,38 +207,136 @@ func Test_textAnalyze(t *testing.T) {
FileName: "cfile-6.txt",
RegexPattern: "([a-zA-Z0-9\\-_:*\\s])*succe([a-zA-Z0-9\\-_:*\\s!])*",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-6",
Message: "fail",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
expectResult: []AnalyzeResult{
{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "text-collector-6",
Message: "fail",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
},
},
files: map[string][]byte{
"text-collector-6/cfile-6.txt": []byte("A different message"),
},
},
{
name: "multiple results case 1",
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "pass",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
},
CollectorName: "text-collector-1",
FileName: "cfile",
RegexPattern: "succeeded",
},
expectResult: []AnalyzeResult{
{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "text-collector-1",
Message: "pass",
IconKey: "kubernetes_text_analyze",
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
},
{
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-2.txt": []byte("no success here"),
"text-collector-2/cfile-3.txt": []byte("Yes it all succeeded"),
},
},
{
name: "multiple results case 2 globbing",
analyzer: troubleshootv1beta2.TextAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "pass",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "fail",
},
},
},
CollectorName: "text-collector-1",
FileName: "cfile*.log",
RegexPattern: "succeeded",
},
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-2.log": []byte("no success here"),
"text-collector-2/cfile-3.txt": []byte("Yes it all succeeded"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
getFiles := func(n string) ([]byte, error) {
val, ok := test.files[n]
if !ok {
getFiles := func(n string) (map[string][]byte, error) {
matching := make(map[string][]byte)
for k, v := range test.files {
if strings.HasPrefix(k, n) {
matching[k] = v
}
}
for k, v := range test.files {
if ok, _ := filepath.Match(n, k); ok {
matching[k] = v
}
}
if len(matching) == 0 {
return nil, fmt.Errorf("File not found: %s", n)
}
return val, nil
return matching, nil
}
actual, err := analyzeTextAnalyze(&test.analyzer, getFiles)
req.NoError(err)
assert.Equal(t, &test.expectResult, actual)
unPointered := []AnalyzeResult{}
for _, v := range actual {
unPointered = append(unPointered, *v)
}
assert.ElementsMatch(t, test.expectResult, unPointered)
})
}
}
@@ -266,8 +379,6 @@ func Test_compareRegex(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)
actual, err := compareRegex(test.conditional, test.foundMatches)

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