mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-09-03 00:47:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afa0bc56d4 | ||
|
|
c1f9b3906d | ||
|
|
0a19d35073 | ||
|
|
7c4135d563 | ||
|
|
4b78c430ca | ||
|
|
09d16ff185 | ||
|
|
bdf843f84d | ||
|
|
47f7d98907 | ||
|
|
a1e0813d55 | ||
|
|
d6acd6d906 | ||
|
|
ce53dbcd29 | ||
|
|
3b10d9c9e1 | ||
|
|
7589b4fed0 | ||
|
|
a459120516 | ||
|
|
b02338762e | ||
|
|
76419237d4 | ||
|
|
87b4c12274 | ||
|
|
7647c039e9 | ||
|
|
c26824a619 | ||
|
|
617e0d55de | ||
|
|
b418334a46 | ||
|
|
b78d7d04d9 | ||
|
|
989d5f7dbd | ||
|
|
8aca23199c | ||
|
|
fe4db40b43 | ||
|
|
5eef177965 | ||
|
|
6498c34da5 | ||
|
|
b0a005796c | ||
|
|
1311064d21 | ||
|
|
450d7570eb | ||
|
|
40af0f8a9c | ||
|
|
529fe8cdbf | ||
|
|
105a718bbb | ||
|
|
c2b53036f9 | ||
|
|
ba22fe9f22 | ||
|
|
8df8a5c792 | ||
|
|
0bcd5183f5 | ||
|
|
e2c0c722ae | ||
|
|
9984fe2caa | ||
|
|
f2e4127111 | ||
|
|
f25149f45c | ||
|
|
2a06cb25e4 | ||
|
|
86bc366ec3 | ||
|
|
10a34c2e58 | ||
|
|
df68f7445f | ||
|
|
c7fdec0291 | ||
|
|
e8dba3cad9 | ||
|
|
3e2d90ee9b | ||
|
|
b05eb6bf75 | ||
|
|
2e16424b44 | ||
|
|
f19ac98055 | ||
|
|
55fbd92673 | ||
|
|
2f2edb5356 | ||
|
|
0ec1c43afd | ||
|
|
b43ef17b8f | ||
|
|
858d00ce41 | ||
|
|
c81576f25f | ||
|
|
1e8e20a295 | ||
|
|
4cdfa5b112 | ||
|
|
8ef720ba9e | ||
|
|
7d2e0d0aa3 | ||
|
|
88685ae5f7 | ||
|
|
364a63533a | ||
|
|
6749f339e8 | ||
|
|
2bf19eaddf | ||
|
|
fd6af65f7e | ||
|
|
9e669b3f13 | ||
|
|
4e74fd209e | ||
|
|
a00a1892d9 | ||
|
|
474a874f82 | ||
|
|
463ba62ffa | ||
|
|
46194fccb7 | ||
|
|
badedf34ee | ||
|
|
af2f49cb07 | ||
|
|
88a6d3f715 | ||
|
|
5a1321da02 | ||
|
|
014a716949 | ||
|
|
ad4893bec5 | ||
|
|
989093394c | ||
|
|
f1868d0ba8 | ||
|
|
3cbdb41c8f | ||
|
|
d2f6594c0c | ||
|
|
56408fab01 | ||
|
|
f0d9418e21 | ||
|
|
1ba3840ad9 | ||
|
|
a3d667298e | ||
|
|
5cf4ae2157 | ||
|
|
dede187841 | ||
|
|
7dc6e22d8d | ||
|
|
f3af73b6e3 | ||
|
|
4e047b2372 | ||
|
|
7f190e3cce | ||
|
|
2436a0c163 | ||
|
|
883abacaf8 | ||
|
|
7186b75f7e | ||
|
|
e16eabd531 | ||
|
|
3ff91a3b92 | ||
|
|
2020049cb7 | ||
|
|
d94954e6dd | ||
|
|
06817c1682 | ||
|
|
0554c2ac0f | ||
|
|
1087eb65d2 | ||
|
|
09dc6b5862 | ||
|
|
3d301f4307 | ||
|
|
446b1d5552 | ||
|
|
63c8422e63 | ||
|
|
616f6e73a5 | ||
|
|
213b52477f | ||
|
|
e9ba73d87f | ||
|
|
6e86cdc803 | ||
|
|
c023a81966 | ||
|
|
7d0bb1a5d7 | ||
|
|
3cf679260e | ||
|
|
53bd30755d | ||
|
|
7fe7216b29 | ||
|
|
2da1f518d3 | ||
|
|
8a30f2ddce | ||
|
|
0ea4e1d52f | ||
|
|
74c9ff2432 | ||
|
|
bfa50c9643 | ||
|
|
74a7e5f19c | ||
|
|
e3c954f460 | ||
|
|
16d3ff372f | ||
|
|
437357f099 | ||
|
|
955e08c35a | ||
|
|
58261cdaf2 | ||
|
|
545313aa06 | ||
|
|
1ceab64b6f | ||
|
|
eb0f692aa0 | ||
|
|
2ae1b9b678 | ||
|
|
2f2501ac3b | ||
|
|
a57894962d | ||
|
|
3f48574071 | ||
|
|
00bbfc0414 | ||
|
|
c4d4af6890 | ||
|
|
4149e4d6ed | ||
|
|
b2cb3bf43b | ||
|
|
e232cc0f34 | ||
|
|
f68e0dbae0 | ||
|
|
0126fb7d7a | ||
|
|
d2bc571a18 | ||
|
|
1f0842484c | ||
|
|
8c31054331 | ||
|
|
4b2e7e153e | ||
|
|
2289e00558 | ||
|
|
949f0f2213 | ||
|
|
703219119e | ||
|
|
5522b0623c | ||
|
|
8b1ae0194e | ||
|
|
a0ce85ae1e | ||
|
|
b1e251ed03 | ||
|
|
f7e4103d9b | ||
|
|
442c97f138 | ||
|
|
6862f0f6c1 | ||
|
|
6ad9103f86 | ||
|
|
016d6e4f18 | ||
|
|
97db83b270 | ||
|
|
20e82be101 | ||
|
|
4e08ce4d93 | ||
|
|
e8dc5ae1b8 | ||
|
|
0e667e9685 | ||
|
|
99f5e2d896 | ||
|
|
407b80daf5 | ||
|
|
c84dc6de45 | ||
|
|
a686e47fa2 | ||
|
|
56e58c0849 | ||
|
|
3b8e53bf90 | ||
|
|
cc9d3aedec | ||
|
|
a70843cd69 | ||
|
|
08c52770a9 | ||
|
|
52e385ffce | ||
|
|
d63321bc43 | ||
|
|
37ae457aa1 | ||
|
|
4b1852c3e1 | ||
|
|
dc80733397 | ||
|
|
78e328be86 | ||
|
|
ea43d4d9b6 | ||
|
|
8ce751f989 | ||
|
|
f55b9b6ae4 | ||
|
|
086f85845a | ||
|
|
eb90b6d935 | ||
|
|
13df9a0f87 | ||
|
|
89b4979cbc | ||
|
|
6e874483b6 | ||
|
|
afd2ee95ca | ||
|
|
e66d12dab2 | ||
|
|
d98172eb9f | ||
|
|
b070b4009c | ||
|
|
890cbb1378 | ||
|
|
0f1f8b511e | ||
|
|
19fb95e99b | ||
|
|
a22e2e25df | ||
|
|
8d991e1f9e | ||
|
|
91e856d6d5 | ||
|
|
65f957db81 | ||
|
|
a561254756 | ||
|
|
53626bb025 | ||
|
|
54d8d7585d | ||
|
|
4021a6c176 | ||
|
|
8c9734c19c | ||
|
|
b6ac6e4702 | ||
|
|
f256bdb653 | ||
|
|
1c3496e2f7 | ||
|
|
f5d737f2c5 | ||
|
|
619c4550bc | ||
|
|
6f42f85705 | ||
|
|
661d7fcfc4 | ||
|
|
47e715a350 | ||
|
|
b9c3ad2635 | ||
|
|
2787cf385c | ||
|
|
f03916cfd2 | ||
|
|
252b29fb0a | ||
|
|
553718eada | ||
|
|
171c440f27 | ||
|
|
869a9eafd3 | ||
|
|
6c61040f74 | ||
|
|
bde882047f | ||
|
|
0d84c62c78 | ||
|
|
f5183c62d6 | ||
|
|
5a2c153026 | ||
|
|
669cb2a145 | ||
|
|
fb2f028fb5 | ||
|
|
d37ace6e51 | ||
|
|
b6e92e1bdf | ||
|
|
27369acbf4 | ||
|
|
22d397cc7b | ||
|
|
428db221c3 | ||
|
|
f6a5d40bfd | ||
|
|
8bc627d20f | ||
|
|
7c95363afe | ||
|
|
f7aebd0745 | ||
|
|
523332268b | ||
|
|
ca6f118c62 | ||
|
|
769d616ede | ||
|
|
620343e3a1 | ||
|
|
994978ccd8 | ||
|
|
0f31088bd1 | ||
|
|
4b9680be8b | ||
|
|
20033b12d5 | ||
|
|
f795f909b3 | ||
|
|
b3828b175d | ||
|
|
fa84d3fbd3 | ||
|
|
f377b7c1a8 | ||
|
|
6d3321cb43 | ||
|
|
15b100e0ee | ||
|
|
cd84cfa629 | ||
|
|
94ccf284b8 | ||
|
|
2f092ae4a1 | ||
|
|
56150c3345 | ||
|
|
8f594e8764 | ||
|
|
7752a33268 | ||
|
|
94e27eb5e1 | ||
|
|
33580e9611 | ||
|
|
09bc81e144 | ||
|
|
7626f7d7d7 | ||
|
|
37f75c1a3f | ||
|
|
8f8b8b2c78 | ||
|
|
b1e1dbb42a | ||
|
|
1605b56090 | ||
|
|
a58913d1f6 | ||
|
|
eb84fc64e1 | ||
|
|
087505f8e5 | ||
|
|
8b54c28b4b | ||
|
|
7be40ddf16 | ||
|
|
de9b832357 | ||
|
|
fc65ceb5b4 | ||
|
|
d016e3269c | ||
|
|
079765b558 | ||
|
|
45ccd7be5b | ||
|
|
2bc9938a2a | ||
|
|
5050f46b1e | ||
|
|
222a93162d | ||
|
|
f06cd9e4a2 | ||
|
|
7dc93ddade | ||
|
|
a7c4f04a63 | ||
|
|
527fe16bcc | ||
|
|
406e1e6d1a | ||
|
|
cbaf96691e | ||
|
|
4dfb02e7f7 | ||
|
|
e19a5dee38 | ||
|
|
2c495b8a7a | ||
|
|
c3646bacd9 | ||
|
|
3844a3dbd3 | ||
|
|
544c832d6e | ||
|
|
38aef191ab | ||
|
|
545ce24c04 | ||
|
|
c262320cea | ||
|
|
d1c45f1456 | ||
|
|
c4f841d420 | ||
|
|
75ab7bb917 | ||
|
|
9692d5a457 | ||
|
|
2775a6393e | ||
|
|
b0c46c5ac8 | ||
|
|
e9ad961c0e | ||
|
|
bd71222715 | ||
|
|
818f8b26dd | ||
|
|
9dbf282b5b | ||
|
|
374856798f | ||
|
|
d754b3bb9f | ||
|
|
225ebfbcbe | ||
|
|
821a132980 | ||
|
|
4e3a627b21 | ||
|
|
206e5c01d7 | ||
|
|
e54ad171d8 | ||
|
|
8f78827002 | ||
|
|
77eba55ee8 | ||
|
|
562a565f1f | ||
|
|
db8533e69c | ||
|
|
f37bbdb14c | ||
|
|
35808423b2 | ||
|
|
86b745d6c2 | ||
|
|
c773ebd894 | ||
|
|
2ba2849a38 | ||
|
|
a1c5247c8b | ||
|
|
47ff99bfe2 | ||
|
|
6d19794dc5 | ||
|
|
0c3cc7afce | ||
|
|
5798a4aa11 | ||
|
|
ae64a4d591 | ||
|
|
983aaaacea | ||
|
|
e74101070d | ||
|
|
d9625477d2 | ||
|
|
1c66eed2cd | ||
|
|
1dda832933 | ||
|
|
d019f87284 | ||
|
|
983481da07 | ||
|
|
7462e1cdee | ||
|
|
6563290de9 | ||
|
|
ff627dcf83 | ||
|
|
2158dee58d | ||
|
|
f6a56a9def | ||
|
|
63a81314bb | ||
|
|
1d352298e1 | ||
|
|
26d8211a08 | ||
|
|
6c212e24f0 | ||
|
|
c9b01f981d | ||
|
|
4fca3ff4fc | ||
|
|
d81cff0d67 | ||
|
|
62d12d8fff | ||
|
|
8126cb3d3d | ||
|
|
6277101574 | ||
|
|
346c751bdf | ||
|
|
812aa9bc30 | ||
|
|
5a7b155093 | ||
|
|
c6ecab39cc | ||
|
|
648564eb4d | ||
|
|
1b9a220e2d | ||
|
|
45c7d95ebe | ||
|
|
42288a4ff3 | ||
|
|
2eb6518792 | ||
|
|
78d2364199 | ||
|
|
1f9e478a78 | ||
|
|
0d3c58eccd | ||
|
|
1a117220cb | ||
|
|
1c79403934 | ||
|
|
d1ab5a6991 | ||
|
|
c91a8ee60d | ||
|
|
899d5d7672 | ||
|
|
b466188679 | ||
|
|
f15579a3b2 | ||
|
|
1424faacab | ||
|
|
e94b152733 | ||
|
|
717ba13614 | ||
|
|
6765e6c030 | ||
|
|
eea2315c52 | ||
|
|
1e834430c2 | ||
|
|
729caa4a7b | ||
|
|
130aa88f3a | ||
|
|
f8870e5fc0 | ||
|
|
10b2941c57 |
@@ -1,39 +0,0 @@
|
||||
steps:
|
||||
- label: build
|
||||
commands:
|
||||
- make
|
||||
plugins:
|
||||
- docker#v3.2.0:
|
||||
image: replicated/gitops-builder:buildkite-go12-node10
|
||||
always-pull: true
|
||||
mount-checkout: true
|
||||
workdir: /go/src/github.com/replicatedhq/troubleshoot
|
||||
|
||||
- wait
|
||||
|
||||
- commands:
|
||||
- make snapshot-release
|
||||
branches: "master"
|
||||
plugins:
|
||||
- docker#v3.2.0:
|
||||
image: replicated/gitops-builder:buildkite-go12-node10
|
||||
always-pull: true
|
||||
mount-checkout: true
|
||||
workdir: /go/src/github.com/replicatedhq/troubleshoot
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/var/lib/buildkite-agent/.docker/config.json:/home/builder/.docker/config.json"
|
||||
|
||||
- label: release
|
||||
commands:
|
||||
- if [ -z "$BUILDKITE_TAG" ]; then echo "Not a tag, not releasing"; else git tag -f "$BUILDKITE_TAG" && make release; fi
|
||||
plugins:
|
||||
- docker#v3.2.0:
|
||||
always-pull: true
|
||||
image: replicated/gitops-builder:buildkite-go12-node10
|
||||
workdir: /go/src/github.com/replicatedhq/troubleshoot
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/var/lib/buildkite-agent/.docker/config.json:/home/builder/.docker/config.json"
|
||||
environment:
|
||||
- GITHUB_TOKEN_TROUBLESHOOT
|
||||
@@ -0,0 +1,133 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: "1.14"
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- run: make
|
||||
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make preflight
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/preflight
|
||||
|
||||
validate-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- run: chmod +x bin/preflight
|
||||
- run: ./bin/preflight --interactive=false --format=json https://preflight.replicated.com
|
||||
|
||||
compile-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make support-bundle
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/support-bundle
|
||||
|
||||
validate-supportbundle:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-supportbundle
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download support-bundle binary
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- run: chmod +x bin/support-bundle
|
||||
- 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:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- validate-preflight
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: azure/docker-login@v1
|
||||
with:
|
||||
username: repldeploy2
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: "1.14"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist --config deploy/.goreleaser.yaml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Update new preflight version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.38
|
||||
with:
|
||||
krew_template_file: deploy/krew/preflight.yaml
|
||||
- name: Update new support-bundle version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.38
|
||||
with:
|
||||
krew_template_file: deploy/krew/support-bundle.yaml
|
||||
@@ -1,35 +0,0 @@
|
||||
name: "Run preflights on a cluster"
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
compile-preflight:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.12.14'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "::set-env name=GOPATH::$(go env GOPATH)"
|
||||
echo "::add-path::$(go env GOPATH)/bin"
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make preflight
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/preflight
|
||||
|
||||
run-preflights:
|
||||
runs-on: ubuntu-latest
|
||||
needs: compile-preflight
|
||||
steps:
|
||||
- name: Download preflight binary
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.2.0
|
||||
- run: chmod +x bin/preflight
|
||||
- run: bin/preflight --interactive=false --format=json https://preflight.replicated.com
|
||||
|
||||
@@ -28,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} \
|
||||
@@ -46,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:
|
||||
@@ -66,23 +67,39 @@ vet:
|
||||
|
||||
.PHONY: generate
|
||||
generate: controller-gen client-gen
|
||||
controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/...
|
||||
client-gen --output-package=github.com/replicatedhq/troubleshoot/pkg/client --clientset-name troubleshootclientset --input-base github.com/replicatedhq/troubleshoot/pkg/apis --input troubleshoot/v1beta1 -h ./hack/boilerplate.go.txt
|
||||
$(CONTROLLER_GEN) \
|
||||
object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/...
|
||||
$(CLIENT_GEN) \
|
||||
--output-package=github.com/replicatedhq/troubleshoot/pkg/client \
|
||||
--clientset-name troubleshootclientset \
|
||||
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
|
||||
--input troubleshoot/v1beta1 \
|
||||
--input troubleshoot/v1beta2 \
|
||||
-h ./hack/boilerplate.go.txt
|
||||
|
||||
# find or download controller-gen
|
||||
# download controller-gen if necessary
|
||||
.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
|
||||
go build ${LDFLAGS} -o bin/schemagen github.com/replicatedhq/troubleshoot/cmd/schemagen
|
||||
./bin/schemagen --output-dir ./schemas
|
||||
|
||||
.PHONY: contoller-gen
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.4
|
||||
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)
|
||||
endif
|
||||
|
||||
# find or download client-gen
|
||||
.PHONY: client-gen
|
||||
client-gen:
|
||||
ifeq (, $(shell which client-gen))
|
||||
go get k8s.io/code-generator/cmd/client-gen@kubernetes-1.16.4
|
||||
go get k8s.io/code-generator/cmd/client-gen@kubernetes-1.18.0
|
||||
CLIENT_GEN=$(shell go env GOPATH)/bin/client-gen
|
||||
else
|
||||
CLIENT_GEN=$(shell which client-gen)
|
||||
@@ -101,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
|
||||
@@ -113,8 +130,8 @@ run-preflight: preflight
|
||||
|
||||
.PHONY: run-troubleshoot
|
||||
run-troubleshoot: support-bundle
|
||||
./bin/support-bundle ./examples/troubleshoot/sample-troubleshoot.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
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+7
-15
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -16,11 +16,8 @@ func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
specPath := v.GetString("analyzers")
|
||||
|
||||
specContent := ""
|
||||
if !isURL(specPath) {
|
||||
if _, err := os.Stat(specPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s was not found", specPath)
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, err = os.Stat(specPath); err == nil {
|
||||
b, err := ioutil.ReadFile(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -28,6 +25,10 @@ func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
|
||||
specContent = string(b)
|
||||
} else {
|
||||
if !util.IsURL(specPath) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -64,12 +65,3 @@ func runAnalyzers(v *viper.Viper, bundlePath string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
parsed, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return parsed.Scheme != ""
|
||||
}
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
|
||||
@@ -5,17 +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
|
||||
)
|
||||
|
||||
@@ -63,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>":
|
||||
@@ -71,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)
|
||||
}
|
||||
@@ -94,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
|
||||
@@ -126,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
|
||||
@@ -214,13 +218,13 @@ func estimateNumberOfLines(text string, width int) int {
|
||||
}
|
||||
|
||||
func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
|
||||
filename := path.Join(homeDir(), fmt.Sprintf("%s-results.txt", preflightName))
|
||||
filename := path.Join(util.HomeDir(), fmt.Sprintf("%s-results.txt", preflightName))
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
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 := ""
|
||||
|
||||
@@ -269,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, " ")
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func receivePreflightResults(preflightJobNamespace string, preflightJobName string) error {
|
||||
// poll until there are no more "running" collectors
|
||||
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(bundlePath)
|
||||
|
||||
receivedPreflights := []string{}
|
||||
for {
|
||||
job, err := troubleshootClient.PreflightJobs(preflightJobNamespace).Get(preflightJobName, metav1.GetOptions{})
|
||||
if err != nil && kuberneteserrors.IsNotFound(err) {
|
||||
// where did it go!
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, readyPreflight := range job.Status.AnalyzersSuccessful {
|
||||
alreadyReceived := false
|
||||
for _, receivedPreflight := range receivedPreflights {
|
||||
if receivedPreflight == readyPreflight {
|
||||
alreadyReceived = true
|
||||
}
|
||||
}
|
||||
|
||||
if alreadyReceived {
|
||||
continue
|
||||
}
|
||||
|
||||
preflightResp, err := http.Get(fmt.Sprintf("http://localhost:8000/preflight/%s", readyPreflight))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer preflightResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(preflightResp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Printf("%s\n", body)
|
||||
receivedPreflights = append(receivedPreflights, readyPreflight)
|
||||
}
|
||||
|
||||
if len(job.Status.AnalyzersRunning) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"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 {
|
||||
@@ -37,19 +32,15 @@ that a cluster meets the requirements to run an application.`,
|
||||
|
||||
cmd.Flags().Bool("interactive", true, "interactive preflights")
|
||||
cmd.Flags().String("format", "human", "output format, one of human, json, yaml. only used when interactive is set to false")
|
||||
cmd.Flags().String("preflight", "", "name of the preflight to use")
|
||||
cmd.Flags().String("image", "", "the full name of the preflight image to use")
|
||||
cmd.Flags().String("pullpolicy", "", "the pull policy of the preflight image")
|
||||
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("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
|
||||
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
|
||||
}
|
||||
@@ -64,16 +55,3 @@ func initConfig() {
|
||||
viper.SetEnvPrefix("PREFLIGHT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
|
||||
for _, inList := range list {
|
||||
if collector.ClusterResources != nil && inList.ClusterResources != nil {
|
||||
return list
|
||||
}
|
||||
if collector.ClusterInfo != nil && inList.ClusterInfo != nil {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
return append(list, &collector)
|
||||
}
|
||||
|
||||
+165
-171
@@ -1,49 +1,65 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
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"
|
||||
"gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func runPreflights(v *viper.Viper, arg string) error {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
|
||||
preflightContent := ""
|
||||
if !isURL(arg) {
|
||||
if _, err := os.Stat(arg); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s was not found", arg)
|
||||
var preflightContent []byte
|
||||
var err error
|
||||
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)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
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
|
||||
@@ -55,88 +71,91 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = string(body)
|
||||
preflightContent = body
|
||||
}
|
||||
|
||||
preflight := troubleshootv1beta1.Preflight{}
|
||||
if err := yaml.Unmarshal([]byte(preflightContent), &preflight); err != nil {
|
||||
return errors.Wrapf(err, "failed to parse %s as a preflight", arg)
|
||||
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
s := spin.New()
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(preflightContent), nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse %s", arg)
|
||||
}
|
||||
|
||||
var collectResults preflight.CollectResult
|
||||
preflightSpecName := ""
|
||||
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:
|
||||
if !ok {
|
||||
continue
|
||||
progressCh := make(chan interface{}, 0) // non-zero buffer will result in missed messages
|
||||
|
||||
if v.GetBool("interactive") {
|
||||
s := spin.New()
|
||||
go func() {
|
||||
lastMsg := ""
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-progressCh:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
|
||||
case string:
|
||||
if lastMsg == msg {
|
||||
break
|
||||
}
|
||||
lastMsg = msg
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println(fmt.Sprintf("%s\r * %s", cursor.ClearEntireLine(), msg))
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
|
||||
case string:
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println(fmt.Sprintf("%s\r * %s", cursor.ClearEntireLine(), msg))
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
|
||||
case <-finishedCh:
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
close(finishedCh)
|
||||
close(progressCh)
|
||||
}()
|
||||
|
||||
allCollectedData, err := runCollectors(v, preflight, progressChan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(fileName string) ([]byte, error) {
|
||||
contents, ok := allCollectedData[fileName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file %s was not collected", fileName)
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
|
||||
matching := make(map[string][]byte)
|
||||
for k, v := range allCollectedData {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
matching[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return matching, nil
|
||||
}
|
||||
|
||||
analyzeResults := []*analyzerunner.AnalyzeResult{}
|
||||
for _, analyzer := range preflight.Spec.Analyzers {
|
||||
analyzeResult, err := analyzerunner.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
r, err := collectInCluster(preflightSpec, finishedCh, progressCh)
|
||||
if err != nil {
|
||||
analyzeResult = &analyzerunner.AnalyzeResult{
|
||||
IsFail: true,
|
||||
Title: "Analyzer Failed",
|
||||
Message: err.Error(),
|
||||
}
|
||||
return errors.Wrap(err, "failed to collect in cluster")
|
||||
}
|
||||
|
||||
if analyzeResult != nil {
|
||||
analyzeResults = append(analyzeResults, analyzeResult)
|
||||
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 preflight.Spec.UploadResultsTo != "" {
|
||||
err := uploadResults(preflight.Spec.UploadResultsTo, analyzeResults)
|
||||
if err != nil {
|
||||
progressChan <- err
|
||||
if collectResults == nil {
|
||||
return errors.New("no results")
|
||||
}
|
||||
|
||||
analyzeResults := collectResults.Analyze()
|
||||
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,117 +165,92 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
if len(analyzeResults) == 0 {
|
||||
return errors.New("no data has been collected")
|
||||
}
|
||||
return showInteractiveResults(preflight.Name, analyzeResults)
|
||||
return showInteractiveResults(preflightSpecName, analyzeResults)
|
||||
}
|
||||
|
||||
return showStdoutResults(v.GetString("format"), preflight.Name, analyzeResults)
|
||||
return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults)
|
||||
}
|
||||
|
||||
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight, progressChan chan interface{}) (map[string][]byte, error) {
|
||||
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
|
||||
collectSpecs = append(collectSpecs, preflight.Spec.Collectors...)
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
|
||||
func collectInCluster(preflightSpec *troubleshootv1beta2.Preflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
allCollectedData := make(map[string][]byte)
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
var collectors collect.Collectors
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: config,
|
||||
Namespace: v.GetString("namespace"),
|
||||
}
|
||||
collectors = append(collectors, &collector)
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: v.GetString("namespace"),
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressCh,
|
||||
KubernetesRestConfig: restConfig,
|
||||
}
|
||||
|
||||
if err := collectors.CheckRBAC(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
}
|
||||
|
||||
foundForbidden := false
|
||||
for _, c := range collectors {
|
||||
for _, e := range c.RBACErrors {
|
||||
foundForbidden = true
|
||||
progressChan <- e
|
||||
}
|
||||
}
|
||||
|
||||
if foundForbidden && !v.GetBool("collect-without-permissions") {
|
||||
if preflight.Spec.UploadResultsTo != "" {
|
||||
err := uploadErrors(preflight.Spec.UploadResultsTo, collectors)
|
||||
if err != nil {
|
||||
progressChan <- err
|
||||
}
|
||||
}
|
||||
return nil, errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, collector := range collectors {
|
||||
if len(collector.RBACErrors) > 0 {
|
||||
// don't skip clusterResources collector due to RBAC issues
|
||||
if collector.Collect.ClusterResources == nil {
|
||||
progressChan <- fmt.Sprintf("skipping collector %s with insufficient RBAC permissions", collector.GetDisplayName())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result, err := collector.RunCollectorSync()
|
||||
if v.GetString("since") != "" || v.GetString("since-time") != "" {
|
||||
err := parseTimeFlags(v, progressCh, preflightSpec.Spec.Collectors)
|
||||
if err != nil {
|
||||
progressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
output, err := parseCollectorOutput(string(result))
|
||||
if err != nil {
|
||||
progressChan <- errors.Errorf("failed to parse collector output %s: %v\n", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
for k, v := range output {
|
||||
allCollectedData[k] = v
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return allCollectedData, nil
|
||||
}
|
||||
|
||||
func parseCollectorOutput(output string) (map[string][]byte, error) {
|
||||
input := make(map[string]interface{})
|
||||
files := make(map[string][]byte)
|
||||
if err := json.Unmarshal([]byte(output), &input); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
for filename, maybeContents := range input {
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
switch maybeContents.(type) {
|
||||
case string:
|
||||
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files[filepath.Join(fileDir, fileName)] = decoded
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v := range maybeContents.(map[string]interface{}) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(v.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files[filepath.Join(fileDir, fileName, k)] = decoded
|
||||
}
|
||||
}
|
||||
func collectHost(hostPreflightSpec *troubleshootv1beta2.HostPreflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
collectOpts := preflight.CollectOpts{
|
||||
ProgressChan: progressCh,
|
||||
}
|
||||
|
||||
return files, nil
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func createServiceAccount(preflight troubleshootv1beta1.Preflight, namespace string, clientset *kubernetes.Clientset) (string, error) {
|
||||
name := fmt.Sprintf("preflight-%s", preflight.Name)
|
||||
|
||||
serviceAccount := corev1.ServiceAccount{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Secrets: []corev1.ObjectReference{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := clientset.CoreV1().ServiceAccounts(namespace).Get(serviceAccount.Name, metav1.GetOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
return "", errors.Wrap(err, "failed to get existing service account")
|
||||
}
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
_, err := clientset.CoreV1().ServiceAccounts(namespace).Create(&serviceAccount)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create service account")
|
||||
}
|
||||
}
|
||||
|
||||
role := rbacv1.ClusterRole{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{
|
||||
"namespaces",
|
||||
"pods",
|
||||
"services",
|
||||
"secrets",
|
||||
},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"extensions"},
|
||||
Resources: []string{"ingresses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"storage.k8s.io"},
|
||||
Resources: []string{"storageclasses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apiextensions.k8s.io"},
|
||||
Resources: []string{"customresourcedefinitions"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoles().Get(role.Name, metav1.GetOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
return "", errors.Wrap(err, "failed to get eisting cluster role")
|
||||
}
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
_, err = clientset.RbacV1().ClusterRoles().Create(&role)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create cluster role")
|
||||
}
|
||||
}
|
||||
|
||||
roleBinding := rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRoleBinding",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoleBindings().Get(roleBinding.Name, metav1.GetOptions{})
|
||||
if err != nil && !kuberneteserrors.IsNotFound(err) {
|
||||
return "", errors.Wrap(err, "failed to get existing cluster role binding")
|
||||
}
|
||||
if kuberneteserrors.IsNotFound(err) {
|
||||
_, err = clientset.RbacV1().ClusterRoleBindings().Create(&roleBinding)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create cluster role binding")
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func removeServiceAccount(name string, namespace string, clientset *kubernetes.Clientset) error {
|
||||
if err := clientset.RbacV1().ClusterRoleBindings().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrap(err, "failed to delete cluster role binding")
|
||||
}
|
||||
|
||||
if err := clientset.RbacV1().ClusterRoles().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrap(err, "failed to delete cluster role")
|
||||
}
|
||||
|
||||
if err := clientset.CoreV1().ServiceAccounts(namespace).Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return errors.Wrap(err, "failed to delete service account")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -19,6 +19,7 @@ func showStdoutResults(format string, preflightName string, analyzeResults []*an
|
||||
}
|
||||
|
||||
func showStdoutResultsHuman(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
fmt.Println("")
|
||||
var failed bool
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
testResultfailed := outputResult(analyzeResult)
|
||||
|
||||
@@ -8,33 +8,15 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
)
|
||||
|
||||
type UploadPreflightResult struct {
|
||||
IsFail bool `json:"isFail,omitempty"`
|
||||
IsWarn bool `json:"isWarn,omitempty"`
|
||||
IsPass bool `json:"isPass,omitempty"`
|
||||
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
type UploadPreflightError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type UploadPreflightResults struct {
|
||||
Results []*UploadPreflightResult `json:"results,omitempty"`
|
||||
Errors []*UploadPreflightError `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
uploadPreflightResults := &UploadPreflightResults{
|
||||
Results: []*UploadPreflightResult{},
|
||||
uploadPreflightResults := &preflight.UploadPreflightResults{
|
||||
Results: []*preflight.UploadPreflightResult{},
|
||||
}
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
uploadPreflightResult := &UploadPreflightResult{
|
||||
uploadPreflightResult := &preflight.UploadPreflightResult{
|
||||
IsFail: analyzeResult.IsFail,
|
||||
IsWarn: analyzeResult.IsWarn,
|
||||
IsPass: analyzeResult.IsPass,
|
||||
@@ -50,23 +32,23 @@ func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) er
|
||||
}
|
||||
|
||||
func uploadErrors(uri string, collectors collect.Collectors) error {
|
||||
errors := []*UploadPreflightError{}
|
||||
errors := []*preflight.UploadPreflightError{}
|
||||
for _, collector := range collectors {
|
||||
for _, e := range collector.RBACErrors {
|
||||
errors = append(errors, &UploadPreflightError{
|
||||
errors = append(errors, &preflight.UploadPreflightError{
|
||||
Error: e.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results := &UploadPreflightResults{
|
||||
results := &preflight.UploadPreflightResults{
|
||||
Errors: errors,
|
||||
}
|
||||
|
||||
return upload(uri, results)
|
||||
}
|
||||
|
||||
func upload(uri string, payload *UploadPreflightResults) error {
|
||||
func upload(uri string, payload *preflight.UploadPreflightResults) error {
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal payload")
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
parsed, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return parsed.Scheme != ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
extensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
extensionsscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "schemagen",
|
||||
Short: "Generate openapischemas for the kinds in this project",
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
return generateSchemas(v)
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.Flags().String("output-dir", "./schemas", "directory to save the schemas in")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func InitAndExecute() {
|
||||
if err := RootCmd().Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetEnvPrefix("TROUBLESHOOT")
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func generateSchemas(v *viper.Viper) error {
|
||||
// we generate schemas from the config/crds in the root of this project
|
||||
// those crds can be created from controller-gen or by running `make openapischema`
|
||||
|
||||
workdir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get workdir")
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func generateSchemaFromCRD(crd []byte, outfile string) error {
|
||||
extensionsscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode(crd, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to decode crd")
|
||||
}
|
||||
|
||||
customResourceDefinition := obj.(*extensionsv1beta1.CustomResourceDefinition)
|
||||
|
||||
b, err := json.MarshalIndent(customResourceDefinition.Spec.Validation.OpenAPIV3Schema, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal json")
|
||||
}
|
||||
|
||||
_, err = os.Stat(outfile)
|
||||
if err == nil {
|
||||
if err := os.Remove(outfile); err != nil {
|
||||
return errors.Wrap(err, "failed to remove file")
|
||||
}
|
||||
}
|
||||
|
||||
d, _ := path.Split(outfile)
|
||||
_, err = os.Stat(d)
|
||||
if os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(d, 0755); err != nil {
|
||||
return errors.Wrap(err, "failed to mkdir")
|
||||
}
|
||||
}
|
||||
|
||||
// whoa now
|
||||
// working around the fact that controller-gen doesn't have tags to generate oneOf schemas, so this is hacky.
|
||||
// going to work to add an issue there to support and if they accept, this terrible thing can go away
|
||||
boolStringed := strings.ReplaceAll(string(b), `"type": "BoolString"`, `"oneOf": [{"type": "string"},{"type": "boolean"}]`)
|
||||
|
||||
err = ioutil.WriteFile(outfile, []byte(boolStringed), 0644)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/schemagen/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.InitAndExecute()
|
||||
}
|
||||
@@ -3,7 +3,11 @@ package cli
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
@@ -14,11 +18,12 @@ import (
|
||||
|
||||
func Analyze() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "analyze",
|
||||
Use: "analyze [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "analyze a support bundle",
|
||||
Long: `...`,
|
||||
Long: `Analyze a support bundle using the Analyzer definitions provided`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlag("url", cmd.Flags().Lookup("url"))
|
||||
viper.BindPFlag("bundle", cmd.Flags().Lookup("bundle"))
|
||||
viper.BindPFlag("output", cmd.Flags().Lookup("output"))
|
||||
viper.BindPFlag("quiet", cmd.Flags().Lookup("quiet"))
|
||||
},
|
||||
@@ -27,7 +32,13 @@ func Analyze() *cobra.Command {
|
||||
|
||||
logger.SetQuiet(v.GetBool("quiet"))
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(v.GetString("url"), "")
|
||||
specPath := args[0]
|
||||
analyzerSpec, err := downloadAnalyzerSpec(specPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := analyzer.DownloadAndAnalyze(v.GetString("bundle"), analyzerSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,8 +70,8 @@ func Analyze() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("url", "", "URL of the support bundle to analyze")
|
||||
cmd.MarkFlagRequired("url")
|
||||
cmd.Flags().String("bundle", "", "filename of the support bundle to analyze")
|
||||
cmd.MarkFlagRequired("bundle")
|
||||
cmd.Flags().String("output", "", "output format: json, yaml")
|
||||
cmd.Flags().String("compatibility", "", "output compatibility mode: support-bundle")
|
||||
cmd.Flags().MarkHidden("compatibility")
|
||||
@@ -70,3 +81,43 @@ func Analyze() *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func downloadAnalyzerSpec(specPath string) (string, error) {
|
||||
specContent := ""
|
||||
var err error
|
||||
if _, err = os.Stat(specPath); err == nil {
|
||||
if _, err := os.Stat(specPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("%s was not found", specPath)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(specPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
specContent = string(b)
|
||||
} else {
|
||||
if !util.IsURL(specPath) {
|
||||
return "", fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", specPath, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Analyzer/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
specContent = string(body)
|
||||
}
|
||||
return specContent, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"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(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(supportBundleName, analyzeResults)
|
||||
|
||||
uiEvents := ui.PollEvents()
|
||||
for {
|
||||
select {
|
||||
case e := <-uiEvents:
|
||||
switch e.ID {
|
||||
case "<C-c>":
|
||||
return nil
|
||||
case "q":
|
||||
if isShowingSaved == true {
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case "s":
|
||||
filename, err := save(analyzeResults)
|
||||
if err != nil {
|
||||
// show
|
||||
} else {
|
||||
showSaved(filename)
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}()
|
||||
}
|
||||
case "<Resize>":
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Down>":
|
||||
if selectedResult < len(analyzeResults)-1 {
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Up>":
|
||||
if selectedResult > 0 {
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
left := termWidth/2 - 2*len(title.Text)/3
|
||||
right := termWidth/2 + (termWidth/2 - left)
|
||||
|
||||
title.SetRect(left, 0, right, 1)
|
||||
ui.Render(title)
|
||||
}
|
||||
|
||||
func drawFooter() {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
instructions := widgets.NewParagraph()
|
||||
instructions.Text = "[q] quit [s] save [↑][↓] scroll"
|
||||
instructions.Border = false
|
||||
|
||||
left := 0
|
||||
right := termWidth
|
||||
top := termHeight - 1
|
||||
bottom := termHeight
|
||||
|
||||
instructions.SetRect(left, top, right, bottom)
|
||||
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("Support Bundle analysis results saved to\n\n%s", filename)
|
||||
savedMessage.WrapText = true
|
||||
savedMessage.Border = true
|
||||
|
||||
left := termWidth/2 - 20
|
||||
right := termWidth/2 + 20
|
||||
top := termHeight/2 - 4
|
||||
bottom := termHeight/2 + 4
|
||||
|
||||
savedMessage.SetRect(left, top, right, bottom)
|
||||
ui.Render(savedMessage)
|
||||
|
||||
isShowingSaved = true
|
||||
}
|
||||
|
||||
func save(analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
|
||||
filename := path.Join(util.HomeDir(), fmt.Sprintf("%s-results.txt", "support-bundle"))
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
os.Remove(filename)
|
||||
}
|
||||
|
||||
results := ""
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
result := ""
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
result = "Check PASS\n"
|
||||
} else if analyzeResult.IsWarn {
|
||||
result = "Check WARN\n"
|
||||
} else if analyzeResult.IsFail {
|
||||
result = "Check FAIL\n"
|
||||
}
|
||||
|
||||
result = result + fmt.Sprintf("Title: %s\n", analyzeResult.Title)
|
||||
result = result + fmt.Sprintf("Message: %s\n", analyzeResult.Message)
|
||||
|
||||
if analyzeResult.URI != "" {
|
||||
result = result + fmt.Sprintf("URI: %s\n", analyzeResult.URI)
|
||||
}
|
||||
|
||||
result = result + "\n------------\n"
|
||||
|
||||
results = results + result
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, []byte(results), 0644); err != nil {
|
||||
return "", errors.Wrap(err, "failed to save preflight results")
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func receiveSupportBundle(collectorJobNamespace string, collectorJobName string) error {
|
||||
// poll until there are no more "running" collectors
|
||||
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(bundlePath)
|
||||
|
||||
if err = writeVersionFile(bundlePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
receivedCollectors := []string{}
|
||||
for {
|
||||
job, err := troubleshootClient.CollectorJobs(collectorJobNamespace).Get(collectorJobName, metav1.GetOptions{})
|
||||
if err != nil && kuberneteserrors.IsNotFound(err) {
|
||||
// where did it go!
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, readyCollector := range job.Status.Successful {
|
||||
alreadyReceived := false
|
||||
for _, receivedCollector := range receivedCollectors {
|
||||
if receivedCollector == readyCollector {
|
||||
alreadyReceived = true
|
||||
}
|
||||
}
|
||||
|
||||
if alreadyReceived {
|
||||
continue
|
||||
}
|
||||
|
||||
collectorResp, err := http.Get(fmt.Sprintf("http://localhost:8000/collector/%s", readyCollector))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer collectorResp.Body.Close()
|
||||
body, err := ioutil.ReadAll(collectorResp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(string(body))
|
||||
if err != nil {
|
||||
logger.Printf("failed to output for collector %s\n", readyCollector)
|
||||
continue
|
||||
}
|
||||
|
||||
files := make(map[string]interface{})
|
||||
if err := json.Unmarshal(decoded, &files); err != nil {
|
||||
logger.Printf("failed to unmarshal output for collector %s\n", readyCollector)
|
||||
}
|
||||
|
||||
for filename, maybeContents := range files {
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
outPath := filepath.Join(bundlePath, fileDir)
|
||||
|
||||
if err := os.MkdirAll(outPath, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch maybeContents.(type) {
|
||||
case string:
|
||||
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v := range maybeContents.(map[string]interface{}) {
|
||||
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
|
||||
if err := os.MkdirAll(s, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(v.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
receivedCollectors = append(receivedCollectors, readyCollector)
|
||||
}
|
||||
|
||||
if len(job.Status.Running) == 0 {
|
||||
filename, err := findFileName("support-bundle", "tar.gz")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func Retrieve() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "retrieve",
|
||||
Short: "retrieve a support bundle from a job in the cluster",
|
||||
Long: `...`,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlag("collectors", cmd.Flags().Lookup("collectors"))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
|
||||
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
collectorJobs, err := troubleshootClient.CollectorJobs(v.GetString("namespace")).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var collectorJob *troubleshootv1beta1.CollectorJob
|
||||
if v.GetString("collectors") == "" && len(collectorJobs.Items) == 1 {
|
||||
collectorJob = &collectorJobs.Items[0]
|
||||
} else {
|
||||
for _, foundCollectorJob := range collectorJobs.Items {
|
||||
if foundCollectorJob.Spec.Collector.Name == v.GetString("collectors") {
|
||||
collectorJob = &foundCollectorJob
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if collectorJob == nil {
|
||||
logger.Printf("unable to find collector job\n")
|
||||
return errors.New("no collectors")
|
||||
}
|
||||
|
||||
logger.Printf("connecting to collector job %s\n", collectorJob.Name)
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
stopChan, err := k8sutil.PortForward(config, 8000, 8000, collectorJob.Status.ServerPodNamespace, collectorJob.Status.ServerPodName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := receiveSupportBundle(collectorJob.Namespace, collectorJob.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write a zip
|
||||
|
||||
close(stopChan)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("collectors", "", "name of the collectors to use")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"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())
|
||||
@@ -39,19 +36,21 @@ from a server that can be used to assist when troubleshooting a server.`,
|
||||
cmd.AddCommand(Analyze())
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().String("collectors", "", "name of the collectors to use")
|
||||
cmd.Flags().String("image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("pullpolicy", "", "the pull policy of the collector image")
|
||||
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, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
cmd.Flags().MarkHidden("allow-insecure-connections")
|
||||
|
||||
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
KubernetesConfigFlags.AddFlags(cmd.Flags())
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -67,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
|
||||
@@ -79,3 +78,11 @@ func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubl
|
||||
|
||||
return append(list, &collector)
|
||||
}
|
||||
|
||||
func writeFile(filename string, contents []byte) error {
|
||||
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+603
-164
@@ -1,72 +1,122 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
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"
|
||||
"gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
var (
|
||||
httpClient *http.Client
|
||||
)
|
||||
|
||||
func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
|
||||
collectorContent := ""
|
||||
if !isURL(arg) {
|
||||
if _, err := os.Stat(arg); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s was not found", arg)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read spec file")
|
||||
}
|
||||
|
||||
collectorContent = string(b)
|
||||
if v.GetBool("allow-insecure-connections") || v.GetBool("insecure-skip-tls-verify") {
|
||||
httpClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
} else {
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "make request")
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read responce body")
|
||||
}
|
||||
|
||||
collectorContent = string(body)
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
collector := troubleshootv1beta1.Collector{}
|
||||
if err := yaml.Unmarshal([]byte(collectorContent), &collector); err != nil {
|
||||
return errors.Wrapf(err, "failed to parse %s collectors", arg)
|
||||
collectorContent, err := loadSupportBundleSpec(v, arg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
// we suppory both raw collector kinds and supportbundle kinds here
|
||||
supportBundleSpec, err := parseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse collector")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorContent, err := loadRedactorSpec(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.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
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 {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode(additionalDoc, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse additional doc %d", i)
|
||||
}
|
||||
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
|
||||
}
|
||||
|
||||
s := spin.New()
|
||||
finishedCh := make(chan bool, 1)
|
||||
progressChan := make(chan interface{}, 0) // non-zero buffer can result in missed messages
|
||||
isFinishedChClosed := false
|
||||
go func() {
|
||||
currentDir := ""
|
||||
for {
|
||||
@@ -92,19 +142,90 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
close(finishedCh)
|
||||
if !isFinishedChClosed {
|
||||
close(finishedCh)
|
||||
}
|
||||
}()
|
||||
|
||||
archivePath, err := runCollectors(v, collector, progressChan)
|
||||
archivePath, err := runCollectors(v, supportBundleSpec.Spec.Collectors, additionalRedactors, progressChan)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "run collectors")
|
||||
}
|
||||
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
|
||||
if len(collector.Spec.AfterCollection) == 0 {
|
||||
// upload if needed
|
||||
fileUploaded := false
|
||||
if len(supportBundleSpec.Spec.AfterCollection) > 0 {
|
||||
for _, ac := range supportBundleSpec.Spec.AfterCollection {
|
||||
if ac.UploadResultsTo != nil {
|
||||
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to upload support bundle: %v\n", cursor.ClearEntireLine(), err)
|
||||
} else {
|
||||
fileUploaded = true
|
||||
}
|
||||
} else if ac.Callback != nil {
|
||||
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to notify API that support bundle has been uploaded: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// perform analysis, if possible
|
||||
if len(supportBundleSpec.Spec.Analyzers) > 0 {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to make directory for analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to open support bundle for analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
|
||||
}
|
||||
if err := analyzer.ExtractTroubleshootBundle(f, tmpDir); err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to extract support bundle for analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
analyzeResults, err := analyzer.AnalyzeLocal(tmpDir, supportBundleSpec.Spec.Analyzers)
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to analyze support bundle: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
interactive := isatty.IsTerminal(os.Stdout.Fd())
|
||||
|
||||
if interactive {
|
||||
close(finishedCh) // this removes the spinner
|
||||
isFinishedChClosed = true
|
||||
|
||||
if err := showInteractiveResults(supportBundleSpec.Name, analyzeResults); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
}
|
||||
|
||||
if !interactive {
|
||||
data := convert.FromAnalyzerResult(analyzeResults)
|
||||
formatted, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
c := color.New(color.FgHiRed)
|
||||
c.Printf("%s\r * Failed to format analysis: %v\n", cursor.ClearEntireLine(), err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s", formatted)
|
||||
}
|
||||
}
|
||||
|
||||
if !fileUploaded {
|
||||
msg := archivePath
|
||||
if appName := collector.Labels["applicationName"]; appName != "" {
|
||||
if appName := supportBundleSpec.Labels["applicationName"]; appName != "" {
|
||||
f := `A support bundle for %s has been created in this directory
|
||||
named %s. Please upload it on the Troubleshoot page of
|
||||
the %s Admin Console to begin analysis.`
|
||||
@@ -116,20 +237,6 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
fileUploaded := false
|
||||
for _, ac := range collector.Spec.AfterCollection {
|
||||
if ac.UploadResultsTo != nil {
|
||||
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
|
||||
return errors.Wrap(err, "upload support bundle")
|
||||
}
|
||||
fileUploaded = true
|
||||
} else if ac.Callback != nil {
|
||||
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
|
||||
return errors.Wrap(err, "execute callback")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
if fileUploaded {
|
||||
fmt.Printf("A support bundle has been created and uploaded to your cluster for analysis. Please visit the Troubleshoot page to continue.\n")
|
||||
@@ -140,28 +247,202 @@ the %s Admin Console to begin analysis.`
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, progressChan chan interface{}) (string, error) {
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
func loadSupportBundleSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return nil, errors.Errorf("secret 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
|
||||
}
|
||||
|
||||
return loadSpec(v, arg)
|
||||
}
|
||||
|
||||
func loadRedactorSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
if strings.HasPrefix(arg, "configmap/") {
|
||||
// format configmap/namespace-name/configmap-name[/data-key]
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) > 4 {
|
||||
return nil, errors.Errorf("configmap path %s must have at most 4 components", arg)
|
||||
}
|
||||
if len(pathParts) < 3 {
|
||||
return nil, errors.Errorf("configmap path %s must have at least 3 components", arg)
|
||||
}
|
||||
|
||||
dataKey := "redactor-spec"
|
||||
if len(pathParts) == 4 {
|
||||
dataKey = pathParts[3]
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromConfigMap(pathParts[1], pathParts[2], dataKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from configmap")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
spec, err := loadSpec(v, arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load spec")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
var err error
|
||||
if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read spec file")
|
||||
}
|
||||
|
||||
return b, nil
|
||||
} else if !util.IsURL(arg) {
|
||||
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 {
|
||||
return nil, errors.Wrap(err, "make request")
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
|
||||
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 shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrap(err, "execute request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read responce body")
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
obj, _, err := decode(doc, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse document")
|
||||
}
|
||||
|
||||
collector, ok := obj.(*troubleshootv1beta2.Collector)
|
||||
if ok {
|
||||
supportBundle := troubleshootv1beta2.SupportBundle{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
},
|
||||
ObjectMeta: collector.ObjectMeta,
|
||||
Spec: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: collector.Spec.Collectors,
|
||||
Analyzers: []*troubleshootv1beta2.Analyze{},
|
||||
AfterCollection: collector.Spec.AfterCollection,
|
||||
},
|
||||
}
|
||||
|
||||
return &supportBundle, nil
|
||||
}
|
||||
|
||||
supportBundle, ok := obj.(*troubleshootv1beta2.SupportBundle)
|
||||
if ok {
|
||||
return supportBundle, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("spec was not parseable as a troubleshoot kind")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Connection appears to be insecure. Would you like to attempt to create a support bundle anyway?",
|
||||
IsConfirm: true,
|
||||
}
|
||||
|
||||
_, err := prompt.Run()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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 = append(collectSpecs, collector.Spec.Collectors...)
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
|
||||
collectSpecs := make([]*troubleshootv1beta2.Collect, 0, 0)
|
||||
collectSpecs = append(collectSpecs, collectors...)
|
||||
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")
|
||||
}
|
||||
|
||||
var collectors collect.Collectors
|
||||
var cleanedCollectors collect.Collectors
|
||||
for _, desiredCollector := range collectSpecs {
|
||||
collector := collect.Collector{
|
||||
Redact: true,
|
||||
@@ -169,15 +450,15 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
|
||||
ClientConfig: config,
|
||||
Namespace: v.GetString("namespace"),
|
||||
}
|
||||
collectors = append(collectors, &collector)
|
||||
cleanedCollectors = append(cleanedCollectors, &collector)
|
||||
}
|
||||
|
||||
if err := collectors.CheckRBAC(); err != nil {
|
||||
if err := cleanedCollectors.CheckRBAC(context.Background()); err != nil {
|
||||
return "", errors.Wrap(err, "failed to check RBAC for collectors")
|
||||
}
|
||||
|
||||
foundForbidden := false
|
||||
for _, c := range collectors {
|
||||
for _, c := range cleanedCollectors {
|
||||
for _, e := range c.RBACErrors {
|
||||
foundForbidden = true
|
||||
progressChan <- e
|
||||
@@ -188,8 +469,19 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
|
||||
return "", errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
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 collectors {
|
||||
for _, collector := range cleanedCollectors {
|
||||
if len(collector.RBACErrors) > 0 {
|
||||
// don't skip clusterResources collector due to RBAC issues
|
||||
if collector.Collect.ClusterResources == nil {
|
||||
@@ -200,14 +492,14 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
|
||||
|
||||
progressChan <- collector.GetDisplayName()
|
||||
|
||||
result, err := collector.RunCollectorSync()
|
||||
result, err := collector.RunCollectorSync(globalRedactors)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to run collector %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
err = parseAndSaveCollectorOutput(string(result), bundlePath)
|
||||
err = saveCollectorOutput(result, bundlePath, collector)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
@@ -215,11 +507,6 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -227,13 +514,15 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func parseAndSaveCollectorOutput(output string, bundlePath string) error {
|
||||
input := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(output), &input); err != nil {
|
||||
return errors.Wrap(err, "unmarshal output")
|
||||
}
|
||||
|
||||
for filename, maybeContents := range input {
|
||||
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)
|
||||
|
||||
@@ -241,71 +530,141 @@ func parseAndSaveCollectorOutput(output string, bundlePath string) error {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
|
||||
switch maybeContents.(type) {
|
||||
case string:
|
||||
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decode collector output")
|
||||
}
|
||||
|
||||
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
|
||||
return errors.Wrap(err, "write collector output")
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v := range maybeContents.(map[string]interface{}) {
|
||||
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
|
||||
if err := os.MkdirAll(s, 0777); err != nil {
|
||||
return errors.Wrap(err, "write output directories")
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(v.(string))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decode output")
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
|
||||
return errors.Wrap(err, "write output")
|
||||
}
|
||||
}
|
||||
if err := writeFile(filepath.Join(outPath, fileName), maybeContents); err != nil {
|
||||
return errors.Wrap(err, "write collector output")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
// send redaction report
|
||||
if r.RedactURI != "" {
|
||||
type PutSupportBundleRedactions struct {
|
||||
Redactions redact.RedactionList `json:"redactions"`
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
redactBytes, err := json.Marshal(PutSupportBundleRedactions{Redactions: redact.GetRedactionList()})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get redaction report")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -319,54 +678,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 := http.DefaultClient.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
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func createServiceAccount(collector troubleshootv1beta1.Collector, namespace string, clientset *kubernetes.Clientset) (string, error) {
|
||||
name := fmt.Sprintf("troubleshoot-%s", collector.Name)
|
||||
|
||||
serviceAccount := corev1.ServiceAccount{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
_, err := clientset.CoreV1().ServiceAccounts(namespace).Create(&serviceAccount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
role := rbacv1.ClusterRole{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{
|
||||
"namespaces",
|
||||
"pods",
|
||||
"services",
|
||||
"secrets",
|
||||
},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"extensions"},
|
||||
Resources: []string{"ingresses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"storage.k8s.io"},
|
||||
Resources: []string{"storageclasses"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apiextensions.k8s.io"},
|
||||
Resources: []string{"customresourcedefinitions"},
|
||||
Verbs: metav1.Verbs{"list"},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoles().Create(&role)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
roleBinding := rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ClusterRoleBinding",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
_, err = clientset.RbacV1().ClusterRoleBindings().Create(&roleBinding)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func removeServiceAccount(name string, namespace string, clientset *kubernetes.Clientset) error {
|
||||
if err := clientset.RbacV1().ClusterRoleBindings().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clientset.RbacV1().ClusterRoles().Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clientset.CoreV1().ServiceAccounts(namespace).Delete(name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,44 +2,11 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
parsed, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return parsed.Scheme != ""
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func HomeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func IsURL(str string) bool {
|
||||
parsed, err := url.ParseRequestURI(str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
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, " ")
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: analyzerjobs.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: AnalyzerJob
|
||||
listKind: AnalyzerJobList
|
||||
plural: analyzerjobs
|
||||
singular: analyzerjob
|
||||
scope: Namespaced
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: AnalyzerJob is the Schema for the analyzerjobs 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: AnalyzerJobSpec defines the desired state of AnalyzerJob
|
||||
type: object
|
||||
status:
|
||||
description: AnalyzerJobStatus defines the observed state of AnalyzerJob
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -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.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
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: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: collectorjobs.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: CollectorJob
|
||||
listKind: CollectorJobList
|
||||
plural: collectorjobs
|
||||
singular: collectorjob
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: CollectorJob is the Schema for the collectorjobs 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: CollectorJobSpec defines the desired state of CollectorJob
|
||||
properties:
|
||||
collector:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
redact:
|
||||
type: boolean
|
||||
required:
|
||||
- collector
|
||||
type: object
|
||||
status:
|
||||
description: CollectorJobStatus defines the observed state of CollectorJob
|
||||
properties:
|
||||
failed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
isServerReady:
|
||||
type: boolean
|
||||
running:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
serverPodName:
|
||||
type: string
|
||||
serverPodNamespace:
|
||||
type: string
|
||||
serverPodPort:
|
||||
type: integer
|
||||
successful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- failed
|
||||
- isServerReady
|
||||
- running
|
||||
- serverPodName
|
||||
- serverPodNamespace
|
||||
- serverPodPort
|
||||
- successful
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -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.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
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: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -0,0 +1,120 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
creationTimestamp: null
|
||||
name: preflightjobs.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: PreflightJob
|
||||
listKind: PreflightJobList
|
||||
plural: preflightjobs
|
||||
singular: preflightjob
|
||||
scope: Namespaced
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: PreflightJob is the Schema for the preflightjobs 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: PreflightJobSpec defines the desired state of PreflightJob
|
||||
properties:
|
||||
collectorImage:
|
||||
type: string
|
||||
collectorImagePullPolicy:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
preflight:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
preflightImage:
|
||||
type: string
|
||||
required:
|
||||
- preflight
|
||||
type: object
|
||||
status:
|
||||
description: PreflightJobStatus defines the observed state of PreflightJob
|
||||
properties:
|
||||
analyzersFailed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
analyzersRunning:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
analyzersSuccessful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsFailed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsRunning:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
collectorsSuccessful:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
isAnalyzersComplete:
|
||||
type: boolean
|
||||
isServerReady:
|
||||
type: boolean
|
||||
serverPodName:
|
||||
type: string
|
||||
serverPodNamespace:
|
||||
type: string
|
||||
serverPodPort:
|
||||
type: integer
|
||||
required:
|
||||
- analyzersFailed
|
||||
- analyzersRunning
|
||||
- analyzersSuccessful
|
||||
- collectorsFailed
|
||||
- collectorsRunning
|
||||
- collectorsSuccessful
|
||||
- isAnalyzersComplete
|
||||
- isServerReady
|
||||
- serverPodName
|
||||
- serverPodNamespace
|
||||
- serverPodPort
|
||||
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
@@ -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
@@ -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: []
|
||||
@@ -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,9 +0,0 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
kind: AnalyzerJob
|
||||
metadata:
|
||||
labels:
|
||||
controller-tools.k8s.io: "1.0"
|
||||
name: analyzerjob-sample
|
||||
spec:
|
||||
# Add fields here
|
||||
foo: bar
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
kind: CollectorJob
|
||||
metadata:
|
||||
name: collectorjob-sample
|
||||
spec:
|
||||
image: localhost:32000/troubleshoot:alpha
|
||||
imagePullPolicy: Always
|
||||
collector:
|
||||
name: collector-sample
|
||||
namespace: default
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
kind: PreflightJob
|
||||
metadata:
|
||||
name: preflightjob-sample
|
||||
spec:
|
||||
image: localhost:32000/troubleshoot:alpha
|
||||
imagePullPolicy: Always
|
||||
preflight:
|
||||
name: preflight-sample
|
||||
namespace: default
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
labels:
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Collector
|
||||
metadata:
|
||||
name: collector-sample
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: support-io
|
||||
@@ -1,66 +0,0 @@
|
||||
project_name: troubleshoot
|
||||
release:
|
||||
github:
|
||||
owner: replicatedhq
|
||||
name: troubleshoot
|
||||
builds:
|
||||
- id: preflight
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
main: cmd/preflight/main.go
|
||||
ldflags: -s -w
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: preflight
|
||||
hooks: {}
|
||||
- id: support-bundle
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
main: cmd/troubleshoot/main.go
|
||||
ldflags: -s -w
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: support-bundle
|
||||
hooks: {}
|
||||
archives:
|
||||
- id: tar
|
||||
format: tar.gz
|
||||
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}-alpha'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
- license*
|
||||
- LICENSE*
|
||||
- readme*
|
||||
- README*
|
||||
- changelog*
|
||||
- CHANGELOG*
|
||||
dockers:
|
||||
- dockerfile: ./deploy/Dockerfile.troubleshoot
|
||||
image_templates:
|
||||
- "replicated/troubleshoot:alpha"
|
||||
binaries:
|
||||
- support-bundle
|
||||
- preflight
|
||||
- dockerfile: ./deploy/Dockerfile.troubleshoot
|
||||
image_templates:
|
||||
- "replicated/preflight:alpha"
|
||||
binaries:
|
||||
- support-bundle
|
||||
- preflight
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
@@ -1,86 +0,0 @@
|
||||
project_name: troubleshoot
|
||||
release:
|
||||
github:
|
||||
owner: replicatedhq
|
||||
name: troubleshoot
|
||||
builds:
|
||||
- id: so
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GO111MODULE=on
|
||||
main: ffi/main.go
|
||||
flags: -buildmode=c-shared
|
||||
binary: troubleshoot.so
|
||||
hooks: {}
|
||||
- id: preflight
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
main: cmd/preflight/main.go
|
||||
ldflags: -s -w
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: preflight
|
||||
hooks: {}
|
||||
- id: support-bundle
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
main: cmd/troubleshoot/main.go
|
||||
ldflags: -s -w
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
|
||||
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
|
||||
-extldflags "-static"
|
||||
flags: -tags netgo -installsuffix netgo
|
||||
binary: support-bundle
|
||||
hooks: {}
|
||||
archives:
|
||||
- id: so
|
||||
builds:
|
||||
- so
|
||||
format: tar.gz
|
||||
- id: tar
|
||||
format: tar.gz
|
||||
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}-alpha'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
- license*
|
||||
- LICENSE*
|
||||
- readme*
|
||||
- README*
|
||||
- changelog*
|
||||
- CHANGELOG*
|
||||
dockers:
|
||||
- dockerfile: ./deploy/Dockerfile.troubleshoot
|
||||
image_templates:
|
||||
- "replicated/troubleshoot:alpha"
|
||||
binaries:
|
||||
- support-bundle
|
||||
- preflight
|
||||
- dockerfile: ./deploy/Dockerfile.troubleshoot
|
||||
image_templates:
|
||||
- "replicated/preflight:alpha"
|
||||
binaries:
|
||||
- support-bundle
|
||||
- preflight
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
@@ -65,7 +65,7 @@ archives:
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}'
|
||||
name_template: 'preflight_{{ .Os }}_{{ .Arch }}'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
@@ -82,7 +82,7 @@ archives:
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}'
|
||||
name_template: 'support-bundle_{{ .Os }}_{{ .Arch }}'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
@@ -110,4 +110,4 @@ dockers:
|
||||
- "replicated/preflight:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
|
||||
binaries:
|
||||
- support-bundle
|
||||
- preflight
|
||||
- preflight
|
||||
+21
-25
@@ -3,14 +3,13 @@ kind: Plugin
|
||||
metadata:
|
||||
name: preflight
|
||||
spec:
|
||||
version: "v0.9.14"
|
||||
version: {{ .TagName }}
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.14/preflight_linux_amd64.tar.gz
|
||||
sha256: "25b062760a23e546d22f1687fd460095d61f9fd4a210eec5cc4bacb648b043da"
|
||||
{{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/preflight_linux_amd64.tar.gz" .TagName }}
|
||||
files:
|
||||
- from: preflight
|
||||
to: .
|
||||
@@ -21,8 +20,7 @@ spec:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.14/preflight_darwin_amd64.tar.gz
|
||||
sha256: "00e21d1fe38fd8373136a064e15586b3d4eab49a4d3617167680eea27ebbcc65"
|
||||
{{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/preflight_darwin_amd64.tar.gz" .TagName }}
|
||||
files:
|
||||
- from: preflight
|
||||
to: .
|
||||
@@ -33,8 +31,7 @@ spec:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.14/preflight_windows_amd64.zip
|
||||
sha256: "1b10f6a7ca73722c458a44afb9e3a91af2d41681d0e0ea16483413691e363460"
|
||||
{{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/preflight_windows_amd64.zip" .TagName }}
|
||||
files:
|
||||
- from: preflight.exe
|
||||
to: .
|
||||
@@ -43,7 +40,23 @@ spec:
|
||||
bin: preflight.exe
|
||||
shortDescription: Executes application preflight tests in a cluster
|
||||
homepage: https://github.com/replicatedhq/troubleshoot
|
||||
caveats: |
|
||||
description: |
|
||||
This plugin executes application-specific preflight checks and conformance
|
||||
tests against a cluster, prior to installation of an application.
|
||||
|
||||
Application developers can create and host a Preflight manifest that
|
||||
defines the minimum and desired Kubernetes environment for an application.
|
||||
Before installing the application, a cluster admin can use this plugin to
|
||||
execute the application preflight checksto identify any missing
|
||||
components, configuration or incompatibilities between the cluster and the
|
||||
desired environment.
|
||||
|
||||
When executing Preflight tests, the test results will be displayed in a
|
||||
terminal-based UI on the workstation that executed the command.
|
||||
|
||||
For information on creating a Preflight manifest, view the documentation
|
||||
at https://help.replicated.com/docs/troubleshoot/kubernetes/analysis/
|
||||
|
||||
Usage:
|
||||
$ kubectl preflight <uri/path>
|
||||
|
||||
@@ -62,20 +75,3 @@ spec:
|
||||
|
||||
For application developers writing collectors and analyzers:
|
||||
https://help.replicated.com/docs/troubleshoot/kubernetes/collectors/defining-collectors/
|
||||
|
||||
description: |
|
||||
This plugin executes application-specific preflight checks and conformance
|
||||
tests against a cluster, prior to installation of an application.
|
||||
|
||||
Application developers can create and host a Preflight manifest that
|
||||
defines the minimum and desired Kubernetes environment for an application.
|
||||
Before installing the application, a cluster admin can use this plugin to
|
||||
execute the application preflight checksto identify any missing
|
||||
components, configuration or incompatibilities between the cluster and the
|
||||
desired environment.
|
||||
|
||||
When executing Preflight tests, the test results will be displayed in a
|
||||
terminal-based UI on the workstation that executed the command.
|
||||
|
||||
For information on creating a Preflight manifest, view the documentation
|
||||
at https://help.replicated.com/docs/troubleshoot/kubernetes/analysis/
|
||||
|
||||
@@ -3,14 +3,13 @@ kind: Plugin
|
||||
metadata:
|
||||
name: support-bundle
|
||||
spec:
|
||||
version: "v0.9.14"
|
||||
version: {{ .TagName }}
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.14/support-bundle_linux_amd64.tar.gz
|
||||
sha256: "81ed16f3e423535f026909df2823f5da9909ec5762a150c76fde94d985d101c7"
|
||||
{{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/support-bundle_linux_amd64.tar.gz" .TagName }}
|
||||
files:
|
||||
- from: support-bundle
|
||||
to: .
|
||||
@@ -21,8 +20,7 @@ spec:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.14/support-bundle_darwin_amd64.tar.gz
|
||||
sha256: "84d2cd6b3d77ed2e8e83cda6f79b553e443acb2cc7cb0099be2a73934dac46f4"
|
||||
{{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/support-bundle_darwin_amd64.tar.gz" .TagName }}
|
||||
files:
|
||||
- from: support-bundle
|
||||
to: .
|
||||
@@ -33,8 +31,7 @@ spec:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.14/support-bundle_windows_amd64.zip
|
||||
sha256: "b968d9168ce97c7dc43cb4e043a8d6762c1e231d68f90826a019571f8d450719"
|
||||
{{addURIAndSha "https://github.com/replicatedhq/troubleshoot/releases/download/{{ .TagName }}/support-bundle_windows_amd64.zip" .TagName }}
|
||||
files:
|
||||
- from: support-bundle.exe
|
||||
to: .
|
||||
@@ -43,7 +40,20 @@ spec:
|
||||
bin: support-bundle.exe
|
||||
shortDescription: Creates support bundles for off-cluster analysis
|
||||
homepage: https://github.com/replicatedhq/troubleshoot
|
||||
caveats: |
|
||||
description: |
|
||||
This plugin collects information about the cluster, and automatically
|
||||
redacts sensitive data from being collected. This can optionally include
|
||||
application-specific data. The plugin writes the collected files into a
|
||||
single archive named support-bundle.tar.gz. This archive can be manually
|
||||
inspected or uploaded to https://vendor.replicated.com for automated
|
||||
analysis.
|
||||
|
||||
Application developers can create and host a Collector manifest that
|
||||
defines information to be collected.
|
||||
|
||||
For information on creating a Collector manifest, view the documentation
|
||||
at https://help.replicated.com/docs/troubleshoot/kubernetes/collectors/defining-collectors/
|
||||
|
||||
Usage:
|
||||
$ kubectl support-bundle <uri>
|
||||
|
||||
@@ -62,17 +72,3 @@ spec:
|
||||
|
||||
For application developers writing collectors and analyzers:
|
||||
https://help.replicated.com/docs/troubleshoot/kubernetes/collectors/defining-collectors/
|
||||
|
||||
description: |
|
||||
This plugin collects information about the cluster, and automatically
|
||||
redacts sensitive data from being collected. This can optionally include
|
||||
application-specific data. The plugin writes the collected files into a
|
||||
single archive named support-bundle.tar.gz. This archive can be manually
|
||||
inspected or uploaded to https://vendor.replicated.com for automated
|
||||
analysis.
|
||||
|
||||
Application developers can create and host a Collector manifest that
|
||||
defines information to be collected.
|
||||
|
||||
For information on creating a Collector manifest, view the documentation
|
||||
at https://help.replicated.com/docs/troubleshoot/kubernetes/collectors/defining-collectors/
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: certificate
|
||||
spec:
|
||||
collectors:
|
||||
- certificate:
|
||||
certificatePath: /etc/ssl/corp.crt
|
||||
keyPath: /etc/ssl/corp.key
|
||||
analyzers:
|
||||
- certificate:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "key-pair-missing"
|
||||
message: Certificate key pair not found in /etc/ssl
|
||||
- fail:
|
||||
when: "key-pair-switched"
|
||||
message: Cert and key pair are switched
|
||||
- fail:
|
||||
when: "key-pair-encrypted"
|
||||
message: Private key is encrypted
|
||||
- fail:
|
||||
when: "key-pair-mismatch"
|
||||
message: Cert and key do not match
|
||||
- fail:
|
||||
when: "key-pair-invalid"
|
||||
message: Certificate key pair is invalid
|
||||
- pass:
|
||||
when: "key-pair-valid"
|
||||
message: Certificate key pair is valid
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: fsperf
|
||||
spec:
|
||||
collectors:
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
directory: /var/lib/etcd
|
||||
fileSize: 22Mi
|
||||
operationSizeBytes: 2300
|
||||
datasync: true
|
||||
analyzers:
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "iops < 50"
|
||||
message: Insufficient random read IOPS
|
||||
- pass:
|
||||
when: "p99 < 3ms"
|
||||
message: Write latency is great!
|
||||
- pass:
|
||||
when: "p99 < 5ms"
|
||||
message: Write latency is ok
|
||||
- warn:
|
||||
when: "p99 < 8ms"
|
||||
message: Write latency is high
|
||||
- fail:
|
||||
when: "p99 >= 8ms"
|
||||
message: Write latency is too high
|
||||
@@ -0,0 +1,33 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: httploadbalancer
|
||||
spec:
|
||||
collectors:
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
port: 80
|
||||
address: http://app.corporate.internal
|
||||
timeout: 10s
|
||||
analyzers:
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to port 80 via load balancer was refused.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: Another process was already listening on port 80.
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to port 80 via load balancer. Check your firewall.
|
||||
- fail:
|
||||
when: "bind-permission-denied"
|
||||
message: Bind permission denied. Try running as root.
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Failed to connect to port 80 via load balancer.
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to port 80 via load balancer.
|
||||
@@ -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"
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: ipv4Interfaces
|
||||
spec:
|
||||
collectors:
|
||||
- ipv4Interfaces: {}
|
||||
analyzers:
|
||||
- ipv4Interfaces:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count == 0"
|
||||
message: No IPv4 interfaces detected
|
||||
- warn:
|
||||
when: "count >= 2"
|
||||
message: Multiple IPv4 interfaces detected
|
||||
- pass:
|
||||
when: "count == 1"
|
||||
message: IPv4 interface detected
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,238 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
collectors:
|
||||
- blockDevices: {}
|
||||
- certificate:
|
||||
certificatePath: /etc/ssl/corp.crt
|
||||
keyPath: /etc/ssl/corp.key
|
||||
- cpu: {}
|
||||
- diskUsage:
|
||||
collectorName: ephemeral
|
||||
path: /var/lib/kubelet
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
directory: /var/lib/etcd
|
||||
fileSize: 22Mi
|
||||
operationSizeBytes: 2300
|
||||
datasync: true
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
port: 80
|
||||
address: http://app.corporate.internal
|
||||
timeout: 10s
|
||||
- http:
|
||||
collectorName: registry
|
||||
get:
|
||||
url: https://registry.replicated.com
|
||||
- ipv4Interfaces: {}
|
||||
- memory: {}
|
||||
- time: {}
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
address: 10.128.0.2:6783
|
||||
timeout: 2s
|
||||
- tcpLoadBalancer:
|
||||
collectorName: LB1
|
||||
address: 10.128.0.20:6443
|
||||
port: 6443
|
||||
timeout: 5000ms
|
||||
- tcpPortStatus:
|
||||
collectorName: k8s
|
||||
port: 6443
|
||||
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
|
||||
- certificate:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "key-pair-missing"
|
||||
message: Certificate key pair not found in /etc/ssl
|
||||
- fail:
|
||||
when: "key-pair-switched"
|
||||
message: Cert and key pair are switched
|
||||
- fail:
|
||||
when: "key-pair-encrypted"
|
||||
message: Private key is encrypted
|
||||
- fail:
|
||||
when: "key-pair-mismatch"
|
||||
message: Cert and key do not match
|
||||
- fail:
|
||||
when: "key-pair-invalid"
|
||||
message: Certificate key pair is invalid
|
||||
- pass:
|
||||
when: "key-pair-valid"
|
||||
message: Certificate key pair is valid
|
||||
- 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
|
||||
- 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
|
||||
- filesystemPerformance:
|
||||
collectorName: etcd-perf
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "iops < 50"
|
||||
message: Insufficient random read IOPS
|
||||
- pass:
|
||||
when: "p99 < 3ms"
|
||||
message: Write latency is great!
|
||||
- pass:
|
||||
when: "p99 < 5ms"
|
||||
message: Write latency is ok
|
||||
- warn:
|
||||
when: "p99 < 8ms"
|
||||
message: Write latency is high
|
||||
- fail:
|
||||
when: "p99 >= 8ms"
|
||||
message: Write latency is too high
|
||||
- httpLoadBalancer:
|
||||
collectorName: httploadbalancer
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to port 80 via load balancer was refused.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: Another process was already listening on port 80.
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to port 80 via load balancer. Check your firewall.
|
||||
- fail:
|
||||
when: "bind-permission-denied"
|
||||
message: Bind permission denied. Try running as root.
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Failed to connect to port 80 via load balancer.
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to port 80 via load balancer.
|
||||
- http:
|
||||
collectorName: registry
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Error connecting to registry
|
||||
- pass:
|
||||
when: "statusCode == 404"
|
||||
message: Connected to registry
|
||||
- fail:
|
||||
message: "Unexpected response"
|
||||
- ipv4Interfaces:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count == 0"
|
||||
message: No IPv4 interfaces detected
|
||||
- warn:
|
||||
when: "count >= 2"
|
||||
message: Multiple IPv4 interfaces detected
|
||||
- pass:
|
||||
when: "count == 1"
|
||||
message: IPv4 interface detected
|
||||
- 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
|
||||
- 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
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to weave on host 1 was refused
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to weave on host 1
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Unexpected error connecting to weave on host 1
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to weave on host 1
|
||||
- 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.
|
||||
- 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
|
||||
- time:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "timezone == UTC"
|
||||
message: Timezone is UTC
|
||||
- fail:
|
||||
message: Timezone is not UTC
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: connect
|
||||
spec:
|
||||
collectors:
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
address: 10.128.0.2:6783
|
||||
analyzers:
|
||||
- tcpConnect:
|
||||
collectorName: weave host 1
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to weave on host 1 was refused
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to weave on host 1
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Unexpected error connecting to weave on host 1
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to weave on host 1
|
||||
@@ -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
|
||||
@@ -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,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,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: sample
|
||||
@@ -36,3 +36,19 @@ spec:
|
||||
message: This application requires at least 1 node with 16GB available memory
|
||||
- pass:
|
||||
message: This cluster has a node with enough memory.
|
||||
- nodeResources:
|
||||
checkName: There must be at least 3 nodes in the cluster
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count() < 3"
|
||||
message: This application requires at least 3 nodes in the cluster
|
||||
- pass:
|
||||
message: This cluster haas sufficient nodes
|
||||
- nodeResources:
|
||||
checkName: There must be a total of at least 32Gi of memory on all nodes
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "sum(memoryCapacity) < 32Gi"
|
||||
message: This application requires that 32Gi or more memory be available to the cluster
|
||||
- pass:
|
||||
message: This cluster haas sufficient memory
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: sample
|
||||
spec:
|
||||
collectors:
|
||||
- postgres:
|
||||
collectorName: pg
|
||||
uri: postgresql://user:password@hostname:5432/defaultdb?sslmode=require
|
||||
analyzers:
|
||||
- postgres:
|
||||
checkName: Must be postgres 10.x or later
|
||||
collectorName: pg
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connected == false"
|
||||
message: Cannot connect to postgres server
|
||||
- fail:
|
||||
when: "version < 10.x"
|
||||
message: The postgres server must be at least version 10
|
||||
- pass:
|
||||
message: The postgres connection checks out
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: sample
|
||||
spec:
|
||||
collectors:
|
||||
- redis:
|
||||
collectorName: my-redis
|
||||
uri: rediss://default:password@hostname:6379
|
||||
analyzers:
|
||||
- redis:
|
||||
checkName: Must be redis 5.x or later
|
||||
collectorName: my-redis
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connected == false"
|
||||
message: Cannot connect to postgres server
|
||||
- fail:
|
||||
when: "version < 5.0.0"
|
||||
message: The redis server must be at least version 5
|
||||
- pass:
|
||||
message: The redis connection checks out
|
||||
|
||||
@@ -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,107 @@ 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
|
||||
- pass:
|
||||
when: "== kurl"
|
||||
message: KURL is a supported distribution
|
||||
- pass:
|
||||
when: "== digitalocean"
|
||||
message: DigitalOcean is a supported distribution
|
||||
- pass:
|
||||
when: "== rke2"
|
||||
message: RKE2 is a supported distribution
|
||||
- pass:
|
||||
when: "== k3s"
|
||||
message: K3S 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.
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: example-preflight-checks
|
||||
spec:
|
||||
collectors:
|
||||
- data:
|
||||
name: config/replicas.txt
|
||||
data: "2"
|
||||
analyzers:
|
||||
- textAnalyze:
|
||||
checkName: Replica Count
|
||||
fileName: config/replicas.txt
|
||||
regexGroups: '(?P<Replicas>\d+)'
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "Replicas < 5"
|
||||
message: That's not enough replicas!
|
||||
- pass:
|
||||
message: You've selected at leat 5 replicas
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Collector
|
||||
metadata:
|
||||
name: collector-sample
|
||||
spec:
|
||||
collectors: []
|
||||
@@ -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,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
name: a
|
||||
@@ -7,11 +7,14 @@ spec:
|
||||
- distribution:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "= docker desktop"
|
||||
when: "= dockerdesktop"
|
||||
message: "docker for desktop is not allowed"
|
||||
# - fail:
|
||||
# when: "microk8s"
|
||||
# message: "mickrk8s is not prod"
|
||||
- fail:
|
||||
when: "microk8s"
|
||||
message: "mickrk8s is not prod"
|
||||
when: "!= openshift"
|
||||
message: "this should fail on anything other than openshift"
|
||||
- warn:
|
||||
when: "!= eks"
|
||||
message: "YMMV on not eks"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Collector
|
||||
metadata:
|
||||
name: collector-sample
|
||||
|
||||
@@ -4,40 +4,52 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
|
||||
github.com/aws/aws-sdk-go v1.22.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.25.18 // indirect
|
||||
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/gin-gonic/gin v1.4.0
|
||||
github.com/gizak/termui/v3 v3.1.0
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
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/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
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/mholt/archiver v3.1.1+incompatible
|
||||
github.com/imdario/mergo v0.3.8 // indirect
|
||||
github.com/lib/pq v1.3.0
|
||||
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/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||
github.com/pierrec/lz4 v2.2.6+incompatible // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/nicksnyder/go-i18n v1.10.1 // indirect
|
||||
github.com/onsi/gomega v1.9.0 // 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.4.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tj/go-spin v1.1.0
|
||||
github.com/ugorji/go v1.1.7 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
k8s.io/api v0.17.0
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783
|
||||
k8s.io/apimachinery v0.17.0
|
||||
k8s.io/cli-runtime v0.17.0
|
||||
k8s.io/client-go v0.17.0
|
||||
k8s.io/code-generator v0.16.5-beta.1 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
sigs.k8s.io/controller-tools v0.2.4 // indirect
|
||||
github.com/ulikunitz/xz v0.5.6 // indirect
|
||||
go.opencensus.io v0.22.0 // indirect
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // 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.2
|
||||
k8s.io/apimachinery v0.18.3
|
||||
k8s.io/cli-runtime v0.18.0
|
||||
k8s.io/client-go v0.18.2
|
||||
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
|
||||
)
|
||||
|
||||
replace github.com/appscode/jsonpatch => github.com/gomodules/jsonpatch v2.0.1+incompatible
|
||||
|
||||
@@ -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=
|
||||
@@ -29,33 +34,47 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412 h1:vOVO0ypMfTt6tZacyI0kp+iCZb1XSNiYDqnzBWYgfe4=
|
||||
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412/go.mod h1:AI9hp1tkp10pAlK5TCwL+7yWbRgtDm9jhToq6qij2xs=
|
||||
github.com/alecthomas/gometalinter v2.0.11+incompatible h1:ENdXMllZNSVDTJUUVIzBW9CSEpntTrQa76iRsEFLX/M=
|
||||
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/aws/aws-sdk-go v1.22.0 h1:e88V6+dSEyBibUy0ekOydtTfNWzqG3hrtCR8SF6UqqY=
|
||||
github.com/aws/aws-sdk-go v1.22.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
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/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=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b h1:i4AJWjdpwyE7iKKxsp8NbWy3gQVQ9nZEBG/Pm+cBaww=
|
||||
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@@ -63,26 +82,27 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
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/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
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=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
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=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
|
||||
@@ -91,25 +111,19 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w=
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
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/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
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=
|
||||
@@ -124,6 +138,7 @@ github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70t
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
|
||||
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
@@ -143,17 +158,21 @@ github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf
|
||||
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
|
||||
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
|
||||
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
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=
|
||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
|
||||
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
@@ -162,30 +181,35 @@ github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tF
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
|
||||
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
||||
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.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo=
|
||||
github.com/gobuffalo/flect v0.1.5/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=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3 h1:I4BOK3PBMjhWfQM2zPJKK7lOBGsrsvOB7kBELP33hiE=
|
||||
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
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=
|
||||
@@ -195,12 +219,15 @@ 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=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -210,20 +237,24 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0
|
||||
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
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=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
|
||||
@@ -245,8 +276,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
@@ -254,18 +285,17 @@ 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=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
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=
|
||||
@@ -275,8 +305,12 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@@ -286,20 +320,20 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
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 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
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.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
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=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
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=
|
||||
@@ -314,70 +348,78 @@ 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=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc=
|
||||
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.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.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.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
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/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/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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 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.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
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/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.0.0-20181126121408-4724e9255275/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-20181204211112-1dc9a6cbc91a/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=
|
||||
@@ -402,7 +444,6 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
@@ -412,11 +453,13 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -426,43 +469,43 @@ 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=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c=
|
||||
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
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/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-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
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/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=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
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.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
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 v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
@@ -474,20 +517,17 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/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-20180112015858-5ccada7d0a7b/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=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -495,20 +535,22 @@ golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
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/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=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -523,28 +565,33 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-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.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -558,23 +605,22 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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-20190206041539-40960b6deb8e/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-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=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
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=
|
||||
@@ -593,28 +639,30 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
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-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/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-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=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
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=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 h1:CEBpW6C191eozfEuWdUmIAHn7lwlLxJ7HVdr2e2Tsrw=
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
@@ -623,67 +671,62 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
|
||||
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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.0.0/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.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
|
||||
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
|
||||
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
|
||||
k8s.io/cli-runtime v0.17.0 h1:XEuStbJBHCQlEKFyTQmceDKEWOSYHZkcYWKp3SsQ9Hk=
|
||||
k8s.io/cli-runtime v0.17.0/go.mod h1:1E5iQpMODZq2lMWLUJELwRu2MLWIzwvMgDBpn3Y81Qo=
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
|
||||
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
|
||||
k8s.io/code-generator v0.16.5-beta.1 h1:+zWxMQH3a6fd8lZe6utWyW/V7nmG2ZMXwtovSJI2p+0=
|
||||
k8s.io/code-generator v0.16.5-beta.1/go.mod h1:mJUgkl06XV4kstAnLHAIzJPVCOzVR+ZcfPIv4fUsFCY=
|
||||
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
|
||||
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.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
|
||||
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.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
|
||||
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.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
|
||||
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.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 h1:ZY6yclUKVbZ+SdWnkfY+Je5vrMpKOxmGeKRbsXVmqYM=
|
||||
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 v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
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-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
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/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/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/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
|
||||
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
|
||||
sigs.k8s.io/controller-tools v0.2.4 h1:la1h46EzElvWefWLqfsXrnsO3lZjpkI0asTpX6h8PLA=
|
||||
sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
|
||||
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-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
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/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 v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
|
||||
sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
|
||||
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
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/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=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
+229
-25
@@ -1,8 +1,12 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
type AnalyzeResult struct {
|
||||
@@ -13,80 +17,280 @@ type AnalyzeResult struct {
|
||||
Title string
|
||||
Message string
|
||||
URI string
|
||||
IconKey string
|
||||
IconURI string
|
||||
}
|
||||
|
||||
type getCollectedFileContents func(string) ([]byte, error)
|
||||
type getChildCollectedFileContents func(string) (map[string][]byte, error)
|
||||
|
||||
func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) (*AnalyzeResult, error) {
|
||||
func isExcluded(excludeVal multitype.BoolOrString) (bool, error) {
|
||||
if excludeVal.Type == multitype.Bool {
|
||||
return excludeVal.BoolVal, nil
|
||||
}
|
||||
|
||||
if excludeVal.StrVal == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
parsed, err := strconv.ParseBool(excludeVal.StrVal)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse bool string")
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) []*AnalyzeResult {
|
||||
analyzer, ok := GetHostAnalyzer(hostAnalyzer)
|
||||
if !ok {
|
||||
return NewAnalyzeResultError(analyzer, errors.New("invalid analyzer"))
|
||||
}
|
||||
|
||||
isExcluded, _ := analyzer.IsExcluded()
|
||||
if isExcluded {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := analyzer.Analyze(getFile)
|
||||
if err != nil {
|
||||
return NewAnalyzeResultError(analyzer, errors.Wrap(err, "analyze"))
|
||||
}
|
||||
return []*AnalyzeResult{result}
|
||||
}
|
||||
|
||||
func NewAnalyzeResultError(analyzer HostAnalyzer, err error) []*AnalyzeResult {
|
||||
return []*AnalyzeResult{{
|
||||
IsFail: true,
|
||||
Title: analyzer.Title(),
|
||||
Message: fmt.Sprintf("Analyzer Failed: %v", err),
|
||||
}}
|
||||
}
|
||||
|
||||
func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if analyzer.ClusterVersion != nil {
|
||||
if analyzer.ClusterVersion.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.ClusterVersion.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.StorageClass.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.StorageClass.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.CustomResourceDefinition.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.CustomResourceDefinition.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.Ingress.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.Ingress.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.Secret.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.Secret.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.ImagePullSecret.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.ImagePullSecret.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.DeploymentStatus.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.DeploymentStatus.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.StatefulsetStatus.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.StatefulsetStatus.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.ContainerRuntime.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.ContainerRuntime.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.Distribution.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.Distribution.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if analyzer.NodeResources.Exclude {
|
||||
isExcluded, err := isExcluded(analyzer.NodeResources.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
return analyzeTextAnalyze(analyzer.TextAnalyze, getFile)
|
||||
isExcluded, err := isExcluded(analyzer.TextAnalyze.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
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")
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,8 +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) {
|
||||
result := AnalyzeResult{}
|
||||
func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
for _, outcome := range outcomes {
|
||||
when := ""
|
||||
message := ""
|
||||
@@ -41,8 +40,10 @@ func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubles
|
||||
title = "Required Kubernetes Version"
|
||||
}
|
||||
|
||||
result = AnalyzeResult{
|
||||
Title: title,
|
||||
result := AnalyzeResult{
|
||||
Title: title,
|
||||
IconKey: "kubernetes_cluster_version",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
}
|
||||
|
||||
if outcome.Fail != nil {
|
||||
|
||||
@@ -5,27 +5,27 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
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.",
|
||||
},
|
||||
},
|
||||
@@ -33,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 {
|
||||
@@ -54,6 +54,8 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
Title: "Check Fail",
|
||||
Message: "Sentry requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.",
|
||||
URI: "https://www.kubernetes.io",
|
||||
IconKey: "kubernetes_cluster_version",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,6 +70,8 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
Title: "Check Warn",
|
||||
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",
|
||||
IconKey: "kubernetes_cluster_version",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -81,6 +85,8 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
IsPass: true,
|
||||
Title: "Check Pass",
|
||||
Message: "Your cluster meets the recommended and required versions of Kubernetes.",
|
||||
IconKey: "kubernetes_cluster_version",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ 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 string, readyReplicas int) (*AnalyzeResult, error) {
|
||||
func commonStatus(outcomes []*troubleshootv1beta2.Outcome, title, iconKey string, iconURI string, readyReplicas int) (*AnalyzeResult, error) {
|
||||
result := &AnalyzeResult{
|
||||
Title: title,
|
||||
Title: title,
|
||||
IconKey: iconKey,
|
||||
IconURI: iconURI,
|
||||
}
|
||||
|
||||
// ordering from the spec is important, the first one that matches returns
|
||||
|
||||
@@ -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,14 @@ 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",
|
||||
}
|
||||
|
||||
// ordering is important for passthrough
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -69,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",
|
||||
},
|
||||
},
|
||||
@@ -96,6 +96,8 @@ func Test_containerRuntime(t *testing.T) {
|
||||
IsFail: true,
|
||||
Title: "Container Runtime",
|
||||
Message: "containerd detected",
|
||||
IconKey: "kubernetes_container_runtime",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/container-runtime.svg?w=23&h=16",
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/nodes.json": []byte(collectedNodes),
|
||||
|
||||
+5
-3
@@ -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
|
||||
@@ -25,7 +25,9 @@ func analyzeCustomResourceDefinition(analyzer *troubleshootv1beta1.CustomResourc
|
||||
}
|
||||
|
||||
result := AnalyzeResult{
|
||||
Title: title,
|
||||
Title: title,
|
||||
IconKey: "kubernetes_custom_resource_definition",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/custom-resource-definition.svg?w=13&h=16",
|
||||
}
|
||||
|
||||
for _, storageClass := range crds {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func compareDatabaseConditionalToActual(conditional string, result *collect.DatabaseConnection) (bool, error) {
|
||||
parts := strings.Split(strings.TrimSpace(conditional), " ")
|
||||
|
||||
if len(parts) != 3 {
|
||||
return false, errors.New("unable to parse conditional")
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "connected":
|
||||
expected, err := strconv.ParseBool(parts[2])
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse bool")
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "=", "==", "===":
|
||||
return expected == result.IsConnected, nil
|
||||
case "!=", "!==":
|
||||
return expected != result.IsConnected, nil
|
||||
|
||||
}
|
||||
|
||||
return false, errors.New("unable to parse postgres connected analyzer")
|
||||
|
||||
case "version":
|
||||
expected, err := semver.ParseTolerant(strings.Replace(parts[2], "x", "0", -1))
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], expected.String()))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse semver range")
|
||||
}
|
||||
|
||||
return expectedRange(actual), nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_compareDatabaseConditionalToActual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
result collect.DatabaseConnection
|
||||
expectedMatch bool
|
||||
}{
|
||||
{
|
||||
name: "not connected, expected not connected",
|
||||
conditional: "connected == false",
|
||||
result: collect.DatabaseConnection{
|
||||
IsConnected: false,
|
||||
},
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "connected, expected connected",
|
||||
conditional: "connected == true",
|
||||
result: collect.DatabaseConnection{
|
||||
IsConnected: true,
|
||||
},
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "not connected, expected connected",
|
||||
conditional: "connected == true",
|
||||
result: collect.DatabaseConnection{
|
||||
IsConnected: false,
|
||||
},
|
||||
expectedMatch: false,
|
||||
},
|
||||
{
|
||||
name: "version 9.3.0, want > 10.0.0",
|
||||
conditional: "version >= 10.0.0",
|
||||
result: collect.DatabaseConnection{
|
||||
IsConnected: true,
|
||||
Version: "9.3.0",
|
||||
},
|
||||
expectedMatch: false,
|
||||
},
|
||||
{
|
||||
name: "version 12.0.0, want > 10.0.0",
|
||||
conditional: "version >= 10.0.0",
|
||||
result: collect.DatabaseConnection{
|
||||
IsConnected: true,
|
||||
Version: "12.0.0",
|
||||
},
|
||||
expectedMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareDatabaseConditionalToActual(test.conditional, &test.result)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expectedMatch, actual)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,12 @@ func analyzeDeploymentStatus(analyzer *troubleshootv1beta1.DeploymentStatus, get
|
||||
// there's not an error, but maybe the requested deployment is not even deployed
|
||||
return &AnalyzeResult{
|
||||
Title: fmt.Sprintf("%s Deployment Status", analyzer.Name),
|
||||
IconKey: "kubernetes_deployment_status",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
|
||||
IsFail: true,
|
||||
Message: fmt.Sprintf("The deployment %q was not found", analyzer.Name),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return commonStatus(analyzer.Outcomes, fmt.Sprintf("%s Status", analyzer.Name), int(status.ReadyReplicas))
|
||||
return commonStatus(analyzer.Outcomes, fmt.Sprintf("%s Status", analyzer.Name), "kubernetes_deployment_status", "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17", int(status.ReadyReplicas))
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -11,22 +11,22 @@ import (
|
||||
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",
|
||||
},
|
||||
},
|
||||
@@ -40,6 +40,8 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
IsFail: false,
|
||||
Title: "kotsadm-api Status",
|
||||
Message: "pass",
|
||||
IconKey: "kubernetes_deployment_status",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
|
||||
@@ -47,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",
|
||||
},
|
||||
},
|
||||
@@ -70,6 +72,8 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
IsFail: true,
|
||||
Title: "kotsadm-api Status",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_deployment_status",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
|
||||
@@ -77,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",
|
||||
},
|
||||
},
|
||||
@@ -106,6 +110,8 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
IsFail: false,
|
||||
Title: "kotsadm-api Status",
|
||||
Message: "warn",
|
||||
IconKey: "kubernetes_deployment_status",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/deployment-status.svg?w=17&h=17",
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
|
||||
|
||||
+143
-47
@@ -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"
|
||||
)
|
||||
@@ -18,6 +19,11 @@ type providers struct {
|
||||
digitalOcean bool
|
||||
openShift bool
|
||||
kurl bool
|
||||
aks bool
|
||||
ibm bool
|
||||
minikube bool
|
||||
rke2 bool
|
||||
k3s bool
|
||||
}
|
||||
|
||||
type Provider int
|
||||
@@ -31,9 +37,103 @@ const (
|
||||
digitalOcean Provider = iota
|
||||
openShift Provider = iota
|
||||
kurl Provider = iota
|
||||
aks Provider = iota
|
||||
ibm Provider = iota
|
||||
minikube Provider = iota
|
||||
rke2 Provider = iota
|
||||
k3s Provider = iota
|
||||
)
|
||||
|
||||
func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func CheckOpenShift(foundProviders *providers, apiResources []*metav1.APIResourceList, provider string) string {
|
||||
for _, resource := range apiResources {
|
||||
if strings.Contains(resource.GroupVersion, "openshift") {
|
||||
foundProviders.openShift = true
|
||||
return "openShift"
|
||||
}
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
foundProviders := providers{}
|
||||
foundMaster := false
|
||||
stringProvider := ""
|
||||
|
||||
for _, node := range nodes {
|
||||
for k, v := range node.ObjectMeta.Labels {
|
||||
|
||||
if k == "kurl.sh/cluster" && v == "true" {
|
||||
foundProviders.kurl = true
|
||||
stringProvider = "kurl"
|
||||
} else if k == "microk8s.io/cluster" && v == "true" {
|
||||
foundProviders.microk8s = true
|
||||
stringProvider = "microk8s"
|
||||
}
|
||||
if k == "node-role.kubernetes.io/master" {
|
||||
foundMaster = true
|
||||
}
|
||||
if k == "kubernetes.azure.com/role" {
|
||||
foundProviders.aks = true
|
||||
stringProvider = "aks"
|
||||
}
|
||||
if k == "minikube.k8s.io/version" {
|
||||
foundProviders.minikube = true
|
||||
stringProvider = "minikube"
|
||||
}
|
||||
if k == "node.kubernetes.io/instance-type" && v == "k3s" {
|
||||
foundProviders.k3s = true
|
||||
stringProvider = "k3s"
|
||||
}
|
||||
if k == "beta.kubernetes.io/instance-type" && v == "k3s" {
|
||||
foundProviders.k3s = true
|
||||
stringProvider = "k3s"
|
||||
}
|
||||
}
|
||||
|
||||
for k := range node.ObjectMeta.Annotations {
|
||||
if k == "rke2.io/node-args" {
|
||||
foundProviders.rke2 = true
|
||||
stringProvider = "rke2"
|
||||
}
|
||||
}
|
||||
|
||||
if node.Status.NodeInfo.OSImage == "Docker Desktop" {
|
||||
foundProviders.dockerDesktop = true
|
||||
stringProvider = "dockerDesktop"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "digitalocean:") {
|
||||
foundProviders.digitalOcean = true
|
||||
stringProvider = "digitalOcean"
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "aws:") {
|
||||
foundProviders.eks = true
|
||||
stringProvider = "eks"
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "gce:") {
|
||||
foundProviders.gke = true
|
||||
stringProvider = "gke"
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "ibm:") {
|
||||
foundProviders.ibm = true
|
||||
stringProvider = "ibm"
|
||||
}
|
||||
}
|
||||
|
||||
if foundMaster {
|
||||
// eks does not have masters within the node list
|
||||
foundProviders.eks = false
|
||||
if stringProvider == "eks" {
|
||||
stringProvider = ""
|
||||
}
|
||||
}
|
||||
|
||||
return foundProviders, stringProvider
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -44,40 +144,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
return nil, errors.Wrap(err, "failed to unmarshal node list")
|
||||
}
|
||||
|
||||
foundProviders := providers{}
|
||||
foundMaster := false
|
||||
|
||||
for _, node := range nodes {
|
||||
for k, v := range node.ObjectMeta.Labels {
|
||||
if k == "microk8s.io/cluster" && v == "true" {
|
||||
foundProviders.microk8s = true
|
||||
} else if k == "kurl.sh/cluster" && v == "true" {
|
||||
foundProviders.kurl = true
|
||||
}
|
||||
if k == "node-role.kubernetes.io/master" {
|
||||
foundMaster = true
|
||||
}
|
||||
}
|
||||
|
||||
if node.Status.NodeInfo.OSImage == "Docker Desktop" {
|
||||
foundProviders.dockerDesktop = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "digitalocean:") {
|
||||
foundProviders.digitalOcean = true
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "aws:") {
|
||||
foundProviders.eks = true
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "gce:") {
|
||||
foundProviders.gke = true
|
||||
}
|
||||
}
|
||||
|
||||
if foundMaster {
|
||||
// eks does not have masters within the node list
|
||||
foundProviders.eks = false
|
||||
}
|
||||
foundProviders, _ := ParseNodesForProviders(nodes)
|
||||
|
||||
apiResourcesBytes, err := getCollectedFileContents("cluster-resources/resources.json")
|
||||
// if the file is not found, that is not a fatal error
|
||||
@@ -87,15 +154,16 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
if err := json.Unmarshal(apiResourcesBytes, &apiResources); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal api resource list")
|
||||
}
|
||||
for _, resource := range apiResources {
|
||||
if strings.Contains(resource.GroupVersion, "openshift") {
|
||||
foundProviders.openShift = true
|
||||
}
|
||||
}
|
||||
_ = 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",
|
||||
}
|
||||
|
||||
// ordering is important for passthrough
|
||||
@@ -109,7 +177,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")
|
||||
}
|
||||
@@ -130,7 +198,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")
|
||||
}
|
||||
@@ -151,7 +219,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")
|
||||
}
|
||||
@@ -167,10 +235,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
|
||||
@@ -188,6 +263,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
|
||||
}
|
||||
|
||||
@@ -207,6 +283,16 @@ func compareDistributionConditionalToActual(conditional string, actual providers
|
||||
isMatch = actual.openShift
|
||||
case kurl:
|
||||
isMatch = actual.kurl
|
||||
case aks:
|
||||
isMatch = actual.aks
|
||||
case ibm:
|
||||
isMatch = actual.ibm
|
||||
case minikube:
|
||||
isMatch = actual.minikube
|
||||
case rke2:
|
||||
isMatch = actual.rke2
|
||||
case k3s:
|
||||
isMatch = actual.k3s
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
@@ -235,6 +321,16 @@ func mustNormalizeDistributionName(raw string) Provider {
|
||||
return openShift
|
||||
case "kurl":
|
||||
return kurl
|
||||
case "aks":
|
||||
return aks
|
||||
case "ibm", "ibmcloud", "ibm cloud":
|
||||
return ibm
|
||||
case "minikube":
|
||||
return minikube
|
||||
case "rke2":
|
||||
return rke2
|
||||
case "k3s":
|
||||
return k3s
|
||||
}
|
||||
|
||||
return unknown
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_compareDistributionConditionalToActual(t *testing.T) {
|
||||
var unknownDistribution string
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
@@ -43,13 +44,12 @@ func Test_compareDistributionConditionalToActual(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_mustNormalizeDistributionName(t *testing.T) {
|
||||
|
||||
+101
-31
@@ -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"
|
||||
)
|
||||
@@ -20,6 +21,31 @@ type fileContentProvider struct {
|
||||
rootDir string
|
||||
}
|
||||
|
||||
// Analyze local will analyze a locally available (already downloaded) bundle
|
||||
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 {
|
||||
analyzeResult, err := Analyze(analyzer, fcp.getFileContents, fcp.getChildFileContents)
|
||||
if err != nil {
|
||||
logger.Printf("an analyzer failed to run: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if analyzeResult != nil {
|
||||
analyzeResults = append(analyzeResults, analyzeResult...)
|
||||
}
|
||||
}
|
||||
|
||||
return analyzeResults, nil
|
||||
}
|
||||
|
||||
func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResult, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot-k8s")
|
||||
if err != nil {
|
||||
@@ -31,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()
|
||||
@@ -52,22 +83,7 @@ func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResul
|
||||
analyzers = parsedAnalyzers
|
||||
}
|
||||
|
||||
fcp := fileContentProvider{rootDir: tmpDir}
|
||||
|
||||
analyzeResults := []*AnalyzeResult{}
|
||||
for _, analyzer := range analyzers {
|
||||
analyzeResult, err := Analyze(analyzer, fcp.getFileContents, fcp.getChildFileContents)
|
||||
if err != nil {
|
||||
logger.Printf("an analyzer failed to run: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if analyzeResult != nil {
|
||||
analyzeResults = append(analyzeResults, analyzeResult)
|
||||
}
|
||||
}
|
||||
|
||||
return analyzeResults, nil
|
||||
return AnalyzeLocal(rootDir, analyzers)
|
||||
}
|
||||
|
||||
func downloadTroubleshootBundle(bundleURL string, destDir string) error {
|
||||
@@ -77,7 +93,7 @@ func downloadTroubleshootBundle(bundleURL string, destDir string) error {
|
||||
return errors.Wrap(err, "failed to open support bundle")
|
||||
}
|
||||
defer f.Close()
|
||||
return extractTroubleshootBundle(f, destDir)
|
||||
return ExtractTroubleshootBundle(f, destDir)
|
||||
}
|
||||
|
||||
pwd, err := os.Getwd()
|
||||
@@ -85,7 +101,7 @@ func downloadTroubleshootBundle(bundleURL string, destDir string) error {
|
||||
return errors.Wrap(err, "failed to get workdir")
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "getter")
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create tmp dir")
|
||||
}
|
||||
@@ -107,10 +123,10 @@ func downloadTroubleshootBundle(bundleURL string, destDir string) error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return extractTroubleshootBundle(f, destDir)
|
||||
return ExtractTroubleshootBundle(f, destDir)
|
||||
}
|
||||
|
||||
func extractTroubleshootBundle(reader io.Reader, destDir string) error {
|
||||
func ExtractTroubleshootBundle(reader io.Reader, destDir string) error {
|
||||
gzReader, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create gzip reader")
|
||||
@@ -134,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")
|
||||
@@ -149,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
|
||||
@@ -186,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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package analyzer
|
||||
|
||||
import troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
|
||||
type HostAnalyzer interface {
|
||||
Title() string
|
||||
IsExcluded() (bool, error)
|
||||
Analyze(getFile func(string) ([]byte, error)) (*AnalyzeResult, error)
|
||||
}
|
||||
|
||||
func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, bool) {
|
||||
switch {
|
||||
case analyzer.CPU != nil:
|
||||
return &AnalyzeHostCPU{analyzer.CPU}, true
|
||||
case analyzer.Memory != nil:
|
||||
return &AnalyzeHostMemory{analyzer.Memory}, true
|
||||
case analyzer.TCPLoadBalancer != nil:
|
||||
return &AnalyzeHostTCPLoadBalancer{analyzer.TCPLoadBalancer}, true
|
||||
case analyzer.HTTPLoadBalancer != nil:
|
||||
return &AnalyzeHostHTTPLoadBalancer{analyzer.HTTPLoadBalancer}, true
|
||||
case analyzer.DiskUsage != nil:
|
||||
return &AnalyzeHostDiskUsage{analyzer.DiskUsage}, true
|
||||
case analyzer.TCPPortStatus != nil:
|
||||
return &AnalyzeHostTCPPortStatus{analyzer.TCPPortStatus}, true
|
||||
case analyzer.HTTP != nil:
|
||||
return &AnalyzeHostHTTP{analyzer.HTTP}, true
|
||||
case analyzer.Time != nil:
|
||||
return &AnalyzeHostTime{analyzer.Time}, true
|
||||
case analyzer.BlockDevices != nil:
|
||||
return &AnalyzeHostBlockDevices{analyzer.BlockDevices}, true
|
||||
case analyzer.TCPConnect != nil:
|
||||
return &AnalyzeHostTCPConnect{analyzer.TCPConnect}, true
|
||||
case analyzer.IPV4Interfaces != nil:
|
||||
return &AnalyzeHostIPV4Interfaces{analyzer.IPV4Interfaces}, true
|
||||
case analyzer.FilesystemPerformance != nil:
|
||||
return &AnalyzeHostFilesystemPerformance{analyzer.FilesystemPerformance}, true
|
||||
case analyzer.Certificate != nil:
|
||||
return &AnalyzeHostCertificate{analyzer.Certificate}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func hostAnalyzerTitleOrDefault(meta troubleshootv1beta2.AnalyzeMeta, defaultTitle string) string {
|
||||
if meta.CheckName != "" {
|
||||
return meta.CheckName
|
||||
}
|
||||
return defaultTitle
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type AnalyzeHostBlockDevices struct {
|
||||
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostBlockDevices) Title() string {
|
||||
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Block Devices")
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostBlockDevices) IsExcluded() (bool, error) {
|
||||
return isExcluded(a.hostAnalyzer.Exclude)
|
||||
}
|
||||
|
||||
func (a *AnalyzeHostBlockDevices) Analyze(getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
hostAnalyzer := a.hostAnalyzer
|
||||
|
||||
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{}
|
||||
|
||||
result.Title = a.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
|
||||
}
|
||||
@@ -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}).Analyze(getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user