mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
079765b558 | ||
|
|
45ccd7be5b | ||
|
|
2bc9938a2a | ||
|
|
5050f46b1e | ||
|
|
222a93162d |
@@ -18,8 +18,8 @@ jobs:
|
||||
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "::set-env name=GOPATH::$(go env GOPATH)"
|
||||
echo "::add-path::$(go env GOPATH)/bin"
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
@@ -35,8 +35,8 @@ jobs:
|
||||
go-version: '1.14'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "::set-env name=GOPATH::$(go env GOPATH)"
|
||||
echo "::add-path::$(go env GOPATH)/bin"
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make preflight
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
with:
|
||||
name: preflight
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.2.0
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- run: chmod +x bin/preflight
|
||||
- run: ./bin/preflight --interactive=false --format=json https://preflight.replicated.com
|
||||
|
||||
@@ -67,8 +67,8 @@ jobs:
|
||||
go-version: '1.14'
|
||||
- name: setup env
|
||||
run: |
|
||||
echo "::set-env name=GOPATH::$(go env GOPATH)"
|
||||
echo "::add-path::$(go env GOPATH)/bin"
|
||||
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
|
||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- uses: actions/checkout@master
|
||||
- run: make support-bundle
|
||||
@@ -87,10 +87,10 @@ jobs:
|
||||
with:
|
||||
name: support-bundle
|
||||
path: bin/
|
||||
- uses: engineerd/setup-kind@v0.2.0
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- run: chmod +x bin/support-bundle
|
||||
- run: ./bin/support-bundle ./examples/troubleshoot/sample-collectors.yaml
|
||||
- run: ./bin/support-bundle ./examples/troubleshoot/sample-supportbundle.yaml
|
||||
- run: ./bin/support-bundle ./examples/support-bundle/sample-collectors.yaml
|
||||
- run: ./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
|
||||
- run: ./bin/support-bundle https://kots.io
|
||||
|
||||
goreleaser:
|
||||
|
||||
@@ -26,7 +26,4 @@ vendor
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# scope test logs
|
||||
scope-go-*.log
|
||||
|
||||
dist
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
IMG ?= controller:latest
|
||||
export GO111MODULE=on
|
||||
export GOPROXY=https://proxy.golang.org
|
||||
export SCOPE_LOG_ROOT_PATH=/dev/null
|
||||
|
||||
SHELL := /bin/bash -o pipefail
|
||||
VERSION_PACKAGE = github.com/replicatedhq/troubleshoot/pkg/version
|
||||
@@ -29,6 +28,7 @@ endif
|
||||
|
||||
define LDFLAGS
|
||||
-ldflags "\
|
||||
-s -w \
|
||||
-X ${VERSION_PACKAGE}.version=${VERSION} \
|
||||
-X ${VERSION_PACKAGE}.gitSHA=${GIT_SHA} \
|
||||
-X ${VERSION_PACKAGE}.buildTime=${DATE} \
|
||||
@@ -47,15 +47,15 @@ test: generate fmt vet
|
||||
|
||||
.PHONY: support-bundle
|
||||
support-bundle: generate fmt vet
|
||||
go build ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
go build -tags netgo ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
|
||||
|
||||
.PHONY: preflight
|
||||
preflight: generate fmt vet
|
||||
go build ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
go build -tags netgo ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
|
||||
|
||||
.PHONY: analyze
|
||||
analyze: generate fmt vet
|
||||
go build ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
go build -tags netgo ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@@ -74,11 +74,13 @@ generate: controller-gen client-gen
|
||||
--clientset-name troubleshootclientset \
|
||||
--input-base github.com/replicatedhq/troubleshoot/pkg/apis \
|
||||
--input troubleshoot/v1beta1 \
|
||||
--input troubleshoot/v1beta2 \
|
||||
-h ./hack/boilerplate.go.txt
|
||||
|
||||
.PHONY: openapischema
|
||||
openapischema: controller-gen
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta1
|
||||
controller-gen crd +output:dir=./config/crds paths=./pkg/apis/troubleshoot/v1beta2
|
||||
|
||||
.PHONY: schemas
|
||||
schemas: fmt vet openapischema
|
||||
@@ -88,7 +90,7 @@ schemas: fmt vet openapischema
|
||||
.PHONY: contoller-gen
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.8
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0
|
||||
CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen
|
||||
else
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
@@ -116,7 +118,7 @@ snapshot-release:
|
||||
|
||||
.PHONY: local-release
|
||||
local-release:
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.local.yml
|
||||
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.yaml
|
||||
docker tag replicated/troubleshoot:alpha localhost:32000/troubleshoot:alpha
|
||||
docker tag replicated/preflight:alpha localhost:32000/preflight:alpha
|
||||
docker push localhost:32000/troubleshoot:alpha
|
||||
@@ -128,8 +130,8 @@ run-preflight: preflight
|
||||
|
||||
.PHONY: run-troubleshoot
|
||||
run-troubleshoot: support-bundle
|
||||
./bin/support-bundle ./examples/troubleshoot/sample-supportbundle.yaml
|
||||
./bin/support-bundle ./examples/support-bundle/sample-supportbundle.yaml
|
||||
|
||||
.PHONY: run-analyze
|
||||
run-analyze: analyze
|
||||
./bin/analyze --analyzers ./examples/troubleshoot/sample-analyzers.yaml ./support-bundle.tar.gz
|
||||
./bin/analyze --analyzers ./examples/support-bundle/sample-analyzers.yaml ./support-bundle.tar.gz
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+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,18 +5,18 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/gizak/termui/v3/widgets"
|
||||
"github.com/pkg/errors"
|
||||
ui "github.com/replicatedhq/termui/v3"
|
||||
"github.com/replicatedhq/termui/v3/widgets"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedResult = 0
|
||||
table = widgets.NewTable()
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
@@ -64,7 +64,9 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
case "<Up>":
|
||||
@@ -72,7 +74,9 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
}
|
||||
@@ -95,7 +99,7 @@ func drawHeader(preflightName string) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = fmt.Sprintf("%s Preflight Checks", appName(preflightName))
|
||||
title.Text = fmt.Sprintf("%s Preflight Checks", util.AppName(preflightName))
|
||||
title.TextStyle.Fg = ui.ColorWhite
|
||||
title.TextStyle.Bg = ui.ColorClear
|
||||
title.TextStyle.Modifier = ui.ModifierBold
|
||||
@@ -127,7 +131,6 @@ func drawFooter() {
|
||||
func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
table := widgets.NewTable()
|
||||
table.SetRect(0, 3, termWidth/2, termHeight-6)
|
||||
table.FillRow = true
|
||||
table.Border = true
|
||||
@@ -221,7 +224,7 @@ func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (
|
||||
os.Remove(filename)
|
||||
}
|
||||
|
||||
results := fmt.Sprintf("%s Preflight Checks\n\n", appName(preflightName))
|
||||
results := fmt.Sprintf("%s Preflight Checks\n\n", util.AppName(preflightName))
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
result := ""
|
||||
|
||||
@@ -270,19 +273,3 @@ func showSaved(filename string) {
|
||||
|
||||
isShowingSaved = true
|
||||
}
|
||||
|
||||
func appName(preflightName string) string {
|
||||
words := strings.Split(strings.Title(strings.Replace(preflightName, "-", " ", -1)), " ")
|
||||
casedWords := []string{}
|
||||
for i, word := range words {
|
||||
if strings.ToLower(word) == "ai" {
|
||||
casedWords = append(casedWords, "AI")
|
||||
} else if strings.ToLower(word) == "io" && i > 0 {
|
||||
casedWords[i-1] += ".io"
|
||||
} else {
|
||||
casedWords = append(casedWords, word)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(casedWords, " ")
|
||||
}
|
||||
|
||||
@@ -4,13 +4,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
var (
|
||||
KubernetesConfigFlags *genericclioptions.ConfigFlags
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
@@ -39,11 +35,12 @@ that a cluster meets the requirements to run an application.`,
|
||||
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always run preflight checks even if some require permissions that preflight does not have")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
KubernetesConfigFlags.AddFlags(cmd.Flags())
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+146
-38
@@ -5,17 +5,22 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
@@ -23,15 +28,28 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
|
||||
preflightContent := ""
|
||||
var preflightContent []byte
|
||||
var err error
|
||||
if _, err = os.Stat(arg); err == nil {
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return errors.Errorf("path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "preflight-spec")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
preflightContent = spec
|
||||
} else if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = string(b)
|
||||
preflightContent = b
|
||||
} else {
|
||||
if !util.IsURL(arg) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
@@ -41,7 +59,7 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta1")
|
||||
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -53,7 +71,12 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = string(body)
|
||||
preflightContent = body
|
||||
}
|
||||
|
||||
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
@@ -63,15 +86,16 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
return errors.Wrapf(err, "failed to parse %s", arg)
|
||||
}
|
||||
|
||||
preflightSpec := obj.(*troubleshootv1beta1.Preflight)
|
||||
var collectResults preflight.CollectResult
|
||||
preflightSpecName := ""
|
||||
finishedCh := make(chan bool, 1)
|
||||
progressCh := make(chan interface{}, 0) // non-zero buffer will result in missed messages
|
||||
|
||||
s := spin.New()
|
||||
finishedCh := make(chan bool, 1)
|
||||
progressChan := make(chan interface{}, 0) // non-zero buffer will result in missed messages
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-progressChan:
|
||||
case msg, ok := <-progressCh:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -91,40 +115,40 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
close(finishedCh)
|
||||
close(progressCh)
|
||||
}()
|
||||
|
||||
restConfig, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: v.GetString("namespace"),
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressChan,
|
||||
KubernetesRestConfig: restConfig,
|
||||
}
|
||||
|
||||
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
|
||||
if err != nil {
|
||||
if !collectResults.IsRBACAllowed {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, collectResults.Collectors)
|
||||
if err != nil {
|
||||
progressChan <- err
|
||||
}
|
||||
}
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
r, err := collectInCluster(preflightSpec, finishedCh, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect in cluster")
|
||||
}
|
||||
return err
|
||||
collectResults = *r
|
||||
preflightSpecName = preflightSpec.Name
|
||||
} else if hostPreflightSpec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
|
||||
r, err := collectHost(hostPreflightSpec, finishedCh, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
collectResults = *r
|
||||
preflightSpecName = hostPreflightSpec.Name
|
||||
}
|
||||
|
||||
if collectResults == nil {
|
||||
return errors.New("no results")
|
||||
}
|
||||
|
||||
analyzeResults := collectResults.Analyze()
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
|
||||
if err != nil {
|
||||
progressChan <- err
|
||||
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +158,92 @@ func runPreflights(v *viper.Viper, arg string) error {
|
||||
if len(analyzeResults) == 0 {
|
||||
return errors.New("no data has been collected")
|
||||
}
|
||||
return showInteractiveResults(preflightSpec.Name, analyzeResults)
|
||||
return showInteractiveResults(preflightSpecName, analyzeResults)
|
||||
}
|
||||
|
||||
return showStdoutResults(v.GetString("format"), preflightSpec.Name, analyzeResults)
|
||||
return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults)
|
||||
}
|
||||
|
||||
func collectInCluster(preflightSpec *troubleshootv1beta2.Preflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: v.GetString("namespace"),
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressCh,
|
||||
KubernetesRestConfig: restConfig,
|
||||
}
|
||||
|
||||
if v.GetString("since") != "" || v.GetString("since-time") != "" {
|
||||
err := parseTimeFlags(v, progressCh, preflightSpec.Spec.Collectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
|
||||
if err != nil {
|
||||
if !collectResults.IsRBACAllowed() {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
clusterCollectResults := collectResults.(preflight.ClusterCollectResult)
|
||||
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, clusterCollectResults.Collectors)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func collectHost(hostPreflightSpec *troubleshootv1beta2.HostPreflight, finishedCh chan bool, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
collectOpts := preflight.CollectOpts{
|
||||
ProgressChan: progressCh,
|
||||
}
|
||||
|
||||
collectResults, err := preflight.CollectHost(collectOpts, hostPreflightSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper, progressChan chan interface{}, collectors []*troubleshootv1beta2.Collect) error {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
for _, collector := range collectors {
|
||||
if collector.Logs != nil {
|
||||
if collector.Logs.Limits == nil {
|
||||
collector.Logs.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
collector.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
func createTroubleshootK8sClient(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
|
||||
config, err := configFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create troubleshoot client")
|
||||
}
|
||||
|
||||
return troubleshootClient, nil
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
+52
-20
@@ -62,28 +62,60 @@ func generateSchemas(v *viper.Viper) error {
|
||||
return errors.Wrap(err, "failed to get workdir")
|
||||
}
|
||||
|
||||
preflightContents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", "troubleshoot.replicated.com_preflights.yaml"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read preflight crd")
|
||||
}
|
||||
if err := generateSchemaFromCRD(preflightContents, filepath.Join(workdir, v.GetString("output-dir"), "preflight-troubleshoot-v1beta1.json")); err != nil {
|
||||
return errors.Wrap(err, "failed to write preflight schema")
|
||||
files := []struct {
|
||||
inFilename string
|
||||
outFilename string
|
||||
}{
|
||||
{
|
||||
"troubleshoot.replicated.com_preflights.yaml",
|
||||
"preflight-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_analyzers.yaml",
|
||||
"analyzer-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_collectors.yaml",
|
||||
"collector-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_redactors.yaml",
|
||||
"redactor-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.replicated.com_supportbundles.yaml",
|
||||
"supportbundle-troubleshoot-v1beta1.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_analyzers.yaml",
|
||||
"analyzer-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_collectors.yaml",
|
||||
"collector-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_preflights.yaml",
|
||||
"preflight-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_redactors.yaml",
|
||||
"redactor-troubleshoot-v1beta2.json",
|
||||
},
|
||||
{
|
||||
"troubleshoot.sh_supportbundles.yaml",
|
||||
"supportbundle-troubleshoot-v1beta2.json",
|
||||
},
|
||||
}
|
||||
|
||||
analyzersContents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", "troubleshoot.replicated.com_analyzers.yaml"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read analyzers crd")
|
||||
}
|
||||
if err := generateSchemaFromCRD(analyzersContents, filepath.Join(workdir, v.GetString("output-dir"), "analyzer-troubleshoot-v1beta1.json")); err != nil {
|
||||
return errors.Wrap(err, "failed to write analyzers schema")
|
||||
}
|
||||
|
||||
collectorsContents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", "troubleshoot.replicated.com_collectors.yaml"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read collectors crd")
|
||||
}
|
||||
if err := generateSchemaFromCRD(collectorsContents, filepath.Join(workdir, v.GetString("output-dir"), "collector-troubleshoot-v1beta1.json")); err != nil {
|
||||
return errors.Wrap(err, "failed to write collectors schema")
|
||||
for _, file := range files {
|
||||
contents, err := ioutil.ReadFile(filepath.Join(workdir, "config", "crds", file.inFilename))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read crd from %s", file.inFilename)
|
||||
}
|
||||
if err := generateSchemaFromCRD(contents, filepath.Join(workdir, v.GetString("output-dir"), file.outFilename)); err != nil {
|
||||
return errors.Wrapf(err, "failed to write crd schema to %s", file.outFilename)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -7,24 +7,25 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/gizak/termui/v3/widgets"
|
||||
"github.com/pkg/errors"
|
||||
ui "github.com/replicatedhq/termui/v3"
|
||||
"github.com/replicatedhq/termui/v3/widgets"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedResult = 0
|
||||
table = widgets.NewTable()
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
func showInteractiveResults(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
if err := ui.Init(); err != nil {
|
||||
return errors.Wrap(err, "failed to create terminal ui")
|
||||
}
|
||||
defer ui.Close()
|
||||
drawUI(analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
|
||||
uiEvents := ui.PollEvents()
|
||||
for {
|
||||
@@ -37,7 +38,7 @@ func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error
|
||||
if isShowingSaved == true {
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@@ -51,79 +52,63 @@ func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error
|
||||
time.Sleep(time.Second * 5)
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}()
|
||||
}
|
||||
case "<Resize>":
|
||||
ui.Clear()
|
||||
drawUI(analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Down>":
|
||||
if selectedResult < len(analyzeResults)-1 {
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
case "<Up>":
|
||||
if selectedResult > 0 {
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(analyzeResults)
|
||||
drawUI(supportBundleName, analyzeResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawUI(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
func drawUI(supportBundleName string, analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawGrid(analyzeResults)
|
||||
drawHeader(supportBundleName)
|
||||
drawFooter()
|
||||
}
|
||||
|
||||
func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawAnalyzersTable(analyzeResults)
|
||||
drawDetails(analyzeResults[selectedResult])
|
||||
}
|
||||
|
||||
func drawHeader(supportBundleName string) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
tileWidth := 40
|
||||
tileHeight := 10
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = fmt.Sprintf("%s Support Bundle Analysis", util.AppName(supportBundleName))
|
||||
title.TextStyle.Fg = ui.ColorWhite
|
||||
title.TextStyle.Bg = ui.ColorClear
|
||||
title.TextStyle.Modifier = ui.ModifierBold
|
||||
title.Border = false
|
||||
|
||||
columnCount := termWidth / tileWidth
|
||||
left := termWidth/2 - 2*len(title.Text)/3
|
||||
right := termWidth/2 + (termWidth/2 - left)
|
||||
|
||||
row := 0
|
||||
col := 0
|
||||
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
// draw this file
|
||||
|
||||
tile := widgets.NewParagraph()
|
||||
tile.Title = analyzeResult.Title
|
||||
tile.Text = analyzeResult.Message
|
||||
tile.PaddingLeft = 1
|
||||
tile.PaddingBottom = 1
|
||||
tile.PaddingRight = 1
|
||||
tile.PaddingTop = 1
|
||||
|
||||
tile.SetRect(col*tileWidth, row*tileHeight, col*tileWidth+tileWidth, row*tileHeight+tileHeight)
|
||||
|
||||
if analyzeResult.IsFail {
|
||||
tile.BorderStyle.Fg = ui.ColorRed
|
||||
} else if analyzeResult.IsWarn {
|
||||
tile.BorderStyle.Fg = ui.ColorYellow
|
||||
} else {
|
||||
tile.BorderStyle.Fg = ui.ColorGreen
|
||||
}
|
||||
|
||||
ui.Render(tile)
|
||||
|
||||
col++
|
||||
|
||||
if col >= columnCount {
|
||||
col = 0
|
||||
row++
|
||||
}
|
||||
}
|
||||
title.SetRect(left, 0, right, 1)
|
||||
ui.Render(title)
|
||||
}
|
||||
|
||||
func drawFooter() {
|
||||
@@ -142,11 +127,100 @@ func drawFooter() {
|
||||
ui.Render(instructions)
|
||||
}
|
||||
|
||||
func drawAnalyzersTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
table.SetRect(0, 3, termWidth/2, termHeight-6)
|
||||
table.FillRow = true
|
||||
table.Border = true
|
||||
table.Rows = [][]string{}
|
||||
table.ColumnWidths = []int{termWidth}
|
||||
|
||||
for i, analyzeResult := range analyzeResults {
|
||||
title := analyzeResult.Title
|
||||
if analyzeResult.IsPass {
|
||||
title = fmt.Sprintf("✔ %s", title)
|
||||
} else if analyzeResult.IsWarn {
|
||||
title = fmt.Sprintf("⚠️ %s", title)
|
||||
} else if analyzeResult.IsFail {
|
||||
title = fmt.Sprintf("✘ %s", title)
|
||||
}
|
||||
table.Rows = append(table.Rows, []string{
|
||||
title,
|
||||
})
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear)
|
||||
}
|
||||
} else if analyzeResult.IsWarn {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear)
|
||||
}
|
||||
} else if analyzeResult.IsFail {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.Render(table)
|
||||
}
|
||||
|
||||
func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
currentTop := 4
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = analysisResult.Title
|
||||
title.Border = false
|
||||
if analysisResult.IsPass {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierBold)
|
||||
} else if analysisResult.IsWarn {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierBold)
|
||||
} else if analysisResult.IsFail {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierBold)
|
||||
}
|
||||
height := estimateNumberOfLines(title.Text, termWidth/2)
|
||||
title.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(title)
|
||||
currentTop = currentTop + height + 1
|
||||
|
||||
message := widgets.NewParagraph()
|
||||
message.Text = analysisResult.Message
|
||||
message.Border = false
|
||||
height = estimateNumberOfLines(message.Text, termWidth/2) + 2
|
||||
message.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(message)
|
||||
currentTop = currentTop + height + 1
|
||||
|
||||
if analysisResult.URI != "" {
|
||||
uri := widgets.NewParagraph()
|
||||
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
|
||||
uri.Border = false
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2)
|
||||
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(uri)
|
||||
currentTop = currentTop + height + 1
|
||||
}
|
||||
}
|
||||
|
||||
func estimateNumberOfLines(text string, width int) int {
|
||||
lines := len(text)/width + 1
|
||||
return lines
|
||||
}
|
||||
|
||||
func showSaved(filename string) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
savedMessage := widgets.NewParagraph()
|
||||
savedMessage.Text = fmt.Sprintf("Preflight results saved to\n\n%s", filename)
|
||||
savedMessage.Text = fmt.Sprintf("Support Bundle analysis results saved to\n\n%s", filename)
|
||||
savedMessage.WrapText = true
|
||||
savedMessage.Border = true
|
||||
|
||||
|
||||
@@ -5,24 +5,20 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
var (
|
||||
KubernetesConfigFlags *genericclioptions.ConfigFlags
|
||||
)
|
||||
|
||||
func RootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "troubleshoot [url]",
|
||||
Use: "support-bundle [url]",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Generate and manage support bundles",
|
||||
Short: "Generate a support bundle",
|
||||
Long: `A support bundle is an archive of files, output, metrics and state
|
||||
from a server that can be used to assist when troubleshooting a server.`,
|
||||
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
|
||||
SilenceUsage: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
@@ -42,18 +38,19 @@ from a server that can be used to assist when troubleshooting a server.`,
|
||||
|
||||
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
|
||||
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always run troubleshoot collectors even if some require permissions that troubleshoot does not have")
|
||||
cmd.Flags().Bool("collect-without-permissions", false, "always generate a support bundle, even if it some require additional permissions")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
|
||||
// hidden in favor of the `insecure-skip-tls-verify` flag
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "don't verify TLS certs when retrieving spec and reporting results")
|
||||
cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results")
|
||||
cmd.Flags().MarkHidden("allow-insecure-connections")
|
||||
|
||||
viper.BindPFlags(cmd.Flags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||
KubernetesConfigFlags.AddFlags(cmd.Flags())
|
||||
k8sutil.AddFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -69,7 +66,7 @@ func initConfig() {
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func ensureCollectorInList(list []*troubleshootv1beta1.Collect, collector troubleshootv1beta1.Collect) []*troubleshootv1beta1.Collect {
|
||||
func ensureCollectorInList(list []*troubleshootv1beta2.Collect, collector troubleshootv1beta2.Collect) []*troubleshootv1beta2.Collect {
|
||||
for _, inList := range list {
|
||||
if collector.ClusterResources != nil && inList.ClusterResources != nil {
|
||||
return list
|
||||
|
||||
+324
-105
@@ -1,16 +1,20 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -18,16 +22,18 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mholt/archiver"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/redact"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -54,7 +60,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
return errors.Wrap(err, "failed to load collector spec")
|
||||
}
|
||||
|
||||
multidocs := strings.Split(string(collectorContent), "---")
|
||||
multidocs := strings.Split(string(collectorContent), "\n---\n")
|
||||
|
||||
// we suppory both raw collector kinds and supportbundle kinds here
|
||||
supportBundleSpec, err := parseSupportBundleFromDoc([]byte(multidocs[0]))
|
||||
@@ -65,31 +71,42 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
additionalRedactors := &troubleshootv1beta1.Redactor{}
|
||||
additionalRedactors := &troubleshootv1beta2.Redactor{}
|
||||
for idx, redactor := range v.GetStringSlice("redactors") {
|
||||
redactorContent, err := loadSpec(v, redactor)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to load redactor spec #%d", idx)
|
||||
}
|
||||
redactorContent, err = docrewrite.ConvertToV1Beta2(redactorContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode([]byte(redactorContent), nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse redactors %s", redactor)
|
||||
}
|
||||
loopRedactors, ok := obj.(*troubleshootv1beta1.Redactor)
|
||||
loopRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
return fmt.Errorf("%s is not a troubleshootv1beta1 redactor type", redactor)
|
||||
return fmt.Errorf("%s is not a troubleshootv1beta2 redactor type", redactor)
|
||||
}
|
||||
if loopRedactors != nil {
|
||||
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, loopRedactors.Spec.Redactors...)
|
||||
}
|
||||
}
|
||||
|
||||
for i, additionalDoc := range multidocs[1:] {
|
||||
obj, _, err := decode([]byte(additionalDoc), nil, nil)
|
||||
for i, additionalDoc := range multidocs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
obj, _, err := decode(additionalDoc, nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse additional doc %d", i)
|
||||
}
|
||||
multidocRedactors, ok := obj.(*troubleshootv1beta1.Redactor)
|
||||
multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -189,7 +206,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
|
||||
close(finishedCh) // this removes the spinner
|
||||
isFinishedChClosed = true
|
||||
|
||||
if err := showInteractiveResults(analyzeResults); err != nil {
|
||||
if err := showInteractiveResults(supportBundleSpec.Name, analyzeResults); err != nil {
|
||||
interactive = false
|
||||
}
|
||||
}
|
||||
@@ -232,6 +249,21 @@ the %s Admin Console to begin analysis.`
|
||||
|
||||
func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return nil, errors.Errorf("path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "support-bundle-spec")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
@@ -243,6 +275,14 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
}
|
||||
|
||||
spec, err := loadSpecFromURL(v, arg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get spec from URL")
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func loadSpecFromURL(v *viper.Viper, arg string) ([]byte, error) {
|
||||
for {
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
@@ -252,10 +292,7 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
req.Header.Set("Bundle-Upload-Host", fmt.Sprintf("%s://%s", req.URL.Scheme, req.URL.Host))
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "x509") && httpClient == http.DefaultClient && canTryInsecure(v) {
|
||||
httpClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrap(err, "execute request")
|
||||
@@ -271,7 +308,12 @@ func loadSpec(v *viper.Viper, arg string) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle, error) {
|
||||
func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta2.SupportBundle, error) {
|
||||
doc, err := docrewrite.ConvertToV1Beta2(doc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
@@ -280,17 +322,17 @@ func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle,
|
||||
return nil, errors.Wrap(err, "failed to parse document")
|
||||
}
|
||||
|
||||
collector, ok := obj.(*troubleshootv1beta1.Collector)
|
||||
collector, ok := obj.(*troubleshootv1beta2.Collector)
|
||||
if ok {
|
||||
supportBundle := troubleshootv1beta1.SupportBundle{
|
||||
supportBundle := troubleshootv1beta2.SupportBundle{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "troubleshoot.replicated.com/v1beta1",
|
||||
APIVersion: "troubleshoot.sh/v1beta2",
|
||||
Kind: "SupportBundle",
|
||||
},
|
||||
ObjectMeta: collector.ObjectMeta,
|
||||
Spec: troubleshootv1beta1.SupportBundleSpec{
|
||||
Spec: troubleshootv1beta2.SupportBundleSpec{
|
||||
Collectors: collector.Spec.Collectors,
|
||||
Analyzers: []*troubleshootv1beta1.Analyze{},
|
||||
Analyzers: []*troubleshootv1beta2.Analyze{},
|
||||
AfterCollection: collector.Spec.AfterCollection,
|
||||
},
|
||||
}
|
||||
@@ -298,7 +340,7 @@ func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle,
|
||||
return &supportBundle, nil
|
||||
}
|
||||
|
||||
supportBundle, ok := obj.(*troubleshootv1beta1.SupportBundle)
|
||||
supportBundle, ok := obj.(*troubleshootv1beta2.SupportBundle)
|
||||
if ok {
|
||||
return supportBundle, nil
|
||||
}
|
||||
@@ -306,7 +348,17 @@ func parseSupportBundleFromDoc(doc []byte) (*troubleshootv1beta1.SupportBundle,
|
||||
return nil, errors.New("spec was not parseable as a troubleshoot kind")
|
||||
}
|
||||
|
||||
func canTryInsecure(v *viper.Viper) bool {
|
||||
func shouldRetryRequest(err error) bool {
|
||||
if strings.Contains(err.Error(), "x509") && httpClient == http.DefaultClient && canTryInsecure() {
|
||||
httpClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func canTryInsecure() bool {
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
return false
|
||||
}
|
||||
@@ -323,23 +375,33 @@ func canTryInsecure(v *viper.Viper) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, additionalRedactors *troubleshootv1beta1.Redactor, progressChan chan interface{}) (string, error) {
|
||||
bundlePath, err := ioutil.TempDir("", "troubleshoot")
|
||||
func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta2.Collect, additionalRedactors *troubleshootv1beta2.Redactor, progressChan chan interface{}) (string, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "troubleshoot")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(bundlePath)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
filename, err := findFileName("support-bundle-"+time.Now().Format("2006-01-02T15_04_05"), "tar.gz")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "find file name")
|
||||
}
|
||||
|
||||
bundlePath := filepath.Join(tmpDir, strings.TrimSuffix(filename, ".tar.gz"))
|
||||
if err := os.MkdirAll(bundlePath, 0777); err != nil {
|
||||
return "", errors.Wrap(err, "create bundle dir")
|
||||
}
|
||||
|
||||
if err = writeVersionFile(bundlePath); err != nil {
|
||||
return "", errors.Wrap(err, "write version file")
|
||||
}
|
||||
|
||||
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
|
||||
collectSpecs := make([]*troubleshootv1beta2.Collect, 0, 0)
|
||||
collectSpecs = append(collectSpecs, collectors...)
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}})
|
||||
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}})
|
||||
|
||||
config, err := KubernetesConfigFlags.ToRESTConfig()
|
||||
config, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
@@ -351,6 +413,7 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
|
||||
Collect: desiredCollector,
|
||||
ClientConfig: config,
|
||||
Namespace: v.GetString("namespace"),
|
||||
PathPrefix: filepath.Base(bundlePath),
|
||||
}
|
||||
cleanedCollectors = append(cleanedCollectors, &collector)
|
||||
}
|
||||
@@ -371,10 +434,16 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
|
||||
return "", errors.New("insufficient permissions to run all collectors")
|
||||
}
|
||||
|
||||
globalRedactors := []*troubleshootv1beta1.Redact{}
|
||||
globalRedactors := []*troubleshootv1beta2.Redact{}
|
||||
if additionalRedactors != nil {
|
||||
globalRedactors = additionalRedactors.Spec.Redactors
|
||||
}
|
||||
if v.GetString("since-time") != "" || v.GetString("since") != "" {
|
||||
err := parseTimeFlags(v, progressChan, &cleanedCollectors)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Run preflights collectors synchronously
|
||||
for _, collector := range cleanedCollectors {
|
||||
@@ -395,7 +464,8 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
err = saveCollectorOutput(result, bundlePath)
|
||||
// results already contain the bundle dir name in their paths
|
||||
err = saveCollectorOutput(result, filepath.Dir(bundlePath), collector)
|
||||
if err != nil {
|
||||
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
|
||||
continue
|
||||
@@ -403,11 +473,6 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
|
||||
}
|
||||
}
|
||||
|
||||
filename, err := findFileName("support-bundle", "tar.gz")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "find file name")
|
||||
}
|
||||
|
||||
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
|
||||
return "", errors.Wrap(err, "create bundle file")
|
||||
}
|
||||
@@ -415,8 +480,15 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func saveCollectorOutput(output map[string][]byte, bundlePath string) error {
|
||||
func saveCollectorOutput(output map[string][]byte, bundlePath string, c *collect.Collector) error {
|
||||
for filename, maybeContents := range output {
|
||||
if c.Collect.Copy != nil {
|
||||
err := untarAndSave(maybeContents, filepath.Join(bundlePath, filepath.Dir(filename)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "extract copied files")
|
||||
}
|
||||
continue
|
||||
}
|
||||
fileDir, fileName := filepath.Split(filename)
|
||||
outPath := filepath.Join(bundlePath, fileDir)
|
||||
|
||||
@@ -432,39 +504,99 @@ func saveCollectorOutput(output map[string][]byte, bundlePath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
|
||||
func untarAndSave(tarFile []byte, bundlePath string) error {
|
||||
keys := make([]string, 0)
|
||||
dirs := make(map[string]*tar.Header)
|
||||
files := make(map[string][]byte)
|
||||
fileHeaders := make(map[string]*tar.Header)
|
||||
tarReader := tar.NewReader(bytes.NewBuffer(tarFile))
|
||||
//Extract and separate tar contentes in file and folders, keeping header info from each one.
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
dirs[header.Name] = header
|
||||
case tar.TypeReg:
|
||||
file := new(bytes.Buffer)
|
||||
_, err = io.Copy(file, tarReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files[header.Name] = file.Bytes()
|
||||
fileHeaders[header.Name] = header
|
||||
default:
|
||||
return fmt.Errorf("Tar file entry %s contained unsupported file type %v", header.Name, header.FileInfo().Mode())
|
||||
}
|
||||
}
|
||||
//Create directories from base path: <namespace>/<pod name>/containerPath
|
||||
if err := os.MkdirAll(filepath.Join(bundlePath), 0777); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
//Order folders stored in variable keys to start always by parent folder. That way folder info is preserved.
|
||||
for k := range dirs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
//Orderly create folders.
|
||||
for _, k := range keys {
|
||||
if err := os.Mkdir(filepath.Join(bundlePath, k), dirs[k].FileInfo().Mode().Perm()); err != nil {
|
||||
return errors.Wrap(err, "create output file")
|
||||
}
|
||||
}
|
||||
//Populate folders with respective files and its permissions stored in the header.
|
||||
for k, v := range files {
|
||||
if err := ioutil.WriteFile(filepath.Join(bundlePath, k), v, fileHeaders[k].FileInfo().Mode().Perm()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func uploadSupportBundle(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
|
||||
contentType := getExpectedContentType(r.URI)
|
||||
if contentType != "" && contentType != "application/tar+gzip" {
|
||||
return fmt.Errorf("cannot upload content type %s", contentType)
|
||||
}
|
||||
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
defer f.Close()
|
||||
for {
|
||||
f, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileStat, err := f.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "stat file")
|
||||
}
|
||||
fileStat, err := f.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "stat file")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.Method, r.URI, f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
req.ContentLength = fileStat.Size()
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
req, err := http.NewRequest(r.Method, r.URI, f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
req.ContentLength = fileStat.Size()
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// send redaction report
|
||||
@@ -478,19 +610,26 @@ func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath strin
|
||||
return errors.Wrap(err, "get redaction report")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create redaction report request")
|
||||
}
|
||||
req.ContentLength = int64(len(redactBytes))
|
||||
for {
|
||||
req, err := http.NewRequest("PUT", r.RedactURI, bytes.NewReader(redactBytes))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create redaction report request")
|
||||
}
|
||||
req.ContentLength = int64(len(redactBytes))
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute redaction request")
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute redaction request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected redaction status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,54 +644,134 @@ func getExpectedContentType(uploadURL string) string {
|
||||
return parsedURL.Query().Get("Content-Type")
|
||||
}
|
||||
|
||||
func callbackSupportBundleAPI(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
|
||||
req, err := http.NewRequest(r.Method, r.URI, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
func callbackSupportBundleAPI(r *troubleshootv1beta2.ResultRequest, archivePath string) error {
|
||||
for {
|
||||
req, err := http.NewRequest(r.Method, r.URI, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create request")
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if shouldRetryRequest(err) {
|
||||
continue
|
||||
}
|
||||
return errors.Wrap(err, "execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tarSupportBundleDir(inputDir, outputFilename string) error {
|
||||
tarGz := archiver.TarGz{
|
||||
Tar: &archiver.Tar{
|
||||
ImplicitTopLevelFolder: false,
|
||||
},
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
filepath.Join(inputDir, VersionFilename), // version file should be first in tar archive for quick extraction
|
||||
}
|
||||
|
||||
topLevelFiles, err := ioutil.ReadDir(inputDir)
|
||||
fileWriter, err := os.Create(outputFilename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "list bundle directory contents")
|
||||
}
|
||||
for _, f := range topLevelFiles {
|
||||
if f.Name() == VersionFilename {
|
||||
continue
|
||||
}
|
||||
paths = append(paths, filepath.Join(inputDir, f.Name()))
|
||||
return errors.Wrap(err, "failed to create output file")
|
||||
}
|
||||
defer fileWriter.Close()
|
||||
|
||||
if err := tarGz.Archive(paths, outputFilename); err != nil {
|
||||
return errors.Wrap(err, "create archive")
|
||||
gzipWriter := gzip.NewWriter(fileWriter)
|
||||
defer gzipWriter.Close()
|
||||
|
||||
tarWriter := tar.NewWriter(gzipWriter)
|
||||
defer tarWriter.Close()
|
||||
|
||||
err = filepath.Walk(inputDir, func(filename string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileMode := info.Mode()
|
||||
if !fileMode.IsRegular() { // support bundle can have only files
|
||||
return nil
|
||||
}
|
||||
|
||||
parentDirName := filepath.Dir(inputDir) // this is to have the files inside a subdirectory
|
||||
nameInArchive, err := filepath.Rel(parentDirName, filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create relative file name")
|
||||
}
|
||||
|
||||
// tar.FileInfoHeader call causes a crash in static builds
|
||||
// https://github.com/golang/go/issues/24787
|
||||
hdr := &tar.Header{
|
||||
Name: nameInArchive,
|
||||
ModTime: info.ModTime(),
|
||||
Mode: int64(fileMode.Perm()),
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: info.Size(),
|
||||
}
|
||||
|
||||
err = tarWriter.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write tar header")
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
fileReader, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open source file")
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
_, err = io.Copy(tarWriter, fileReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to copy file into archive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to walk source dir")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CollectorFailure struct {
|
||||
Collector *troubleshootv1beta1.Collect
|
||||
Collector *troubleshootv1beta2.Collect
|
||||
Failure string
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper, progressChan chan interface{}, collectors *collect.Collectors) error {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
for _, collector := range *collectors {
|
||||
if collector.Collect.Logs != nil {
|
||||
if collector.Collect.Logs.Limits == nil {
|
||||
collector.Collect.Logs.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
collector.Collect.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,24 +5,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
func createTroubleshootK8sClient(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
|
||||
config, err := configFlags.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create troubleshoot client")
|
||||
}
|
||||
|
||||
return troubleshootClient, nil
|
||||
}
|
||||
|
||||
func findFileName(basename, extension string) (string, error) {
|
||||
n := 1
|
||||
name := basename
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package util
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func HomeDir() string {
|
||||
@@ -20,3 +21,19 @@ func IsURL(str string) bool {
|
||||
|
||||
return parsed.Scheme != ""
|
||||
}
|
||||
|
||||
func AppName(name string) string {
|
||||
words := strings.Split(strings.Title(strings.Replace(name, "-", " ", -1)), " ")
|
||||
casedWords := []string{}
|
||||
for i, word := range words {
|
||||
if strings.ToLower(word) == "ai" {
|
||||
casedWords = append(casedWords, "AI")
|
||||
} else if strings.ToLower(word) == "io" && i > 0 {
|
||||
casedWords[i-1] += ".io"
|
||||
} else {
|
||||
casedWords = append(casedWords, word)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(casedWords, " ")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
controller-gen.kubebuilder.io/version: v0.3.0
|
||||
creationTimestamp: null
|
||||
name: analyzers.troubleshoot.replicated.com
|
||||
spec:
|
||||
@@ -42,7 +42,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -83,7 +88,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -126,7 +136,12 @@ spec:
|
||||
customResourceDefinitionName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -168,7 +183,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -215,7 +235,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -256,7 +281,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -300,7 +330,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
ingressName:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -342,12 +377,68 @@ spec:
|
||||
- namespace
|
||||
- outcomes
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
nodeResources:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
filters:
|
||||
properties:
|
||||
cpuAllocatable:
|
||||
@@ -366,6 +457,13 @@ spec:
|
||||
type: string
|
||||
podCapacity:
|
||||
type: string
|
||||
selector:
|
||||
properties:
|
||||
matchLabel:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
outcomes:
|
||||
items:
|
||||
@@ -402,12 +500,119 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
key:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -456,7 +661,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -503,7 +713,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -549,7 +764,12 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
controller-gen.kubebuilder.io/version: v0.3.0
|
||||
creationTimestamp: null
|
||||
name: collectors.troubleshoot.replicated.com
|
||||
spec:
|
||||
@@ -41,20 +41,26 @@ spec:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
uploadResultsTo:
|
||||
properties:
|
||||
method:
|
||||
type: string
|
||||
redactUri:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- method
|
||||
- redactUri
|
||||
- uri
|
||||
type: object
|
||||
type: object
|
||||
@@ -67,14 +73,24 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
type: object
|
||||
clusterResources:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
@@ -85,7 +101,12 @@ spec:
|
||||
containerPath:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -106,7 +127,12 @@ spec:
|
||||
data:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
@@ -127,7 +153,12 @@ spec:
|
||||
containerName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -147,7 +178,12 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
get:
|
||||
properties:
|
||||
headers:
|
||||
@@ -203,7 +239,12 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
limits:
|
||||
properties:
|
||||
maxAge:
|
||||
@@ -223,6 +264,54 @@ spec:
|
||||
required:
|
||||
- selector
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
@@ -236,7 +325,12 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
@@ -256,7 +350,12 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
includeValue:
|
||||
type: boolean
|
||||
key:
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.4
|
||||
controller-gen.kubebuilder.io/version: v0.3.0
|
||||
creationTimestamp: null
|
||||
name: preflights.troubleshoot.replicated.com
|
||||
spec:
|
||||
@@ -42,7 +42,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -83,7 +88,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -126,7 +136,12 @@ spec:
|
||||
customResourceDefinitionName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -168,7 +183,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -215,7 +235,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -256,7 +281,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -300,7 +330,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
ingressName:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -342,12 +377,68 @@ spec:
|
||||
- namespace
|
||||
- outcomes
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
nodeResources:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
filters:
|
||||
properties:
|
||||
cpuAllocatable:
|
||||
@@ -366,6 +457,13 @@ spec:
|
||||
type: string
|
||||
podCapacity:
|
||||
type: string
|
||||
selector:
|
||||
properties:
|
||||
matchLabel:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
outcomes:
|
||||
items:
|
||||
@@ -402,12 +500,119 @@ spec:
|
||||
required:
|
||||
- outcomes
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
fail:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
pass:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
warn:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
when:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- collectorName
|
||||
- outcomes
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
key:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -456,7 +661,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -503,7 +713,12 @@ spec:
|
||||
checkName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
outcomes:
|
||||
items:
|
||||
properties:
|
||||
@@ -549,7 +764,12 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
fileName:
|
||||
type: string
|
||||
outcomes:
|
||||
@@ -601,14 +821,24 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
type: object
|
||||
clusterResources:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
@@ -619,7 +849,12 @@ spec:
|
||||
containerPath:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -640,7 +875,12 @@ spec:
|
||||
data:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
@@ -661,7 +901,12 @@ spec:
|
||||
containerName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
@@ -681,7 +926,12 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
get:
|
||||
properties:
|
||||
headers:
|
||||
@@ -737,7 +987,12 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
limits:
|
||||
properties:
|
||||
maxAge:
|
||||
@@ -757,6 +1012,54 @@ spec:
|
||||
required:
|
||||
- selector
|
||||
type: object
|
||||
mysql:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
postgres:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
redis:
|
||||
properties:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
uri:
|
||||
type: string
|
||||
required:
|
||||
- uri
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
@@ -770,7 +1073,12 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
@@ -790,7 +1098,12 @@ spec:
|
||||
collectorName:
|
||||
type: string
|
||||
exclude:
|
||||
type: boolean
|
||||
description: BoolOrString is a type that can hold an bool
|
||||
or a string. When used in JSON or YAML marshalling and
|
||||
unmarshalling, it produces or consumes the inner type. This
|
||||
allows you to have, for example, a JSON field that can accept
|
||||
a booolean string or raw bool.
|
||||
type: BoolString
|
||||
includeValue:
|
||||
type: boolean
|
||||
key:
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.3.0
|
||||
creationTimestamp: null
|
||||
name: redactors.troubleshoot.replicated.com
|
||||
spec:
|
||||
group: troubleshoot.replicated.com
|
||||
names:
|
||||
kind: Redactor
|
||||
listKind: RedactorList
|
||||
plural: redactors
|
||||
singular: redactor
|
||||
scope: Namespaced
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Redactor is the Schema for the redaction API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RedactorSpec defines the desired state of Redactor
|
||||
properties:
|
||||
redactors:
|
||||
items:
|
||||
properties:
|
||||
fileSelector:
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
files:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
removals:
|
||||
properties:
|
||||
regex:
|
||||
items:
|
||||
properties:
|
||||
redactor:
|
||||
type: string
|
||||
selector:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
values:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
yamlPath:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: RedactorStatus defines the observed state of Redactor
|
||||
type: object
|
||||
type: object
|
||||
version: v1beta1
|
||||
versions:
|
||||
- name: v1beta1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
-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
|
||||
@@ -110,4 +110,4 @@ dockers:
|
||||
- "replicated/preflight:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
|
||||
binaries:
|
||||
- support-bundle
|
||||
- preflight
|
||||
- preflight
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: block
|
||||
spec:
|
||||
collectors:
|
||||
- blockDevices: {}
|
||||
analyzers:
|
||||
- blockDevices:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: ".* == 1"
|
||||
message: One available block device
|
||||
- pass:
|
||||
when: ".* > 1"
|
||||
message: Multiple available block devices
|
||||
- fail:
|
||||
message: No available block devices
|
||||
@@ -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,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: 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,30 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: port
|
||||
spec:
|
||||
collectors:
|
||||
- tcpPortStatus:
|
||||
collectorName: k8s
|
||||
port: 7443
|
||||
analyzers:
|
||||
- tcpPortStatus:
|
||||
collectorName: k8s
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to port 7443 was refused.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: Another process was already listening on port 7443.
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to port 7443. Check your firewall.
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Unexpected port status
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Port 7443 is open
|
||||
- warn:
|
||||
message: Unexpected port status
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: loadbalancer
|
||||
spec:
|
||||
collectors:
|
||||
- tcpLoadBalancer:
|
||||
collectorName: loadbalancer
|
||||
port: 7443
|
||||
address: 10.128.0.29:7444
|
||||
analyzers:
|
||||
- tcpLoadBalancer:
|
||||
collectorName: loadbalancer
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-refused"
|
||||
message: Connection to port 7443 via load balancer was refused.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: Another process was already listening on port 7443.
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: Timed out connecting to port 7443 via load balancer. Check your firewall.
|
||||
- fail:
|
||||
when: "error"
|
||||
message: Unexpected port status
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: Successfully connected to port 7443 via load balancer
|
||||
- warn:
|
||||
message: Unexpected port status
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: sample
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: sample
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: HostPreflight
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
collectors:
|
||||
- tcpLoadBalancer:
|
||||
collectorName: LB1
|
||||
address: 10.1.1.1
|
||||
port: 6443
|
||||
timeout: 5000ms
|
||||
- diskUsage:
|
||||
collectorName: ephemeral
|
||||
path: /var/lib/kubelet
|
||||
analyzers:
|
||||
- cpu:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 4"
|
||||
message: This server has less than 4 CPU cores, and we require 8, but recommend 16
|
||||
- warn:
|
||||
when: "< 16"
|
||||
message: This server has at least 4 CPU cores, but we recommend 16 or more
|
||||
- pass:
|
||||
message: This server has sufficient CPU cores
|
||||
- tcpLoadBalancer:
|
||||
collectorName: LB1
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "connection-timeout"
|
||||
message: The TCP Load Balancer is not forwarding traffic to this server.
|
||||
- fail:
|
||||
when: "address-in-use"
|
||||
message: The local port is not available to validate the Load Balancer configuration.
|
||||
- pass:
|
||||
when: "connected"
|
||||
message: The specified TCP Load Balancer appears to be properly forwarding traffic to this server.
|
||||
- diskUsage:
|
||||
collectorName: ephemeral
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "total < 20Gi"
|
||||
message: /var/lib/kubelet has less than 20Gi of total space
|
||||
- fail:
|
||||
when: "available < 10Gi"
|
||||
message: /var/lib/kubelet has less than 10Gi of disk space available
|
||||
- fail:
|
||||
when: "used/total > 70%"
|
||||
message: /var/lib/kubelet is more than 70% full
|
||||
- pass:
|
||||
message: /var/lib/kubelet has sufficient disk space available
|
||||
@@ -1,20 +1,14 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: example-preflight-checks
|
||||
name: example
|
||||
spec:
|
||||
collectors:
|
||||
- secret:
|
||||
name: myapp-postgres
|
||||
namespace: default
|
||||
key: uri
|
||||
includeValue: false
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1.13.0"
|
||||
message: The application requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.
|
||||
message: This application requires at least Kubernetes 1.13.0 or later, and recommends 1.15.0.
|
||||
uri: https://www.kubernetes.io
|
||||
- warn:
|
||||
when: "< 1.15.0"
|
||||
@@ -24,32 +18,102 @@ spec:
|
||||
when: ">= 1.15.0"
|
||||
message: Your cluster meets the recommended and required versions of Kubernetes.
|
||||
- customResourceDefinition:
|
||||
customResourceDefinitionName: constrainttemplates.templates.gatekeeper.sh
|
||||
checkName: Gatekeeper policy runtime
|
||||
checkName: Ingress
|
||||
customResourceDefinitionName: ingressroutes.contour.heptio.com
|
||||
outcomes:
|
||||
- fail:
|
||||
message: Gatekeeper is required for the application, but not found in the cluster.
|
||||
uri: https://enterprise.support.io/installing/gatekeeper
|
||||
message: Contour ingress not found!
|
||||
- pass:
|
||||
message: Found a supported version of Gatekeeper installed and running in the cluster.
|
||||
- imagePullSecret:
|
||||
checkName: Registry credentials for Quay.io
|
||||
registryName: quay.io
|
||||
message: Contour ingress found!
|
||||
- containerRuntime:
|
||||
outcomes:
|
||||
- pass:
|
||||
when: "== docker"
|
||||
message: Docker container runtime was found.
|
||||
- fail:
|
||||
message: Did not find Docker container runtime.
|
||||
- storageClass:
|
||||
checkName: Required storage classes
|
||||
storageClassName: "default"
|
||||
outcomes:
|
||||
- fail:
|
||||
message: |
|
||||
Cannot pull from quay.io. An image pull secret should be deployed to the cluster that has credentials to pull the images. To obtain this secret, please contact your support rep.
|
||||
uri: https://enterprise.support.io/installing/registry-credentials
|
||||
message: Could not find a storage class called default.
|
||||
- pass:
|
||||
message: Found credentials to pull from quay.io
|
||||
- secret:
|
||||
checkName: Postgres connection string
|
||||
secretName: myapp-postgres
|
||||
namespace: default
|
||||
key: uri
|
||||
message: All good on storage classes
|
||||
- distribution:
|
||||
outcomes:
|
||||
- fail:
|
||||
message: A secret named "myapp-postgres" must be deployed with a "uri" key
|
||||
uri: https://enterprise.support.io/installing/postgres
|
||||
when: "== docker-desktop"
|
||||
message: The application does not support Docker Desktop Clusters
|
||||
- fail:
|
||||
when: "== microk8s"
|
||||
message: The application does not support Microk8s Clusters
|
||||
- fail:
|
||||
when: "== minikube"
|
||||
message: The application does not support Minikube Clusters
|
||||
- pass:
|
||||
message: Found a valid postgres connection string
|
||||
when: "== eks"
|
||||
message: EKS is a supported distribution
|
||||
- pass:
|
||||
when: "== gke"
|
||||
message: GKE is a supported distribution
|
||||
- pass:
|
||||
when: "== aks"
|
||||
message: AKS is a supported distribution
|
||||
# Will be supported in the future
|
||||
- pass:
|
||||
when: "== kurl"
|
||||
message: KURL is a supported distribution
|
||||
- pass:
|
||||
when: "== digitalocean"
|
||||
message: DigitalOcean is a supported distribution
|
||||
- warn:
|
||||
message: Unable to determine the distribution of Kubernetes
|
||||
- nodeResources:
|
||||
checkName: Must have at least 3 nodes in the cluster, with 5 recommended
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count() < 3"
|
||||
message: This application requires at least 3 nodes.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/adding-nodes
|
||||
- warn:
|
||||
when: "count() < 5"
|
||||
message: This application recommends at last 5 nodes.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/adding-nodes
|
||||
- pass:
|
||||
message: This cluster has enough nodes.
|
||||
- nodeResources:
|
||||
checkName: Every node in the cluster must have at least 10 GB of memory, with 32 GB recommended
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "min(memoryCapacity) < 10Gi"
|
||||
message: All nodes must have at least 10 GB of memory.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- warn:
|
||||
when: "min(memoryCapacity) < 32Gi"
|
||||
message: All nodes are recommended to have at least 32 GB of memory.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- pass:
|
||||
message: All nodes have at least 32 GB of memory.
|
||||
- nodeResources:
|
||||
checkName: Total CPU Cores in the cluster is 4 or greater
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "sum(cpuCapacity) < 4"
|
||||
message: The cluster must contain at least 4 cores
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- pass:
|
||||
message: There are at least 4 cores in the cluster
|
||||
- nodeResources:
|
||||
checkName: Every node in the cluster must have at least 40 GB of ephemeral storage, with 100 GB recommended
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "min(ephemeralStorageCapacity) < 40Gi"
|
||||
message: All nodes must have at least 40 GB of ephemeral storage.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- warn:
|
||||
when: "min(ephemeralStorageCapacity) < 100Gi"
|
||||
message: All nodes are recommended to have at least 100 GB of ephemeral storage.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- pass:
|
||||
message: All nodes have at least 100 GB of ephemeral storage.
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Preflight
|
||||
metadata:
|
||||
name: example-preflight-checks
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
name: a
|
||||
spec:
|
||||
analyzers:
|
||||
- distribution:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "= dockerdesktop"
|
||||
message: "docker for desktop is not allowed"
|
||||
# - fail:
|
||||
# when: "microk8s"
|
||||
# message: "mickrk8s is not prod"
|
||||
- fail:
|
||||
when: "!= openshift"
|
||||
message: "this should fail on anything other than openshift"
|
||||
- warn:
|
||||
when: "!= eks"
|
||||
message: "YMMV on not eks"
|
||||
- pass:
|
||||
message: "good work"
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Collector
|
||||
metadata:
|
||||
name: collector-sample
|
||||
@@ -0,0 +1,101 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
collectors:
|
||||
- logs:
|
||||
selector:
|
||||
- app=example
|
||||
- component=nginx
|
||||
namespace: repl{{ Namespace }}
|
||||
limits:
|
||||
maxAge: 30d
|
||||
maxLines: 10000
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1.16.0"
|
||||
message: The application requires at least Kubernetes 1.16.0 or later
|
||||
uri: https://kubernetes.io
|
||||
- warn:
|
||||
when: "< 1.17.0"
|
||||
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.17.0 or later.
|
||||
uri: https://kubernetes.io
|
||||
- pass:
|
||||
message: Your cluster meets the recommended and required versions of Kubernetes.
|
||||
- customResourceDefinition:
|
||||
customResourceDefinitionName: rook
|
||||
outcomes:
|
||||
- fail:
|
||||
message: The Rook CRD was not found in the cluster.
|
||||
- pass:
|
||||
message: Rook is installed and available.
|
||||
- containerRuntime:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "== docker"
|
||||
message: The application does not support docker
|
||||
- pass:
|
||||
message: A supported container runtime was found
|
||||
- storageClass:
|
||||
checkName: Required storage classes
|
||||
storageClassName: "microk8s-hostpath"
|
||||
outcomes:
|
||||
- fail:
|
||||
message: The microk8s storage class thing was not found
|
||||
- pass:
|
||||
message: All good on storage classes
|
||||
- nodeResources:
|
||||
checkName: Must have at least 3 nodes in the cluster
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "count() < 3"
|
||||
message: This application requires at least 3 nodes
|
||||
- warn:
|
||||
when: "count() < 5"
|
||||
message: This application recommends at last 5 nodes.
|
||||
- pass:
|
||||
message: This cluster has enough nodes.
|
||||
- nodeResources:
|
||||
checkName: Total CPU Cores in the cluster is 4 or greater
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "sum(cpuCapacity) < 4"
|
||||
message: The cluster must contain at least 4 cores
|
||||
- pass:
|
||||
message: There are at least 4 cores in the cluster
|
||||
- nodeResources:
|
||||
checkName: Each node must have at least 40 GB of ephemeral storage
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "min(ephemeralStorageCapacity) < 40Gi"
|
||||
message: Noees in this cluster do not have at least 40 GB of ephemeral storage.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- warn:
|
||||
when: "min(ephemeralStorageCapacity) < 100Gi"
|
||||
message: Nodes in this cluster are recommended to have at least 100 GB of ephemeral storage.
|
||||
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
|
||||
- pass:
|
||||
message: The nodes in this cluster have enough ephemeral storage.
|
||||
- ingress:
|
||||
namespace: default
|
||||
ingressName: connect-to-me
|
||||
outcomes:
|
||||
- fail:
|
||||
message: The ingress isn't ingressing
|
||||
- pass:
|
||||
message: All systems ok on ingress
|
||||
- deploymentStatus:
|
||||
name: api
|
||||
namespace: default
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1"
|
||||
message: The API deployment does not have any ready replicas.
|
||||
- warn:
|
||||
when: "= 1"
|
||||
message: The API deployment has only a single ready replica.
|
||||
- pass:
|
||||
message: There are multiple replicas of the API deployment ready.
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
name: a
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
kind: SupportBundle
|
||||
metadata:
|
||||
name: supportbundle-sample
|
||||
spec:
|
||||
collectors: []
|
||||
analyzers:
|
||||
- clusterVersion:
|
||||
outcomes:
|
||||
- fail:
|
||||
when: "< 1.13.0"
|
||||
message: The application requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.
|
||||
uri: https://www.kubernetes.io
|
||||
- warn:
|
||||
when: "< 1.15.0"
|
||||
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.
|
||||
uri: https://kubernetes.io
|
||||
- pass:
|
||||
when: ">= 1.15.0"
|
||||
message: Your cluster meets the recommended and required versions of Kubernetes.
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Collector
|
||||
metadata:
|
||||
name: collector-sample
|
||||
spec:
|
||||
collectors: []
|
||||
@@ -8,17 +8,15 @@ require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/chzyer/logex v1.1.11-0.20160617073814-96a4d311aa9b // indirect
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect
|
||||
github.com/emicklei/go-restful v2.9.6+incompatible // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/frankban/quicktest v1.7.2 // indirect
|
||||
github.com/gizak/termui/v3 v3.1.0
|
||||
github.com/go-openapi/spec v0.19.4 // indirect
|
||||
github.com/go-redis/redis/v7 v7.2.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/godbus/dbus v4.1.0+incompatible
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/google/gofuzz v1.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e
|
||||
@@ -28,32 +26,30 @@ require (
|
||||
github.com/manifoldco/promptui v0.3.2
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/mholt/archiver v3.1.1+incompatible
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/nicksnyder/go-i18n v1.10.1 // indirect
|
||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||
github.com/onsi/gomega v1.9.0 // indirect
|
||||
github.com/pierrec/lz4 v2.2.6+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/procfs v0.0.5 // indirect
|
||||
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851
|
||||
github.com/segmentio/ksuid v1.0.3
|
||||
github.com/shirou/gopsutil v3.21.1+incompatible
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tj/go-spin v1.1.0
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/ulikunitz/xz v0.5.6 // indirect
|
||||
go.opencensus.io v0.22.0 // indirect
|
||||
go.undefinedlabs.com/scopeagent v0.1.7
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff // indirect
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
k8s.io/api v0.18.3
|
||||
k8s.io/apiextensions-apiserver v0.18.0
|
||||
k8s.io/apiextensions-apiserver v0.18.2
|
||||
k8s.io/apimachinery v0.18.3
|
||||
k8s.io/cli-runtime v0.18.0
|
||||
k8s.io/client-go v0.18.0
|
||||
k8s.io/code-generator v0.18.3-beta.0 // indirect
|
||||
k8s.io/client-go v0.18.2
|
||||
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901
|
||||
sigs.k8s.io/controller-tools v0.2.8 // indirect
|
||||
)
|
||||
|
||||
@@ -10,16 +10,21 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
@@ -45,8 +50,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
|
||||
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/aws/aws-sdk-go v1.25.18 h1:fMEkpli4r+FS4xZRqjgjYHP+uKaSwfk2MOcDUpLYwIE=
|
||||
github.com/aws/aws-sdk-go v1.25.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
|
||||
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
@@ -84,10 +87,10 @@ github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfc
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
@@ -97,12 +100,8 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 h1:pEtiCjIXx3RvGjlUJuCNxNOw0MNblyR9Wi+vJGBFh+8=
|
||||
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
@@ -120,20 +119,14 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
|
||||
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
@@ -173,7 +166,6 @@ github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nA
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
|
||||
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
@@ -195,14 +187,12 @@ github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRf
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM=
|
||||
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
@@ -220,8 +210,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@@ -231,8 +219,6 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -254,6 +240,7 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
|
||||
@@ -298,7 +285,6 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
@@ -310,8 +296,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -339,11 +323,9 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
||||
github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
|
||||
github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
@@ -352,8 +334,6 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
|
||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -368,7 +348,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -382,75 +361,65 @@ github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDa
|
||||
github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
|
||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw=
|
||||
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851 h1:eRlNDHxGfVkPCRXbA4BfQJvt5DHjFiTtWy3R/t4djyY=
|
||||
github.com/replicatedhq/termui/v3 v3.1.1-0.20200811145416-f40076d26851/go.mod h1:JDxG6+uubnk9/BZ2yUsyAJJwlptjrnmB2MPF5d2Xe/8=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/segmentio/ksuid v1.0.3 h1:FoResxvleQwYiPAVKe1tMUlEirodZqlqglIuFsdDntY=
|
||||
github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v3.20.12+incompatible h1:6VEGkOXP/eP4o2Ilk8cSsX0PhOEfX6leqAnD+urrp9M=
|
||||
github.com/shirou/gopsutil v3.20.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.1+incompatible h1:2LwXWdbjXwyDgq26Yy/OT4xozlpmssQfy/rtfhWb0bY=
|
||||
github.com/shirou/gopsutil v3.21.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
@@ -500,10 +469,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -520,14 +487,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98 h1:Z/megzdoMbuZ0H/oLmfTw92PAEfyTi1DkvAr8AUzFgw=
|
||||
github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98/go.mod h1:TyJZDQ/5AgyN7FSLiBJ8RO9u2c6wbtRvK827b6AVqY4=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
@@ -537,7 +498,6 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
@@ -548,8 +508,6 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.undefinedlabs.com/scopeagent v0.1.7 h1:TPALQQNkPah8NgVjPKKhaBNZL2hg+hY2GRPfRMSt48c=
|
||||
go.undefinedlabs.com/scopeagent v0.1.7/go.mod h1:XCD8wnuBZ+eskhynYNP+uvBiFan9cFrWLwHnWOETXDU=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -560,20 +518,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -590,16 +543,12 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -628,21 +577,19 @@ golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -661,10 +608,8 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
@@ -676,9 +621,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+y
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
|
||||
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
@@ -690,8 +632,6 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
@@ -700,7 +640,6 @@ google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
@@ -709,7 +648,6 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
@@ -732,93 +670,61 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
|
||||
k8s.io/api v0.17.4 h1:HbwOhDapkguO8lTAE8OX3hdF2qp8GtpC9CW/MQATXXo=
|
||||
k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=
|
||||
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
||||
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||
k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0=
|
||||
k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA=
|
||||
k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=
|
||||
k8s.io/apiextensions-apiserver v0.18.0 h1:HN4/P8vpGZFvB5SOMuPPH2Wt9Y/ryX+KRvIyAkchu1Q=
|
||||
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
|
||||
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
|
||||
k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw=
|
||||
k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
|
||||
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
|
||||
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
|
||||
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
|
||||
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
|
||||
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
|
||||
k8s.io/cli-runtime v0.17.4 h1:ZIJdxpBEszZqUhydrCoiI5rLXS2J/1AF5xFok2QJ9bc=
|
||||
k8s.io/cli-runtime v0.17.4/go.mod h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc=
|
||||
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
|
||||
k8s.io/cli-runtime v0.18.0 h1:jG8XpSqQ5TrV0N+EZ3PFz6+gqlCk71dkggWCCq9Mq34=
|
||||
k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ=
|
||||
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
|
||||
k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8=
|
||||
k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
|
||||
k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4=
|
||||
k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
|
||||
k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
k8s.io/code-generator v0.18.0 h1:0xIRWzym+qMgVpGmLESDeMfz/orwgxwxFFAo1xfGNtQ=
|
||||
k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
|
||||
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
|
||||
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/code-generator v0.18.3-beta.0 h1:OIi8tEf8j3ppkJP8pyV9whXqnmdon4okY9GjQnbU0dc=
|
||||
k8s.io/code-generator v0.18.3-beta.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
|
||||
k8s.io/code-generator v0.18.2 h1:C1Nn2JiMf244CvBDKVPX0W2mZFJkVBg54T8OV7/Imso=
|
||||
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c=
|
||||
k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120 h1:RPscN6KhmG54S33L+lr3GS+oD1jmchIU0ll519K6FA4=
|
||||
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM=
|
||||
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
|
||||
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
|
||||
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
|
||||
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901 h1:qwHvTyQQBjATQeKPdJ0TwcXdtScjyI6GuiAQ5CSqEvM=
|
||||
sigs.k8s.io/controller-runtime v0.5.1-0.20200402191424-df180accb901/go.mod h1:j4echH3Y/UPHRpXS65rxGXujda8iWOheMQvDh1uNgaY=
|
||||
sigs.k8s.io/controller-tools v0.2.8 h1:UmYsnu89dn8/wBhjKL3lkGyaDGRnPDYUx2+iwXRnylA=
|
||||
sigs.k8s.io/controller-tools v0.2.8/go.mod h1:9VKHPszmf2DHz/QmHkcfZoewO6BL7pPs9uAiBVsaJSE=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
||||
+152
-17
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/multitype"
|
||||
)
|
||||
|
||||
@@ -40,7 +40,68 @@ func isExcluded(excludeVal multitype.BoolOrString) (bool, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) (*AnalyzeResult, error) {
|
||||
func HostAnalyze(hostAnalyzer *troubleshootv1beta2.HostAnalyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if hostAnalyzer.CPU != nil {
|
||||
result, err := analyzeHostCPU(hostAnalyzer.CPU, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.TCPLoadBalancer != nil {
|
||||
result, err := analyzeHostTCPLoadBalancer(hostAnalyzer.TCPLoadBalancer, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.DiskUsage != nil {
|
||||
result, err := analyzeHostDiskUsage(hostAnalyzer.DiskUsage, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.Memory != nil {
|
||||
result, err := analyzeHostMemory(hostAnalyzer.Memory, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.TCPPortStatus != nil {
|
||||
result, err := analyzeHostTCPPortStatus(hostAnalyzer.TCPPortStatus, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.HTTP != nil {
|
||||
result, err := analyzeHostHTTP(hostAnalyzer.HTTP, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.Time != nil {
|
||||
result, err := analyzeHostTime(hostAnalyzer.Time, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if hostAnalyzer.BlockDevices != nil {
|
||||
result, err := analyzeHostBlockDevices(hostAnalyzer.BlockDevices, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid analyzer")
|
||||
}
|
||||
|
||||
func Analyze(analyzer *troubleshootv1beta2.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) {
|
||||
if analyzer.ClusterVersion != nil {
|
||||
isExcluded, err := isExcluded(analyzer.ClusterVersion.Exclude)
|
||||
if err != nil {
|
||||
@@ -49,7 +110,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeClusterVersion(analyzer.ClusterVersion, getFile)
|
||||
result, err := analyzeClusterVersion(analyzer.ClusterVersion, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.StorageClass != nil {
|
||||
isExcluded, err := isExcluded(analyzer.StorageClass.Exclude)
|
||||
@@ -59,7 +124,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeStorageClass(analyzer.StorageClass, getFile)
|
||||
result, err := analyzeStorageClass(analyzer.StorageClass, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.CustomResourceDefinition != nil {
|
||||
isExcluded, err := isExcluded(analyzer.CustomResourceDefinition.Exclude)
|
||||
@@ -69,7 +138,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeCustomResourceDefinition(analyzer.CustomResourceDefinition, getFile)
|
||||
result, err := analyzeCustomResourceDefinition(analyzer.CustomResourceDefinition, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.Ingress != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Ingress.Exclude)
|
||||
@@ -79,7 +152,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeIngress(analyzer.Ingress, getFile)
|
||||
result, err := analyzeIngress(analyzer.Ingress, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.Secret != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Secret.Exclude)
|
||||
@@ -89,7 +166,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeSecret(analyzer.Secret, getFile)
|
||||
result, err := analyzeSecret(analyzer.Secret, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.ImagePullSecret != nil {
|
||||
isExcluded, err := isExcluded(analyzer.ImagePullSecret.Exclude)
|
||||
@@ -99,7 +180,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeImagePullSecret(analyzer.ImagePullSecret, findFiles)
|
||||
result, err := analyzeImagePullSecret(analyzer.ImagePullSecret, findFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.DeploymentStatus != nil {
|
||||
isExcluded, err := isExcluded(analyzer.DeploymentStatus.Exclude)
|
||||
@@ -109,7 +194,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeDeploymentStatus(analyzer.DeploymentStatus, getFile)
|
||||
result, err := analyzeDeploymentStatus(analyzer.DeploymentStatus, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.StatefulsetStatus != nil {
|
||||
isExcluded, err := isExcluded(analyzer.StatefulsetStatus.Exclude)
|
||||
@@ -119,7 +208,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeStatefulsetStatus(analyzer.StatefulsetStatus, getFile)
|
||||
result, err := analyzeStatefulsetStatus(analyzer.StatefulsetStatus, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.ContainerRuntime != nil {
|
||||
isExcluded, err := isExcluded(analyzer.ContainerRuntime.Exclude)
|
||||
@@ -129,7 +222,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeContainerRuntime(analyzer.ContainerRuntime, getFile)
|
||||
result, err := analyzeContainerRuntime(analyzer.ContainerRuntime, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.Distribution != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Distribution.Exclude)
|
||||
@@ -139,7 +236,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeDistribution(analyzer.Distribution, getFile)
|
||||
result, err := analyzeDistribution(analyzer.Distribution, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.NodeResources != nil {
|
||||
isExcluded, err := isExcluded(analyzer.NodeResources.Exclude)
|
||||
@@ -149,7 +250,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeNodeResources(analyzer.NodeResources, getFile)
|
||||
result, err := analyzeNodeResources(analyzer.NodeResources, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.TextAnalyze != nil {
|
||||
isExcluded, err := isExcluded(analyzer.TextAnalyze.Exclude)
|
||||
@@ -159,7 +264,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeTextAnalyze(analyzer.TextAnalyze, getFile)
|
||||
multiResult, err := analyzeTextAnalyze(analyzer.TextAnalyze, findFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return multiResult, nil
|
||||
}
|
||||
if analyzer.Postgres != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Postgres.Exclude)
|
||||
@@ -169,7 +278,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzePostgres(analyzer.Postgres, getFile)
|
||||
result, err := analyzePostgres(analyzer.Postgres, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.Mysql != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Mysql.Exclude)
|
||||
@@ -179,7 +292,11 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeMysql(analyzer.Mysql, getFile)
|
||||
result, err := analyzeMysql(analyzer.Mysql, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.Redis != nil {
|
||||
isExcluded, err := isExcluded(analyzer.Redis.Exclude)
|
||||
@@ -189,7 +306,25 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
return analyzeRedis(analyzer.Redis, getFile)
|
||||
result, err := analyzeRedis(analyzer.Redis, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
if analyzer.CephStatus != nil {
|
||||
isExcluded, err := isExcluded(analyzer.CephStatus.Exclude)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isExcluded {
|
||||
return nil, nil
|
||||
}
|
||||
result, err := cephStatus(analyzer.CephStatus, getFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*AnalyzeResult{result}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid analyzer")
|
||||
|
||||
@@ -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,7 +29,7 @@ func analyzeClusterVersion(analyzer *troubleshootv1beta1.ClusterVersion, getColl
|
||||
return analyzeClusterVersionResult(k8sVersion, analyzer.Outcomes, analyzer.CheckName)
|
||||
}
|
||||
|
||||
func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubleshootv1beta1.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
func analyzeClusterVersionResult(k8sVersion semver.Version, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
for _, outcome := range outcomes {
|
||||
when := ""
|
||||
message := ""
|
||||
|
||||
@@ -5,28 +5,27 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
outcomes := []*troubleshootv1beta1.Outcome{
|
||||
outcomes := []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 1.13.0",
|
||||
Message: "Sentry requires at Kubernetes 1.13.0 or later, and recommends 1.15.0.",
|
||||
URI: "https://www.kubernetes.io",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta1.SingleOutcome{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 1.15.0",
|
||||
Message: "Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.15.0 or later.",
|
||||
URI: "https://www.kubernetes.io",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Your cluster meets the recommended and required versions of Kubernetes.",
|
||||
},
|
||||
},
|
||||
@@ -34,7 +33,7 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
k8sVersion semver.Version
|
||||
outcomes []*troubleshootv1beta1.Outcome
|
||||
outcomes []*troubleshootv1beta2.Outcome
|
||||
checkName string
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -93,8 +92,6 @@ func Test_analyzeClusterVersionResult(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
got, err := analyzeClusterVersionResult(tt.args.k8sVersion, tt.args.outcomes, tt.args.checkName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("analyzeClusterVersionResult() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func commonStatus(outcomes []*troubleshootv1beta1.Outcome, title, iconKey string, iconURI string, readyReplicas int) (*AnalyzeResult, error) {
|
||||
func commonStatus(outcomes []*troubleshootv1beta2.Outcome, title, iconKey string, iconURI string, readyReplicas int) (*AnalyzeResult, error) {
|
||||
result := &AnalyzeResult{
|
||||
Title: title,
|
||||
IconKey: iconKey,
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func analyzeContainerRuntime(analyzer *troubleshootv1beta1.ContainerRuntime, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeContainerRuntime(analyzer *troubleshootv1beta2.ContainerRuntime, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
|
||||
@@ -26,8 +26,12 @@ func analyzeContainerRuntime(analyzer *troubleshootv1beta1.ContainerRuntime, get
|
||||
foundRuntimes = append(foundRuntimes, node.Status.NodeInfo.ContainerRuntimeVersion)
|
||||
}
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Container Runtime"
|
||||
}
|
||||
result := &AnalyzeResult{
|
||||
Title: "Container Runtime",
|
||||
Title: title,
|
||||
IconKey: "kubernetes_container_runtime",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/container-runtime.svg?w=23&h=16",
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ package analyzer
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
)
|
||||
|
||||
func Test_compareRuntimeConditionalToActual(t *testing.T) {
|
||||
@@ -56,8 +55,6 @@ func Test_compareRuntimeConditionalToActual(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareRuntimeConditionalToActual(test.conditional, test.actual)
|
||||
@@ -72,22 +69,22 @@ func Test_compareRuntimeConditionalToActual(t *testing.T) {
|
||||
func Test_containerRuntime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer troubleshootv1beta1.ContainerRuntime
|
||||
analyzer troubleshootv1beta2.ContainerRuntime
|
||||
expectResult AnalyzeResult
|
||||
files map[string][]byte
|
||||
}{
|
||||
{
|
||||
name: "no containerd, when it's containerd",
|
||||
analyzer: troubleshootv1beta1.ContainerRuntime{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.ContainerRuntime{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "!= containerd",
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "containerd detected",
|
||||
},
|
||||
},
|
||||
@@ -110,8 +107,6 @@ func Test_containerRuntime(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) ([]byte, error) {
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -35,14 +35,18 @@ func compareDatabaseConditionalToActual(conditional string, result *collect.Data
|
||||
return false, errors.New("unable to parse postgres connected analyzer")
|
||||
|
||||
case "version":
|
||||
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], parts[2]))
|
||||
expected, err := semver.ParseTolerant(strings.Replace(parts[2], "x", "0", -1))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse semver range")
|
||||
return false, errors.Wrap(err, "failed to parse expected version")
|
||||
}
|
||||
actual, err := semver.ParseTolerant(strings.Replace(result.Version, "x", "0", -1))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse postgres db actual version")
|
||||
}
|
||||
|
||||
actual, err := semver.Parse(result.Version)
|
||||
expectedRange, err := semver.ParseRange(fmt.Sprintf("%s %s", parts[1], expected.String()))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse actual psotgres version")
|
||||
return false, errors.Wrap(err, "failed to parse semver range")
|
||||
}
|
||||
|
||||
return expectedRange(actual), nil
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
)
|
||||
|
||||
func Test_compareDatabaseConditionalToActual(t *testing.T) {
|
||||
@@ -62,8 +61,6 @@ func Test_compareDatabaseConditionalToActual(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareDatabaseConditionalToActual(test.conditional, &test.result)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,31 +3,30 @@ package analyzer
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
)
|
||||
|
||||
func Test_deploymentStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer troubleshootv1beta1.DeploymentStatus
|
||||
analyzer troubleshootv1beta2.DeploymentStatus
|
||||
expectResult AnalyzeResult
|
||||
files map[string][]byte
|
||||
}{
|
||||
{
|
||||
name: "1/1, pass when = 1",
|
||||
analyzer: troubleshootv1beta1.DeploymentStatus{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.DeploymentStatus{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "= 1",
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -50,16 +49,16 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "1/1, pass when = 2",
|
||||
analyzer: troubleshootv1beta1.DeploymentStatus{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.DeploymentStatus{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "= 2",
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -82,22 +81,22 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "1/1, pass when >= 2, warn when = 1, fail when 0",
|
||||
analyzer: troubleshootv1beta1.DeploymentStatus{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.DeploymentStatus{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ">= 2",
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta1.SingleOutcome{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "= 1",
|
||||
Message: "warn",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -122,8 +121,6 @@ func Test_deploymentStatus(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) ([]byte, error) {
|
||||
|
||||
@@ -2,10 +2,11 @@ package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@@ -19,6 +20,8 @@ type providers struct {
|
||||
openShift bool
|
||||
kurl bool
|
||||
aks bool
|
||||
ibm bool
|
||||
minikube bool
|
||||
}
|
||||
|
||||
type Provider int
|
||||
@@ -33,6 +36,8 @@ const (
|
||||
openShift Provider = iota
|
||||
kurl Provider = iota
|
||||
aks Provider = iota
|
||||
ibm Provider = iota
|
||||
minikube Provider = iota
|
||||
)
|
||||
|
||||
func CheckOpenShift(foundProviders *providers, apiResources []*metav1.APIResourceList, provider string) string {
|
||||
@@ -68,6 +73,10 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
foundProviders.aks = true
|
||||
stringProvider = "aks"
|
||||
}
|
||||
if k == "minikube.k8s.io/version" {
|
||||
foundProviders.minikube = true
|
||||
stringProvider = "minikube"
|
||||
}
|
||||
}
|
||||
|
||||
if node.Status.NodeInfo.OSImage == "Docker Desktop" {
|
||||
@@ -87,6 +96,10 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
foundProviders.gke = true
|
||||
stringProvider = "gke"
|
||||
}
|
||||
if strings.HasPrefix(node.Spec.ProviderID, "ibm:") {
|
||||
foundProviders.ibm = true
|
||||
stringProvider = "ibm"
|
||||
}
|
||||
}
|
||||
|
||||
if foundMaster {
|
||||
@@ -100,7 +113,8 @@ func ParseNodesForProviders(nodes []corev1.Node) (providers, string) {
|
||||
return foundProviders, stringProvider
|
||||
}
|
||||
|
||||
func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeDistribution(analyzer *troubleshootv1beta2.Distribution, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
var unknownDistribution string
|
||||
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
|
||||
@@ -123,9 +137,12 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
}
|
||||
_ = CheckOpenShift(&foundProviders, apiResources, "")
|
||||
}
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Kubernetes Distribution"
|
||||
}
|
||||
result := &AnalyzeResult{
|
||||
Title: "Kubernetes Distribution",
|
||||
Title: title,
|
||||
IconKey: "kubernetes_distribution",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/distribution.svg?w=20&h=14",
|
||||
}
|
||||
@@ -141,7 +158,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareDistributionConditionalToActual(outcome.Fail.When, foundProviders)
|
||||
isMatch, err := compareDistributionConditionalToActual(outcome.Fail.When, foundProviders, &unknownDistribution)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare distribution conditional")
|
||||
}
|
||||
@@ -162,7 +179,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareDistributionConditionalToActual(outcome.Warn.When, foundProviders)
|
||||
isMatch, err := compareDistributionConditionalToActual(outcome.Warn.When, foundProviders, &unknownDistribution)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare distribution conditional")
|
||||
}
|
||||
@@ -183,7 +200,7 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
return result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareDistributionConditionalToActual(outcome.Pass.When, foundProviders)
|
||||
isMatch, err := compareDistributionConditionalToActual(outcome.Pass.When, foundProviders, &unknownDistribution)
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to compare distribution conditional")
|
||||
}
|
||||
@@ -199,10 +216,17 @@ func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, getCollecte
|
||||
}
|
||||
}
|
||||
|
||||
result.IsWarn = true
|
||||
if unknownDistribution != "" {
|
||||
result.Message = unknownDistribution
|
||||
} else {
|
||||
result.Message = "None of the conditionals were met"
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func compareDistributionConditionalToActual(conditional string, actual providers) (bool, error) {
|
||||
func compareDistributionConditionalToActual(conditional string, actual providers, unknownDistribution *string) (bool, error) {
|
||||
parts := strings.Split(strings.TrimSpace(conditional), " ")
|
||||
|
||||
// we can make this a lot more flexible
|
||||
@@ -220,6 +244,7 @@ func compareDistributionConditionalToActual(conditional string, actual providers
|
||||
normalizedName := mustNormalizeDistributionName(parts[1])
|
||||
|
||||
if normalizedName == unknown {
|
||||
*unknownDistribution += fmt.Sprintf("- Unknown distribution: %s ", parts[1])
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -241,6 +266,10 @@ func compareDistributionConditionalToActual(conditional string, actual providers
|
||||
isMatch = actual.kurl
|
||||
case aks:
|
||||
isMatch = actual.aks
|
||||
case ibm:
|
||||
isMatch = actual.ibm
|
||||
case minikube:
|
||||
isMatch = actual.minikube
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
@@ -271,6 +300,10 @@ func mustNormalizeDistributionName(raw string) Provider {
|
||||
return kurl
|
||||
case "aks":
|
||||
return aks
|
||||
case "ibm", "ibmcloud", "ibm cloud":
|
||||
return ibm
|
||||
case "minikube":
|
||||
return minikube
|
||||
}
|
||||
|
||||
return unknown
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
)
|
||||
|
||||
func Test_compareDistributionConditionalToActual(t *testing.T) {
|
||||
var unknownDistribution string
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
@@ -42,11 +42,9 @@ func Test_compareDistributionConditionalToActual(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareDistributionConditionalToActual(test.conditional, test.input)
|
||||
actual, err := compareDistributionConditionalToActual(test.conditional, test.input, &unknownDistribution)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
@@ -79,8 +77,6 @@ func Test_mustNormalizeDistributionName(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.raw, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
actual := mustNormalizeDistributionName(test.raw)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
|
||||
+80
-15
@@ -10,8 +10,9 @@ import (
|
||||
|
||||
getter "github.com/hashicorp/go-getter"
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/logger"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
@@ -21,8 +22,13 @@ type fileContentProvider struct {
|
||||
}
|
||||
|
||||
// Analyze local will analyze a locally available (already downloaded) bundle
|
||||
func AnalyzeLocal(localBundlePath string, analyzers []*troubleshootv1beta1.Analyze) ([]*AnalyzeResult, error) {
|
||||
fcp := fileContentProvider{rootDir: localBundlePath}
|
||||
func AnalyzeLocal(localBundlePath string, analyzers []*troubleshootv1beta2.Analyze) ([]*AnalyzeResult, error) {
|
||||
rootDir, err := FindBundleRootDir(localBundlePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find root dir")
|
||||
}
|
||||
|
||||
fcp := fileContentProvider{rootDir: rootDir}
|
||||
|
||||
analyzeResults := []*AnalyzeResult{}
|
||||
for _, analyzer := range analyzers {
|
||||
@@ -33,7 +39,7 @@ func AnalyzeLocal(localBundlePath string, analyzers []*troubleshootv1beta1.Analy
|
||||
}
|
||||
|
||||
if analyzeResult != nil {
|
||||
analyzeResults = append(analyzeResults, analyzeResult)
|
||||
analyzeResults = append(analyzeResults, analyzeResult...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +57,17 @@ func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResul
|
||||
return nil, errors.Wrap(err, "failed to download bundle")
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(tmpDir, "version.yaml"))
|
||||
rootDir, err := FindBundleRootDir(tmpDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find root dir")
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(rootDir, "version.yaml"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read version.yaml")
|
||||
}
|
||||
|
||||
analyzers := []*troubleshootv1beta1.Analyze{}
|
||||
analyzers := []*troubleshootv1beta2.Analyze{}
|
||||
|
||||
if analyzersSpec == "" {
|
||||
defaultAnalyzers, err := getDefaultAnalyzers()
|
||||
@@ -72,7 +83,7 @@ func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResul
|
||||
analyzers = parsedAnalyzers
|
||||
}
|
||||
|
||||
return AnalyzeLocal(tmpDir, analyzers)
|
||||
return AnalyzeLocal(rootDir, analyzers)
|
||||
}
|
||||
|
||||
func downloadTroubleshootBundle(bundleURL string, destDir string) error {
|
||||
@@ -139,6 +150,12 @@ func ExtractTroubleshootBundle(reader io.Reader, destDir string) error {
|
||||
}
|
||||
case tar.TypeReg:
|
||||
name := filepath.Join(destDir, header.Name)
|
||||
|
||||
dirName := filepath.Dir(name)
|
||||
if err := os.MkdirAll(dirName, 0755); err != nil {
|
||||
return errors.Wrapf(err, "failed to mkdir for file %s", header.Name)
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open tar file")
|
||||
@@ -154,21 +171,26 @@ func ExtractTroubleshootBundle(reader io.Reader, destDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAnalyzers(spec string) ([]*troubleshootv1beta1.Analyze, error) {
|
||||
func parseAnalyzers(spec string) ([]*troubleshootv1beta2.Analyze, error) {
|
||||
troubleshootscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
obj, _, err := decode([]byte(spec), nil, nil)
|
||||
convertedSpec, err := docrewrite.ConvertToV1Beta2([]byte(spec))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
obj, _, err := decode(convertedSpec, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode analyzers")
|
||||
}
|
||||
|
||||
analyzer := obj.(*troubleshootv1beta1.Analyzer)
|
||||
analyzer := obj.(*troubleshootv1beta2.Analyzer)
|
||||
return analyzer.Spec.Analyzers, nil
|
||||
}
|
||||
|
||||
func getDefaultAnalyzers() ([]*troubleshootv1beta1.Analyze, error) {
|
||||
spec := `apiVersion: troubleshoot.replicated.com/v1beta1
|
||||
func getDefaultAnalyzers() ([]*troubleshootv1beta2.Analyze, error) {
|
||||
spec := `apiVersion: troubleshoot.sh/v1beta2
|
||||
kind: Analyzer
|
||||
metadata:
|
||||
name: defaultAnalyzers
|
||||
@@ -191,12 +213,55 @@ spec:
|
||||
return parseAnalyzers(spec)
|
||||
}
|
||||
|
||||
// FindBundleRootDir detects whether the bundle is stored inside a subdirectory or not.
|
||||
// returns the subdirectory path if so, otherwise, returns the path unchanged
|
||||
func FindBundleRootDir(localBundlePath string) (string, error) {
|
||||
f, err := os.Open(localBundlePath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to open bundle dir")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
names, err := f.Readdirnames(0)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to read dirnames")
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
return "", errors.New("bundle directory is empty")
|
||||
}
|
||||
|
||||
isInSubDir := true
|
||||
for _, name := range names {
|
||||
if name == "version.yaml" {
|
||||
isInSubDir = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isInSubDir {
|
||||
return filepath.Join(localBundlePath, names[0]), nil
|
||||
}
|
||||
|
||||
return localBundlePath, nil
|
||||
}
|
||||
|
||||
func (f fileContentProvider) getFileContents(fileName string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filepath.Join(f.rootDir, fileName))
|
||||
}
|
||||
|
||||
func (f fileContentProvider) getChildFileContents(dirName string) (map[string][]byte, error) {
|
||||
// TODO: walk sub-dirs
|
||||
// return nil, errors.New("not implemnted")
|
||||
return map[string][]byte{}, nil
|
||||
files, err := filepath.Glob(filepath.Join(f.rootDir, dirName))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid glob %q", dirName)
|
||||
}
|
||||
fileArr := map[string][]byte{}
|
||||
for _, filePath := range files {
|
||||
bytes, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "read %q", filePath)
|
||||
}
|
||||
fileArr[filePath] = bytes
|
||||
}
|
||||
return fileArr, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostBlockDevices(hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
contents, err := getCollectedFileContents("system/block_devices.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
var devices []collect.BlockDeviceInfo
|
||||
if err := json.Unmarshal(contents, &devices); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal block devices info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Block Devices"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Fail.When, devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Warn.When, devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostBlockDevicesConditionalToActual(outcome.Pass.When, devices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// <regexp> <op> <count>
|
||||
// example: sdb > 0
|
||||
func compareHostBlockDevicesConditionalToActual(conditional string, devices []collect.BlockDeviceInfo) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
|
||||
rx, err := regexp.Compile(parts[0])
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to compile regex %q", parts[0])
|
||||
}
|
||||
count := countEligibleBlockDevices(rx, devices)
|
||||
|
||||
desiredInt, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to parse desired quantity %q", parts[2])
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case ">":
|
||||
return count > desiredInt, nil
|
||||
case ">=":
|
||||
return count >= desiredInt, nil
|
||||
case "<":
|
||||
return count < desiredInt, nil
|
||||
case "<=":
|
||||
return count <= desiredInt, nil
|
||||
case "=", "==", "===":
|
||||
return count == desiredInt, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unexpected operator %q", parts[1])
|
||||
}
|
||||
|
||||
func countEligibleBlockDevices(rx *regexp.Regexp, devices []collect.BlockDeviceInfo) int {
|
||||
count := 0
|
||||
|
||||
for _, device := range devices {
|
||||
if isEligibleBlockDevice(rx, device, devices) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func isEligibleBlockDevice(rx *regexp.Regexp, device collect.BlockDeviceInfo, devices []collect.BlockDeviceInfo) bool {
|
||||
if !rx.MatchString(device.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.Type != "disk" {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.Mountpoint != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.FilesystemType != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.ReadOnly {
|
||||
return false
|
||||
}
|
||||
|
||||
if device.Removable {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, d := range devices {
|
||||
if d.ParentKernelName == device.KernelName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeBlockDevices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
devices []collect.BlockDeviceInfo
|
||||
hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "sdb == 1, pass when there is an empty /dev/sdb",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sdb == 1",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsPass: true,
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sdb == 1, fail when partitioned",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
},
|
||||
{
|
||||
Name: "sdb1",
|
||||
KernelName: "sdb1",
|
||||
ParentKernelName: "sdb",
|
||||
Type: "part",
|
||||
Major: 8,
|
||||
Minor: 1,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sdb == 1",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsFail: true,
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sdb == 1, fail when it has a filesystem",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "sdb",
|
||||
KernelName: "sdb",
|
||||
Type: "disk",
|
||||
Major: 8,
|
||||
Serial: "disk1",
|
||||
FilesystemType: "ext4",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sdb == 1",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsFail: true,
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ".* > 0, fail when only loop devices are found",
|
||||
devices: []collect.BlockDeviceInfo{
|
||||
{
|
||||
Name: "loop0",
|
||||
KernelName: "loop0",
|
||||
Type: "loop",
|
||||
Major: 7,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ".* > 0",
|
||||
Message: "Block device available",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Block Devices",
|
||||
IsFail: true,
|
||||
Message: "No block device available",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.devices)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostBlockDevices(test.hostAnalyzer, getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostCPU(hostAnalyzer *troubleshootv1beta2.CPUAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
contents, err := getCollectedFileContents("system/cpu.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
cpuInfo := collect.CPUInfo{}
|
||||
if err := json.Unmarshal(contents, &cpuInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal cpu info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Number of CPUs"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Fail.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Warn.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostCPUConditionalToActual(outcome.Pass.When, cpuInfo.LogicalCount, cpuInfo.PhysicalCount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to compare")
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostCPUConditionalToActual(conditional string, logicalCount int, physicalCount int) (res bool, err error) {
|
||||
compareLogical := false
|
||||
comparePhysical := false
|
||||
compareUnspecified := false
|
||||
|
||||
comparator := ""
|
||||
desired := ""
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) == 3 {
|
||||
comparator = parts[1]
|
||||
desired = parts[2]
|
||||
if strings.ToLower(parts[0]) == "logical" {
|
||||
compareLogical = true
|
||||
} else if strings.ToLower(parts[0]) == "physical" {
|
||||
comparePhysical = true
|
||||
} else if strings.ToLower(parts[0]) == "count" {
|
||||
compareUnspecified = true
|
||||
}
|
||||
} else if len(parts) == 2 {
|
||||
compareUnspecified = true
|
||||
comparator = parts[0]
|
||||
desired = parts[1]
|
||||
}
|
||||
|
||||
if !compareLogical && !comparePhysical && !compareUnspecified {
|
||||
return false, errors.New("unable to parse conditional")
|
||||
}
|
||||
|
||||
if compareLogical {
|
||||
return doCompareHostCPU(comparator, desired, logicalCount)
|
||||
} else if comparePhysical {
|
||||
return doCompareHostCPU(comparator, desired, physicalCount)
|
||||
} else {
|
||||
actual := logicalCount
|
||||
if physicalCount > logicalCount {
|
||||
actual = physicalCount
|
||||
}
|
||||
|
||||
return doCompareHostCPU(comparator, desired, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func doCompareHostCPU(operator string, desired string, actual int) (bool, error) {
|
||||
desiredInt, err := strconv.ParseInt(desired, 10, 64)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to parse")
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < int(desiredInt), nil
|
||||
case "<=":
|
||||
return actual <= int(desiredInt), nil
|
||||
case ">":
|
||||
return actual > int(desiredInt), nil
|
||||
case ">=":
|
||||
return actual >= int(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return actual == int(desiredInt), nil
|
||||
}
|
||||
|
||||
return false, errors.New("unknown operator")
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_doCompareHostCPU(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operator string
|
||||
desired string
|
||||
actual int
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "< 16",
|
||||
operator: "<",
|
||||
desired: "16",
|
||||
actual: 8,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "< 8 when actual is 8",
|
||||
operator: "<",
|
||||
desired: "8",
|
||||
actual: 8,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "<= 8 when actual is 8",
|
||||
operator: "<=",
|
||||
desired: "8",
|
||||
actual: 8,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "<= 8 when actual is 16",
|
||||
operator: "<=",
|
||||
desired: "8",
|
||||
actual: 16,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "== 8 when actual is 16",
|
||||
operator: "==",
|
||||
desired: "8",
|
||||
actual: 16,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "== 8 when actual is 8",
|
||||
operator: "==",
|
||||
desired: "8",
|
||||
actual: 8,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := doCompareHostCPU(test.operator, test.desired, test.actual)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_compareHostCPUConditionalToActual(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
when string
|
||||
logicalCount int
|
||||
physicalCount int
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "physical > 4, when physical is 8",
|
||||
when: "physical > 4",
|
||||
logicalCount: 0,
|
||||
physicalCount: 8,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "physical > 4, when physical is 4",
|
||||
when: "physical > 4",
|
||||
logicalCount: 0,
|
||||
physicalCount: 4,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "physical > 4, when physical is 3, logical is 6",
|
||||
when: "physical > 4",
|
||||
logicalCount: 6,
|
||||
physicalCount: 3,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "logical > 4, when physical is 4, logical is 8",
|
||||
when: "logical > 4",
|
||||
logicalCount: 8,
|
||||
physicalCount: 4,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: ">= 4, when physical is 2, logical is 4",
|
||||
when: ">= 4",
|
||||
logicalCount: 4,
|
||||
physicalCount: 2,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "count < 4, when physical is 2, logical is 4",
|
||||
when: "count < 4",
|
||||
logicalCount: 4,
|
||||
physicalCount: 2,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "count <= 4, when physical is 2, logical is 4",
|
||||
when: "count <= 4",
|
||||
logicalCount: 4,
|
||||
physicalCount: 2,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "== 4, physical is 4, logical is 4",
|
||||
when: "== 4",
|
||||
logicalCount: 4,
|
||||
physicalCount: 4,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareHostCPUConditionalToActual(test.when, test.logicalCount, test.physicalCount)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func analyzeHostDiskUsage(hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
key := collect.HostDiskUsageKey(hostAnalyzer.CollectorName)
|
||||
contents, err := getCollectedFileContents(key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get collected file %s", key)
|
||||
}
|
||||
|
||||
diskUsageInfo := collect.DiskUsageInfo{}
|
||||
if err := json.Unmarshal(contents, &diskUsageInfo); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal disk usage info from %s", key)
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = fmt.Sprintf("Disk Usage %s", hostAnalyzer.CollectorName)
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostDiskUsageConditionalToActual(outcome.Fail.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %q", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostDiskUsageConditionalToActual(outcome.Warn.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %q", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostDiskUsageConditionalToActual(outcome.Pass.When, diskUsageInfo.TotalBytes, diskUsageInfo.UsedBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %q", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostDiskUsageConditionalToActual(conditional string, totalBytes uint64, usedBytes uint64) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("conditional must have exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
stat := strings.ToLower(parts[0])
|
||||
comparator := parts[1]
|
||||
desired := parts[2]
|
||||
|
||||
switch stat {
|
||||
case "total":
|
||||
return doCompareHostDiskUsage(comparator, desired, totalBytes)
|
||||
case "used":
|
||||
return doCompareHostDiskUsage(comparator, desired, usedBytes)
|
||||
case "available":
|
||||
return doCompareHostDiskUsage(comparator, desired, totalBytes-usedBytes)
|
||||
case "used/total":
|
||||
used := float64(usedBytes) / float64(totalBytes)
|
||||
return doCompareHostDiskUsagePercent(comparator, desired, used)
|
||||
case "available/total":
|
||||
available := float64(totalBytes-usedBytes) / float64(totalBytes)
|
||||
return doCompareHostDiskUsagePercent(comparator, desired, available)
|
||||
}
|
||||
return false, fmt.Errorf("unknown disk usage statistic %q", stat)
|
||||
}
|
||||
|
||||
func doCompareHostDiskUsage(operator string, desired string, actual uint64) (bool, error) {
|
||||
quantity, err := resource.ParseQuantity(desired)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not parse quantity %q", desired)
|
||||
}
|
||||
desiredInt, ok := quantity.AsInt64()
|
||||
if !ok {
|
||||
return false, fmt.Errorf("could not parse quantity %q", desired)
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < uint64(desiredInt), nil
|
||||
case "<=":
|
||||
return actual <= uint64(desiredInt), nil
|
||||
case ">":
|
||||
return actual > uint64(desiredInt), nil
|
||||
case ">=":
|
||||
return actual >= uint64(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return actual == uint64(desiredInt), nil
|
||||
}
|
||||
|
||||
return false, errors.New("unknown operator")
|
||||
}
|
||||
|
||||
func doCompareHostDiskUsagePercent(operator string, desired string, actual float64) (bool, error) {
|
||||
isPercent := false
|
||||
if strings.HasSuffix(desired, "%") {
|
||||
desired = strings.TrimSuffix(desired, "%")
|
||||
isPercent = true
|
||||
}
|
||||
desiredPercent, err := strconv.ParseFloat(desired, 64)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "parsed desired quantity")
|
||||
}
|
||||
if isPercent {
|
||||
desiredPercent = desiredPercent / 100.0
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return actual < desiredPercent, nil
|
||||
case "<=":
|
||||
return actual <= desiredPercent, nil
|
||||
case ">":
|
||||
return actual > desiredPercent, nil
|
||||
case ">=":
|
||||
return actual >= desiredPercent, nil
|
||||
case "=", "==", "===":
|
||||
return actual == desiredPercent, nil
|
||||
}
|
||||
|
||||
return false, errors.New("unknown operator")
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_doCompareHostDiskUsage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operator string
|
||||
desired string
|
||||
actual uint64
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: ">= 1Gi, when actual is 2Gi",
|
||||
operator: ">=",
|
||||
desired: "1Gi",
|
||||
actual: 2147483648,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "<= 1Gi, when actual is 1GB",
|
||||
operator: "<=",
|
||||
desired: "1Gi",
|
||||
actual: 1000000000,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "< 20Gi, when actual is 15Gi",
|
||||
operator: "<",
|
||||
desired: "20Gi",
|
||||
actual: 15 * 1024 * 1024 * 1024,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "< 20Gi, when actual is 20Gi",
|
||||
operator: "<",
|
||||
desired: "20Gi",
|
||||
actual: 20 * 1024 * 1024 * 1024,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "> 1073741824, when actual is 1024",
|
||||
operator: ">",
|
||||
desired: "1073741824",
|
||||
actual: 1024,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "= 4096, when actual is 4096",
|
||||
operator: "=",
|
||||
desired: "4096",
|
||||
actual: 4096,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "= 4096, when actual is 1024",
|
||||
operator: "=",
|
||||
desired: "4096",
|
||||
actual: 1024,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "= 4096, when actual is 5000",
|
||||
operator: "=",
|
||||
desired: "4096",
|
||||
actual: 5000,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := doCompareHostDiskUsage(test.operator, test.desired, test.actual)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_doCompareHostDiskUsagePercent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
operator string
|
||||
desired string
|
||||
actual float64
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: ">= .20, when actual is .30",
|
||||
operator: ">=",
|
||||
desired: ".20",
|
||||
actual: .30,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: ">= .20, when actual is .10",
|
||||
operator: ">=",
|
||||
desired: ".20",
|
||||
actual: .10,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: ">= .20, when actual is .20",
|
||||
operator: ">=",
|
||||
desired: ".20",
|
||||
actual: .20,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "> .20, when actual is .30",
|
||||
operator: ">",
|
||||
desired: ".20",
|
||||
actual: .30,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "> .20, when actual is .10",
|
||||
operator: ">",
|
||||
desired: ".20",
|
||||
actual: .10,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "> .20, when actual is .20",
|
||||
operator: ">",
|
||||
desired: ".20",
|
||||
actual: .20,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "<= .20, when actual is .30",
|
||||
operator: "<=",
|
||||
desired: ".20",
|
||||
actual: .30,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "<= .20, when actual is .10",
|
||||
operator: "<=",
|
||||
desired: ".20",
|
||||
actual: .10,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "<= .20, when actual is .20",
|
||||
operator: "<=",
|
||||
desired: ".20",
|
||||
actual: .20,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "< .20, when actual is .30",
|
||||
operator: "<",
|
||||
desired: ".20",
|
||||
actual: .30,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "< .20, when actual is .10",
|
||||
operator: "<",
|
||||
desired: ".20",
|
||||
actual: .10,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "< .20, when actual is .20",
|
||||
operator: "<",
|
||||
desired: ".20",
|
||||
actual: .20,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "= .20, when actual is .30",
|
||||
operator: "=",
|
||||
desired: ".20",
|
||||
actual: .30,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "= .20, when actual is .10",
|
||||
operator: "=",
|
||||
desired: ".20",
|
||||
actual: .10,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "= .20, when actual is .20",
|
||||
operator: "=",
|
||||
desired: ".20",
|
||||
actual: .20,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "= 20%, when actual is .20",
|
||||
operator: "=",
|
||||
desired: "20%",
|
||||
actual: .20,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "= 20%, when actual is 20",
|
||||
operator: "=",
|
||||
desired: "20%",
|
||||
actual: 20,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := doCompareHostDiskUsagePercent(test.operator, test.desired, test.actual)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeHostDiskUsage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
diskUsageInfo *collect.DiskUsageInfo
|
||||
hostAnalyzer *troubleshootv1beta2.DiskUsageAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Fail on insuffient total ephemeral disk space",
|
||||
diskUsageInfo: &collect.DiskUsageInfo{
|
||||
TotalBytes: 10 * 1024 * 1024 * 1024,
|
||||
UsedBytes: 5 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
|
||||
CollectorName: "ephemeral",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "used/total >= 80%",
|
||||
Message: "/var/lib/kubelet is more than 80% full",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "total <= 10Gi",
|
||||
Message: "/var/lib/kubelet requires at least 10Gi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Disk Usage ephemeral",
|
||||
IsFail: true,
|
||||
Message: "/var/lib/kubelet requires at least 10Gi",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fail on insuffient available ephemeral disk space percentage",
|
||||
diskUsageInfo: &collect.DiskUsageInfo{
|
||||
TotalBytes: 10 * 1024 * 1024 * 1024,
|
||||
UsedBytes: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
|
||||
CollectorName: "ephemeral",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "total < 10Gi",
|
||||
Message: "/var/lib/kubelet requires at least 10Gi",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "used/total >= 80%",
|
||||
Message: "/var/lib/kubelet is more than 80% full",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Disk Usage ephemeral",
|
||||
IsFail: true,
|
||||
Message: "/var/lib/kubelet is more than 80% full",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Warn on high ephemeral disk space usage",
|
||||
diskUsageInfo: &collect.DiskUsageInfo{
|
||||
TotalBytes: 1024 * 1024 * 1024 * 1024,
|
||||
UsedBytes: 100 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
|
||||
CollectorName: "ephemeral",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "total < 10Gi",
|
||||
Message: "/var/lib/kubelet requires at least 10Gi",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "used/total >= 80%",
|
||||
Message: "/var/lib/kubelet is more than 80% full",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "used >= 100Gi",
|
||||
Message: "/var/lib/kubelet has more than 100Gi used",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Disk Usage ephemeral",
|
||||
IsWarn: true,
|
||||
Message: "/var/lib/kubelet has more than 100Gi used",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pass on ephemeral disk space available",
|
||||
diskUsageInfo: &collect.DiskUsageInfo{
|
||||
TotalBytes: 12 * 1024 * 1024 * 1024,
|
||||
UsedBytes: 1 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.DiskUsageAnalyze{
|
||||
CollectorName: "ephemeral",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "available > 10Gi",
|
||||
Message: "/var/lib/kubelet has at least 10Gi available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Disk Usage ephemeral",
|
||||
IsPass: true,
|
||||
Message: "/var/lib/kubelet has at least 10Gi available",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.diskUsageInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostDiskUsage(test.hostAnalyzer, getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
type httpResult struct {
|
||||
Error *collect.HTTPError
|
||||
Response *collect.HTTPResponse
|
||||
}
|
||||
|
||||
func analyzeHostHTTP(hostAnalyzer *troubleshootv1beta2.HTTPAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
name := filepath.Join("http", "result.json")
|
||||
if hostAnalyzer.CollectorName != "" {
|
||||
name = filepath.Join("http", hostAnalyzer.CollectorName+".json")
|
||||
}
|
||||
contents, err := getCollectedFileContents(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
httpInfo := &httpResult{}
|
||||
if err := json.Unmarshal(contents, httpInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal http result")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "HTTP Request"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostHTTPConditionalToActual(outcome.Fail.When, httpInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostHTTPConditionalToActual(outcome.Warn.When, httpInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostHTTPConditionalToActual(outcome.Pass.When, httpInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostHTTPConditionalToActual(conditional string, result *httpResult) (res bool, err error) {
|
||||
if conditional == "error" {
|
||||
return result.Error != nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Failed to parse conditional: got %d parts", len(parts))
|
||||
}
|
||||
|
||||
if parts[0] != "statusCode" {
|
||||
return false, errors.New(`Conditional must begin with keyword "statusCode"`)
|
||||
}
|
||||
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
|
||||
return false, errors.New(`Only supported operator is "=="`)
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if result.Response == nil {
|
||||
return false, err
|
||||
}
|
||||
return result.Response.Status == i, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostHTTP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
httpResult *httpResult
|
||||
hostAnalyzer *troubleshootv1beta2.HTTPAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "error",
|
||||
httpResult: &httpResult{
|
||||
Error: &collect.HTTPError{
|
||||
Message: "i/o timeout",
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
|
||||
CollectorName: "registry",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "error",
|
||||
Message: "Failed to reach replicated.registry.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "HTTP Request",
|
||||
IsFail: true,
|
||||
Message: "Failed to reach replicated.registry.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "200",
|
||||
httpResult: &httpResult{
|
||||
Response: &collect.HTTPResponse{
|
||||
Status: 200,
|
||||
},
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.HTTPAnalyze{
|
||||
CollectorName: "registry",
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "error",
|
||||
Message: "Failed to reach replicated.registry.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "statusCode == 204",
|
||||
Message: "No content",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "statusCode == 200",
|
||||
Message: "Successfully reached registry",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "HTTP Request",
|
||||
IsPass: true,
|
||||
Message: "Successfully reached registry",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.httpResult)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostHTTP(test.hostAnalyzer, getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func analyzeHostMemory(hostAnalyzer *troubleshootv1beta2.MemoryAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
contents, err := getCollectedFileContents("system/memory.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
memoryInfo := collect.MemoryInfo{}
|
||||
if err := json.Unmarshal(contents, &memoryInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal memory info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Amount of Memory"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostMemoryConditionalToActual(outcome.Fail.When, memoryInfo.Total)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostMemoryConditionalToActual(outcome.Warn.When, memoryInfo.Total)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostMemoryConditionalToActual(outcome.Pass.When, memoryInfo.Total)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostMemoryConditionalToActual(conditional string, total uint64) (res bool, err error) {
|
||||
parts := strings.Split(conditional, " ")
|
||||
if len(parts) != 2 {
|
||||
return false, fmt.Errorf("Expected 2 parts in conditional, got %d", len(parts))
|
||||
}
|
||||
|
||||
operator := parts[0]
|
||||
desired := parts[1]
|
||||
quantity, err := resource.ParseQuantity(desired)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not parse quantity %q", desired)
|
||||
}
|
||||
desiredInt, ok := quantity.AsInt64()
|
||||
if !ok {
|
||||
return false, fmt.Errorf("could not parse quantity %q", desired)
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "<":
|
||||
return total < uint64(desiredInt), nil
|
||||
case "<=":
|
||||
return total <= uint64(desiredInt), nil
|
||||
case ">":
|
||||
return total > uint64(desiredInt), nil
|
||||
case ">=":
|
||||
return total >= uint64(desiredInt), nil
|
||||
case "=", "==", "===":
|
||||
return total == uint64(desiredInt), nil
|
||||
}
|
||||
|
||||
return false, errors.New("unknown operator")
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_doCompareHostMemory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conditional string
|
||||
actual uint64
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "< 16Gi when actual is 8Gi",
|
||||
conditional: "< 16Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "< 8Gi when actual is 8Gi",
|
||||
conditional: "< 8Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "<= 8Gi when actual is 8Gi",
|
||||
conditional: "<= 8Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "<= 8Gi when actual is 16Gi",
|
||||
conditional: "<= 8Gi",
|
||||
actual: 16 * 1024 * 1024 * 1024,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "== 8Gi when actual is 16Gi",
|
||||
conditional: "== 8Gi",
|
||||
actual: 16 * 1024 * 1024 * 1024,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "== 8Gi when actual is 8Gi",
|
||||
conditional: "== 8Gi",
|
||||
actual: 8 * 1024 * 1024 * 1024,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "== 8000000000 when actual is 8000000000",
|
||||
conditional: "== 8000000000",
|
||||
actual: 8 * 1000 * 1000 * 1000,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareHostMemoryConditionalToActual(test.conditional, test.actual)
|
||||
req.NoError(err)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeHostMemory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
memoryInfo *collect.MemoryInfo
|
||||
hostAnalyzer *troubleshootv1beta2.MemoryAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Pass on memory available",
|
||||
memoryInfo: &collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: ">= 4Gi",
|
||||
Message: "System has at least 4Gi of memory",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Amount of Memory",
|
||||
IsPass: true,
|
||||
Message: "System has at least 4Gi of memory",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fail on memory available",
|
||||
memoryInfo: &collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 16Gi",
|
||||
Message: "System requires at least 16Gi of memory",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Amount of Memory",
|
||||
IsFail: true,
|
||||
Message: "System requires at least 16Gi of memory",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Warn on memory available",
|
||||
memoryInfo: &collect.MemoryInfo{
|
||||
Total: 8 * 1024 * 1024 * 1024,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.MemoryAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "< 4Gi",
|
||||
Message: "System requires at least 4Gi of memory",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "<= 8Gi",
|
||||
Message: "System performs best with more than 8Gi of memory",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Amount of Memory",
|
||||
IsWarn: true,
|
||||
Message: "System performs best with more than 8Gi of memory",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.memoryInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostMemory(test.hostAnalyzer, getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostTCPLoadBalancer(hostAnalyzer *troubleshootv1beta2.TCPLoadBalancerAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collectorName := hostAnalyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "tcpLoadBalancer"
|
||||
}
|
||||
|
||||
fullPath := path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", collectorName))
|
||||
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
|
||||
}
|
||||
actual := collect.NetworkStatusResult{}
|
||||
if err := json.Unmarshal(collected, &actual); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal collected")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "TCP Load Balancer"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if string(actual.Status) == outcome.Fail.When {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if string(actual.Status) == outcome.Warn.When {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if string(actual.Status) == outcome.Pass.When {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeHostTCPPortStatus(hostAnalyzer *troubleshootv1beta2.TCPPortStatusAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
fullPath := path.Join("tcpPortStatus", "tcpPortStatus.json")
|
||||
if hostAnalyzer.CollectorName != "" {
|
||||
fullPath = path.Join("tcpPortStatus", fmt.Sprintf("%s.json", hostAnalyzer.CollectorName))
|
||||
}
|
||||
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath)
|
||||
}
|
||||
actual := collect.NetworkStatusResult{}
|
||||
if err := json.Unmarshal(collected, &actual); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal collected")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "TCP Port Status"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if string(actual.Status) == outcome.Fail.When {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if string(actual.Status) == outcome.Warn.When {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if string(actual.Status) == outcome.Pass.When {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
const (
|
||||
SynchronizedActive = "synchronized+active"
|
||||
SynchronizedInactive = "synchronized+inactive"
|
||||
UnsynchronizedActive = "unsynchronized+active"
|
||||
UnsynchronizedInactive = "unsynchronized+inactive"
|
||||
)
|
||||
|
||||
func analyzeHostTime(hostAnalyzer *troubleshootv1beta2.TimeAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
contents, err := getCollectedFileContents("system/time.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get collected file")
|
||||
}
|
||||
|
||||
timeInfo := collect.TimeInfo{}
|
||||
if err := json.Unmarshal(contents, &timeInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal time info")
|
||||
}
|
||||
|
||||
result := AnalyzeResult{}
|
||||
|
||||
title := hostAnalyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Time"
|
||||
}
|
||||
result.Title = title
|
||||
|
||||
for _, outcome := range hostAnalyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
if outcome.Fail.When == "" {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostTimeStatusToActual(outcome.Fail.When, timeInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Fail.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsFail = true
|
||||
result.Message = outcome.Fail.Message
|
||||
result.URI = outcome.Fail.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Warn != nil {
|
||||
if outcome.Warn.When == "" {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostTimeStatusToActual(outcome.Warn.When, timeInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Warn.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsWarn = true
|
||||
result.Message = outcome.Warn.Message
|
||||
result.URI = outcome.Warn.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
} else if outcome.Pass != nil {
|
||||
if outcome.Pass.When == "" {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
isMatch, err := compareHostTimeStatusToActual(outcome.Pass.When, timeInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compare %s", outcome.Pass.When)
|
||||
}
|
||||
|
||||
if isMatch {
|
||||
result.IsPass = true
|
||||
result.Message = outcome.Pass.Message
|
||||
result.URI = outcome.Pass.URI
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func compareHostTimeStatusToActual(status string, timeInfo collect.TimeInfo) (res bool, err error) {
|
||||
parts := strings.Split(status, " ")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("Expected exactly 3 parts, got %d", len(parts))
|
||||
}
|
||||
if parts[0] == "timezone" {
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" && parts[1] != "!=" {
|
||||
return false, errors.New(`Only supported operators are "==" and "!="`)
|
||||
}
|
||||
if parts[1] == "!=" {
|
||||
return parts[2] != timeInfo.Timezone, nil
|
||||
}
|
||||
return parts[2] == timeInfo.Timezone, nil
|
||||
}
|
||||
|
||||
if parts[0] == "ntp" {
|
||||
if parts[1] != "=" && parts[1] != "==" && parts[1] != "===" {
|
||||
return false, errors.New(`Only supported operator is "=="`)
|
||||
}
|
||||
|
||||
switch parts[2] {
|
||||
case SynchronizedActive:
|
||||
return timeInfo.NTPSynchronized && timeInfo.NTPActive, nil
|
||||
case SynchronizedInactive:
|
||||
return timeInfo.NTPSynchronized && !timeInfo.NTPActive, nil
|
||||
case UnsynchronizedActive:
|
||||
return !timeInfo.NTPSynchronized && timeInfo.NTPActive, nil
|
||||
case UnsynchronizedInactive:
|
||||
return !timeInfo.NTPSynchronized && !timeInfo.NTPActive, nil
|
||||
default:
|
||||
return false, fmt.Errorf("Unknown status %q. Allowed values are %q, %q, %q, or %q", parts[2], SynchronizedActive, SynchronizedInactive, UnsynchronizedActive, UnsynchronizedInactive)
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Unknown keyword: %s", parts[0])
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeHostTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timeInfo *collect.TimeInfo
|
||||
hostAnalyzer *troubleshootv1beta2.TimeAnalyze
|
||||
result *AnalyzeResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "ntp == synchronized+active",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsPass: true,
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ntp == unsynchronized+inactive",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: false,
|
||||
NTPActive: false,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsFail: true,
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ntp == unsynchronized+active",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: false,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+active",
|
||||
Message: "System clock not yet synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsWarn: true,
|
||||
Message: "System clock not yet synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ntp == synchronized+inactive",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: false,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+inactive",
|
||||
Message: "System clock not synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == unsynchronized+active",
|
||||
Message: "System clock not yet synchronized",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+inactive",
|
||||
Message: "System clock synchronized for now",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "ntp == synchronized+active",
|
||||
Message: "System clock synchronized and NTP is active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsWarn: true,
|
||||
Message: "System clock synchronized for now",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timezone",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "UTC",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "timezone == UTC",
|
||||
Message: "Timezone is set to UTC",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "timezone not set to UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsPass: true,
|
||||
Message: "Timezone is set to UTC",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timezone is not UTC",
|
||||
timeInfo: &collect.TimeInfo{
|
||||
Timezone: "PST",
|
||||
NTPSynchronized: true,
|
||||
NTPActive: true,
|
||||
},
|
||||
hostAnalyzer: &troubleshootv1beta2.TimeAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "timezone != UTC",
|
||||
Message: "Timezone is not set to UTC",
|
||||
},
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "Timezone is set to UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: &AnalyzeResult{
|
||||
Title: "Time",
|
||||
IsFail: true,
|
||||
Message: "Timezone is not set to UTC",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
b, err := json.Marshal(test.timeInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getCollectedFileContents := func(filename string) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
result, err := analyzeHostTime(test.hostAnalyzer, getCollectedFileContents)
|
||||
if test.expectErr {
|
||||
req.Error(err)
|
||||
} else {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,41 @@ package analyzer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func analyzeImagePullSecret(analyzer *troubleshootv1beta1.ImagePullSecret, getChildCollectedFileContents func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeImagePullSecret(analyzer *troubleshootv1beta2.ImagePullSecret, getChildCollectedFileContents func(string) (map[string][]byte, error)) (*AnalyzeResult, error) {
|
||||
imagePullSecrets, err := getChildCollectedFileContents("cluster-resources/image-pull-secrets")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get file contents for image pull secrets")
|
||||
}
|
||||
|
||||
var failOutcome *troubleshootv1beta1.Outcome
|
||||
var passOutcome *troubleshootv1beta1.Outcome
|
||||
var failOutcome *troubleshootv1beta2.SingleOutcome
|
||||
var passOutcome *troubleshootv1beta2.SingleOutcome
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
failOutcome = outcome
|
||||
failOutcome = outcome.Fail
|
||||
} else if outcome.Pass != nil {
|
||||
passOutcome = outcome
|
||||
passOutcome = outcome.Pass
|
||||
}
|
||||
}
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = "Image Pull Secrets"
|
||||
}
|
||||
|
||||
result := AnalyzeResult{
|
||||
Title: analyzer.CheckName,
|
||||
Title: title,
|
||||
IconKey: "kubernetes_image_pull_secret",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/image-pull-secret.svg?w=16&h=14",
|
||||
IsFail: true,
|
||||
Message: failOutcome.Fail.Message,
|
||||
URI: failOutcome.Fail.URI,
|
||||
}
|
||||
if failOutcome != nil {
|
||||
result.Message = failOutcome.Message
|
||||
result.URI = failOutcome.URI
|
||||
}
|
||||
|
||||
for _, v := range imagePullSecrets {
|
||||
@@ -42,11 +49,20 @@ func analyzeImagePullSecret(analyzer *troubleshootv1beta1.ImagePullSecret, getCh
|
||||
if registry == analyzer.RegistryName {
|
||||
result.IsPass = true
|
||||
result.IsFail = false
|
||||
result.Message = passOutcome.Pass.Message
|
||||
result.URI = passOutcome.Pass.URI
|
||||
if passOutcome != nil {
|
||||
result.Message = passOutcome.Message
|
||||
result.URI = passOutcome.URI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.Message == "" {
|
||||
if result.IsPass {
|
||||
result.Message = fmt.Sprintf("Credentials to pull from: %s found", analyzer.RegistryName)
|
||||
} else {
|
||||
result.Message = fmt.Sprintf("Credentials to pull from: %s not ound", analyzer.RegistryName)
|
||||
}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ package analyzer
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
func analyzeIngress(analyzer *troubleshootv1beta1.Ingress, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
ingressData, err := getCollectedFileContents("cluster-resources/storage-classes.json")
|
||||
func analyzeIngress(analyzer *troubleshootv1beta2.Ingress, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
ingressData, err := getCollectedFileContents(filepath.Join("cluster-resources", "ingress", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(scopeagent.Run(m))
|
||||
}
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeMysql(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeMysql(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collectorName := analyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "mysql"
|
||||
@@ -28,13 +28,13 @@ func analyzeMysql(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFil
|
||||
return nil, errors.Wrap(err, "failed to unmarshal databased connection result")
|
||||
}
|
||||
|
||||
checkName := analyzer.CheckName
|
||||
if checkName == "" {
|
||||
checkName = collectorName
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = collectorName
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: checkName,
|
||||
Title: title,
|
||||
IconKey: "kubernetes_mysql_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/mysql-analyze.svg",
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeNodeResources(analyzer *troubleshootv1beta2.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
|
||||
@@ -313,12 +313,22 @@ func findMax(nodes []corev1.Node, property string) *resource.Quantity {
|
||||
return max
|
||||
}
|
||||
|
||||
func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta1.NodeResourceFilters) (bool, error) {
|
||||
func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta2.NodeResourceFilters) (bool, error) {
|
||||
if filters == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// all filters must pass for this to pass
|
||||
if filters.Selector != nil {
|
||||
for k, v := range filters.Selector.MatchLabel {
|
||||
l, found := node.Labels[k]
|
||||
if !found {
|
||||
return false, nil
|
||||
} else if l != v {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filters.CPUCapacity != "" {
|
||||
parsed, err := resource.ParseQuantity(filters.CPUCapacity)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ package analyzer
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -386,6 +386,11 @@ func Test_compareNodeResourceConditionalToActual(t *testing.T) {
|
||||
|
||||
func Test_nodeMatchesFilters(t *testing.T) {
|
||||
node := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"label": "value",
|
||||
},
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Capacity: corev1.ResourceList{
|
||||
"attachable-volumes-aws-ebs": resource.MustParse("25"),
|
||||
@@ -411,13 +416,13 @@ func Test_nodeMatchesFilters(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
node corev1.Node
|
||||
filters *troubleshootv1beta1.NodeResourceFilters
|
||||
filters *troubleshootv1beta2.NodeResourceFilters
|
||||
expectResult bool
|
||||
}{
|
||||
{
|
||||
name: "true when empty filters",
|
||||
node: node,
|
||||
filters: &troubleshootv1beta1.NodeResourceFilters{},
|
||||
filters: &troubleshootv1beta2.NodeResourceFilters{},
|
||||
expectResult: true,
|
||||
},
|
||||
{
|
||||
@@ -428,7 +433,7 @@ func Test_nodeMatchesFilters(t *testing.T) {
|
||||
{
|
||||
name: "false when allocatable memory is too high",
|
||||
node: node,
|
||||
filters: &troubleshootv1beta1.NodeResourceFilters{
|
||||
filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
MemoryAllocatable: "16Gi",
|
||||
},
|
||||
expectResult: false,
|
||||
@@ -436,11 +441,47 @@ func Test_nodeMatchesFilters(t *testing.T) {
|
||||
{
|
||||
name: "true when allocatable memory is available",
|
||||
node: node,
|
||||
filters: &troubleshootv1beta1.NodeResourceFilters{
|
||||
filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
MemoryAllocatable: "4Gi",
|
||||
},
|
||||
expectResult: true,
|
||||
},
|
||||
{
|
||||
name: "false when the label does not exist",
|
||||
node: node,
|
||||
filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
Selector: &troubleshootv1beta2.NodeResourceSelectors{
|
||||
MatchLabel: map[string]string{
|
||||
"label2": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: false,
|
||||
},
|
||||
{
|
||||
name: "false when the label value differs",
|
||||
node: node,
|
||||
filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
Selector: &troubleshootv1beta2.NodeResourceSelectors{
|
||||
MatchLabel: map[string]string{
|
||||
"label": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: false,
|
||||
},
|
||||
{
|
||||
name: "true when the label key and value match",
|
||||
node: node,
|
||||
filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
Selector: &troubleshootv1beta2.NodeResourceSelectors{
|
||||
MatchLabel: map[string]string{
|
||||
"label": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -455,3 +496,388 @@ func Test_nodeMatchesFilters(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_analyzeNodeResources(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer *troubleshootv1beta2.NodeResources
|
||||
want *AnalyzeResult
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "at least one pod per node capacity", // this is intended as a general "yes, the end-to-end test works"
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(podCapacity) < 1",
|
||||
Message: "There exist nodes with no pod capacity",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(podCapacity) < 50",
|
||||
Message: "There exist nodes with under 50 pod capacity",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(podCapacity) >= 50",
|
||||
Message: "All nodes can host at least 50 pods",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: nil,
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "Node Resources",
|
||||
Message: "All nodes can host at least 50 pods",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "at least 16GB ram", // this is intended as a general "yes, the end-to-end fails properly"
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(memoryCapacity) < 16Gi",
|
||||
Message: "There exist nodes with under 16Gb of RAM",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: nil,
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsFail: true,
|
||||
IsWarn: false,
|
||||
Title: "Node Resources",
|
||||
Message: "There exist nodes with under 16Gb of RAM",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "at least 16GB ram in g-8vcpu-32gb nodes", // this is intended as a "does filtering work" test
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(memoryCapacity) < 16Gi",
|
||||
Message: "There exist nodes with under 16Gb of RAM",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(memoryCapacity) >= 16Gi",
|
||||
Message: "All nodes have at least 16Gb of RAM",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
Selector: &troubleshootv1beta2.NodeResourceSelectors{
|
||||
MatchLabel: map[string]string{
|
||||
"node.kubernetes.io/instance-type": "g-8vcpu-32gb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "Node Resources",
|
||||
Message: "All nodes have at least 16Gb of RAM",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "at least 4 cores in all nodes", // cpu count end-to-end
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "quadcore",
|
||||
},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(cpuCapacity) < 4",
|
||||
Message: "There exist nodes with under 4 cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsFail: true,
|
||||
IsWarn: false,
|
||||
Title: "quadcore",
|
||||
Message: "There exist nodes with under 4 cores",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "at least 4 cores in all g-8vcpu-32gb nodes", // cpu count end-to-end with filtering
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(cpuCapacity) < 4",
|
||||
Message: "There exist nodes with under 4 cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "min(cpuCapacity) >= 4",
|
||||
Message: "All nodes have at least 4 cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
Selector: &troubleshootv1beta2.NodeResourceSelectors{
|
||||
MatchLabel: map[string]string{
|
||||
"node.kubernetes.io/instance-type": "g-8vcpu-32gb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "Node Resources",
|
||||
Message: "All nodes have at least 4 cores",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "at least 8 cores in one node", // "max" e2e test
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "bignode-exists",
|
||||
},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "max(cpuCapacity) < 8",
|
||||
Message: "There isn't a node with 8 or more cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "max(cpuCapacity) >= 8",
|
||||
Message: "There is a node with at least 8 cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "bignode-exists",
|
||||
Message: "There is a node with at least 8 cores",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unfiltered CPU totals",
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "total-cpu",
|
||||
},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) < 6",
|
||||
Message: "there are less than 6 total cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) > 6",
|
||||
Message: "there are more than 6 total cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) = 6",
|
||||
Message: "There are exactly 6 total cores",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsFail: false,
|
||||
IsWarn: true,
|
||||
Title: "total-cpu",
|
||||
Message: "there are more than 6 total cores",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "6 cores in s-2vcpu-4gb nodes",
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "s-2vcpu-4gb total",
|
||||
},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) < 6",
|
||||
Message: "there are less than 3 s-2vcpu-4gb nodes",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) > 6",
|
||||
Message: "there are more than 3 s-2vcpu-4gb nodes",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) = 6",
|
||||
Message: "There are exactly 3 s-2vcpu-4gb nodes",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
Selector: &troubleshootv1beta2.NodeResourceSelectors{
|
||||
MatchLabel: map[string]string{
|
||||
"node.kubernetes.io/instance-type": "s-2vcpu-4gb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "s-2vcpu-4gb total",
|
||||
Message: "There are exactly 3 s-2vcpu-4gb nodes",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "8 cores in nodes with at least 8gb of ram", // validate that filtering based on memory capacity works
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "memory filter",
|
||||
},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) < 8",
|
||||
Message: "less than 8 CPUs in nodes with 8Gb of ram",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Warn: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) > 8",
|
||||
Message: "more than 8 CPUs in nodes with 8Gb of ram",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) = 8",
|
||||
Message: "exactly 8 CPUs total in nodes with 8Gb of ram",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: &troubleshootv1beta2.NodeResourceFilters{
|
||||
MemoryCapacity: "8Gi",
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "memory filter",
|
||||
Message: "exactly 8 CPUs total in nodes with 8Gb of ram",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "no pass or fail", // validate that the pass message is not always shown
|
||||
analyzer: &troubleshootv1beta2.NodeResources{
|
||||
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
|
||||
CheckName: "no outcome",
|
||||
},
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
When: "sum(cpuCapacity) = 8",
|
||||
Message: "exactly 8 CPUs total in nodes",
|
||||
URI: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsFail: false,
|
||||
IsWarn: false,
|
||||
Title: "no outcome",
|
||||
Message: "",
|
||||
URI: "",
|
||||
IconKey: "kubernetes_node_resources",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/node-resources.svg?w=16&h=18",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
|
||||
got, err := analyzeNodeResources(tt.analyzer, getExampleNodeContents)
|
||||
req.NoError(err)
|
||||
req.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzePostgres(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzePostgres(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collectorName := analyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "postgres"
|
||||
@@ -28,13 +28,13 @@ func analyzePostgres(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollected
|
||||
return nil, errors.Wrap(err, "failed to unmarshal databased connection result")
|
||||
}
|
||||
|
||||
checkName := analyzer.CheckName
|
||||
if checkName == "" {
|
||||
checkName = collectorName
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = collectorName
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: checkName,
|
||||
Title: title,
|
||||
IconKey: "kubernetes_postgres_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/postgres-analyze.svg",
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeRedis(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeRedis(analyzer *troubleshootv1beta2.DatabaseAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collectorName := analyzer.CollectorName
|
||||
if collectorName == "" {
|
||||
collectorName = "redis"
|
||||
@@ -28,13 +28,13 @@ func analyzeRedis(analyzer *troubleshootv1beta1.DatabaseAnalyze, getCollectedFil
|
||||
return nil, errors.Wrap(err, "failed to unmarshal database connection result")
|
||||
}
|
||||
|
||||
checkName := analyzer.CheckName
|
||||
if checkName == "" {
|
||||
checkName = collectorName
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = collectorName
|
||||
}
|
||||
|
||||
result := &AnalyzeResult{
|
||||
Title: checkName,
|
||||
Title: title,
|
||||
IconKey: "kubernetes_redis_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/redis-analyze.svg",
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
)
|
||||
|
||||
func analyzeSecret(analyzer *troubleshootv1beta1.AnalyzeSecret, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeSecret(analyzer *troubleshootv1beta2.AnalyzeSecret, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
secretData, err := getCollectedFileContents(fmt.Sprintf("secrets/%s/%s.json", analyzer.Namespace, analyzer.SecretName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -30,7 +30,7 @@ func analyzeSecret(analyzer *troubleshootv1beta1.AnalyzeSecret, getCollectedFile
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/secret.svg?w=13&h=16",
|
||||
}
|
||||
|
||||
var failOutcome *troubleshootv1beta1.Outcome
|
||||
var failOutcome *troubleshootv1beta2.Outcome
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Fail != nil {
|
||||
failOutcome = outcome
|
||||
|
||||
@@ -3,15 +3,15 @@ package analyzer
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
func analyzeStatefulsetStatus(analyzer *troubleshootv1beta1.StatefulsetStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collected, err := getCollectedFileContents(path.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
func analyzeStatefulsetStatus(analyzer *troubleshootv1beta2.StatefulsetStatus, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
collected, err := getCollectedFileContents(filepath.Join("cluster-resources", "statefulsets", fmt.Sprintf("%s.json", analyzer.Namespace)))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read collected statefulsets from namespace")
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func analyzeStatefulsetStatus(analyzer *troubleshootv1beta1.StatefulsetStatus, g
|
||||
var status *appsv1.StatefulSetStatus
|
||||
for _, statefulset := range statefulsets {
|
||||
if statefulset.Name == analyzer.Name {
|
||||
status = &statefulset.Status
|
||||
status = statefulset.Status.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
storagev1beta1 "k8s.io/api/storage/v1beta1"
|
||||
)
|
||||
|
||||
func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeStorageClass(analyzer *troubleshootv1beta2.StorageClass, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
storageClassesData, err := getCollectedFileContents("cluster-resources/storage-classes.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -21,7 +21,11 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
|
||||
|
||||
title := analyzer.CheckName
|
||||
if title == "" {
|
||||
title = fmt.Sprintf("Storage class %s", analyzer.StorageClassName)
|
||||
if analyzer.StorageClassName != "" {
|
||||
title = fmt.Sprintf("Storage class %s", analyzer.StorageClassName)
|
||||
} else {
|
||||
title = "Default Storage Class"
|
||||
}
|
||||
}
|
||||
|
||||
result := AnalyzeResult{
|
||||
@@ -31,7 +35,8 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
|
||||
}
|
||||
|
||||
for _, storageClass := range storageClasses {
|
||||
if storageClass.Name == analyzer.StorageClassName {
|
||||
val, _ := storageClass.Annotations["storageclass.kubernetes.io/is-default-class"]
|
||||
if (storageClass.Name == analyzer.StorageClassName) || (analyzer.StorageClassName == "" && val == "true") {
|
||||
result.IsPass = true
|
||||
for _, outcome := range analyzer.Outcomes {
|
||||
if outcome.Pass != nil {
|
||||
@@ -39,6 +44,9 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
|
||||
result.URI = outcome.Pass.URI
|
||||
}
|
||||
}
|
||||
if analyzer.StorageClassName == "" && result.Message == "" {
|
||||
result.Message = "Default Storage Class found"
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -51,6 +59,9 @@ func analyzeStorageClass(analyzer *troubleshootv1beta1.StorageClass, getCollecte
|
||||
result.URI = outcome.Fail.URI
|
||||
}
|
||||
}
|
||||
if analyzer.StorageClassName == "" && result.Message == "" {
|
||||
result.Message = "No Default Storage Class found"
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
+69
-31
@@ -8,10 +8,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
)
|
||||
|
||||
func analyzeTextAnalyze(analyzer *troubleshootv1beta1.TextAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
|
||||
func analyzeTextAnalyze(analyzer *troubleshootv1beta2.TextAnalyze, getCollectedFileContents func(string) (map[string][]byte, error)) ([]*AnalyzeResult, error) {
|
||||
fullPath := filepath.Join(analyzer.CollectorName, analyzer.FileName)
|
||||
collected, err := getCollectedFileContents(fullPath)
|
||||
if err != nil {
|
||||
@@ -23,59 +23,97 @@ func analyzeTextAnalyze(analyzer *troubleshootv1beta1.TextAnalyze, getCollectedF
|
||||
checkName = analyzer.CollectorName
|
||||
}
|
||||
|
||||
if len(collected) == 0 {
|
||||
return []*AnalyzeResult{
|
||||
{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
IsFail: false,
|
||||
Message: "No matching files",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
results := []*AnalyzeResult{}
|
||||
|
||||
if analyzer.RegexPattern != "" {
|
||||
return analyzeRegexPattern(analyzer.RegexPattern, collected, analyzer.Outcomes, checkName)
|
||||
for _, fileContents := range collected {
|
||||
result, err := analyzeRegexPattern(analyzer.RegexPattern, fileContents, analyzer.Outcomes, checkName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result != nil {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if analyzer.RegexGroups != "" {
|
||||
return analyzeRegexGroups(analyzer.RegexGroups, collected, analyzer.Outcomes, checkName)
|
||||
for _, fileContents := range collected {
|
||||
result, err := analyzeRegexGroups(analyzer.RegexGroups, fileContents, analyzer.Outcomes, checkName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result != nil {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &AnalyzeResult{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
IsFail: true,
|
||||
Message: "Invalid analyzer",
|
||||
if len(results) > 0 {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
return []*AnalyzeResult{
|
||||
{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
IsFail: true,
|
||||
Message: "Invalid analyzer",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func analyzeRegexPattern(pattern string, collected []byte, outcomes []*troubleshootv1beta1.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
func analyzeRegexPattern(pattern string, collected []byte, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compile regex: %s", pattern)
|
||||
}
|
||||
|
||||
var failOutcome *troubleshootv1beta1.Outcome
|
||||
var passOutcome *troubleshootv1beta1.Outcome
|
||||
var failOutcome *troubleshootv1beta2.SingleOutcome
|
||||
var passOutcome *troubleshootv1beta2.SingleOutcome
|
||||
for _, outcome := range outcomes {
|
||||
if outcome.Fail != nil {
|
||||
failOutcome = outcome
|
||||
failOutcome = outcome.Fail
|
||||
} else if outcome.Pass != nil {
|
||||
passOutcome = outcome
|
||||
passOutcome = outcome.Pass
|
||||
}
|
||||
}
|
||||
|
||||
if re.MatchString(string(collected)) {
|
||||
return &AnalyzeResult{
|
||||
Title: checkName,
|
||||
IsPass: true,
|
||||
Message: passOutcome.Pass.Message,
|
||||
URI: passOutcome.Pass.URI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &AnalyzeResult{
|
||||
result := AnalyzeResult{
|
||||
Title: checkName,
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
IsFail: true,
|
||||
Message: failOutcome.Fail.Message,
|
||||
URI: failOutcome.Fail.URI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if re.MatchString(string(collected)) {
|
||||
result.IsPass = true
|
||||
if passOutcome != nil {
|
||||
result.Message = passOutcome.Message
|
||||
result.URI = passOutcome.URI
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
result.IsFail = true
|
||||
if failOutcome != nil {
|
||||
result.Message = failOutcome.Message
|
||||
result.URI = failOutcome.URI
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troubleshootv1beta1.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compile regex: %s", pattern)
|
||||
|
||||
@@ -2,32 +2,33 @@ package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.undefinedlabs.com/scopeagent"
|
||||
)
|
||||
|
||||
func Test_textAnalyze(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
analyzer troubleshootv1beta1.TextAnalyze
|
||||
expectResult AnalyzeResult
|
||||
analyzer troubleshootv1beta2.TextAnalyze
|
||||
expectResult []AnalyzeResult
|
||||
files map[string][]byte
|
||||
}{
|
||||
{
|
||||
name: "success case 1",
|
||||
analyzer: troubleshootv1beta1.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -36,12 +37,16 @@ func Test_textAnalyze(t *testing.T) {
|
||||
FileName: "cfile-1.txt",
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-1",
|
||||
Message: "pass",
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-1",
|
||||
Message: "pass",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"),
|
||||
@@ -49,15 +54,15 @@ func Test_textAnalyze(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "failure case 1",
|
||||
analyzer: troubleshootv1beta1.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "success",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -66,14 +71,16 @@ func Test_textAnalyze(t *testing.T) {
|
||||
FileName: "cfile-2.txt",
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-2",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-2",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-2/cfile-2.txt": []byte(""),
|
||||
@@ -81,15 +88,15 @@ func Test_textAnalyze(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "success case 2",
|
||||
analyzer: troubleshootv1beta1.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "success",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -98,14 +105,16 @@ func Test_textAnalyze(t *testing.T) {
|
||||
FileName: "cfile-3.txt",
|
||||
RegexPattern: "",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-3",
|
||||
Message: "Invalid analyzer",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-3",
|
||||
Message: "Invalid analyzer",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-3/cfile-3.txt": []byte("Connection to service succeeded"),
|
||||
@@ -113,15 +122,15 @@ func Test_textAnalyze(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "success case 3",
|
||||
analyzer: troubleshootv1beta1.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "success",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -130,12 +139,16 @@ func Test_textAnalyze(t *testing.T) {
|
||||
FileName: "cfile-5.txt",
|
||||
RegexPattern: "([a-zA-Z0-9\\-_:*\\s])*succe([a-zA-Z0-9\\-_:*\\s!])*",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-5",
|
||||
Message: "success",
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-5",
|
||||
Message: "success",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-5/cfile-5.txt": []byte("Connection to service succeeded!"),
|
||||
@@ -143,15 +156,15 @@ func Test_textAnalyze(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "failure case 3",
|
||||
analyzer: troubleshootv1beta1.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "success",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -160,14 +173,16 @@ func Test_textAnalyze(t *testing.T) {
|
||||
FileName: "cfile-4.txt",
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-4",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-4",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-4/cfile-4.txt": []byte("A different message"),
|
||||
@@ -175,15 +190,15 @@ func Test_textAnalyze(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "failure case 4",
|
||||
analyzer: troubleshootv1beta1.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta1.Outcome{
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta1.SingleOutcome{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "success",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta1.SingleOutcome{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
@@ -192,38 +207,136 @@ func Test_textAnalyze(t *testing.T) {
|
||||
FileName: "cfile-6.txt",
|
||||
RegexPattern: "([a-zA-Z0-9\\-_:*\\s])*succe([a-zA-Z0-9\\-_:*\\s!])*",
|
||||
},
|
||||
expectResult: AnalyzeResult{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-6",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-6",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-6/cfile-6.txt": []byte("A different message"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple results case 1",
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile",
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: true,
|
||||
IsWarn: false,
|
||||
IsFail: false,
|
||||
Title: "text-collector-1",
|
||||
Message: "pass",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-1",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"),
|
||||
"text-collector-1/cfile-2.txt": []byte("no success here"),
|
||||
"text-collector-2/cfile-3.txt": []byte("Yes it all succeeded"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple results case 2 globbing",
|
||||
analyzer: troubleshootv1beta2.TextAnalyze{
|
||||
Outcomes: []*troubleshootv1beta2.Outcome{
|
||||
{
|
||||
Pass: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "pass",
|
||||
},
|
||||
},
|
||||
{
|
||||
Fail: &troubleshootv1beta2.SingleOutcome{
|
||||
Message: "fail",
|
||||
},
|
||||
},
|
||||
},
|
||||
CollectorName: "text-collector-1",
|
||||
FileName: "cfile*.log",
|
||||
RegexPattern: "succeeded",
|
||||
},
|
||||
expectResult: []AnalyzeResult{
|
||||
{
|
||||
IsPass: false,
|
||||
IsWarn: false,
|
||||
IsFail: true,
|
||||
Title: "text-collector-1",
|
||||
Message: "fail",
|
||||
IconKey: "kubernetes_text_analyze",
|
||||
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
|
||||
},
|
||||
},
|
||||
files: map[string][]byte{
|
||||
"text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"),
|
||||
"text-collector-1/cfile-2.log": []byte("no success here"),
|
||||
"text-collector-2/cfile-3.txt": []byte("Yes it all succeeded"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
getFiles := func(n string) ([]byte, error) {
|
||||
val, ok := test.files[n]
|
||||
if !ok {
|
||||
getFiles := func(n string) (map[string][]byte, error) {
|
||||
matching := make(map[string][]byte)
|
||||
for k, v := range test.files {
|
||||
if strings.HasPrefix(k, n) {
|
||||
matching[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range test.files {
|
||||
if ok, _ := filepath.Match(n, k); ok {
|
||||
matching[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(matching) == 0 {
|
||||
return nil, fmt.Errorf("File not found: %s", n)
|
||||
}
|
||||
return val, nil
|
||||
return matching, nil
|
||||
}
|
||||
|
||||
actual, err := analyzeTextAnalyze(&test.analyzer, getFiles)
|
||||
req.NoError(err)
|
||||
assert.Equal(t, &test.expectResult, actual)
|
||||
|
||||
unPointered := []AnalyzeResult{}
|
||||
for _, v := range actual {
|
||||
unPointered = append(unPointered, *v)
|
||||
}
|
||||
assert.ElementsMatch(t, test.expectResult, unPointered)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -266,8 +379,6 @@ func Test_compareRegex(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
scopetest := scopeagent.StartTest(t)
|
||||
defer scopetest.End()
|
||||
req := require.New(t)
|
||||
|
||||
actual, err := compareRegex(test.conditional, test.foundMatches)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user