Compare commits

...
117 Commits
Author SHA1 Message Date
Marc Campbell c962dc59f0 Merge pull request #97 from replicatedhq/build-fix
one more fix
2019-12-18 11:10:14 -08:00
Marc Campbell 8ba2c576c3 one more fix 2019-12-18 18:42:03 +00:00
Marc Campbell fd47d23ac0 Merge pull request #96 from replicatedhq/build-fix
Build fix
2019-12-18 10:03:52 -08:00
Marc Campbell 59a2281290 More cleanup 2019-12-18 18:01:39 +00:00
Marc Campbell 1465af2c09 Fix build 2019-12-18 17:59:43 +00:00
Marc Campbell 7f82ec1f09 Add snapshot release back 2019-12-18 17:45:13 +00:00
Marc Campbell 4338b22505 Merge pull request #95 from replicatedhq/github-actions
GitHub actions
2019-12-18 08:01:22 -08:00
Marc Campbell 1757af58eb only push event 2019-12-18 15:18:43 +00:00
Marc Campbell d8bae5dc8b spelling 2019-12-18 15:18:01 +00:00
Marc Campbell ca9106e7c9 Merge pull request #93 from replicatedhq/no-collector-cmd
Remove manager and collector commands
2019-12-18 07:14:43 -08:00
Marc Campbell 57094dbb44 json preflights 2019-12-18 15:12:11 +00:00
Marc Campbell 7ac973647d Add json output 2019-12-18 15:12:11 +00:00
Marc Campbell 94f812c7bd Preflights 2019-12-18 15:12:11 +00:00
Marc Campbell e5f0e09236 Work on github actions 2019-12-18 15:12:11 +00:00
Marc Campbell 1ee662944f Merge pull request #92 from replicatedhq/optional-collectors
Optional collectors and analyzers
2019-12-18 06:55:56 -08:00
Marc Campbell 7ab1734cb6 Remove manager and collector commands 2019-12-18 01:52:06 +00:00
Marc Campbell c61acdc5de Regenerate 2019-12-18 01:40:06 +00:00
Marc Campbell 98f926b46c Support for optional collectors and analyzers 2019-12-18 01:38:51 +00:00
Marc Campbell d0a0d925bb Cleaning up name / collectorName 2019-12-18 01:12:16 +00:00
Andrew Lavery fd257b5618 Merge pull request #91 from bearium/stdout_results
Added Namespace for Collection and Allows for tests to output to stdout
2019-12-13 15:01:54 -08:00
bearium 3afe32cd1e change global fail to return result 2019-12-11 07:34:15 -05:00
Marc Campbell 029282451e Merge pull request #89 from replicatedhq/version-command
Add version command
2019-12-06 07:22:41 -08:00
bearium 68c76867c7 go mod tidy 2019-12-02 10:36:48 -05:00
bearium e13cb35c82 added std out support and namespace functionality 2019-12-02 09:56:07 -05:00
Marc Campbell 9c4fa8ac14 Add version command 2019-11-30 00:03:43 +00:00
Frank Shotwell 1d3831f6d1 Merge pull request #86 from replicatedhq/gke
add missing gke test
2019-11-22 15:56:26 -08:00
Frank Shotwell 5812414093 add missing gke test 2019-11-22 15:40:38 -08:00
Marc Campbell 94e468ade3 Merge pull request #85 from replicatedhq/docker-desktop
More friendly parsing of Docker Desktop
2019-11-22 15:20:02 -08:00
Marc Campbell 3dc78d2ce8 More friendly parsing of Docker Desktop 2019-11-22 23:14:28 +00:00
divolgin c4dd568267 Merge pull request #84 from replicatedhq/divolgin/krew
Adding changes made in the krew-index repo
2019-11-18 16:02:07 -08:00
divolgin 00fa236525 Adding changes made in the krew-index repo 2019-11-18 23:57:16 +00:00
divolgin ec24967e50 Merge pull request #83 from laverya/update-release-tarball-naming
update tarball naming scheme to bin_os_arch, no longer include version
2019-11-14 14:05:02 -08:00
Andrew Lavery 430fdaa9e4 update tarball naming scheme to bin_os_arch, no longer include version 2019-11-14 13:48:52 -08:00
divolgin 58ed4c8fc2 Merge pull request #82 from replicatedhq/divolgin/data
support for data collector
2019-11-12 13:03:40 -08:00
divolgin 353569d59a support for data collector 2019-11-12 20:53:50 +00:00
Marc Campbell cd43fd6f56 Merge pull request #81 from replicatedhq/collector-node
Collector node
2019-11-08 15:29:03 -08:00
Marc Campbell ed8453ce6b Distribution analyzer 2019-11-08 23:15:58 +00:00
Marc Campbell 5744d608a4 Container runtime analyzer 2019-11-08 17:25:11 +00:00
Marc Campbell 0e3bcd29e4 Add nodes to the bundle 2019-11-08 16:57:33 +00:00
Marc Campbell 2a3e4e7d75 Merge pull request #79 from replicatedhq/regen
Updated from controller-gen
2019-11-07 18:00:46 -08:00
Marc Campbell 67b25e319b Updated from controller-gen 2019-11-08 01:31:20 +00:00
Marc Campbell b0bbaa4e6b Merge pull request #78 from replicatedhq/status-analyzer
Status analyzer
2019-11-07 17:22:04 -08:00
Marc Campbell 8a1e129dbe Add names so the analysis UI renders 2019-11-08 00:22:57 +00:00
Marc Campbell b926ff6fa2 Additional test 2019-11-07 17:19:01 +00:00
Marc Campbell fb5f1522ea Deployment and statefulset 2019-11-07 02:07:05 +00:00
Marc Campbell 6e7715a145 Deployment and Statefulset status analyzers 2019-11-07 01:54:32 +00:00
divolgin c0415dd100 Merge pull request #77 from replicatedhq/divolgin/krew
bump krew manifests to 0.9.11
2019-11-05 11:16:03 -08:00
divolgin 1d031d6897 bump krew manifests to 0.9.11 2019-11-04 15:32:25 +00:00
divolgin b19ac90512 Merge pull request #76 from replicatedhq/divolgin/containerz
Change containers to containerNames to not break kustomize
2019-10-31 16:09:27 -07:00
divolgin 13fb6abcaf Change containers to containerNames to not break kustomize 2019-10-31 19:52:04 +00:00
Marc Campbell 218b9c006d Merge pull request #75 from replicatedhq/analyze-spec
Fix
2019-10-29 14:10:05 -07:00
Marc Campbell 0b493a533b Fix 2019-10-29 21:08:58 +00:00
Marc Campbell 5830eb34a0 Merge pull request #74 from replicatedhq/analyze-spec
Analyze spec
2019-10-29 13:00:32 -07:00
Marc Campbell 8a1bb8a422 isURL 2019-10-29 19:56:29 +00:00
Marc Campbell 14ace5294f Analyze spec 2019-10-29 19:45:25 +00:00
Marc Campbell 25ec38bdf5 Merge pull request #73 from replicatedhq/url
Replace default collector URL
2019-10-29 10:49:37 -07:00
divolgin 223a4a6e0a Replace default collector URL 2019-10-29 03:28:27 +00:00
divolgin 3b6ab41e1b Merge pull request #72 from replicatedhq/divolgin/output-names
use file paths specified by the spec instead of hardcoded ones
2019-10-25 09:26:11 -07:00
divolgin 60ca35a5a6 use file paths specified by the spec instead of hardcoded ones 2019-10-25 16:23:22 +00:00
divolgin 36e3bf294d Merge pull request #71 from replicatedhq/divolgin/output-names
support multiple containers in pod logs
2019-10-24 11:31:45 -07:00
divolgin 3ea71c1d5a support multiple containers in pod logs 2019-10-24 18:27:53 +00:00
divolgin 26baaacee8 Merge pull request #70 from replicatedhq/divolgin/krew
bump krew manifests to 0.9.9
2019-10-18 09:31:24 -07:00
divolgin 6f868877d8 bump krew manifests to 0.9.9 2019-10-18 16:23:08 +00:00
Marc Campbell 075c0f6f16 Merge pull request #69 from replicatedhq/analyze-kotsadm
Wraping errors, no synlink in kotsadm
2019-10-17 13:03:29 -04:00
divolgin 2455f87ee1 Merge pull request #68 from replicatedhq/divolgin/better-messaging
Better messaging when support bundle is uploaded
2019-10-17 09:15:58 -07:00
Marc Campbell a4c482c74c Wraping errors, no synlink in kotsadm 2019-10-17 14:46:37 +00:00
divolgin 642c1d7e5b Better messaging when support bundle is uploaded 2019-10-15 22:45:57 +00:00
divolgin e88e3a4694 Merge pull request #66 from replicatedhq/divolgin/krew
bump krew manifests to 0.9.8
2019-09-09 15:22:07 -07:00
divolgin e23f799bed bump krew manifests to 0.9.8 2019-09-09 22:16:32 +00:00
Marc Campbell 25e884c43a Proxy 2019-09-08 15:09:02 +00:00
Marc Campbell 481aab7820 Merge pull request #65 from replicatedhq/error-handling
Error handling in cluster resources
2019-09-08 08:01:39 -07:00
Marc Campbell 0b7aff6597 Error handling in cluster resources 2019-09-08 14:35:56 +00:00
Marc Campbell 4153b9ac44 One extension is enough 2019-08-28 22:13:33 +00:00
Marc Campbell 48750ba1e5 Better release name for so 2019-08-28 21:59:57 +00:00
Marc Campbell 086d5569b3 Merge pull request #64 from replicatedhq/ffi
Support CGO for calling analysis from other languages
2019-08-28 14:39:09 -07:00
Marc Campbell 996a55bf90 Support CGO for calling analysis from other languages 2019-08-28 21:30:59 +00:00
divolgin 8125d5534b Merge pull request #63 from alrs/fix-analyze-swallowed-error
pkg/analyze: Fix swallowed error
2019-08-27 13:55:03 -07:00
Lars Lehtonen 09deffb507 pkg/analyze: Fix swallowed error 2019-08-26 17:16:18 -07:00
divolgin a8bdda483d Merge pull request #62 from replicatedhq/divolgin/run
run collector times out
2019-08-22 15:55:31 -07:00
divolgin c3e2748f3c run collector times out 2019-08-22 22:39:30 +00:00
divolgin 0c7c8c3927 Merge pull request #61 from replicatedhq/krew
update krew manifests
2019-08-22 15:16:45 -07:00
divolgin 9a1bb9fa7b update krew manifests 2019-08-22 16:35:22 +00:00
divolgin 143f24b1e6 Merge pull request #60 from replicatedhq/krew
update krew manifests
2019-08-22 09:27:13 -07:00
divolgin 45d537744f update krew manifests 2019-08-22 16:02:54 +00:00
divolgin 1855c42b16 Merge pull request #59 from replicatedhq/divolgin/cli
adding support for generic CLI options. closes #54
2019-08-22 08:04:27 -07:00
divolgin 2fb3a17783 adding support for generic CLI options. closes #54 2019-08-22 14:25:08 +00:00
divolgin b195b34fbe Merge pull request #57 from replicatedhq/divolgin/file
add numbers to support bundle name as needed
2019-08-21 10:01:37 -07:00
divolgin 1c6fe9f262 add numbers to support bundle name as needed 2019-08-21 16:18:57 +00:00
divolgin ed9264ed04 Merge pull request #53 from replicatedhq/divolgin/upload
Automatically upload generated support bundle file
2019-08-21 07:18:54 -07:00
divolgin 68344b25a4 Automatically upload generated support bundle file 2019-08-20 23:54:28 +00:00
divolgin 123225c2ce Merge pull request #56 from replicatedhq/divolgin/run
errors are masked when collector fails
2019-08-20 13:31:49 -07:00
divolgin b6ca58dbcf errors are masked when collector fails 2019-08-20 20:25:55 +00:00
divolgin beb0514243 Merge pull request #52 from replicatedhq/krew
krew plugin manifests 0.9.4
2019-08-16 08:26:05 -07:00
divolgin a71f2e4e0f krew plugin manifests 0.9.4 2019-08-16 15:22:39 +00:00
Marc Campbell ff81cac235 Merge pull request #50 from replicatedhq/run-in-cli
Run in cli
2019-08-14 20:35:17 -07:00
Marc Campbell 91b33b0316 Merge pull request #49 from replicatedhq/fixes
Some fixes to troubleshoot, better error messages
2019-08-14 20:35:10 -07:00
Marc Campbell 3b1428a225 Add gke 2019-08-15 02:24:55 +00:00
Marc Campbell c6da43b73f Run in the CLI, not in the cluster 2019-08-15 02:10:24 +00:00
Marc Campbell 4d61c49619 Run preflights in the CLI 2019-08-15 01:59:57 +00:00
Marc Campbell 0f421ce104 Remove 2019-08-15 01:23:38 +00:00
Marc Campbell d37d037ad1 Some fixes to troubleshoot, better error messages 2019-08-15 01:22:17 +00:00
divolgin e6ecddf42e Merge pull request #48 from replicatedhq/krew
Updating krew plugin manifests
2019-08-08 13:42:39 -07:00
divolgin 07fedd29ad Updating krew plugin manifests 2019-08-08 20:36:36 +00:00
divolgin c83184d9f5 Merge pull request #47 from replicatedhq/fix-release
fixing release build
2019-08-08 10:43:42 -07:00
divolgin 37ab1960d5 fixing release build 2019-08-08 17:41:40 +00:00
divolgin 5811a7b0ae Merge pull request #46 from replicatedhq/make-release
fix release build
2019-08-08 10:12:32 -07:00
divolgin bc7614f875 fix release build 2019-08-08 17:11:20 +00:00
divolgin d900950c5f Merge pull request #44 from replicatedhq/support-bundle
rename troubleshoot binary to support-bundle
2019-08-08 09:24:06 -07:00
divolgin c84bc86a50 Merge pull request #45 from replicatedhq/message
Better message at the end of support bundle generation
2019-08-07 17:06:01 -07:00
divolgin 2e61a8e9a9 Better message at the end of support bundle generation 2019-08-07 23:56:08 +00:00
divolgin 012df52f11 rename troubleshoot binary to support-bundle 2019-08-07 21:23:12 +00:00
divolgin f9efac3182 Merge pull request #43 from replicatedhq/progress
Show progress when collecting support bundle
2019-08-07 13:42:27 -07:00
divolgin 920e1efc27 Show progress when collecting support bundle 2019-08-07 20:41:06 +00:00
divolgin e90b39733d Merge pull request #42 from replicatedhq/krew
updating troubleshoot krew manifest
2019-08-06 16:27:13 -07:00
divolgin 8718888af1 updating troubleshoot krew manifest 2019-08-06 23:09:34 +00:00
divolgin 64119e48c6 Merge pull request #41 from replicatedhq/krew
updating krew plugin version
2019-08-06 14:13:17 -07:00
divolgin afae2ae06f updating krew plugin version 2019-08-06 14:25:18 +00:00
85 changed files with 5126 additions and 2293 deletions
+35
View File
@@ -0,0 +1,35 @@
name: "Run preflights on a cluster"
on: [push]
jobs:
compile-preflight:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v1
with:
go-version: '1.12.14'
- name: setup env
run: |
echo "::set-env name=GOPATH::$(go env GOPATH)"
echo "::add-path::$(go env GOPATH)/bin"
shell: bash
- uses: actions/checkout@master
- run: make preflight
- uses: actions/upload-artifact@v1
with:
name: preflight
path: bin/preflight
run-preflights:
runs-on: ubuntu-latest
needs: compile-preflight
steps:
- name: Download preflight binary
uses: actions/download-artifact@v1
with:
name: preflight
path: bin/
- uses: engineerd/setup-kind@v0.2.0
- run: chmod +x bin/preflight
- run: bin/preflight --interactive=false --format=json https://preflight.replicated.com
+55 -33
View File
@@ -2,32 +2,59 @@
# Image URL to use all building/pushing image targets
IMG ?= controller:latest
export GO111MODULE=on
export GOPROXY=https://proxy.golang.org
all: test manager
SHELL := /bin/bash -o pipefail
VERSION_PACKAGE = github.com/replicatedhq/troubleshoot/pkg/version
VERSION ?=`git describe --tags --dirty`
DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"`
GIT_TREE = $(shell git rev-parse --is-inside-work-tree 2>/dev/null)
ifneq "$(GIT_TREE)" ""
define GIT_UPDATE_INDEX_CMD
git update-index --assume-unchanged
endef
define GIT_SHA
`git rev-parse HEAD`
endef
else
define GIT_UPDATE_INDEX_CMD
echo "Not a git repo, skipping git update-index"
endef
define GIT_SHA
""
endef
endif
define LDFLAGS
-ldflags "\
-X ${VERSION_PACKAGE}.version=${VERSION} \
-X ${VERSION_PACKAGE}.gitSHA=${GIT_SHA} \
-X ${VERSION_PACKAGE}.buildTime=${DATE} \
"
endef
all: test
.PHONY: ffi
ffi: fmt vet
go build ${LDFLAGS} -o bin/troubleshoot.so -buildmode=c-shared ffi/main.go
# Run tests
test: generate fmt vet manifests
go test ./pkg/... ./cmd/... -coverprofile cover.out
.PHONY: manager
manager: generate fmt vet
go build -o bin/manager github.com/replicatedhq/troubleshoot/cmd/manager
.PHONY: troubleshoot
troubleshoot: generate fmt vet
go build -o bin/troubleshoot github.com/replicatedhq/troubleshoot/cmd/troubleshoot
.PHONY: collector
collector: generate fmt vet
go build -o bin/collector github.com/replicatedhq/troubleshoot/cmd/collector
.PHONY: support-bundle
support-bundle: generate fmt vet
go build ${LDFLAGS} -o bin/support-bundle github.com/replicatedhq/troubleshoot/cmd/troubleshoot
.PHONY: preflight
preflight: generate fmt vet
go build -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
go build ${LDFLAGS} -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
.PHONY: run
run: generate fmt vet
TROUBLESHOOT_EXTERNAL_MANAGER=1 go run ./cmd/manager/main.go
.PHONY: analyze
analyze: generate fmt vet
go build ${LDFLAGS} -o bin/analyze github.com/replicatedhq/troubleshoot/cmd/analyze
.PHONY: install
install: manifests
@@ -74,38 +101,33 @@ else
CLIENT_GEN=$(shell which client-gen)
endif
.PHONY: release
release: export GITHUB_TOKEN = $(shell echo ${GITHUB_TOKEN_TROUBLESHOOT})
release:
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --config deploy/.goreleaser.yml
.PHONY: snapshot-release
snapshot-release:
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.snapshot.yml
docker push replicated/troubleshoot:alpha
docker push replicated/preflight:alpha
docker push replicated/troubleshoot-manager:alpha
.PHONY: release
release: export GITHUB_TOKEN = $(shell echo ${GITHUB_TOKEN_TROUBLESHOOT})
release:
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --config deploy/.goreleaser.yml
.PHONY: local-release
local-release:
curl -sL https://git.io/goreleaser | bash -s -- --rm-dist --snapshot --config deploy/.goreleaser.local.yml
docker tag replicated/troubleshoot:alpha localhost:32000/troubleshoot:alpha
docker tag replicated/preflight:alpha localhost:32000/preflight:alpha
docker tag replicated/troubleshoot-manager:alpha localhost:32000/troubleshoot-manager:alpha
docker push localhost:32000/troubleshoot:alpha
docker push localhost:32000/preflight:alpha
docker push localhost:32000/troubleshoot-manager:alpha
.PHONY: run-preflight
run-preflight: preflight
./bin/preflight \
--image=localhost:32000/troubleshoot:alpha \
--pullpolicy=Always \
./config/samples/troubleshoot_v1beta1_preflight.yaml
./bin/preflight ./examples/preflight/sample-preflight.yaml
.PHONY: run-troubleshoot
run-troubleshoot: troubleshoot
./bin/troubleshoot \
--image=localhost:32000/troubleshoot:alpha \
--pullpolicy=Always \
./config/samples/troubleshoot_v1beta1_collector.yaml
run-troubleshoot: support-bundle
./bin/support-bundle ./examples/troubleshoot/sample-troubleshoot.yaml
.PHONY: run-analyze
run-analyze: analyze
./bin/analyze --analyzers ./examples/troubleshoot/sample-analyzers.yaml ./support-bundle.tar.gz
+1 -1
View File
@@ -19,5 +19,5 @@ A support bundle is an archive that's created in-cluster, by collecting logs, cl
To collect a sample support bundle, [install the troubleshoot kubectl plugin](https://help.replicated.com/docs/troubleshoot/kubernetes/support-bundle/collecting/) and run:
```shell
kubectl troubleshoot https://troubleshoot.replicated.com
kubectl support-bundle https://troubleshoot.replicated.com
```
+60
View File
@@ -0,0 +1,60 @@
package cli
import (
"fmt"
"os"
"strings"
"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: "analyze [url]",
Args: cobra.MinimumNArgs(1),
Short: "Analyze a support bundle",
Long: `Run a series of analyzers on a support bundle archive`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
logger.SetQuiet(v.GetBool("quiet"))
return runAnalyzers(v, args[0])
},
}
cobra.OnInitialize(initConfig)
cmd.Flags().String("analyzers", "", "filename or url of the analyzers to use")
viper.BindPFlags(cmd.Flags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
KubernetesConfigFlags.AddFlags(cmd.Flags())
return cmd
}
func InitAndExecute() {
if err := RootCmd().Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func initConfig() {
viper.SetEnvPrefix("TROUBLESHOOT")
viper.AutomaticEnv()
}
+75
View File
@@ -0,0 +1,75 @@
package cli
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"github.com/pkg/errors"
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
"github.com/spf13/viper"
)
func runAnalyzers(v *viper.Viper, bundlePath string) error {
specPath := v.GetString("analyzers")
specContent := ""
if !isURL(specPath) {
if _, err := os.Stat(specPath); os.IsNotExist(err) {
return fmt.Errorf("%s was not found", specPath)
}
b, err := ioutil.ReadFile(specPath)
if err != nil {
return err
}
specContent = string(b)
} else {
req, err := http.NewRequest("GET", specPath, nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Replicated_Analyzer/v1beta1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
specContent = string(body)
}
analyzeResults, err := analyzer.DownloadAndAnalyze(specContent, bundlePath)
if err != nil {
return errors.Wrap(err, "failed to download and analyze bundle")
}
for _, analyzeResult := range analyzeResults {
if analyzeResult.IsPass {
fmt.Printf("Pass: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
} else if analyzeResult.IsWarn {
fmt.Printf("Warn: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
} else if analyzeResult.IsFail {
fmt.Printf("Fail: %s\n %s\n", analyzeResult.Title, analyzeResult.Message)
}
}
return nil
}
func isURL(str string) bool {
parsed, err := url.ParseRequestURI(str)
if err != nil {
return false
}
return parsed.Scheme != ""
}
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"github.com/replicatedhq/troubleshoot/cmd/analyze/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
func main() {
cli.InitAndExecute()
}
-39
View File
@@ -1,39 +0,0 @@
package cli
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "collector",
Short: "Run the cluster-side server for bundle collection",
Long: ``,
SilenceUsage: true,
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(Run())
cmd.AddCommand(Server())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
return cmd
}
func InitAndExecute() {
if err := RootCmd().Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func initConfig() {
viper.SetEnvPrefix("TROUBLESHOOT")
viper.AutomaticEnv()
}
-48
View File
@@ -1,48 +0,0 @@
package cli
import (
"io/ioutil"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func Run() *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Short: "run a single collector",
Long: `...`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("collector", cmd.Flags().Lookup("collector"))
viper.BindPFlag("redact", cmd.Flags().Lookup("redact"))
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
specContents, err := ioutil.ReadFile(v.GetString("collector"))
if err != nil {
return err
}
collector := collect.Collector{
Spec: string(specContents),
Redact: v.GetBool("redact"),
}
if err := collector.RunCollectorSync(); err != nil {
return err
}
return nil
},
}
cmd.Flags().String("collector", "", "path to a single collector spec to collect")
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
cmd.MarkFlagRequired("collector")
viper.BindPFlags(cmd.Flags())
return cmd
}
-43
View File
@@ -1,43 +0,0 @@
package cli
import (
"context"
"fmt"
"os"
"os/signal"
"github.com/replicatedhq/troubleshoot/pkg/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func Server() *cobra.Command {
cmd := &cobra.Command{
Use: "server",
Short: "run the http server",
Hidden: true,
Long: `...`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("port", cmd.Flags().Lookup("port"))
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
server.ServeCollector(context.Background(), fmt.Sprintf(":%d", v.GetInt("port")))
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
select {
case <-c:
return nil
}
},
}
cmd.Flags().Int("port", 8000, "port to listen on")
viper.BindPFlags(cmd.Flags())
return cmd
}
-7
View File
@@ -1,7 +0,0 @@
package main
import "github.com/replicatedhq/troubleshoot/cmd/collector/cli"
func main() {
cli.InitAndExecute()
}
-84
View File
@@ -1,84 +0,0 @@
/*
Copyright 2019 Replicated, Inc..
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"flag"
"os"
"github.com/replicatedhq/troubleshoot/pkg/apis"
"github.com/replicatedhq/troubleshoot/pkg/controller"
"github.com/replicatedhq/troubleshoot/pkg/webhook"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
)
func main() {
var metricsAddr string
flag.StringVar(&metricsAddr, "metrics-addr", ":8088", "The address the metric endpoint binds to.")
flag.Parse()
logf.SetLogger(logf.ZapLogger(false))
log := logf.Log.WithName("entrypoint")
// Get a config to talk to the apiserver
log.Info("setting up client for manager")
cfg, err := config.GetConfig()
if err != nil {
log.Error(err, "unable to set up client config")
os.Exit(1)
}
// Create a new Cmd to provide shared dependencies and start components
log.Info("setting up manager")
mgr, err := manager.New(cfg, manager.Options{MetricsBindAddress: metricsAddr})
if err != nil {
log.Error(err, "unable to set up overall controller manager")
os.Exit(1)
}
log.Info("Registering Components.")
// Setup Scheme for all resources
log.Info("setting up scheme")
if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
log.Error(err, "unable add APIs to scheme")
os.Exit(1)
}
// Setup all Controllers
log.Info("Setting up controller")
if err := controller.AddToManager(mgr); err != nil {
log.Error(err, "unable to register controllers to the manager")
os.Exit(1)
}
log.Info("setting up webhooks")
if err := webhook.AddToManager(mgr); err != nil {
log.Error(err, "unable to register webhooks to the manager")
os.Exit(1)
}
// Start the Cmd
log.Info("Starting the Cmd.")
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
log.Error(err, "unable to run the manager")
os.Exit(1)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
func receivePreflightResults(preflightJobNamespace string, preflightJobName string) error {
// poll until there are no more "running" collectors
troubleshootClient, err := createTroubleshootK8sClient()
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
if err != nil {
return err
}
+13 -12
View File
@@ -3,17 +3,22 @@ package cli
import (
"fmt"
"os"
"path/filepath"
"strings"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"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: "preflight [url]",
Args: cobra.MinimumNArgs(1),
Short: "Run and retrieve preflight checks in a cluster",
Long: `A preflight check is a set of validations that can and should be run to ensure
that a cluster meets the requirements to run an application.`,
@@ -22,25 +27,17 @@ that a cluster meets the requirements to run an application.`,
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
if len(args) == 0 {
return runPreflightsCRD(v)
}
return runPreflightsNoCRD(v, args[0])
return runPreflights(v, args[0])
},
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(VersionCmd())
cmd.Flags().Bool("interactive", true, "interactive preflights")
cmd.Flags().String("format", "human", "output format, one of human, json, yaml. only used when interactive is set to false")
cmd.Flags().String("preflight", "", "name of the preflight to use")
cmd.Flags().String("namespace", "default", "namespace the preflight can be found in")
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
cmd.Flags().String("image", "", "the full name of the preflight image to use")
cmd.Flags().String("pullpolicy", "", "the pull policy of the preflight image")
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
@@ -49,6 +46,10 @@ that a cluster meets the requirements to run an application.`,
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
KubernetesConfigFlags.AddFlags(cmd.Flags())
return cmd
}
+205
View File
@@ -0,0 +1,205 @@
package cli
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/ahmetalpbalkan/go-cursor"
"github.com/pkg/errors"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/viper"
"github.com/tj/go-spin"
"gopkg.in/yaml.v2"
)
func runPreflights(v *viper.Viper, arg string) error {
fmt.Print(cursor.Hide())
defer fmt.Print(cursor.Show())
preflightContent := ""
if !isURL(arg) {
if _, err := os.Stat(arg); os.IsNotExist(err) {
return fmt.Errorf("%s was not found", arg)
}
b, err := ioutil.ReadFile(arg)
if err != nil {
return err
}
preflightContent = string(b)
} else {
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
preflightContent = string(body)
}
preflight := troubleshootv1beta1.Preflight{}
if err := yaml.Unmarshal([]byte(preflightContent), &preflight); err != nil {
return fmt.Errorf("unable to parse %s as a preflight", arg)
}
s := spin.New()
finishedCh := make(chan bool, 1)
go func() {
for {
select {
case <-finishedCh:
fmt.Printf("\r")
return
case <-time.After(time.Millisecond * 100):
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
}
}
}()
defer func() {
finishedCh <- true
}()
allCollectedData, err := runCollectors(v, preflight)
if err != nil {
return err
}
getCollectedFileContents := func(fileName string) ([]byte, error) {
contents, ok := allCollectedData[fileName]
if !ok {
return nil, fmt.Errorf("file %s was not collected", fileName)
}
return contents, nil
}
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
matching := make(map[string][]byte)
for k, v := range allCollectedData {
if strings.HasPrefix(k, prefix) {
matching[k] = v
}
}
return matching, nil
}
analyzeResults := []*analyzerunner.AnalyzeResult{}
for _, analyzer := range preflight.Spec.Analyzers {
analyzeResult, err := analyzerunner.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
if err != nil {
logger.Printf("an analyzer failed to run: %v\n", err)
continue
}
if analyzeResult != nil {
analyzeResults = append(analyzeResults, analyzeResult)
}
}
finishedCh <- true
if preflight.Spec.UploadResultsTo != "" {
tryUploadResults(preflight.Spec.UploadResultsTo, preflight.Name, analyzeResults)
}
if v.GetBool("interactive") {
return showInteractiveResults(preflight.Name, analyzeResults)
}
return showStdoutResults(v.GetString("format"), preflight.Name, analyzeResults)
}
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight) (map[string][]byte, error) {
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
for _, definedCollector := range preflight.Spec.Collectors {
desiredCollectors = append(desiredCollectors, definedCollector)
}
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
allCollectedData := make(map[string][]byte)
config, err := KubernetesConfigFlags.ToRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
// Run preflights collectors synchronously
for _, desiredCollector := range desiredCollectors {
collector := collect.Collector{
Redact: true,
Collect: desiredCollector,
ClientConfig: config,
Namespace: v.GetString("namespace"),
}
result, err := collector.RunCollectorSync()
if err != nil {
return nil, errors.Wrap(err, "failed to run collector")
}
if result != nil {
output, err := parseCollectorOutput(string(result))
if err != nil {
return nil, errors.Wrap(err, "failed to parse collector output")
}
for k, v := range output {
allCollectedData[k] = v
}
}
}
return allCollectedData, nil
}
func parseCollectorOutput(output string) (map[string][]byte, error) {
input := make(map[string]interface{})
files := make(map[string][]byte)
if err := json.Unmarshal([]byte(output), &input); err != nil {
return nil, err
}
for filename, maybeContents := range input {
fileDir, fileName := filepath.Split(filename)
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName)] = decoded
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName, k)] = decoded
}
}
}
return files, nil
}
-103
View File
@@ -1,103 +0,0 @@
package cli
import (
"errors"
"fmt"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/spf13/viper"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func runPreflightsCRD(v *viper.Viper) error {
troubleshootClient, err := createTroubleshootK8sClient()
if err != nil {
return err
}
preflightName := v.GetString("preflight")
if preflightName == "" {
preflights, err := troubleshootClient.Preflights(v.GetString("namespace")).List(metav1.ListOptions{})
if err != nil {
return err
}
if len(preflights.Items) == 1 {
preflightName = preflights.Items[0].Name
}
}
if preflightName == "" {
return errors.New("unable to preflight, try using the --preflight flags")
}
// generate a unique name
now := time.Now()
suffix := fmt.Sprintf("%d", now.Unix())
preflightJobName := fmt.Sprintf("%s-job-%s", preflightName, suffix[len(suffix)-4:])
preflightJob := troubleshootv1beta1.PreflightJob{
ObjectMeta: metav1.ObjectMeta{
Name: preflightJobName,
Namespace: v.GetString("namespace"),
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "preflightjob.troubleshoot.replicated.com",
},
Spec: troubleshootv1beta1.PreflightJobSpec{
Preflight: troubleshootv1beta1.PreflightRef{
Name: preflightName,
Namespace: v.GetString("namespace"),
},
Image: v.GetString("image"),
ImagePullPolicy: v.GetString("pullpolicy"),
CollectorImage: v.GetString("collector-image"),
CollectorImagePullPolicy: v.GetString("collector-pullpolicy"),
},
}
if _, err := troubleshootClient.PreflightJobs(v.GetString("namespace")).Create(&preflightJob); err != nil {
return err
}
// Poll the status of the Custom Resource for it to include a callback
var found *troubleshootv1beta1.PreflightJob
start := time.Now()
for {
current, err := troubleshootClient.PreflightJobs(v.GetString("namespace")).Get(preflightJobName, metav1.GetOptions{})
if err != nil && kuberneteserrors.IsNotFound(err) {
continue
} else if err != nil {
return err
}
if current.Status.IsServerReady {
found = current
break
}
if time.Now().Sub(start) > time.Duration(time.Second*10) {
return errors.New("preflightjob failed to start")
}
time.Sleep(time.Millisecond * 200)
}
// Connect to the callback
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, found.Status.ServerPodNamespace, found.Status.ServerPodName)
if err != nil {
return err
}
if err := receivePreflightResults(found.Namespace, found.Name); err != nil {
return err
}
// Write
close(stopChan)
return nil
}
-343
View File
@@ -1,343 +0,0 @@
package cli
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/pkg/errors"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
collectrunner "github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/viper"
"github.com/tj/go-spin"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
func runPreflightsNoCRD(v *viper.Viper, arg string) error {
preflightContent := ""
if !isURL(arg) {
if _, err := os.Stat(arg); os.IsNotExist(err) {
return fmt.Errorf("%s was not found", arg)
}
b, err := ioutil.ReadFile(arg)
if err != nil {
return err
}
preflightContent = string(b)
} else {
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
preflightContent = string(body)
}
preflight := troubleshootv1beta1.Preflight{}
if err := yaml.Unmarshal([]byte(preflightContent), &preflight); err != nil {
return fmt.Errorf("unable to parse %s as a preflight", arg)
}
s := spin.New()
finishedCh := make(chan bool, 1)
go func() {
for {
select {
case <-finishedCh:
fmt.Printf("\r")
return
case <-time.After(time.Millisecond * 100):
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
}
}
}()
defer func() {
finishedCh <- true
}()
allCollectedData, err := runCollectors(v, preflight)
if err != nil {
return err
}
getCollectedFileContents := func(fileName string) ([]byte, error) {
contents, ok := allCollectedData[fileName]
if !ok {
return nil, fmt.Errorf("file %s was not collected", fileName)
}
return contents, nil
}
getChildCollectedFileContents := func(prefix string) (map[string][]byte, error) {
matching := make(map[string][]byte)
for k, v := range allCollectedData {
if strings.HasPrefix(k, prefix) {
matching[k] = v
}
}
return matching, nil
}
analyzeResults := []*analyzerunner.AnalyzeResult{}
for _, analyzer := range preflight.Spec.Analyzers {
analyzeResult, err := analyzerunner.Analyze(analyzer, getCollectedFileContents, getChildCollectedFileContents)
if err != nil {
logger.Printf("an analyzer failed to run: %v\n", err)
continue
}
analyzeResults = append(analyzeResults, analyzeResult)
}
finishedCh <- true
if preflight.Spec.UploadResultsTo != "" {
tryUploadResults(preflight.Spec.UploadResultsTo, preflight.Name, analyzeResults)
}
if v.GetBool("interactive") {
return showInteractiveResults(preflight.Name, analyzeResults)
}
logger.Printf("only interactive results are supported\n")
return nil
}
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight) (map[string][]byte, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
client, err := client.New(cfg, client.Options{})
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
restClient := clientset.CoreV1().RESTClient()
serviceAccountName := v.GetString("serviceaccount")
if serviceAccountName == "" {
generatedServiceAccountName, err := createServiceAccount(preflight, v.GetString("namespace"), clientset)
if err != nil {
return nil, err
}
defer removeServiceAccount(generatedServiceAccountName, v.GetString("namespace"), clientset)
serviceAccountName = generatedServiceAccountName
}
// deploy an object that "owns" everything to aid in cleanup
configMapNamespacedName := types.NamespacedName{
Name: fmt.Sprintf("preflight-%s-owner", preflight.Name),
Namespace: v.GetString("namespace"),
}
foundConfigMap := &corev1.ConfigMap{}
err = client.Get(context.Background(), configMapNamespacedName, foundConfigMap)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return nil, errors.Wrap(err, "failed to get existing config map")
}
owner := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapNamespacedName.Name,
Namespace: configMapNamespacedName.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
Data: make(map[string]string),
}
if err := client.Create(context.Background(), &owner); err != nil {
return nil, errors.Wrap(err, "failed to create config map")
}
defer func() {
if err := client.Delete(context.Background(), &owner); err != nil {
fmt.Println("failed to clean up preflight.")
}
}()
// deploy all collectors
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
for _, definedCollector := range preflight.Spec.Collectors {
desiredCollectors = append(desiredCollectors, definedCollector)
}
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
podsCreated := make([]*corev1.Pod, 0, 0)
podsDeleted := make([]*corev1.Pod, 0, 0)
allCollectedData := make(map[string][]byte)
resyncPeriod := time.Second
ctx := context.Background()
watchList := cache.NewListWatchFromClient(restClient, "pods", "", fields.Everything())
_, controller := cache.NewInformer(watchList, &corev1.Pod{}, resyncPeriod,
cache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
newPod, ok := newObj.(*corev1.Pod)
if !ok {
return
}
oldPod, ok := oldObj.(*corev1.Pod)
if !ok {
return
}
labels := newPod.Labels
troubleshootRole, ok := labels["troubleshoot-role"]
if !ok || troubleshootRole != "preflight" {
return
}
preflightName, ok := labels["preflight"]
if !ok || preflightName != preflight.Name {
return
}
if oldPod.Status.Phase == newPod.Status.Phase {
return
}
if newPod.Status.Phase == corev1.PodFailed {
podsDeleted = append(podsDeleted, newPod)
return
}
if newPod.Status.Phase != corev1.PodSucceeded {
return
}
podLogOpts := corev1.PodLogOptions{}
req := clientset.CoreV1().Pods(newPod.Namespace).GetLogs(newPod.Name, &podLogOpts)
podLogs, err := req.Stream()
if err != nil {
fmt.Println("get stream")
return
}
defer podLogs.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
fmt.Println("copy logs")
return
}
collectedData, err := parseCollectorOutput(buf.String())
if err != nil {
logger.Printf("parse collected data: %v\n", err)
return
}
for k, v := range collectedData {
allCollectedData[k] = v
}
if err := client.Delete(context.Background(), newPod); err != nil {
fmt.Println("delete pod")
}
podsDeleted = append(podsDeleted, newPod)
},
})
go func() {
controller.Run(ctx.Done())
}()
s := runtime.NewScheme()
s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.ConfigMap{})
for _, collector := range desiredCollectors {
_, pod, err := collectrunner.CreateCollector(client, s, &owner, preflight.Name, v.GetString("namespace"), serviceAccountName, "preflight", collector, v.GetString("image"), v.GetString("pullpolicy"))
if err != nil {
return nil, errors.Wrap(err, "failed to create collector")
}
podsCreated = append(podsCreated, pod)
}
start := time.Now()
for {
if start.Add(time.Second * 30).Before(time.Now()) {
fmt.Println("timeout running preflight")
return nil, err
}
if len(podsDeleted) == len(podsCreated) {
break
}
time.Sleep(time.Millisecond * 200)
}
ctx.Done()
return allCollectedData, nil
}
func parseCollectorOutput(output string) (map[string][]byte, error) {
input := make(map[string]interface{})
files := make(map[string][]byte)
if err := json.Unmarshal([]byte(output), &input); err != nil {
return nil, err
}
for filename, maybeContents := range input {
fileDir, fileName := filepath.Split(filename)
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName)] = decoded
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return nil, err
}
files[filepath.Join(fileDir, fileName, k)] = decoded
}
}
}
return files, nil
}
+96
View File
@@ -0,0 +1,96 @@
package cli
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
)
func showStdoutResults(format string, preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
if format == "human" {
return showStdoutResultsHuman(preflightName, analyzeResults)
} else if format == "json" {
return showStdoutResultsJSON(preflightName, analyzeResults)
}
return errors.Errorf("unknown output format: %q", format)
}
func showStdoutResultsHuman(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
var failed bool
for _, analyzeResult := range analyzeResults {
testResultfailed := outputResult(analyzeResult)
if testResultfailed {
failed = true
}
}
if failed {
fmt.Printf("--- FAIL %s\n", preflightName)
fmt.Println("FAILED")
} else {
fmt.Printf("--- PASS %s\n", preflightName)
fmt.Println("PASS")
}
return nil
}
func showStdoutResultsJSON(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
type ResultOutput struct {
Title string `json:"title"`
Message string `json:"message"`
URI string `json:"uri,omitempty"`
}
type Output struct {
Pass []ResultOutput `json:"pass,omitempty"`
Warn []ResultOutput `json:"warn,omitempty"`
Fail []ResultOutput `json:"fail,omitempty"`
}
output := Output{
Pass: []ResultOutput{},
Warn: []ResultOutput{},
Fail: []ResultOutput{},
}
for _, analyzeResult := range analyzeResults {
resultOutput := ResultOutput{
Title: analyzeResult.Title,
Message: analyzeResult.Message,
URI: analyzeResult.URI,
}
if analyzeResult.IsPass {
output.Pass = append(output.Pass, resultOutput)
} else if analyzeResult.IsWarn {
output.Warn = append(output.Warn, resultOutput)
} else if analyzeResult.IsFail {
output.Fail = append(output.Fail, resultOutput)
}
}
b, err := json.MarshalIndent(output, "", " ")
if err != nil {
return errors.Wrap(err, "failed to marshal results")
}
fmt.Printf("%s\n", b)
return nil
}
func outputResult(analyzeResult *analyzerunner.AnalyzeResult) bool {
if analyzeResult.IsPass {
fmt.Printf(" --- PASS %s\n", analyzeResult.Title)
fmt.Printf(" --- %s\n", analyzeResult.Message)
} else if analyzeResult.IsWarn {
fmt.Printf(" --- WARN: %s\n", analyzeResult.Title)
fmt.Printf(" --- %s\n", analyzeResult.Message)
} else if analyzeResult.IsFail {
fmt.Printf(" --- FAIL: %s\n", analyzeResult.Title)
fmt.Printf(" --- %s\n", analyzeResult.Message)
return true
}
return false
}
+9 -10
View File
@@ -4,9 +4,9 @@ import (
"net/url"
"os"
"github.com/pkg/errors"
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
"github.com/spf13/viper"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func homeDir() string {
@@ -17,24 +17,23 @@ func homeDir() string {
}
func isURL(str string) bool {
_, err := url.ParseRequestURI(str)
parsed, err := url.ParseRequestURI(str)
if err != nil {
return false
}
return true
return parsed.Scheme != ""
}
func createTroubleshootK8sClient() (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
v := viper.GetViper()
config, err := clientcmd.BuildConfigFromFlags("", v.GetString("kubecontext"))
func createTroubleshootK8sClient(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
config, err := configFlags.ToRESTConfig()
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to create troubleshoot client")
}
return troubleshootClient, nil
+23
View File
@@ -0,0 +1,23 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
"github.com/replicatedhq/troubleshoot/pkg/version"
)
func VersionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print the current version and exit",
Long: `Print the current version and exit`,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Replicated Preflight %s\n", version.Version())
return nil
},
}
return cmd
}
+4 -1
View File
@@ -1,6 +1,9 @@
package main
import "github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
import (
"github.com/replicatedhq/troubleshoot/cmd/preflight/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
func main() {
cli.InitAndExecute()
+1 -2
View File
@@ -1,7 +1,6 @@
package cli
import (
"context"
"encoding/json"
"fmt"
@@ -28,7 +27,7 @@ func Analyze() *cobra.Command {
logger.SetQuiet(v.GetBool("quiet"))
result, err := analyzer.DownloadAndAnalyze(context.TODO(), v.GetString("url"))
result, err := analyzer.DownloadAndAnalyze("", v.GetString("url"))
if err != nil {
return err
}
+9 -19
View File
@@ -9,7 +9,6 @@ import (
"os"
"path/filepath"
"github.com/mholt/archiver"
"github.com/replicatedhq/troubleshoot/pkg/logger"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -17,7 +16,7 @@ import (
func receiveSupportBundle(collectorJobNamespace string, collectorJobName string) error {
// poll until there are no more "running" collectors
troubleshootClient, err := createTroubleshootK8sClient()
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
if err != nil {
return err
}
@@ -28,8 +27,7 @@ func receiveSupportBundle(collectorJobNamespace string, collectorJobName string)
}
defer os.RemoveAll(bundlePath)
versionFilename, err := writeVersionFile(bundlePath)
if err != nil {
if err = writeVersionFile(bundlePath); err != nil {
return err
}
@@ -117,23 +115,15 @@ func receiveSupportBundle(collectorJobNamespace string, collectorJobName string)
}
if len(job.Status.Running) == 0 {
tarGz := archiver.TarGz{
Tar: &archiver.Tar{
ImplicitTopLevelFolder: false,
},
}
// version file should be first in tar archive for quick extraction
paths := []string{
versionFilename,
}
for _, id := range receivedCollectors {
paths = append(paths, filepath.Join(bundlePath, id))
}
if err := tarGz.Archive(paths, "support-bundle.tar.gz"); err != nil {
filename, err := findFileName("support-bundle", "tar.gz")
if err != nil {
return err
}
if err := tarSupportBundleDir(bundlePath, filename); err != nil {
return err
}
return nil
}
}
+8 -10
View File
@@ -1,9 +1,7 @@
package cli
import (
"errors"
"path/filepath"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
@@ -19,13 +17,11 @@ func Retrieve() *cobra.Command {
Long: `...`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("collectors", cmd.Flags().Lookup("collectors"))
viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace"))
viper.BindPFlag("kubecontext", cmd.Flags().Lookup("kubecontext"))
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
troubleshootClient, err := createTroubleshootK8sClient()
troubleshootClient, err := createTroubleshootK8sClient(KubernetesConfigFlags)
if err != nil {
return err
}
@@ -53,7 +49,12 @@ func Retrieve() *cobra.Command {
logger.Printf("connecting to collector job %s\n", collectorJob.Name)
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, collectorJob.Status.ServerPodNamespace, collectorJob.Status.ServerPodName)
config, err := KubernetesConfigFlags.ToRESTConfig()
if err != nil {
return errors.Wrap(err, "failed to convert kube flags to rest config")
}
stopChan, err := k8sutil.PortForward(config, 8000, 8000, collectorJob.Status.ServerPodNamespace, collectorJob.Status.ServerPodName)
if err != nil {
return err
}
@@ -70,9 +71,6 @@ func Retrieve() *cobra.Command {
}
cmd.Flags().String("collectors", "", "name of the collectors to use")
cmd.Flags().String("namespace", "", "namespace the collectors can be found in")
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
viper.BindPFlags(cmd.Flags())
+11 -11
View File
@@ -3,13 +3,17 @@ package cli
import (
"fmt"
"os"
"path/filepath"
"strings"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"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 {
@@ -26,24 +30,16 @@ from a server that can be used to assist when troubleshooting a server.`,
v := viper.GetViper()
logger.SetQuiet(v.GetBool("quiet"))
if len(args) == 0 {
return runTroubleshootCRD(v)
}
return runTroubleshootNoCRD(v, args[0])
return runTroubleshoot(v, args[0])
},
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(Analyze())
cmd.AddCommand(VersionCmd())
cmd.Flags().String("collectors", "", "name of the collectors to use")
cmd.Flags().String("namespace", "default", "namespace the collectors can be found in")
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
cmd.Flags().String("image", "", "the full name of the collector image to use")
cmd.Flags().String("pullpolicy", "", "the pull policy of the collector image")
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
@@ -52,6 +48,10 @@ from a server that can be used to assist when troubleshooting a server.`,
viper.BindPFlags(cmd.Flags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
KubernetesConfigFlags.AddFlags(cmd.Flags())
return cmd
}
+340
View File
@@ -0,0 +1,340 @@
package cli
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
"github.com/ahmetalpbalkan/go-cursor"
"github.com/fatih/color"
"github.com/mholt/archiver"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/spf13/viper"
"github.com/tj/go-spin"
"gopkg.in/yaml.v2"
)
func runTroubleshoot(v *viper.Viper, arg string) error {
fmt.Print(cursor.Hide())
defer fmt.Print(cursor.Show())
collectorContent := ""
if !isURL(arg) {
if _, err := os.Stat(arg); os.IsNotExist(err) {
return fmt.Errorf("%s was not found", arg)
}
b, err := ioutil.ReadFile(arg)
if err != nil {
return errors.Wrap(err, "read spec file")
}
collectorContent = string(b)
} else {
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
return errors.Wrap(err, "make request")
}
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "read responce body")
}
collectorContent = string(body)
}
collector := troubleshootv1beta1.Collector{}
if err := yaml.Unmarshal([]byte(collectorContent), &collector); err != nil {
return fmt.Errorf("unable to parse %s collectors", arg)
}
s := spin.New()
finishedCh := make(chan bool, 1)
progressChan := make(chan interface{}, 1)
go func() {
currentDir := ""
for {
select {
case msg := <-progressChan:
switch msg := msg.(type) {
case error:
c := color.New(color.FgHiRed)
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
case string:
currentDir = filepath.Base(msg)
}
case <-finishedCh:
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
return
case <-time.After(time.Millisecond * 100):
if currentDir == "" {
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s", cursor.ClearEntireLine(), s.Next())
} else {
fmt.Printf("\r%s \033[36mCollecting support bundle\033[m %s %s", cursor.ClearEntireLine(), s.Next(), currentDir)
}
}
}
}()
defer func() {
finishedCh <- true
}()
archivePath, err := runCollectors(v, collector, progressChan)
if err != nil {
return errors.Wrap(err, "run collectors")
}
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
if len(collector.Spec.AfterCollection) == 0 {
msg := archivePath
if appName := collector.Labels["applicationName"]; appName != "" {
f := `A support bundle for %s has been created in this directory
named %s. Please upload it on the Troubleshoot page of
the %s Admin Console to begin analysis.`
msg = fmt.Sprintf(f, appName, archivePath, appName)
}
fmt.Printf("%s\n", msg)
return nil
}
fileUploaded := false
for _, ac := range collector.Spec.AfterCollection {
if ac.UploadResultsTo != nil {
if err := uploadSupportBundle(ac.UploadResultsTo, archivePath); err != nil {
return errors.Wrap(err, "upload support bundle")
}
fileUploaded = true
} else if ac.Callback != nil {
if err := callbackSupportBundleAPI(ac.Callback, archivePath); err != nil {
return errors.Wrap(err, "execute callback")
}
}
}
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
if fileUploaded {
fmt.Printf("A support bundle has been created and uploaded to your cluster for analysis. Please visit the Troubleshoot page to continue.\n")
fmt.Printf("A copy of this support bundle was written to the current directory, named %q\n", archivePath)
} else {
fmt.Printf("A support bundle has been created in the current directory named %q\n", archivePath)
}
return nil
}
func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, progressChan chan interface{}) (string, error) {
bundlePath, err := ioutil.TempDir("", "troubleshoot")
if err != nil {
return "", errors.Wrap(err, "create temp dir")
}
defer os.RemoveAll(bundlePath)
if err = writeVersionFile(bundlePath); err != nil {
return "", errors.Wrap(err, "write version file")
}
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
for _, definedCollector := range collector.Spec.Collectors {
desiredCollectors = append(desiredCollectors, definedCollector)
}
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
config, err := KubernetesConfigFlags.ToRESTConfig()
if err != nil {
return "", errors.Wrap(err, "failed to convert kube flags to rest config")
}
// Run preflights collectors synchronously
for _, desiredCollector := range desiredCollectors {
collector := collect.Collector{
Redact: true,
Collect: desiredCollector,
ClientConfig: config,
Namespace: v.GetString("namespace"),
}
progressChan <- collector.GetDisplayName()
result, err := collector.RunCollectorSync()
if err != nil {
progressChan <- fmt.Errorf("failed to run collector %q: %v", collector.GetDisplayName(), err)
continue
}
if result != nil {
err = parseAndSaveCollectorOutput(string(result), bundlePath)
if err != nil {
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
continue
}
}
}
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")
}
return filename, nil
}
func parseAndSaveCollectorOutput(output string, bundlePath string) error {
input := make(map[string]interface{})
if err := json.Unmarshal([]byte(output), &input); err != nil {
return errors.Wrap(err, "unmarshal output")
}
for filename, maybeContents := range input {
fileDir, fileName := filepath.Split(filename)
outPath := filepath.Join(bundlePath, fileDir)
if err := os.MkdirAll(outPath, 0777); err != nil {
return errors.Wrap(err, "create output file")
}
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return errors.Wrap(err, "decode collector output")
}
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
return errors.Wrap(err, "write collector output")
}
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
if err := os.MkdirAll(s, 0777); err != nil {
return errors.Wrap(err, "write output directories")
}
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return errors.Wrap(err, "decode output")
}
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
return errors.Wrap(err, "write output")
}
}
}
}
return nil
}
func uploadSupportBundle(r *troubleshootv1beta1.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()
fileStat, err := f.Stat()
if err != nil {
return errors.Wrap(err, "stat file")
}
req, err := http.NewRequest(r.Method, r.URI, f)
if err != nil {
return errors.Wrap(err, "create request")
}
req.ContentLength = fileStat.Size()
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
return nil
}
func getExpectedContentType(uploadURL string) string {
parsedURL, err := url.Parse(uploadURL)
if err != nil {
return ""
}
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")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "execute request")
}
if resp.StatusCode >= 300 {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
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)
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()))
}
if err := tarGz.Archive(paths, outputFilename); err != nil {
return errors.Wrap(err, "create archive")
}
return nil
}
-102
View File
@@ -1,102 +0,0 @@
package cli
import (
"errors"
"fmt"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/spf13/viper"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func runTroubleshootCRD(v *viper.Viper) error {
troubleshootClient, err := createTroubleshootK8sClient()
if err != nil {
return err
}
collectorName := v.GetString("collectors")
if collectorName == "" {
collectors, err := troubleshootClient.Collectors(v.GetString("namespace")).List(metav1.ListOptions{})
if err != nil {
return err
}
if len(collectors.Items) == 1 {
collectorName = collectors.Items[0].Name
}
}
if collectorName == "" {
return errors.New("unknown collectors, try using the --collectors flags")
}
// generate a unique name
now := time.Now()
suffix := fmt.Sprintf("%d", now.Unix())
collectorJobName := fmt.Sprintf("%s-job-%s", collectorName, suffix[len(suffix)-4:])
collectorJob := troubleshootv1beta1.CollectorJob{
ObjectMeta: metav1.ObjectMeta{
Name: collectorJobName,
Namespace: v.GetString("namespace"),
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "collectorjob.troubleshoot.replicated.com",
},
Spec: troubleshootv1beta1.CollectorJobSpec{
Collector: troubleshootv1beta1.CollectorRef{
Name: collectorName,
Namespace: v.GetString("namespace"),
},
Image: v.GetString("image"),
ImagePullPolicy: v.GetString("pullpolicy"),
Redact: v.GetBool("redact"),
},
}
if _, err := troubleshootClient.CollectorJobs(v.GetString("namespace")).Create(&collectorJob); err != nil {
return err
}
// Poll the status of the Custom Resource for it to include a callback
var found *troubleshootv1beta1.CollectorJob
start := time.Now()
for {
current, err := troubleshootClient.CollectorJobs(v.GetString("namespace")).Get(collectorJobName, metav1.GetOptions{})
if err != nil && kuberneteserrors.IsNotFound(err) {
continue
} else if err != nil {
return err
}
if current.Status.IsServerReady {
found = current
break
}
if time.Now().Sub(start) > time.Duration(time.Second*10) {
return errors.New("collectorjob failed to start")
}
time.Sleep(time.Millisecond * 200)
}
// Connect to the callback
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, found.Status.ServerPodNamespace, found.Status.ServerPodName)
if err != nil {
return err
}
if err := receiveSupportBundle(found.Namespace, found.Name); err != nil {
return err
}
// Write
close(stopChan)
return nil
}
-328
View File
@@ -1,328 +0,0 @@
package cli
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/mholt/archiver"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
collectrunner "github.com/replicatedhq/troubleshoot/pkg/collect"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
func runTroubleshootNoCRD(v *viper.Viper, arg string) error {
collectorContent := ""
if !isURL(arg) {
if _, err := os.Stat(arg); os.IsNotExist(err) {
return fmt.Errorf("%s was not found", arg)
}
b, err := ioutil.ReadFile(arg)
if err != nil {
return err
}
collectorContent = string(b)
} else {
req, err := http.NewRequest("GET", arg, nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Replicated_Troubleshoot/v1beta1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
collectorContent = string(body)
}
collector := troubleshootv1beta1.Collector{}
if err := yaml.Unmarshal([]byte(collectorContent), &collector); err != nil {
return fmt.Errorf("unable to parse %s collectors", arg)
}
archivePath, err := runCollectors(v, collector)
if err != nil {
return err
}
fmt.Printf("%s\n", archivePath)
return nil
}
func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector) (string, error) {
cfg, err := config.GetConfig()
if err != nil {
return "", err
}
client, err := client.New(cfg, client.Options{})
if err != nil {
return "", err
}
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
return "", err
}
restClient := clientset.CoreV1().RESTClient()
serviceAccountName := v.GetString("serviceaccount")
if serviceAccountName == "" {
generatedServiceAccountName, err := createServiceAccount(collector, v.GetString("namespace"), clientset)
if err != nil {
return "", err
}
defer removeServiceAccount(generatedServiceAccountName, v.GetString("namespace"), clientset)
serviceAccountName = generatedServiceAccountName
}
// deploy an object that "owns" everything to aid in cleanup
owner := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("troubleshoot-%s-owner", collector.Name),
Namespace: v.GetString("namespace"),
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
Data: make(map[string]string),
}
if err := client.Create(context.Background(), &owner); err != nil {
return "", err
}
defer func() {
if err := client.Delete(context.Background(), &owner); err != nil {
fmt.Println("failed to clean up preflight.")
}
}()
// deploy all collectors
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
for _, definedCollector := range collector.Spec {
desiredCollectors = append(desiredCollectors, definedCollector)
}
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
podsCreated := make([]*corev1.Pod, 0, 0)
podsDeleted := make([]*corev1.Pod, 0, 0)
collectorDirs := []string{}
bundlePath, err := ioutil.TempDir("", "troubleshoot")
if err != nil {
return "", err
}
defer os.RemoveAll(bundlePath)
versionFilename, err := writeVersionFile(bundlePath)
if err != nil {
return "", err
}
resyncPeriod := time.Second
ctx := context.Background()
watchList := cache.NewListWatchFromClient(restClient, "pods", "", fields.Everything())
_, controller := cache.NewInformer(watchList, &corev1.Pod{}, resyncPeriod,
cache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
newPod, ok := newObj.(*corev1.Pod)
if !ok {
return
}
oldPod, ok := oldObj.(*corev1.Pod)
if !ok {
return
}
labels := newPod.Labels
troubleshootRole, ok := labels["troubleshoot-role"]
if !ok || troubleshootRole != "troubleshoot" {
return
}
collectorName, ok := labels["troubleshoot"]
if !ok || collectorName != collector.Name {
return
}
if oldPod.Status.Phase == newPod.Status.Phase {
return
}
if newPod.Status.Phase == corev1.PodFailed {
podsDeleted = append(podsDeleted, newPod)
return
}
if newPod.Status.Phase != corev1.PodSucceeded {
return
}
podLogOpts := corev1.PodLogOptions{}
req := clientset.CoreV1().Pods(newPod.Namespace).GetLogs(newPod.Name, &podLogOpts)
podLogs, err := req.Stream()
if err != nil {
fmt.Println("get stream")
return
}
defer podLogs.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
fmt.Println("copy logs")
return
}
collectorDir, err := parseAndSaveCollectorOutput(buf.String(), bundlePath)
if err != nil {
logger.Printf("parse collected data: %v\n", err)
return
}
// empty dir name will make tar fail
if collectorDir == "" {
logger.Printf("pod %s did not return any files\n", newPod.Name)
return
}
collectorDirs = append(collectorDirs, collectorDir)
if err := client.Delete(context.Background(), newPod); err != nil {
fmt.Println("delete pod error", err)
}
podsDeleted = append(podsDeleted, newPod)
},
})
go func() {
controller.Run(ctx.Done())
}()
s := runtime.NewScheme()
s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.ConfigMap{})
for _, collect := range desiredCollectors {
_, pod, err := collectrunner.CreateCollector(client, s, &owner, collector.Name, v.GetString("namespace"), serviceAccountName, "troubleshoot", collect, v.GetString("image"), v.GetString("pullpolicy"))
if err != nil {
logger.Printf("A collector pod cannot be created: %v\n", err)
continue
}
podsCreated = append(podsCreated, pod)
}
start := time.Now()
for {
if start.Add(time.Second * 30).Before(time.Now()) {
fmt.Println("timeout running troubleshoot")
return "", err
}
if len(podsDeleted) == len(podsCreated) {
break
}
time.Sleep(time.Millisecond * 200)
}
ctx.Done()
tarGz := archiver.TarGz{
Tar: &archiver.Tar{
ImplicitTopLevelFolder: false,
},
}
// version file should be first in tar archive for quick extraction
paths := []string{
versionFilename,
}
for _, collectorDir := range collectorDirs {
paths = append(paths, collectorDir)
}
if err := tarGz.Archive(paths, "support-bundle.tar.gz"); err != nil {
return "", err
}
return "support-bundle.tar.gz", nil
}
func parseAndSaveCollectorOutput(output string, bundlePath string) (string, error) {
dir := ""
input := make(map[string]interface{})
if err := json.Unmarshal([]byte(output), &input); err != nil {
return "", err
}
for filename, maybeContents := range input {
fileDir, fileName := filepath.Split(filename)
outPath := filepath.Join(bundlePath, fileDir)
dir = outPath
if err := os.MkdirAll(outPath, 0777); err != nil {
return "", err
}
switch maybeContents.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(maybeContents.(string))
if err != nil {
return "", err
}
if err := writeFile(filepath.Join(outPath, fileName), decoded); err != nil {
return "", err
}
case map[string]interface{}:
for k, v := range maybeContents.(map[string]interface{}) {
s, _ := filepath.Split(filepath.Join(outPath, fileName, k))
if err := os.MkdirAll(s, 0777); err != nil {
return "", err
}
decoded, err := base64.StdEncoding.DecodeString(v.(string))
if err != nil {
return "", err
}
if err := writeFile(filepath.Join(outPath, fileName, k), decoded); err != nil {
return "", err
}
}
}
}
return dir, nil
}
+26 -10
View File
@@ -1,12 +1,13 @@
package cli
import (
"fmt"
"net/url"
"os"
"github.com/pkg/errors"
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
"github.com/spf13/viper"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func homeDir() string {
@@ -17,25 +18,40 @@ func homeDir() string {
}
func isURL(str string) bool {
_, err := url.ParseRequestURI(str)
parsed, err := url.ParseRequestURI(str)
if err != nil {
return false
}
return true
return parsed.Scheme != ""
}
func createTroubleshootK8sClient() (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
v := viper.GetViper()
config, err := clientcmd.BuildConfigFromFlags("", v.GetString("kubecontext"))
func createTroubleshootK8sClient(configFlags *genericclioptions.ConfigFlags) (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
config, err := configFlags.ToRESTConfig()
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to create troubleshoot client")
}
return troubleshootClient, nil
}
func findFileName(basename, extension string) (string, error) {
n := 1
name := basename
for {
filename := name + "." + extension
if _, err := os.Stat(filename); os.IsNotExist(err) {
return filename, nil
} else if err != nil {
return "", errors.Wrap(err, "check file exists")
}
name = fmt.Sprintf("%s (%d)", basename, n)
n = n + 1
}
}
+28 -7
View File
@@ -1,27 +1,48 @@
package cli
import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"gopkg.in/yaml.v2"
"fmt"
"io/ioutil"
"path/filepath"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/version"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
func writeVersionFile(path string) (string, error) {
func VersionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print the current version and exit",
Long: `Print the current version and exit`,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Replicated Preflight %s\n", version.Version())
return nil
},
}
return cmd
}
const VersionFilename = "version.yaml"
func writeVersionFile(path string) error {
version := troubleshootv1beta1.SupportBundleVersion{
ApiVersion: "troubleshoot.replicated.com/v1beta1",
Kind: "SupportBundle",
}
b, err := yaml.Marshal(version)
if err != nil {
return "", err
return err
}
filename := filepath.Join(path, "version.yaml")
filename := filepath.Join(path, VersionFilename)
err = ioutil.WriteFile(filename, b, 0644)
if err != nil {
return "", err
return err
}
return filename, nil
return nil
}
+4 -1
View File
@@ -1,6 +1,9 @@
package main
import "github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
import (
"github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
func main() {
cli.InitAndExecute()
@@ -390,6 +390,457 @@ spec:
type: string
type: object
spec:
properties:
analyzers:
items:
properties:
clusterVersion:
properties:
checkName:
type: string
exclude:
type: boolean
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:
type: boolean
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:
type: boolean
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
- customResourceDefinitionName
type: object
deploymentStatus:
properties:
checkName:
type: string
exclude:
type: boolean
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:
- outcomes
- namespace
- name
type: object
distribution:
properties:
checkName:
type: string
exclude:
type: boolean
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:
type: boolean
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:
type: boolean
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:
- outcomes
- ingressName
- namespace
type: object
secret:
properties:
checkName:
type: string
exclude:
type: boolean
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:
- outcomes
- secretName
- namespace
type: object
statefulsetStatus:
properties:
checkName:
type: string
exclude:
type: boolean
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:
- outcomes
- namespace
- name
type: object
storageClass:
properties:
checkName:
type: string
exclude:
type: boolean
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
type: object
type: array
type: object
status:
type: object
@@ -390,167 +390,267 @@ spec:
type: string
type: object
spec:
items:
properties:
clusterInfo:
type: object
clusterResources:
type: object
copy:
properties:
afterCollection:
items:
properties:
collectorName:
type: string
containerName:
type: string
containerPath:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
required:
- selector
- namespace
- containerPath
type: object
exec:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
containerName:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
timeout:
type: string
required:
- selector
- namespace
type: object
http:
properties:
collectorName:
type: string
get:
callback:
properties:
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
method:
type: string
uri:
type: string
required:
- url
- uri
- method
type: object
post:
uploadResultsTo:
properties:
body:
method:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
type: boolean
url:
uri:
type: string
required:
- url
- uri
- method
type: object
put:
type: object
type: array
collectors:
items:
properties:
clusterInfo:
properties:
body:
collectorName:
type: string
headers:
additionalProperties:
type: string
type: object
insecureSkipVerify:
when:
type: boolean
url:
required:
- when
type: object
clusterResources:
properties:
collectorName:
type: string
when:
type: boolean
required:
- when
type: object
copy:
properties:
collectorName:
type: string
containerName:
type: string
containerPath:
type: string
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
when:
type: boolean
required:
- selector
- namespace
- containerPath
- when
type: object
data:
properties:
collectorName:
type: string
data:
type: string
name:
type: string
when:
type: boolean
required:
- data
- when
type: object
data:
properties:
collectorName:
type: string
data:
type: string
name:
type: string
required:
- url
- data
type: object
type: object
logs:
properties:
collectorName:
type: string
limits:
exec:
properties:
maxAge:
args:
items:
type: string
type: array
collectorName:
type: string
maxLines:
format: int64
type: integer
command:
items:
type: string
type: array
containerName:
type: string
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
timeout:
type: string
when:
type: boolean
required:
- selector
- namespace
- when
type: object
http:
properties:
collectorName:
type: string
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
when:
type: boolean
required:
- when
type: object
logs:
properties:
collectorName:
type: string
containerNames:
items:
type: string
type: array
limits:
properties:
maxAge:
type: string
maxLines:
format: int64
type: integer
type: object
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
when:
type: boolean
required:
- selector
- when
type: object
run:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
image:
type: string
imagePullPolicy:
type: string
name:
type: string
namespace:
type: string
timeout:
type: string
when:
type: boolean
required:
- namespace
- image
- when
type: object
secret:
properties:
collectorName:
type: string
includeValue:
type: boolean
key:
type: string
name:
type: string
namespace:
type: string
when:
type: boolean
required:
- name
- when
type: object
namespace:
type: string
selector:
items:
type: string
type: array
required:
- selector
type: object
run:
properties:
args:
items:
type: string
type: array
collectorName:
type: string
command:
items:
type: string
type: array
image:
type: string
imagePullPolicy:
type: string
namespace:
type: string
timeout:
type: string
required:
- namespace
- image
type: object
secret:
properties:
collectorName:
type: string
includeValue:
type: boolean
key:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: object
type: array
type: array
type: object
status:
type: object
type: object
@@ -398,6 +398,49 @@ spec:
properties:
checkName:
type: string
exclude:
type: boolean
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:
type: boolean
outcomes:
items:
properties:
@@ -439,6 +482,8 @@ spec:
type: string
customResourceDefinitionName:
type: string
exclude:
type: boolean
outcomes:
items:
properties:
@@ -475,10 +520,100 @@ spec:
- outcomes
- customResourceDefinitionName
type: object
deploymentStatus:
properties:
checkName:
type: string
exclude:
type: boolean
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:
- outcomes
- namespace
- name
type: object
distribution:
properties:
checkName:
type: string
exclude:
type: boolean
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:
type: boolean
outcomes:
items:
properties:
@@ -521,6 +656,8 @@ spec:
properties:
checkName:
type: string
exclude:
type: boolean
ingressName:
type: string
namespace:
@@ -566,6 +703,8 @@ spec:
properties:
checkName:
type: string
exclude:
type: boolean
key:
type: string
namespace:
@@ -609,10 +748,59 @@ spec:
- secretName
- namespace
type: object
statefulsetStatus:
properties:
checkName:
type: string
exclude:
type: boolean
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:
- outcomes
- namespace
- name
type: object
storageClass:
properties:
checkName:
type: string
exclude:
type: boolean
outcomes:
items:
properties:
@@ -657,8 +845,22 @@ spec:
items:
properties:
clusterInfo:
properties:
collectorName:
type: string
when:
type: boolean
required:
- when
type: object
clusterResources:
properties:
collectorName:
type: string
when:
type: boolean
required:
- when
type: object
copy:
properties:
@@ -668,16 +870,46 @@ spec:
type: string
containerPath:
type: string
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
when:
type: boolean
required:
- selector
- namespace
- containerPath
- when
type: object
data:
properties:
collectorName:
type: string
data:
type: string
name:
type: string
when:
type: boolean
required:
- data
- when
type: object
data:
properties:
collectorName:
type: string
data:
type: string
name:
type: string
required:
- data
type: object
exec:
properties:
@@ -693,6 +925,8 @@ spec:
type: array
containerName:
type: string
name:
type: string
namespace:
type: string
selector:
@@ -701,9 +935,12 @@ spec:
type: array
timeout:
type: string
when:
type: boolean
required:
- selector
- namespace
- when
type: object
http:
properties:
@@ -722,6 +959,8 @@ spec:
required:
- url
type: object
name:
type: string
post:
properties:
body:
@@ -752,11 +991,19 @@ spec:
required:
- url
type: object
when:
type: boolean
required:
- when
type: object
logs:
properties:
collectorName:
type: string
containerNames:
items:
type: string
type: array
limits:
properties:
maxAge:
@@ -765,14 +1012,19 @@ spec:
format: int64
type: integer
type: object
name:
type: string
namespace:
type: string
selector:
items:
type: string
type: array
when:
type: boolean
required:
- selector
- when
type: object
run:
properties:
@@ -790,13 +1042,18 @@ spec:
type: string
imagePullPolicy:
type: string
name:
type: string
namespace:
type: string
timeout:
type: string
when:
type: boolean
required:
- namespace
- image
- when
type: object
secret:
properties:
@@ -810,8 +1067,11 @@ spec:
type: string
namespace:
type: string
when:
type: boolean
required:
- name
- when
type: object
type: object
type: array
+246 -12
View File
@@ -8,6 +8,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AfterCollection) DeepCopyInto(out *AfterCollection) {
*out = *in
if in.UploadResultsTo != nil {
in, out := &in.UploadResultsTo, &out.UploadResultsTo
*out = new(ResultRequest)
**out = **in
}
if in.Callback != nil {
in, out := &in.Callback, &out.Callback
*out = new(ResultRequest)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterCollection.
func (in *AfterCollection) DeepCopy() *AfterCollection {
if in == nil {
return nil
}
out := new(AfterCollection)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
@@ -41,6 +66,26 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(ImagePullSecret)
(*in).DeepCopyInto(*out)
}
if in.DeploymentStatus != nil {
in, out := &in.DeploymentStatus, &out.DeploymentStatus
*out = new(DeploymentStatus)
(*in).DeepCopyInto(*out)
}
if in.StatefulsetStatus != nil {
in, out := &in.StatefulsetStatus, &out.StatefulsetStatus
*out = new(StatefulsetStatus)
(*in).DeepCopyInto(*out)
}
if in.ContainerRuntime != nil {
in, out := &in.ContainerRuntime, &out.ContainerRuntime
*out = new(ContainerRuntime)
(*in).DeepCopyInto(*out)
}
if in.Distribution != nil {
in, out := &in.Distribution, &out.Distribution
*out = new(Distribution)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
@@ -100,7 +145,7 @@ func (in *Analyzer) DeepCopyInto(out *Analyzer) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -246,6 +291,17 @@ func (in *AnalyzerList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AnalyzerSpec) DeepCopyInto(out *AnalyzerSpec) {
*out = *in
if in.Analyzers != nil {
in, out := &in.Analyzers, &out.Analyzers
*out = make([]*Analyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Analyze)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalyzerSpec.
@@ -276,6 +332,7 @@ func (in *AnalyzerStatus) DeepCopy() *AnalyzerStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterInfo) DeepCopyInto(out *ClusterInfo) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterInfo.
@@ -291,6 +348,7 @@ func (in *ClusterInfo) DeepCopy() *ClusterInfo {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterResources) DeepCopyInto(out *ClusterResources) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterResources.
@@ -363,6 +421,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
*out = new(Exec)
(*in).DeepCopyInto(*out)
}
if in.Data != nil {
in, out := &in.Data, &out.Data
*out = new(Data)
**out = **in
}
if in.Copy != nil {
in, out := &in.Copy, &out.Copy
*out = new(Copy)
@@ -390,17 +453,7 @@ func (in *Collector) DeepCopyInto(out *Collector) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Spec != nil {
in, out := &in.Spec, &out.Spec
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -589,6 +642,43 @@ func (in *CollectorRef) DeepCopy() *CollectorRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorSpec) DeepCopyInto(out *CollectorSpec) {
*out = *in
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
if in.AfterCollection != nil {
in, out := &in.AfterCollection, &out.AfterCollection
*out = make([]*AfterCollection, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(AfterCollection)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorSpec.
func (in *CollectorSpec) DeepCopy() *CollectorSpec {
if in == nil {
return nil
}
out := new(CollectorSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorStatus) DeepCopyInto(out *CollectorStatus) {
*out = *in
@@ -604,6 +694,33 @@ func (in *CollectorStatus) DeepCopy() *CollectorStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContainerRuntime) DeepCopyInto(out *ContainerRuntime) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerRuntime.
func (in *ContainerRuntime) DeepCopy() *ContainerRuntime {
if in == nil {
return nil
}
out := new(ContainerRuntime)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Copy) DeepCopyInto(out *Copy) {
*out = *in
@@ -652,6 +769,76 @@ func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Data) DeepCopyInto(out *Data) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Data.
func (in *Data) DeepCopy() *Data {
if in == nil {
return nil
}
out := new(Data)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentStatus.
func (in *DeploymentStatus) DeepCopy() *DeploymentStatus {
if in == nil {
return nil
}
out := new(DeploymentStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Distribution) DeepCopyInto(out *Distribution) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Distribution.
func (in *Distribution) DeepCopy() *Distribution {
if in == nil {
return nil
}
out := new(Distribution)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Exec) DeepCopyInto(out *Exec) {
*out = *in
@@ -814,6 +1001,11 @@ func (in *Logs) DeepCopyInto(out *Logs) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ContainerNames != nil {
in, out := &in.ContainerNames, &out.ContainerNames
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Limits != nil {
in, out := &in.Limits, &out.Limits
*out = new(LogLimits)
@@ -1151,6 +1343,21 @@ func (in *Put) DeepCopy() *Put {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResultRequest) DeepCopyInto(out *ResultRequest) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultRequest.
func (in *ResultRequest) DeepCopy() *ResultRequest {
if in == nil {
return nil
}
out := new(ResultRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Run) DeepCopyInto(out *Run) {
*out = *in
@@ -1208,6 +1415,33 @@ func (in *SingleOutcome) DeepCopy() *SingleOutcome {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StatefulsetStatus) DeepCopyInto(out *StatefulsetStatus) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulsetStatus.
func (in *StatefulsetStatus) DeepCopy() *StatefulsetStatus {
if in == nil {
return nil
}
out := new(StatefulsetStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = *in
@@ -3,76 +3,77 @@ kind: Collector
metadata:
name: collector-sample
spec:
- clusterInfo: {}
- clusterResources: {}
# - secret:
# name: illmannered-cricket-mysql
# namespace: default
# key: mysql-password
# - logs:
# selector:
# - name=nginx-ingress-microk8s
# namespace: default
# limits:
# maxAge: 30d
# maxLines: 10000
# - run:
# collectorName: ping-google
# namespace: default
# image: flungo/netutils
# command: ["ping"]
# args: ["www.google.com"]
# timeout: 5s
- exec:
collectorName: mysql-vars
selector:
- app=mysql
namespace: default
command: ["mysql"]
args: ["-ureplicated", "-ppassword", "-e", "show processlist"]
timeout: 60m
- exec:
collectorName: hosts
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hosts"]
timeout: 60m
- exec:
collectorName: broken
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hostdasddsda"]
timeout: 60m
# - copy:
# selector:
# - app=illmannered-cricket-mysql
# namespace: default
# containerPath: /etc/hosts
- http:
collectorName: test-get
get:
url: https://api.staging.replicated.com/market/v1/echo/ip
insecureSkipVerify: false
headers: {}
- http:
collectorName: test-post
post:
url: http://httpbin.org/headers
insecureSkipVerify: false
headers:
X-Custom-Header: "post-request"
- http:
collectorName: test-put
put:
url: http://httpbin.org/anything
insecureSkipVerify: false
headers:
X-Custom-Header: "put-request"
- http:
collectorName: test-broken
put:
url: ""
collectors:
- clusterInfo: {}
- clusterResources: {}
# - secret:
# name: illmannered-cricket-mysql
# namespace: default
# key: mysql-password
# - logs:
# selector:
# - name=nginx-ingress-microk8s
# namespace: default
# limits:
# maxAge: 30d
# maxLines: 10000
# - run:
# collectorName: ping-google
# namespace: default
# image: flungo/netutils
# command: ["ping"]
# args: ["www.google.com"]
# timeout: 5s
- exec:
collectorName: mysql-vars
selector:
- app=mysql
namespace: default
command: ["mysql"]
args: ["-ureplicated", "-ppassword", "-e", "show processlist"]
timeout: 60m
- exec:
collectorName: hosts
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hosts"]
timeout: 60m
- exec:
collectorName: broken
selector:
- app=graphql-api
namespace: default
command: ["cat"]
args: ["/etc/hostdasddsda"]
timeout: 60m
# - copy:
# selector:
# - app=illmannered-cricket-mysql
# namespace: default
# containerPath: /etc/hosts
- http:
collectorName: test-get
get:
url: https://api.staging.replicated.com/market/v1/echo/ip
insecureSkipVerify: false
headers: {}
- http:
collectorName: test-post
post:
url: http://httpbin.org/headers
insecureSkipVerify: false
headers:
X-Custom-Header: "post-request"
- http:
collectorName: test-put
put:
url: http://httpbin.org/anything
insecureSkipVerify: false
headers:
X-Custom-Header: "put-request"
- http:
collectorName: test-broken
put:
url: ""
+5 -44
View File
@@ -4,22 +4,6 @@ release:
owner: replicatedhq
name: troubleshoot
builds:
- id: collector
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=0
main: cmd/collector/main.go
ldflags: -s -w
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: collector
hooks: {}
- id: preflight
goos:
- linux
@@ -36,7 +20,7 @@ builds:
flags: -tags netgo -installsuffix netgo
binary: preflight
hooks: {}
- id: troubleshoot
- id: support-bundle
goos:
- linux
goarch:
@@ -50,28 +34,12 @@ builds:
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: troubleshoot
hooks: {}
- id: manager
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=0
main: cmd/manager/main.go
ldflags: -s -w
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: manager
binary: support-bundle
hooks: {}
archives:
- id: tar
format: tar.gz
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{.Arm }}{{ end }}-alpha'
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}-alpha'
files:
- licence*
- LICENCE*
@@ -86,20 +54,13 @@ dockers:
image_templates:
- "replicated/troubleshoot:alpha"
binaries:
- collector
- troubleshoot
- support-bundle
- preflight
- dockerfile: ./deploy/Dockerfile.troubleshoot
image_templates:
- "replicated/preflight:alpha"
binaries:
- collector
- troubleshoot
- support-bundle
- preflight
- dockerfile: ./deploy/Dockerfile.manager
image_templates:
- "replicated/troubleshoot-manager:alpha"
binaries:
- manager
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
+15 -38
View File
@@ -4,21 +4,17 @@ release:
owner: replicatedhq
name: troubleshoot
builds:
- id: collector
- id: so
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=0
main: cmd/collector/main.go
ldflags: -s -w
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: collector
- CGO_ENABLED=1
- GO111MODULE=on
main: ffi/main.go
flags: -buildmode=c-shared
binary: troubleshoot.so
hooks: {}
- id: preflight
goos:
@@ -38,7 +34,7 @@ builds:
flags: -tags netgo -installsuffix netgo
binary: preflight
hooks: {}
- id: troubleshoot
- id: support-bundle
goos:
- linux
- windows
@@ -54,28 +50,16 @@ builds:
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: troubleshoot
hooks: {}
- id: manager
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=0
main: cmd/manager/main.go
ldflags: -s -w
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: manager
binary: support-bundle
hooks: {}
archives:
- id: so
builds:
- so
format: tar.gz
- id: tar
format: tar.gz
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{.Arm }}{{ end }}-alpha'
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}-alpha'
files:
- licence*
- LICENCE*
@@ -90,20 +74,13 @@ dockers:
image_templates:
- "replicated/troubleshoot:alpha"
binaries:
- collector
- troubleshoot
- support-bundle
- preflight
- dockerfile: ./deploy/Dockerfile.troubleshoot
image_templates:
- "replicated/preflight:alpha"
binaries:
- collector
- troubleshoot
- support-bundle
- preflight
- dockerfile: ./deploy/Dockerfile.manager
image_templates:
- "replicated/troubleshoot-manager:alpha"
binaries:
- manager
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
+19 -44
View File
@@ -4,21 +4,17 @@ release:
owner: replicatedhq
name: troubleshoot
builds:
- id: collector
- id: so
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=0
main: cmd/collector/main.go
ldflags: -s -w
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: collector
- CGO_ENABLED=1
- GO111MODULE=on
main: ffi/main.go
flags: -buildmode=c-shared
binary: troubleshoot.so
hooks: {}
- id: preflight
goos:
@@ -38,7 +34,7 @@ builds:
flags: -tags netgo -installsuffix netgo
binary: preflight
hooks: {}
- id: troubleshoot
- id: support-bundle
goos:
- linux
- darwin
@@ -54,25 +50,14 @@ builds:
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: troubleshoot
hooks: {}
- id: manager
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=0
main: cmd/manager/main.go
ldflags: -s -w
-X github.com/replicatedhq/troubleshoot/pkg/version.version={{.Version}}
-X github.com/replicatedhq/troubleshoot/pkg/version.gitSHA={{.Commit}}
-X github.com/replicatedhq/troubleshoot/pkg/version.buildTime={{.Date}}
-extldflags "-static"
flags: -tags netgo -installsuffix netgo
binary: manager
binary: support-bundle
hooks: {}
archives:
- id: so
builds:
- so
format: tar.gz
name_template: 'troubleshoot.so_{{ .Os }}_{{ .Arch }}'
- id: preflight
builds:
- preflight
@@ -80,7 +65,7 @@ archives:
format_overrides:
- goos: windows
format: zip
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{.Arm }}{{ end }}-{{ .Major }}.{{ .Minor }}.{{ .Patch }}'
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}'
files:
- licence*
- LICENCE*
@@ -90,14 +75,14 @@ archives:
- README*
- changelog*
- CHANGELOG*
- id: troubleshoot
- id: support-bundle
builds:
- troubleshoot
- support-bundle
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{.Arm }}{{ end }}-{{ .Major }}.{{ .Minor }}.{{ .Patch }}'
name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}'
files:
- licence*
- LICENCE*
@@ -115,8 +100,7 @@ dockers:
- "replicated/troubleshoot:{{ .Major }}.{{ .Minor }}"
- "replicated/troubleshoot:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
binaries:
- collector
- troubleshoot
- support-bundle
- preflight
- dockerfile: ./deploy/Dockerfile.troubleshoot
image_templates:
@@ -125,14 +109,5 @@ dockers:
- "replicated/preflight:{{ .Major }}.{{ .Minor }}"
- "replicated/preflight:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
binaries:
- collector
- troubleshoot
- support-bundle
- preflight
- dockerfile: ./deploy/Dockerfile.manager
image_templates:
- "replicated/troubleshoot-manager:latest"
- "replicated/troubleshoot-manager:{{ .Major }}"
- "replicated/troubleshoot-manager:{{ .Major }}.{{ .Minor }}"
- "replicated/troubleshoot-manager:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
binaries:
- manager
-4
View File
@@ -1,4 +0,0 @@
FROM ubuntu:latest
WORKDIR /
COPY manager /manager
ENTRYPOINT ["/manager"]
+1 -2
View File
@@ -5,8 +5,7 @@ RUN apt-get -qq update \
&& apt-get -qq -y install \
ca-certificates
COPY troubleshoot /troubleshoot/troubleshoot
COPY collector /troubleshoot/collector
COPY support-bundle /troubleshoot/support-bundle
COPY preflight /troubleshoot/preflight
ENV PATH="/troubleshoot:${PATH}"
+22 -16
View File
@@ -3,38 +3,44 @@ kind: Plugin
metadata:
name: preflight
spec:
version: "v0.9.1"
version: "v0.9.11"
platforms:
- selector:
matchLabels:
os: linux
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.1/preflight_0.9.1_linux_amd64-0.9.1.tar.gz
sha256: "2a50b896b2bd0fdd10ae0bc63ecfe417da1de2ab81255990958fa433d123ef85"
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.11/preflight_0.9.11_linux_amd64-0.9.11.tar.gz
sha256: "54c7a692f1c2e90f0f3ee65082ec6e0eebe976414cb20a9343c06a15538e3c8b"
files:
- from: "./preflight"
to: "."
bin: "preflight"
- from: preflight
to: .
- from: LICENSE
to: .
bin: preflight
- selector:
matchLabels:
os: darwin
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.1/preflight_0.9.1_darwin_amd64-0.9.1.tar.gz
sha256: "e8d99442c12606f062edce164c7a503aca316928b796708a08f8ae3fc8e3444d"
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.11/preflight_0.9.11_darwin_amd64-0.9.11.tar.gz
sha256: "03eeb2f40a4afa326ea139000a6befda854da80cdacc7d92e92af15552e664cb"
files:
- from: "./preflight"
to: "."
bin: "preflight"
- from: preflight
to: .
- from: LICENSE
to: .
bin: preflight
- selector:
matchLabels:
os: windows
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.1/preflight_0.9.1_windows_amd64-0.9.1.zip
sha256: "ac1940826bda4025cf2e8be1627f3ba9ef0df0ee259dba388cb7a167c617dfd4"
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.11/preflight_0.9.11_windows_amd64-0.9.11.zip
sha256: "e5395b0b68d319233a69b02953e8b09331866ca574fc99cfddebf264b9000a77"
files:
- from: "/preflight.exe"
to: "."
bin: "preflight.exe"
- from: preflight.exe
to: .
- from: LICENSE
to: .
bin: preflight.exe
shortDescription: Executes application preflight tests in a cluster
homepage: https://github.com/replicatedhq/troubleshoot
caveats: |
+78
View File
@@ -0,0 +1,78 @@
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
name: support-bundle
spec:
version: "v0.9.11"
platforms:
- selector:
matchLabels:
os: linux
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.11/support-bundle_0.9.11_linux_amd64-0.9.11.tar.gz
sha256: "601710ec70a9e9fb68f5395c591f80e72060a9c82eaeee969232a99a44663309"
files:
- from: support-bundle
to: .
- from: LICENSE
to: .
bin: support-bundle
- selector:
matchLabels:
os: darwin
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.11/support-bundle_0.9.11_darwin_amd64-0.9.11.tar.gz
sha256: "fe02a8f88013a55c012e7560856e7d4c36a4d13dbf17170208f870cff57ff030"
files:
- from: support-bundle
to: .
- from: LICENSE
to: .
bin: support-bundle
- selector:
matchLabels:
os: windows
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.9.11/support-bundle_0.9.11_windows_amd64-0.9.11.zip
sha256: "fded59fe9e46b61efbd6808f9f4124de542f2e32525e2fd992cf233578aaeaef"
files:
- from: support-bundle.exe
to: .
- from: LICENSE
to: .
bin: support-bundle.exe
shortDescription: Creates support bundles for off-cluster analysis
homepage: https://github.com/replicatedhq/troubleshoot
caveats: |
Usage:
$ kubectl support-bundle <uri>
where <uri> references a set of application collectors
For example:
$ kubectl support-bundle https://troubleshoot.replicated.com
For additional options:
$ kubectl support-bundle --help
Documentation:
Full documentation on this plugin is available at:
https://help.replicated.com/docs/troubleshoot/kubernetes/support-bundle/overview/
For application developers writing collectors and analyzers:
https://help.replicated.com/docs/troubleshoot/kubernetes/collectors/defining-collectors/
description: |
This plugin collects information about the cluster, and automatically
redacts sensitive data from being collected. This can optionally include
application-specific data. The plugin writes the collected files into a
single archive named support-bundle.tar.gz. This archive can be manually
inspected or uploaded to https://vendor.replicated.com for automated
analysis.
Application developers can create and host a Collector manifest that
defines information to be collected.
For information on creating a Collector manifest, view the documentation
at https://help.replicated.com/docs/troubleshoot/kubernetes/collectors/defining-collectors/
-39
View File
@@ -1,39 +0,0 @@
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
name: troubleshoot
spec:
version: "v0.1.0"
platforms:
- selector:
matchLabels:
os: linux
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/troubleshoot_0.1.0_linux_amd64-alpha.tar.gz
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
files:
- from: "/kubectl-troubleshoot"
to: "."
bin: "./kubectl-troubleshoot"
- selector:
matchLabels:
os: darwin
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/troubleshoot_0.1.0_darwin_amd64-alpha.tar.gz
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
files:
- from: "/kubectl-troubleshoot"
to: "."
bin: "./kubectl-troubleshoot"
- selector:
matchLabels:
os: windows
arch: amd64
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/troubleshoot_0.1.0_windows_amd64-alpha.tar.gz
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
files:
- from: "/kubectl-troubleshoot.exe"
to: "."
bin: "./kubectl-troubleshoot.exe"
shortDescription: Creates support bundles for off-cluster analysis
homepage: https://github.com/replicaetdhq/troubleshoot
+21 -1
View File
@@ -3,6 +3,12 @@ kind: Preflight
metadata:
name: example-preflight-checks
spec:
collectors:
- secret:
name: myapp-postgres
namespace: default
key: uri
includeValue: false
analyzers:
- clusterVersion:
outcomes:
@@ -15,13 +21,15 @@ spec:
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.
- customResourceDefinition:
customResourceDefinitionName: constrainttemplates.templates.gatekeeper.sh
checkName: Gatekeeper policy runtime is installed
checkName: Gatekeeper policy runtime
outcomes:
- fail:
message: Gatekeeper is required for the application, but not found in the cluster.
uri: https://enterprise.support.io/installing/gatekeeper
- pass:
message: Found a supported version of Gatekeeper installed and running in the cluster.
- imagePullSecret:
@@ -31,5 +39,17 @@ spec:
- 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
- pass:
message: Found credentials to pull from quay.io
- secret:
checkName: Postgres connection string
secretName: myapp-postgres
namespace: default
key: uri
outcomes:
- fail:
message: A secret named "myapp-postgres" must be deployed with a "uri" key
uri: https://enterprise.support.io/installing/postgres
- pass:
message: Found a valid postgres connection string
@@ -0,0 +1,21 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Analyzer
metadata:
name: a
spec:
analyzers:
- distribution:
outcomes:
- fail:
when: "= docker desktop"
message: "docker for desktop is not allowed"
- fail:
when: "microk8s"
message: "mickrk8s is not prod"
- warn:
when: "!= eks"
message: "YMMV on not eks"
- pass:
message: "good work"
+1 -26
View File
@@ -3,29 +3,4 @@ kind: Collector
metadata:
name: collector-sample
spec:
- clusterInfo: {}
- clusterResources: {}
- secret:
name: illmannered-cricket-mysql
namespace: default
key: mysql-password
- logs:
selector:
- name=nginx-ingress-microk8s
namespace: default
limits:
maxAge: 30d
maxLines: 10000
- run:
collectorName: ping-google
namespace: default
image: flungo/netutils
command: ["ping"]
args: ["www.google.com"]
timeout: 5s
- http:
collectorName: test-get
get:
url: https://api.staging.replicated.com/market/v1/echo/ip
insecureSkipVerify: false
headers: {}
collectors: []
+53
View File
@@ -0,0 +1,53 @@
package main
import "C"
import (
"fmt"
"encoding/json"
"gopkg.in/yaml.v2"
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/replicatedhq/troubleshoot/pkg/convert"
)
//export Analyze
func Analyze(bundleURL string, analyzers string, outputFormat string, compatibility string) *C.char {
logger.SetQuiet(true)
result, err := analyzer.DownloadAndAnalyze(bundleURL, analyzers)
if err != nil {
fmt.Printf("error downloading and analyzing: %s\n", err.Error())
return C.CString("")
}
var data interface{}
switch compatibility {
case "support-bundle":
data = convert.FromAnalyzerResult(result)
default:
data = result
}
var formatted []byte
switch outputFormat {
case "json":
formatted, err = json.MarshalIndent(data, "", " ")
case "", "yaml":
formatted, err = yaml.Marshal(data)
default:
fmt.Printf("unknown output format: %s\n", outputFormat)
return C.CString("")
}
if err != nil {
fmt.Printf("error formatting output: %#v\n", err)
return C.CString("")
}
return C.CString(string(formatted))
}
func main() {}
+31 -18
View File
@@ -3,43 +3,56 @@ module github.com/replicatedhq/troubleshoot
go 1.12
require (
github.com/Masterminds/goutils v1.1.0 // indirect
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.20.0+incompatible // indirect
github.com/andrewchambers/go-jqpipe v0.0.0-20180509223707-2d54cef8cd94 // indirect
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
github.com/aws/aws-sdk-go v1.22.0 // indirect
github.com/blang/semver v3.5.1+incompatible
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v1.13.1 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // 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/evanphx/json-patch v4.5.0+incompatible // indirect
github.com/fatih/color v1.7.0
github.com/frankban/quicktest v1.7.2 // indirect
github.com/gin-gonic/gin v1.4.0
github.com/gizak/termui/v3 v3.1.0
github.com/go-openapi/spec v0.19.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/hashicorp/go-getter v1.3.0
github.com/google/gofuzz v1.0.0 // indirect
github.com/googleapis/gnostic v0.3.0 // indirect
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e
github.com/hashicorp/go-multierror v1.0.0
github.com/huandu/xstrings v1.2.0 // indirect
github.com/manifoldco/promptui v0.3.2 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mholt/archiver v3.1.1+incompatible
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/nwaples/rardecode v1.0.0 // indirect
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4 v2.2.6+incompatible // indirect
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.3
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.3.0
github.com/tj/go-spin v1.1.0
github.com/ugorji/go v1.1.7 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/dburl v0.0.0-20190203050942-98997a05b24f // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
go.opencensus.io v0.20.2 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
golang.org/x/sys v0.0.0-20190621203818-d432491b9138 // indirect
google.golang.org/appengine v1.5.0 // indirect
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
k8s.io/cli-runtime v0.0.0-20190314001948-2899ed30580f
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
k8s.io/klog v0.4.0 // indirect
k8s.io/kube-openapi v0.0.0-20190815110238-8ff09bc626d6 // indirect
k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a // indirect
sigs.k8s.io/controller-runtime v0.2.0-beta.2
sigs.k8s.io/controller-tools v0.2.0-beta.2 // indirect
sigs.k8s.io/kustomize v2.0.3+incompatible // indirect
)
+112 -197
View File
@@ -5,38 +5,38 @@ cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.41.0 h1:NFvqUTDnSNYPX5oReekmB+D+90jrJIcVImxQ3qrBVgM=
cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
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/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/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.20.0+incompatible h1:dJTKKuUkYW3RMFdQFXPU/s6hg10RgctmTjRcbZ98Ap8=
github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
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 h1:0GoNN3taZV6QI81IXgCbxMyEaJDXMSIjArYBCYzVVvs=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 h1:JCHLVE3B+kJde7bIEo5N4J+ZbLhp0J1Fs+ulyRws4gE=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412 h1:vOVO0ypMfTt6tZacyI0kp+iCZb1XSNiYDqnzBWYgfe4=
github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412/go.mod h1:AI9hp1tkp10pAlK5TCwL+7yWbRgtDm9jhToq6qij2xs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andrewchambers/go-jqpipe v0.0.0-20180509223707-2d54cef8cd94 h1:mOmUCSuraMWgFv6Wr3pCkt9UZ2RBsx2XMZoIXg2Oj6w=
github.com/andrewchambers/go-jqpipe v0.0.0-20180509223707-2d54cef8cd94/go.mod h1:8KsRln2ynWOaJwxYOUwfPU/ix+H9oh3aCJ6L0DsHW3Q=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30 h1:Kn3rqvbUFqSepE2OqVu0Pn1CbDw9IuMlONapol0zuwk=
github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30/go.mod h1:4AJxUpXUhv4N+ziTvIcWWXgeorXpxPZOfk9HdEVr96M=
github.com/appscode/jsonpatch v2.0.0+incompatible h1:DEsgcSnA7ui6pICc75uxDpyN8Bx4DLFTS8aRym702nE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.15.78 h1:LaXy6lWR0YK7LKyuU0QWy2ws/LWTPfYV/UgfiBu4tvY=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.22.0 h1:e88V6+dSEyBibUy0ekOydtTfNWzqG3hrtCR8SF6UqqY=
github.com/aws/aws-sdk-go v1.22.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/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=
@@ -47,10 +47,6 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@@ -66,34 +62,34 @@ 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/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/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/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 v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 h1:pEtiCjIXx3RvGjlUJuCNxNOw0MNblyR9Wi+vJGBFh+8=
github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w=
github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.0.0+incompatible h1:xregGRMLBeuRcwiOTHRCsPPuzCQlqhxUPbqdw+zNkLc=
github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -115,71 +111,65 @@ github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-logr/zapr v0.1.1 h1:qXBXPDdNncunGs7XeEpsJt8wCjYBygluzfdLO0G5baE=
github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1 h1:wSt/4CYxs70xbATrGXhokKF1i0tZjENLOo1ioIO13zk=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9 h1:tF+augKRWlWx0J0B7ZyyKSiTyV6E1zZe+7b3qQlcEf8=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501 h1:C1JKChikHGpXwT5UQDFaryIpDtyyGL/CR6C2kB7F1oc=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87 h1:zP3nY8Tk2E6RTkqGYrarZXuzh+ffyLDljLxCy1iJw80=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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 h1:ScAXWS+TR6MZKex+7Z8rneuSJH+FSDqd6ocQyl+ZHo4=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/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/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
@@ -187,12 +177,12 @@ github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
@@ -201,8 +191,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v1.3.0 h1:pFMSFlI9l5NaeuzkpE3L7BYk9qQ9juTAgXW/H0cqxcU=
github.com/hashicorp/go-getter v1.3.0/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e h1:6krcdHPiS+aIP9XKzJzSahfjD7jG7Z+4+opm0z39V1M=
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
@@ -213,14 +203,10 @@ github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
@@ -230,16 +216,13 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
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.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -251,22 +234,26 @@ github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a h1:TpvdAwDAt1K4ANVOfcihouRdvP+MgAfDWwBuct4l6ZY=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
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/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g=
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 h1:IaSjLMT6WvkoZZjspGxy3rdaTEmWLoRm49WbtVUi9sA=
github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
@@ -278,11 +265,14 @@ github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@@ -293,7 +283,6 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
@@ -303,28 +292,26 @@ github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWk
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.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.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c h1:MUyE44mTvnI5A0xrxIxaMqoWFzPfQvtE2IWUollMDMs=
github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 h1:zNBQb37RGLmJybyMcs983HfUfpkw9OTFD9tbBfAViHE=
github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
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/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
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.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
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 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -335,37 +322,27 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.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-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
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 v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
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-20190115171406-56726106282f/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/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.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
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/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
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-20190117184657-bf6a532e95b1/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 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/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/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2 h1:J7U/N7eRtzjhs26d6GqMh2HBuXP8/Z64Densiiieafo=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -401,8 +378,7 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -415,6 +391,7 @@ github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
@@ -423,23 +400,27 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb
github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/dburl v0.0.0-20190203050942-98997a05b24f/go.mod h1:g6rdekR8vgfVZrkLWfobLTm0kVez7GAN23mWtkGCJ14=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -456,21 +437,16 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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/exp v0.0.0-20190510132918-efd6b22b2522/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/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/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=
@@ -480,27 +456,25 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-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-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
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 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
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-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -520,21 +494,15 @@ golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg=
golang.org/x/sys v0.0.0-20190621203818-d432491b9138/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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -547,71 +515,52 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190501045030-23463209683d h1:D7DVZUZEUgsSIDTivnUtVeGfN5AvhDIKtdIZAqx0ieE=
golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190703212419-2214986f1668/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
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 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.3.1 h1:oJra/lMfmtm13/rgY/8i3MzjFWYXvQIAKjQ3HqofMk8=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
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=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
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-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -625,68 +574,34 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20190222213804-5cb15d344471 h1:MzQGt8qWQCR+39kbYRd0uQqsvSidpYqJLFeWiJ9l4OE=
k8s.io/api v0.0.0-20190222213804-5cb15d344471/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo=
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/api v0.0.0-20190703205437-39734b2a72fe h1:MFaHtAyhZcfBZocN91muHSqnwiF5yfXx7yGoehneNYg=
k8s.io/api v0.0.0-20190703205437-39734b2a72fe/go.mod h1:J5EZ0KSEjvyKOBy5BDHSF3zn82madLLWg7nUKaOHZKU=
k8s.io/api v0.0.0-20190717022910-653c86b0609b h1:WiE134uexhvhHw4DjJuYsghv792UCe2xN5SHQOayf28=
k8s.io/apiextensions-apiserver v0.0.0-20190228180357-d002e88f6236 h1:JfFtjaElBIgYKCWEtYQkcNrTpW+lMO4GJy8NP6SVQmM=
k8s.io/apiextensions-apiserver v0.0.0-20190228180357-d002e88f6236/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA=
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 h1:UYfHH+KEF88OTg+GojQUwFTNxbxwmoktLwutUzR0GPg=
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76 h1:vxMYBaJgczGAIpJAOBco2eHuFYIyDdNIebt60jxLauA=
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA=
k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4 h1:aE8wOCKuoRs2aU0OP/Rz8SXiAB0FTTku3VtGhhrkSmc=
k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/cli-runtime v0.0.0-20190314001948-2899ed30580f h1:gRAqn9Z3rp62UwLU3PdC7Lhmsvd3e9PXLsq7EG+bq1s=
k8s.io/cli-runtime v0.0.0-20190314001948-2899ed30580f/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM=
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ=
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/code-generator v0.0.0-20181117043124-c2090bec4d9b h1:KH0fUlgdFZH8UMxJ/FDCYHpczfSQKefetq5NjL6BVF0=
k8s.io/code-generator v0.0.0-20181117043124-c2090bec4d9b/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8=
k8s.io/code-generator v0.0.0-20190703204957-583809a49343/go.mod h1:gZ0mjdzfPz3P+cILxibAJ25xFoZ1Kf0xQrFUHy66AEs=
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a h1:QoHVuRquf80YZ+/bovwxoMO3Q/A3nt3yTgS0/0nejuk=
k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c=
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM=
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco=
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
k8s.io/kube-openapi v0.0.0-20190815110238-8ff09bc626d6 h1:3zEq2jkpzwO71j00zmrJAokMnI6KQRS3dzRl2ATTkUY=
k8s.io/kube-openapi v0.0.0-20190815110238-8ff09bc626d6/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y=
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/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=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/controller-runtime v0.1.12 h1:ovDq28E64PeY1yR+6H7DthakIC09soiDCrKvfP2tPYo=
sigs.k8s.io/controller-runtime v0.1.12/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8=
k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a h1:uy5HAgt4Ha5rEMbhZA+aM1j2cq5LmR6LQ71EYC2sVH4=
k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
sigs.k8s.io/controller-runtime v0.2.0-beta.2 h1:hOWldx1qmGI9TsU+uUsq1xTgVmUV7AZo08VAYX0dwGI=
sigs.k8s.io/controller-runtime v0.2.0-beta.2/go.mod h1:TSH2R0nSz4WAlUUlNnOFcOR/VUhfwBLlmtq2X6AiQCA=
sigs.k8s.io/controller-tools v0.1.11/go.mod h1:6g08p9m9G/So3sBc1AOQifHfhxH/mb6Sc4z0LMI8XMw=
sigs.k8s.io/controller-tools v0.2.0-beta.2 h1:ucniFzEuW7PFfFDuUxacdY4Fy4q065wPguVl+BE2/t0=
sigs.k8s.io/controller-tools v0.2.0-beta.2/go.mod h1:gC5UAnK1jbxWnDaqTi0yxKIsRsRwshzeRtTUGbM9vos=
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/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs=
sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U=
+42
View File
@@ -20,23 +20,65 @@ type getChildCollectedFileContents func(string) (map[string][]byte, error)
func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileContents, findFiles getChildCollectedFileContents) (*AnalyzeResult, error) {
if analyzer.ClusterVersion != nil {
if analyzer.ClusterVersion.Exclude {
return nil, nil
}
return analyzeClusterVersion(analyzer.ClusterVersion, getFile)
}
if analyzer.StorageClass != nil {
if analyzer.StorageClass.Exclude {
return nil, nil
}
return analyzeStorageClass(analyzer.StorageClass, getFile)
}
if analyzer.CustomResourceDefinition != nil {
if analyzer.CustomResourceDefinition.Exclude {
return nil, nil
}
return analyzeCustomResourceDefinition(analyzer.CustomResourceDefinition, getFile)
}
if analyzer.Ingress != nil {
if analyzer.Ingress.Exclude {
return nil, nil
}
return analyzeIngress(analyzer.Ingress, getFile)
}
if analyzer.Secret != nil {
if analyzer.Secret.Exclude {
return nil, nil
}
return analyzeSecret(analyzer.Secret, getFile)
}
if analyzer.ImagePullSecret != nil {
if analyzer.ImagePullSecret.Exclude {
return nil, nil
}
return analyzeImagePullSecret(analyzer.ImagePullSecret, findFiles)
}
if analyzer.DeploymentStatus != nil {
if analyzer.DeploymentStatus.Exclude {
return nil, nil
}
return analyzeDeploymentStatus(analyzer.DeploymentStatus, getFile)
}
if analyzer.StatefulsetStatus != nil {
if analyzer.StatefulsetStatus.Exclude {
return nil, nil
}
return analyzeStatefulsetStatus(analyzer.StatefulsetStatus, getFile)
}
if analyzer.ContainerRuntime != nil {
if analyzer.ContainerRuntime.Exclude {
return nil, nil
}
return analyzeContainerRuntime(analyzer.ContainerRuntime, getFile)
}
if analyzer.Distribution != nil {
if analyzer.Distribution.Exclude {
return nil, nil
}
return analyzeDistribution(analyzer.Distribution, getFile)
}
return nil, errors.New("invalid analyzer")
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
func analyzeClusterVersion(analyzer *troubleshootv1beta1.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 top get contents of cluster_version.json")
return nil, errors.Wrap(err, "failed to get contents of cluster_version.json")
}
collectorClusterVersion := collect.ClusterVersion{}
+122
View File
@@ -0,0 +1,122 @@
package analyzer
import (
"strconv"
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
)
func commonStatus(outcomes []*troubleshootv1beta1.Outcome, title string, readyReplicas int) (*AnalyzeResult, error) {
result := &AnalyzeResult{
Title: title,
}
// ordering from the spec is important, the first one that matches returns
for _, outcome := range 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
}
match, err := compareActualToWhen(outcome.Fail.When, readyReplicas)
if err != nil {
return nil, errors.Wrap(err, "failed to parse fail range")
}
if match {
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
}
match, err := compareActualToWhen(outcome.Warn.When, readyReplicas)
if err != nil {
return nil, errors.Wrap(err, "failed to parse warn range")
}
if match {
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
}
match, err := compareActualToWhen(outcome.Pass.When, readyReplicas)
if err != nil {
return nil, errors.Wrap(err, "failed to parse pass range")
}
if match {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return result, nil
}
func compareActualToWhen(when string, actual int) (bool, error) {
parts := strings.Split(strings.TrimSpace(when), " ")
// we can make this a lot more flexible
if len(parts) != 2 {
return false, errors.New("unable to parse when range")
}
value, err := strconv.Atoi(parts[1])
if err != nil {
return false, errors.New("unable to parse when value")
}
switch parts[0] {
case "=":
fallthrough
case "==":
fallthrough
case "===":
return actual == value, nil
case "<":
return actual < value, nil
case ">":
return actual > value, nil
case "<=":
return actual <= value, nil
case ">=":
return actual >= value, nil
}
return false, errors.Errorf("unknown comparator: %q", parts[0])
}
+132
View File
@@ -0,0 +1,132 @@
package analyzer
import (
"encoding/json"
"net/url"
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
corev1 "k8s.io/api/core/v1"
)
func analyzeContainerRuntime(analyzer *troubleshootv1beta1.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")
}
var nodes []corev1.Node
if err := json.Unmarshal(collected, &nodes); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal node list")
}
foundRuntimes := []string{}
for _, node := range nodes {
foundRuntimes = append(foundRuntimes, node.Status.NodeInfo.ContainerRuntimeVersion)
}
result := &AnalyzeResult{
Title: "Container Runtime",
}
// ordering is important for passthrough
for _, outcome := range analyzer.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
}
for _, foundRuntime := range foundRuntimes {
isMatch, err := compareRuntimeConditionalToActual(outcome.Fail.When, foundRuntime)
if err != nil {
return nil, errors.Wrap(err, "failed to compare runtime conditional")
}
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
}
for _, foundRuntime := range foundRuntimes {
isMatch, err := compareRuntimeConditionalToActual(outcome.Warn.When, foundRuntime)
if err != nil {
return nil, errors.Wrap(err, "failed to compare runtime conditional")
}
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
}
for _, foundRuntime := range foundRuntimes {
isMatch, err := compareRuntimeConditionalToActual(outcome.Pass.When, foundRuntime)
if err != nil {
return nil, errors.Wrap(err, "failed to compare runtime conditional")
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
}
return result, nil
}
func compareRuntimeConditionalToActual(conditional string, actual string) (bool, error) {
parts := strings.Split(strings.TrimSpace(conditional), " ")
// we can make this a lot more flexible
if len(parts) != 2 {
return false, errors.New("unable to parse conditional")
}
parsedRuntime, err := url.Parse(actual)
if err != nil {
return false, errors.New("unable to parse url")
}
switch parts[0] {
case "=":
fallthrough
case "==":
fallthrough
case "===":
return parts[1] == parsedRuntime.Scheme, nil
}
return false, nil
}
+121
View File
@@ -0,0 +1,121 @@
package analyzer
import (
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_compareRuntimeConditionalToActual(t *testing.T) {
tests := []struct {
name string
conditional string
actual string
expected bool
}{
{
name: "containerd://1.2.5 = containerd",
conditional: "= containerd",
actual: "containerd://1.2.5",
expected: true,
},
{
name: "containerd://1.2.5 == containerd",
conditional: "== containerd",
actual: "containerd://1.2.5",
expected: true,
},
{
name: "containerd://1.2.5 === containerd",
conditional: "=== containerd",
actual: "containerd://1.2.5",
expected: true,
},
{
name: "containerd://1.2.5 != containerd",
conditional: "!= containerd",
actual: "containerd://1.2.5",
expected: false,
},
{
name: "containerd://1.2.5 !== containerd",
conditional: "!== containerd",
actual: "containerd://1.2.5",
expected: false,
},
{
name: "containerd://1.2.5 !== containerd",
conditional: "!=== containerd",
actual: "containerd://1.2.5",
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareRuntimeConditionalToActual(test.conditional, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_containerRuntime(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta1.ContainerRuntime
expectResult AnalyzeResult
files map[string][]byte
}{
{
name: "no containerd, when it's containerd",
analyzer: troubleshootv1beta1.ContainerRuntime{
Outcomes: []*troubleshootv1beta1.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
When: "!= containerd",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Message: "containerd detected",
},
},
},
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "Container Runtime",
Message: "containerd detected",
},
files: map[string][]byte{
"cluster-resources/nodes.json": []byte(collectedNodes),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
getFiles := func(n string) ([]byte, error) {
return test.files[n], nil
}
actual, err := analyzeContainerRuntime(&test.analyzer, getFiles)
req.NoError(err)
assert.Equal(t, &test.expectResult, actual)
})
}
}
+574
View File
@@ -0,0 +1,574 @@
package analyzer
var collectedDeployments = `[
{
"metadata": {
"name": "kotsadm-api",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-api",
"uid": "56526035-cd29-4d08-8375-291503b1a006",
"resourceVersion": "1583068",
"generation": 1,
"creationTimestamp": "2019-11-07T00:34:32Z",
"labels": {
"app.kubernetes.io/managed-by": "skaffold-v0.41.0"
},
"annotations": {
"deployment.kubernetes.io/revision": "1",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"},\"name\":\"kotsadm-api\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"kotsadm-api\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"kotsadm-api\",\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"}},\"spec\":{\"affinity\":{\"podAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"podAffinityTerm\":{\"labelSelector\":{\"matchExpressions\":[{\"key\":\"app\",\"operator\":\"In\",\"values\":[\"ship-www\"]}]},\"topologyKey\":\"kubernetes.io/hostname\"},\"weight\":1}]},\"podAntiAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"podAffinityTerm\":{\"labelSelector\":{\"matchExpressions\":[{\"key\":\"app\",\"operator\":\"In\",\"values\":[\"kotsadm-api\"]}]},\"topologyKey\":\"kubernetes.io/hostname\"},\"weight\":2}]}},\"containers\":[{\"env\":[{\"name\":\"DEV_NAMESPACE\",\"value\":\"test\"},{\"name\":\"LOG_LEVEL\",\"value\":\"debug\"},{\"name\":\"SESSION_KEY\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"key\",\"name\":\"session\"}}},{\"name\":\"POSTGRES_URI\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"uri\",\"name\":\"ship-postgres\"}}},{\"name\":\"API_ENCRYPTION_KEY\",\"value\":\"IvWItkB8+ezMisPjSMBknT1PdKjBx7Xc/txZqOP8Y2Oe7+Jy\"},{\"name\":\"INIT_SERVER_URI\",\"value\":\"http://init-server:3000\"},{\"name\":\"WATCH_SERVER_URI\",\"value\":\"http://watch-server:3000\"},{\"name\":\"PINO_LOG_PRETTY\",\"value\":\"1\"},{\"name\":\"S3_BUCKET_NAME\",\"value\":\"shipbucket\"},{\"name\":\"AIRGAP_BUNDLE_S3_BUCKET\",\"value\":\"airgap\"},{\"name\":\"S3_ENDPOINT\",\"value\":\"http://kotsadm-s3.default.svc.cluster.local:4569/\"},{\"name\":\"S3_ACCESS_KEY_ID\",\"value\":\"***HIDDEN***\"},{\"name\":\"S3_SECRET_ACCESS_KEY\",\"value\":\"***HIDDEN***\"},{\"name\":\"S3_BUCKET_ENDPOINT\",\"value\":\"true\"},{\"name\":\"GITHUB_CLIENT_ID\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"client-id\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_CLIENT_SECRET\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"client-secret\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_INTEGRATION_ID\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"integration-id\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_PRIVATE_KEY_FILE\",\"value\":\"/keys/github/private-key.pem\"},{\"name\":\"SHIP_API_ENDPOINT\",\"value\":\"http://kotsadm-api.default.svc.cluster.local:3000\"},{\"name\":\"SHIP_API_ADVERTISE_ENDPOINT\",\"value\":\"http://localhost:30065\"},{\"name\":\"GRAPHQL_PREM_ENDPOINT\",\"value\":\"http://graphql-api-prem:3000/graphql\"},{\"name\":\"AUTO_CREATE_CLUSTER\",\"value\":\"1\"},{\"name\":\"AUTO_CREATE_CLUSTER_NAME\",\"value\":\"microk8s\"},{\"name\":\"AUTO_CREATE_CLUSTER_TOKEN\",\"value\":\"***HIDDEN***\"},{\"name\":\"ENABLE_SHIP\",\"value\":\"1\"},{\"name\":\"ENABLE_KOTS\",\"value\":\"1\"},{\"name\":\"ENABLE_KURL\",\"value\":\"1\"},{\"name\":\"POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}}],\"image\":\"localhost:32000/kotsadm-api:v1.0.1-30-g8fa13e34-dirty@sha256:4a0ca1a2eae46472bd2d454f9e763a458ddd172689e179b17054262507bb4fc8\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"kotsadm-api\",\"ports\":[{\"containerPort\":3000,\"name\":\"http\"},{\"containerPort\":9229,\"name\":\"debug\"}],\"readinessProbe\":{\"httpGet\":{\"path\":\"/healthz\",\"port\":3000},\"initialDelaySeconds\":2,\"periodSeconds\":2},\"volumeMounts\":[{\"mountPath\":\"/keys/github\",\"name\":\"github-app-private-key\",\"readOnly\":true}]}],\"restartPolicy\":\"Always\",\"securityContext\":{\"runAsUser\":0},\"serviceAccount\":\"kotsadm-api\",\"volumes\":[{\"name\":\"github-app-private-key\",\"secret\":{\"secretName\":\"github-app-private-key\"}}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "kotsadm-api"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "kotsadm-api"
}
},
"spec": {
"containers": [
{
"name": "kotsadm-api",
"image": "localhost:32000/kotsadm-api:v1.0.1-30-g8fa13e34-dirty@sha256:4a0ca1a2eae46472bd2d454f9e763a458ddd172689e179b17054262507bb4fc8",
"ports": [
{
"name": "http",
"containerPort": 3000,
"protocol": "TCP"
}
],
"env": [
{
"name": "DEV_NAMESPACE",
"value": "test"
},
{
"name": "POD_NAMESPACE",
"valueFrom": {
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
}
}
}
],
"resources": {},
"readinessProbe": {
"httpGet": {
"path": "/healthz",
"port": 3000,
"scheme": "HTTP"
},
"initialDelaySeconds": 2,
"timeoutSeconds": 1,
"periodSeconds": 2,
"successThreshold": 1,
"failureThreshold": 3
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "IfNotPresent"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "kotsadm-api",
"serviceAccount": "kotsadm-api",
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 1,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:23Z",
"lastTransitionTime": "2019-11-07T00:35:23Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:23Z",
"lastTransitionTime": "2019-11-07T00:34:32Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-api-6f4b994bd5\" has successfully progressed."
}
]
}
},
{
"metadata": {
"name": "kotsadm-operator",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-operator",
"uid": "cfae9877-eef4-44c9-acac-0bf0d1aa547e",
"resourceVersion": "1583379",
"generation": 2,
"creationTimestamp": "2019-11-07T00:34:32Z",
"labels": {
"app.kubernetes.io/managed-by": "skaffold-v0.41.0"
},
"annotations": {
"deployment.kubernetes.io/revision": "2",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"},\"name\":\"kotsadm-operator\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"kotsadm-operator\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"kotsadm-operator\",\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"}},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"KOTSADM_API_ENDPOINT\",\"value\":\"http://kotsadm-api:3000\"},{\"name\":\"KOTSADM_TOKEN\",\"value\":\"***HIDDEN***\"},{\"name\":\"KOTSADM_TARGET_NAMESPACE\",\"value\":\"test\"}],\"image\":\"localhost:32000/kotsadm-operator:v1.0.1-30-g8fa13e34-dirty@sha256:177c15b6399717048e9355bc8fd8b8ed213be90615c7c6ee7b7fdcee50aca6c5\",\"imagePullPolicy\":\"Always\",\"name\":\"kotsadm-operator\",\"resources\":{\"limits\":{\"cpu\":\"200m\",\"memory\":\"1000Mi\"},\"requests\":{\"cpu\":\"100m\",\"memory\":\"500Mi\"}}}],\"restartPolicy\":\"Always\"}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "kotsadm-operator"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "kotsadm-operator"
}
},
"spec": {
"containers": [
{
"name": "kotsadm-operator",
"image": "localhost:32000/kotsadm-operator:v1.0.1-30-g8fa13e34-dirty@sha256:177c15b6399717048e9355bc8fd8b8ed213be90615c7c6ee7b7fdcee50aca6c5",
"env": [
{
"name": "KOTSADM_API_ENDPOINT",
"value": "http://kotsadm-api:3000"
},
{
"name": "KOTSADM_TOKEN",
"value": "***HIDDEN***"
},
{
"name": "KOTSADM_TARGET_NAMESPACE",
"value": "test"
}
],
"resources": {
"limits": {
"cpu": "200m",
"memory": "1000Mi"
},
"requests": {
"cpu": "100m",
"memory": "500Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 2,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:34:37Z",
"lastTransitionTime": "2019-11-07T00:34:37Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:36:34Z",
"lastTransitionTime": "2019-11-07T00:34:32Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-operator-5b5c977699\" has successfully progressed."
}
]
}
},
{
"metadata": {
"name": "kotsadm-postgres-watch",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-postgres-watch",
"uid": "ec195b07-4fd2-4bbe-8b01-4cbdfbe0e79d",
"resourceVersion": "1582762",
"generation": 1,
"creationTimestamp": "2019-11-07T00:34:39Z",
"annotations": {
"deployment.kubernetes.io/revision": "1"
},
"ownerReferences": [
{
"apiVersion": "databases.schemahero.io/v1alpha2",
"kind": "Database",
"name": "kotsadm-postgres",
"uid": "e6d51ce6-c4c1-428f-9e8e-56f1a3a43588",
"controller": true,
"blockOwnerDeletion": true
}
]
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"deployment": "kotsadm-postgreswatch"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"deployment": "kotsadm-postgreswatch"
}
},
"spec": {
"containers": [
{
"name": "schemahero",
"image": "schemahero/schemahero:alpha",
"args": [
"watch",
"--driver",
"postgres",
"--uri",
"postgres://shipcloud:password@postgres.default.svc.cluster.local:5432/shipcloud?sslmode=disable",
"--namespace",
"default",
"--instance",
"kotsadm-postgres"
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "Always"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"serviceAccountName": "kotsadm-postgres",
"serviceAccount": "kotsadm-postgres",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 1,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:00Z",
"lastTransitionTime": "2019-11-07T00:35:00Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:35:00Z",
"lastTransitionTime": "2019-11-07T00:34:39Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-postgres-watch-5cf76f4c45\" has successfully progressed."
}
]
}
},
{
"metadata": {
"name": "kotsadm-web",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/kotsadm-web",
"uid": "4e657f8a-5edb-498b-9402-1f93f51f5dda",
"resourceVersion": "1582354",
"generation": 1,
"creationTimestamp": "2019-11-07T00:34:32Z",
"labels": {
"app.kubernetes.io/managed-by": "skaffold-v0.41.0",
"skaffold.dev/builder": "local",
"skaffold.dev/cleanup": "true",
"skaffold.dev/deployer": "kustomize",
"skaffold.dev/docker-api-version": "1.40",
"skaffold.dev/run-id": "98f0a02b-9739-4d94-ba11-3e4d273c743e",
"skaffold.dev/tag-policy": "git-commit",
"skaffold.dev/tail": "true"
},
"annotations": {
"deployment.kubernetes.io/revision": "1",
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"},\"name\":\"kotsadm-web\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"kotsadm-web\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"kotsadm-web\",\"app.kubernetes.io/managed-by\":\"skaffold-v0.41.0\",\"skaffold.dev/builder\":\"local\",\"skaffold.dev/cleanup\":\"true\",\"skaffold.dev/deployer\":\"kustomize\",\"skaffold.dev/docker-api-version\":\"1.40\",\"skaffold.dev/run-id\":\"98f0a02b-9739-4d94-ba11-3e4d273c743e\",\"skaffold.dev/tag-policy\":\"git-commit\",\"skaffold.dev/tail\":\"true\"}},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"GITHUB_CLIENT_ID\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"client-id\",\"name\":\"github-app\"}}},{\"name\":\"GITHUB_INSTALL_URL\",\"valueFrom\":{\"secretKeyRef\":{\"key\":\"install-url\",\"name\":\"github-app\"}}},{\"name\":\"SHIP_CLUSTER_API_SERVER\",\"value\":\"http://localhost:30065\"},{\"name\":\"SHIP_CLUSTER_WEB_URI\",\"value\":\"http://localhost:8000\"}],\"image\":\"localhost:32000/kotsadm-web:v1.0.1-30-g8fa13e34@sha256:5b5b5b640b6e09d8b3185d4ae15ac4dc558d4e2ea034ac3e567d8cce04eadb9c\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"kotsadm-web\",\"ports\":[{\"containerPort\":8000,\"name\":\"http\"}]}]}}}}\n"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"app": "kotsadm-web"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "kotsadm-web",
"app.kubernetes.io/managed-by": "skaffold-v0.41.0",
"skaffold.dev/builder": "local",
"skaffold.dev/cleanup": "true",
"skaffold.dev/deployer": "kustomize",
"skaffold.dev/docker-api-version": "1.40",
"skaffold.dev/run-id": "98f0a02b-9739-4d94-ba11-3e4d273c743e",
"skaffold.dev/tag-policy": "git-commit",
"skaffold.dev/tail": "true"
}
},
"spec": {
"containers": [
{
"name": "kotsadm-web",
"image": "localhost:32000/kotsadm-web:v1.0.1-30-g8fa13e34@sha256:5b5b5b640b6e09d8b3185d4ae15ac4dc558d4e2ea034ac3e567d8cce04eadb9c",
"ports": [
{
"name": "http",
"containerPort": 8000,
"protocol": "TCP"
}
],
"env": [
{
"name": "GITHUB_CLIENT_ID",
"valueFrom": {
"secretKeyRef": {
"name": "github-app",
"key": "client-id"
}
}
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"imagePullPolicy": "IfNotPresent"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {},
"schedulerName": "default-scheduler"
}
},
"strategy": {
"type": "RollingUpdate",
"rollingUpdate": {
"maxUnavailable": "25%",
"maxSurge": "25%"
}
},
"revisionHistoryLimit": 10,
"progressDeadlineSeconds": 600
},
"status": {
"observedGeneration": 1,
"replicas": 1,
"updatedReplicas": 1,
"readyReplicas": 1,
"availableReplicas": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"lastUpdateTime": "2019-11-07T00:34:39Z",
"lastTransitionTime": "2019-11-07T00:34:39Z",
"reason": "MinimumReplicasAvailable",
"message": "Deployment has minimum availability."
},
{
"type": "Progressing",
"status": "True",
"lastUpdateTime": "2019-11-07T00:34:39Z",
"lastTransitionTime": "2019-11-07T00:34:32Z",
"reason": "NewReplicaSetAvailable",
"message": "ReplicaSet \"kotsadm-web-79bfb95c48\" has successfully progressed."
}
]
}
}
]`
var collectedNodes = `[
{
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"annotations": {
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2019-10-23T18:16:43Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "repldev-marc",
"kubernetes.io/os": "linux",
"microk8s.io/cluster": "true"
},
"name": "repldev-marc",
"resourceVersion": "1769699",
"selfLink": "/api/v1/nodes/repldev-marc",
"uid": "cd30c57f-b445-437f-9473-f13343124030"
},
"spec": {},
"status": {
"addresses": [
{
"address": "10.168.0.26",
"type": "InternalIP"
},
{
"address": "repldev-marc",
"type": "Hostname"
}
],
"allocatable": {
"cpu": "8",
"ephemeral-storage": "1015018628Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "30770604Ki",
"pods": "110"
},
"capacity": {
"cpu": "8",
"ephemeral-storage": "1016067204Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "30873004Ki",
"pods": "110"
},
"conditions": [
{
"lastHeartbeatTime": "2019-11-08T17:03:39Z",
"lastTransitionTime": "2019-10-31T21:28:36Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2019-11-08T17:03:39Z",
"lastTransitionTime": "2019-10-31T21:28:36Z",
"message": "kubelet has no disk pressure",
"reason": "KubeletHasNoDiskPressure",
"status": "False",
"type": "DiskPressure"
},
{
"lastHeartbeatTime": "2019-11-08T17:03:39Z",
"lastTransitionTime": "2019-10-31T21:28:36Z",
"message": "kubelet has sufficient PID available",
"reason": "KubeletHasSufficientPID",
"status": "False",
"type": "PIDPressure"
},
{
"lastHeartbeatTime": "2019-11-08T17:03:39Z",
"lastTransitionTime": "2019-10-31T21:28:36Z",
"message": "kubelet is posting ready status. AppArmor enabled",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"images": [
{
"names": [
"localhost:32000/kotsadm-api@sha256:d4821b65869454dfac53ad01f295740df6fcd52711f0dcf6aa9d7e515f7ebe3c"
],
"sizeBytes": 755312372
},
{
"names": [
"localhost:32000/kotsadm-api@sha256:fc3c971facc9dbd1b07e19c1ebb33c6361dd219af8efed0616afd1278f81fa4e"
],
"sizeBytes": 755312032
}
],
"nodeInfo": {
"architecture": "amd64",
"bootID": "3401cdf2-129c-473d-a50c-723afd7378d3",
"containerRuntimeVersion": "containerd://1.2.5",
"kernelVersion": "5.0.0-1021-gcp",
"kubeProxyVersion": "v1.16.2",
"kubeletVersion": "v1.16.2",
"machineID": "97f4a34d2aa9e26785177a6b64fb9108",
"operatingSystem": "linux",
"osImage": "Ubuntu 18.04.2 LTS",
"systemUUID": "9dc594e5-ac7b-c649-e61f-cad715a28f79"
}
}
}
]`
+41
View File
@@ -0,0 +1,41 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
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)))
if err != nil {
return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
}
var deployments []appsv1.Deployment
if err := json.Unmarshal(collected, &deployments); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal deployment list")
}
var status *appsv1.DeploymentStatus
for _, deployment := range deployments {
if deployment.Name == analyzer.Name {
status = &deployment.Status
}
}
if status == nil {
// there's not an error, but maybe the requested deployment is not even deployed
return &AnalyzeResult{
Title: fmt.Sprintf("%s Deployment Status", analyzer.Name),
IsFail: true,
Message: fmt.Sprintf("The deployment %q was not found", analyzer.Name),
}, nil
}
return commonStatus(analyzer.Outcomes, fmt.Sprintf("%s Status", analyzer.Name), int(status.ReadyReplicas))
}
+131
View File
@@ -0,0 +1,131 @@
package analyzer
import (
"testing"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_deploymentStatus(t *testing.T) {
tests := []struct {
name string
analyzer troubleshootv1beta1.DeploymentStatus
expectResult AnalyzeResult
files map[string][]byte
}{
{
name: "1/1, pass when = 1",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
When: "= 1",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Message: "fail",
},
},
},
Namespace: "default",
Name: "kotsadm-api",
},
expectResult: AnalyzeResult{
IsPass: true,
IsWarn: false,
IsFail: false,
Title: "kotsadm-api Status",
Message: "pass",
},
files: map[string][]byte{
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
},
},
{
name: "1/1, pass when = 2",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
When: "= 2",
Message: "pass",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Message: "fail",
},
},
},
Namespace: "default",
Name: "kotsadm-api",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: false,
IsFail: true,
Title: "kotsadm-api Status",
Message: "fail",
},
files: map[string][]byte{
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
},
},
{
name: "1/1, pass when >= 2, warn when = 1, fail when 0",
analyzer: troubleshootv1beta1.DeploymentStatus{
Outcomes: []*troubleshootv1beta1.Outcome{
{
Pass: &troubleshootv1beta1.SingleOutcome{
When: ">= 2",
Message: "pass",
},
},
{
Warn: &troubleshootv1beta1.SingleOutcome{
When: "= 1",
Message: "warn",
},
},
{
Fail: &troubleshootv1beta1.SingleOutcome{
Message: "fail",
},
},
},
Namespace: "default",
Name: "kotsadm-api",
},
expectResult: AnalyzeResult{
IsPass: false,
IsWarn: true,
IsFail: false,
Title: "kotsadm-api Status",
Message: "warn",
},
files: map[string][]byte{
"cluster-resources/deployments/default.json": []byte(collectedDeployments),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
getFiles := func(n string) ([]byte, error) {
return test.files[n], nil
}
actual, err := analyzeDeploymentStatus(&test.analyzer, getFiles)
req.NoError(err)
assert.Equal(t, &test.expectResult, actual)
})
}
}
+202
View File
@@ -0,0 +1,202 @@
package analyzer
import (
"encoding/json"
"strings"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
corev1 "k8s.io/api/core/v1"
)
type providers struct {
microk8s bool
dockerDesktop bool
eks bool
gke bool
digitalOcean bool
}
type Provider int
const (
unknown Provider = iota
microk8s Provider = iota
dockerDesktop Provider = iota
eks Provider = iota
gke Provider = iota
digitalOcean Provider = iota
)
func analyzeDistribution(analyzer *troubleshootv1beta1.Distribution, 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")
}
var nodes []corev1.Node
if err := json.Unmarshal(collected, &nodes); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal node list")
}
foundProviders := providers{}
for _, node := range nodes {
for k, v := range node.ObjectMeta.Labels {
if k == "microk8s.io/cluster" && v == "true" {
foundProviders.microk8s = true
}
}
if node.Status.NodeInfo.OSImage == "Docker Desktop" {
foundProviders.dockerDesktop = true
}
if strings.HasPrefix(node.Spec.ProviderID, "digitalocean:") {
foundProviders.digitalOcean = true
}
if strings.HasPrefix(node.Spec.ProviderID, "aws:") {
foundProviders.eks = true
}
if strings.HasPrefix(node.Spec.ProviderID, "gce:") {
foundProviders.gke = true
}
}
result := &AnalyzeResult{
Title: "Kubernetes Distribution",
}
// ordering is important for passthrough
for _, outcome := range analyzer.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 := compareDistributionConditionalToActual(outcome.Fail.When, foundProviders)
if err != nil {
return result, errors.Wrap(err, "failed to compare distribution conditional")
}
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 := compareDistributionConditionalToActual(outcome.Warn.When, foundProviders)
if err != nil {
return result, errors.Wrap(err, "failed to compare distribution conditional")
}
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 := compareDistributionConditionalToActual(outcome.Pass.When, foundProviders)
if err != nil {
return result, errors.Wrap(err, "failed to compare distribution conditional")
}
if isMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return result, nil
}
func compareDistributionConditionalToActual(conditional string, actual providers) (bool, error) {
parts := strings.Split(strings.TrimSpace(conditional), " ")
// we can make this a lot more flexible
if len(parts) == 1 {
parts = []string{
"=",
parts[0],
}
}
if len(parts) != 2 {
return false, errors.New("unable to parse conditional")
}
normalizedName := mustNormalizeDistributionName(parts[1])
if normalizedName == unknown {
return false, nil
}
isMatch := false
switch normalizedName {
case microk8s:
isMatch = actual.microk8s
case dockerDesktop:
isMatch = actual.dockerDesktop
case eks:
isMatch = actual.eks
case gke:
isMatch = actual.gke
case digitalOcean:
isMatch = actual.digitalOcean
}
switch parts[0] {
case "=", "==", "===":
return isMatch, nil
case "!=", "!==":
return !isMatch, nil
}
return false, nil
}
func mustNormalizeDistributionName(raw string) Provider {
switch strings.ReplaceAll(strings.TrimSpace(strings.ToLower(raw)), "-", "") {
case "microk8s":
return microk8s
case "dockerdesktop", "docker desktop", "docker-desktop":
return dockerDesktop
case "eks":
return eks
case "gke":
return gke
case "digitalocean":
return digitalOcean
}
return unknown
}
+85
View File
@@ -0,0 +1,85 @@
package analyzer
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_compareDistributionConditionalToActual(t *testing.T) {
tests := []struct {
name string
conditional string
input providers
expected bool
}{
{
name: "== microk8s when microk8s is found",
conditional: "== microk8s",
input: providers{
microk8s: true,
},
expected: true,
},
{
name: "!= microk8s when microk8s is found",
conditional: "!= microk8s",
input: providers{
microk8s: true,
},
expected: false,
},
{
name: "!== eks when gke is found",
conditional: "!== eks",
input: providers{
gke: true,
},
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareDistributionConditionalToActual(test.conditional, test.input)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_mustNormalizeDistributionName(t *testing.T) {
tests := []struct {
raw string
expected Provider
}{
{
raw: "microk8s",
expected: microk8s,
},
{
raw: "MICROK8S",
expected: microk8s,
},
{
raw: " microk8s ",
expected: microk8s,
},
{
raw: "Docker-Desktop",
expected: dockerDesktop,
},
}
for _, test := range tests {
t.Run(test.raw, func(t *testing.T) {
actual := mustNormalizeDistributionName(test.raw)
assert.Equal(t, test.expected, actual)
})
}
}
+76 -40
View File
@@ -3,43 +3,53 @@ package analyzer
import (
"archive/tar"
"compress/gzip"
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
getter "github.com/hashicorp/go-getter"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"gopkg.in/yaml.v2"
"k8s.io/client-go/kubernetes/scheme"
)
type fileContentProvider struct {
rootDir string
}
func DownloadAndAnalyze(ctx context.Context, bundleURL string) ([]*AnalyzeResult, error) {
func DownloadAndAnalyze(bundleURL string, analyzersSpec string) ([]*AnalyzeResult, error) {
tmpDir, err := ioutil.TempDir("", "troubleshoot-k8s")
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(tmpDir)
if err := downLoadTroubleshootBundle(bundleURL, tmpDir); err != nil {
return nil, err
if err := downloadTroubleshootBundle(bundleURL, tmpDir); err != nil {
return nil, errors.Wrap(err, "failed to download bundle")
}
_, err = os.Stat(filepath.Join(tmpDir, "version.yaml"))
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to read version.yaml")
}
analyzers, err := getTroubleshootAnalyzers()
if err != nil {
return nil, err
analyzers := []*troubleshootv1beta1.Analyze{}
if analyzersSpec == "" {
defaultAnalyzers, err := getDefaultAnalyzers()
if err != nil {
return nil, errors.Wrap(err, "failed to get default analyzers")
}
analyzers = defaultAnalyzers
} else {
parsedAnalyzers, err := parseAnalyzers(analyzersSpec)
if err != nil {
return nil, errors.Wrap(err, "failed to parse analyzers")
}
analyzers = parsedAnalyzers
}
fcp := fileContentProvider{rootDir: tmpDir}
@@ -52,21 +62,32 @@ func DownloadAndAnalyze(ctx context.Context, bundleURL string) ([]*AnalyzeResult
continue
}
analyzeResults = append(analyzeResults, analyzeResult)
if analyzeResult != nil {
analyzeResults = append(analyzeResults, analyzeResult)
}
}
return analyzeResults, nil
}
func downLoadTroubleshootBundle(bundleURL, destDir string) error {
func downloadTroubleshootBundle(bundleURL string, destDir string) error {
if bundleURL[0] == os.PathSeparator {
f, err := os.Open(bundleURL)
if err != nil {
return errors.Wrap(err, "failed to open support bundle")
}
defer f.Close()
return extractTroubleshootBundle(f, destDir)
}
pwd, err := os.Getwd()
if err != nil {
return err
return errors.Wrap(err, "failed to get workdir")
}
tmpDir, err := ioutil.TempDir("", "getter")
if err != nil {
return err
return errors.Wrap(err, "failed to create tmp dir")
}
defer os.RemoveAll(tmpDir)
@@ -77,12 +98,12 @@ func downLoadTroubleshootBundle(bundleURL, destDir string) error {
return nil
})
if err != nil {
return err
return errors.Wrap(err, "failed to read support bundle file")
}
f, err := os.Open(dst)
if err != nil {
return err
return errors.Wrap(err, "failed to open support bundle")
}
defer f.Close()
@@ -92,7 +113,7 @@ func downLoadTroubleshootBundle(bundleURL, destDir string) error {
func extractTroubleshootBundle(reader io.Reader, destDir string) error {
gzReader, err := gzip.NewReader(reader)
if err != nil {
return err
return errors.Wrap(err, "failed to create gzip reader")
}
tarReader := tar.NewReader(gzReader)
@@ -102,22 +123,25 @@ func extractTroubleshootBundle(reader io.Reader, destDir string) error {
break
}
if err != nil {
return err
return errors.Wrap(err, "failed to read header from tar")
}
switch header.Typeflag {
case tar.TypeDir:
name := filepath.Join(destDir, header.Name)
if err := os.MkdirAll(name, os.FileMode(header.Mode)); err != nil {
return err
return errors.Wrap(err, "failed to mkdir")
}
case tar.TypeReg:
name := filepath.Join(destDir, 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")
}
_, err = io.Copy(file, tarReader)
file.Close()
if err != nil {
return err
return errors.Wrap(err, "failed to extract file")
}
}
}
@@ -125,29 +149,41 @@ func extractTroubleshootBundle(reader io.Reader, destDir string) error {
return nil
}
func getTroubleshootAnalyzers() ([]*troubleshootv1beta1.Analyze, error) {
specURL := `https://gist.githubusercontent.com/divolgin/92b512ad4697c7255f383a7c1b56fd83/raw`
resp, err := http.Get(specURL)
func parseAnalyzers(spec string) ([]*troubleshootv1beta1.Analyze, error) {
troubleshootscheme.AddToScheme(scheme.Scheme)
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(spec), nil, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("could not download analyzer spec, status code: %v", resp.StatusCode)
return nil, errors.Wrap(err, "failed to decode analyzers")
}
spec, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
analyzer := obj.(*troubleshootv1beta1.Analyzer)
return analyzer.Spec.Analyzers, nil
}
preflight := troubleshootv1beta1.Preflight{}
if err := yaml.Unmarshal([]byte(spec), &preflight); err != nil {
return nil, err
}
func getDefaultAnalyzers() ([]*troubleshootv1beta1.Analyze, error) {
spec := `apiVersion: troubleshoot.replicated.com/v1beta1
kind: Analyzer
metadata:
name: defaultAnalyzers
spec:
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.`
return preflight.Spec.Analyzers, nil
return parseAnalyzers(spec)
}
func (f fileContentProvider) getFileContents(fileName string) ([]byte, error) {
+41
View File
@@ -0,0 +1,41 @@
package analyzer
import (
"encoding/json"
"fmt"
"path"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
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)))
if err != nil {
return nil, errors.Wrap(err, "failed to read collected deployments from namespace")
}
var statefulsets []appsv1.StatefulSet
if err := json.Unmarshal(collected, &statefulsets); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal statefulset list")
}
var status *appsv1.StatefulSetStatus
for _, statefulset := range statefulsets {
if statefulset.Name == analyzer.Name {
status = &statefulset.Status
}
}
if status == nil {
// there's not an error, but maybe the requested statefulset is not even deployed
return &AnalyzeResult{
Title: fmt.Sprintf("%s Statefulset Status", analyzer.Name),
IsFail: true,
Message: fmt.Sprintf("The statefulset %q was not found", analyzer.Name),
}, nil
}
return commonStatus(analyzer.Outcomes, fmt.Sprintf("%s Status", analyzer.Name), int(status.ReadyReplicas))
}
@@ -50,8 +50,33 @@ type ImagePullSecret struct {
RegistryName string `json:"registryName" yaml:"registryName"`
}
type DeploymentStatus struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Namespace string `json:"namespace" yaml:"namespace"`
Name string `json:"name" yaml:"name"`
}
type StatefulsetStatus struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Namespace string `json:"namespace" yaml:"namespace"`
Name string `json:"name" yaml:"name"`
}
type ContainerRuntime struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type Distribution struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type AnalyzeMeta struct {
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
Exclude bool `json:"exclude,omitempty" yaml:"exclude,omitempty"`
}
type Analyze struct {
@@ -61,4 +86,8 @@ type Analyze struct {
Ingress *Ingress `json:"ingress,omitempty" yaml:"ingress,omitempty"`
Secret *AnalyzeSecret `json:"secret,omitempty" yaml:"secret,omitempty"`
ImagePullSecret *ImagePullSecret `json:"imagePullSecret,omitempty" yaml:"imagePullSecret,omitempty"`
DeploymentStatus *DeploymentStatus `json:"deploymentStatus,omitempty" yaml:"deploymentStatus,omitempty"`
StatefulsetStatus *StatefulsetStatus `json:"statefulsetStatus,omitempty" yaml:"statefulsetStatus,omitempty"`
ContainerRuntime *ContainerRuntime `json:"containerRuntime,omitempty" yaml:"containerRuntime,omitempty"`
Distribution *Distribution `json:"distribution,omitempty" yaml:"distribution,omitempty"`
}
@@ -20,19 +20,13 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// AnalyzerSpec defines the desired state of Analyzer
type AnalyzerSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Analyzers []*Analyze `json:"analyzers,omitempty"`
}
// AnalyzerStatus defines the observed state of Analyzer
type AnalyzerStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
// +genclient
@@ -2,17 +2,20 @@ package v1beta1
type CollectorMeta struct {
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
Exclude bool `json:"when,omitmempty" yaml:"when,omitempty"`
}
type ClusterInfo struct {
CollectorMeta `json:",inline" yaml:",inline"`
}
type ClusterResources struct {
CollectorMeta `json:",inline" yaml:",inline"`
}
type Secret struct {
CollectorMeta `json:",inline" yaml:",inline"`
Name string `json:"name" yaml:"name"`
SecretName string `json:"name" yaml:"name"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Key string `json:"key,omitempty" yaml:"key,omitempty"`
IncludeValue bool `json:"includeValue,omitempty" yaml:"includeValue,omitempty"`
@@ -24,14 +27,23 @@ type LogLimits struct {
}
type Logs struct {
CollectorMeta `json:",inline" yaml:",inline"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Selector []string `json:"selector" yaml:"selector"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
ContainerNames []string `json:"containerNames,omitempty" yaml:"containerNames,omitempty"`
Limits *LogLimits `json:"limits,omitempty" yaml:"omitempty"`
}
type Data struct {
CollectorMeta `json:",inline" yaml:",inline"`
Selector []string `json:"selector" yaml:"selector"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Limits *LogLimits `json:"limits,omitempty" yaml:"omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Data string `json:"data" yaml:"data"`
}
type Run struct {
CollectorMeta `json:",inline" yaml:",inline"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace" yaml:"namespace"`
Image string `json:"image" yaml:"image"`
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
@@ -42,6 +54,7 @@ type Run struct {
type Exec struct {
CollectorMeta `json:",inline" yaml:",inline"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Selector []string `json:"selector" yaml:"selector"`
Namespace string `json:"namespace" yaml:"namespace"`
ContainerName string `json:"containerName,omitempty" yaml:"containerName,omitempty"`
@@ -52,6 +65,7 @@ type Exec struct {
type Copy struct {
CollectorMeta `json:",inline" yaml:",inline"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Selector []string `json:"selector" yaml:"selector"`
Namespace string `json:"namespace" yaml:"namespace"`
ContainerPath string `json:"containerPath" yaml:"containerPath"`
@@ -60,9 +74,10 @@ type Copy struct {
type HTTP struct {
CollectorMeta `json:",inline" yaml:",inline"`
Get *Get `json:"get,omitempty" yaml:"get,omitempty"`
Post *Post `json:"post,omitempty" yaml:"post,omitempty"`
Put *Put `json:"put,omitempty" yaml:"put,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Get *Get `json:"get,omitempty" yaml:"get,omitempty"`
Post *Post `json:"post,omitempty" yaml:"post,omitempty"`
Put *Put `json:"put,omitempty" yaml:"put,omitempty"`
}
type Get struct {
@@ -92,6 +107,7 @@ type Collect struct {
Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"`
Run *Run `json:"run,omitempty" yaml:"run,omitempty"`
Exec *Exec `json:"exec,omitempty" yaml:"exec,omitempty"`
Data *Data `json:"data,omitempty" yaml:"data,omitempty"`
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
HTTP *HTTP `json:"http,omitempty" yaml:"http,omitempty"`
}
@@ -20,6 +20,22 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ResultRequest struct {
URI string `json:"uri" yaml:"uri"`
Method string `json:"method" yaml:"method"`
}
type AfterCollection struct {
UploadResultsTo *ResultRequest `json:"uploadResultsTo,omitempty" yaml:"uploadResultsTo,omitempty"`
Callback *ResultRequest `json:"callback,omitempty" yaml:"callback,omitempty"`
}
// CollectorSpec defines the desired state of Collector
type CollectorSpec struct {
Collectors []*Collect `json:"collectors,omitempty" yaml:"collectors,omitempty"`
AfterCollection []*AfterCollection `json:"afterCollection,omitempty" yaml:"afterCollection,omitempty"`
}
// CollectorStatus defines the observed state of Collector
type CollectorStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
@@ -35,7 +51,7 @@ type Collector struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec []*Collect `json:"spec,omitempty" yaml:"spec,omitempty"`
Spec CollectorSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
Status CollectorStatus `json:"status,omitempty"`
}
@@ -24,6 +24,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AfterCollection) DeepCopyInto(out *AfterCollection) {
*out = *in
if in.UploadResultsTo != nil {
in, out := &in.UploadResultsTo, &out.UploadResultsTo
*out = new(ResultRequest)
**out = **in
}
if in.Callback != nil {
in, out := &in.Callback, &out.Callback
*out = new(ResultRequest)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterCollection.
func (in *AfterCollection) DeepCopy() *AfterCollection {
if in == nil {
return nil
}
out := new(AfterCollection)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
@@ -57,6 +82,26 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = new(ImagePullSecret)
(*in).DeepCopyInto(*out)
}
if in.DeploymentStatus != nil {
in, out := &in.DeploymentStatus, &out.DeploymentStatus
*out = new(DeploymentStatus)
(*in).DeepCopyInto(*out)
}
if in.StatefulsetStatus != nil {
in, out := &in.StatefulsetStatus, &out.StatefulsetStatus
*out = new(StatefulsetStatus)
(*in).DeepCopyInto(*out)
}
if in.ContainerRuntime != nil {
in, out := &in.ContainerRuntime, &out.ContainerRuntime
*out = new(ContainerRuntime)
(*in).DeepCopyInto(*out)
}
if in.Distribution != nil {
in, out := &in.Distribution, &out.Distribution
*out = new(Distribution)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
@@ -116,7 +161,7 @@ func (in *Analyzer) DeepCopyInto(out *Analyzer) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -262,6 +307,17 @@ func (in *AnalyzerList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AnalyzerSpec) DeepCopyInto(out *AnalyzerSpec) {
*out = *in
if in.Analyzers != nil {
in, out := &in.Analyzers, &out.Analyzers
*out = make([]*Analyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Analyze)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalyzerSpec.
@@ -292,6 +348,7 @@ func (in *AnalyzerStatus) DeepCopy() *AnalyzerStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterInfo) DeepCopyInto(out *ClusterInfo) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterInfo.
@@ -307,6 +364,7 @@ func (in *ClusterInfo) DeepCopy() *ClusterInfo {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterResources) DeepCopyInto(out *ClusterResources) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterResources.
@@ -379,6 +437,11 @@ func (in *Collect) DeepCopyInto(out *Collect) {
*out = new(Exec)
(*in).DeepCopyInto(*out)
}
if in.Data != nil {
in, out := &in.Data, &out.Data
*out = new(Data)
**out = **in
}
if in.Copy != nil {
in, out := &in.Copy, &out.Copy
*out = new(Copy)
@@ -406,17 +469,7 @@ func (in *Collector) DeepCopyInto(out *Collector) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Spec != nil {
in, out := &in.Spec, &out.Spec
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -605,6 +658,43 @@ func (in *CollectorRef) DeepCopy() *CollectorRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorSpec) DeepCopyInto(out *CollectorSpec) {
*out = *in
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
if in.AfterCollection != nil {
in, out := &in.AfterCollection, &out.AfterCollection
*out = make([]*AfterCollection, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(AfterCollection)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorSpec.
func (in *CollectorSpec) DeepCopy() *CollectorSpec {
if in == nil {
return nil
}
out := new(CollectorSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorStatus) DeepCopyInto(out *CollectorStatus) {
*out = *in
@@ -620,6 +710,33 @@ func (in *CollectorStatus) DeepCopy() *CollectorStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContainerRuntime) DeepCopyInto(out *ContainerRuntime) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerRuntime.
func (in *ContainerRuntime) DeepCopy() *ContainerRuntime {
if in == nil {
return nil
}
out := new(ContainerRuntime)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Copy) DeepCopyInto(out *Copy) {
*out = *in
@@ -668,6 +785,76 @@ func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Data) DeepCopyInto(out *Data) {
*out = *in
out.CollectorMeta = in.CollectorMeta
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Data.
func (in *Data) DeepCopy() *Data {
if in == nil {
return nil
}
out := new(Data)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeploymentStatus) DeepCopyInto(out *DeploymentStatus) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentStatus.
func (in *DeploymentStatus) DeepCopy() *DeploymentStatus {
if in == nil {
return nil
}
out := new(DeploymentStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Distribution) DeepCopyInto(out *Distribution) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Distribution.
func (in *Distribution) DeepCopy() *Distribution {
if in == nil {
return nil
}
out := new(Distribution)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Exec) DeepCopyInto(out *Exec) {
*out = *in
@@ -830,6 +1017,11 @@ func (in *Logs) DeepCopyInto(out *Logs) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ContainerNames != nil {
in, out := &in.ContainerNames, &out.ContainerNames
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Limits != nil {
in, out := &in.Limits, &out.Limits
*out = new(LogLimits)
@@ -1167,6 +1359,21 @@ func (in *Put) DeepCopy() *Put {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResultRequest) DeepCopyInto(out *ResultRequest) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultRequest.
func (in *ResultRequest) DeepCopy() *ResultRequest {
if in == nil {
return nil
}
out := new(ResultRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Run) DeepCopyInto(out *Run) {
*out = *in
@@ -1224,6 +1431,33 @@ func (in *SingleOutcome) DeepCopy() *SingleOutcome {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StatefulsetStatus) DeepCopyInto(out *StatefulsetStatus) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulsetStatus.
func (in *StatefulsetStatus) DeepCopy() *StatefulsetStatus {
if in == nil {
return nil
}
out := new(StatefulsetStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = *in
+7 -15
View File
@@ -2,11 +2,10 @@ package collect
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type ClusterVersion struct {
@@ -19,15 +18,10 @@ type ClusterInfoOutput struct {
Errors []byte `json:"cluster-info/errors.json,omitempty"`
}
func ClusterInfo() error {
cfg, err := config.GetConfig()
func ClusterInfo(ctx *Context) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
return nil, errors.Wrap(err, "Failed to create kubernetes clientset")
}
clusterInfoOutput := ClusterInfoOutput{}
@@ -37,17 +31,15 @@ func ClusterInfo() error {
clusterInfoOutput.ClusterVersion = clusterVersion
clusterInfoOutput.Errors, err = marshalNonNil(clusterErrors)
if err != nil {
return err
return nil, errors.Wrap(err, "failed to marshal errors")
}
b, err := json.MarshalIndent(clusterInfoOutput, "", " ")
if err != nil {
return err
return nil, errors.Wrap(err, "failed to marshal cluster info")
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func clusterVersion(client *kubernetes.Clientset) ([]byte, []string) {
+81 -36
View File
@@ -11,7 +11,6 @@ import (
apiextensionsv1beta1clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type ClusterResourcesOutput struct {
@@ -31,39 +30,45 @@ type ClusterResourcesOutput struct {
CustomResourceDefinitionsErrors []byte `json:"cluster-resources/custom-resource-definitions-errors.json,omitempty"`
ImagePullSecrets map[string][]byte `json:"cluster-resources/image-pull-secrets,omitempty"`
ImagePullSecretsErrors []byte `json:"cluster-resources/image-pull-secrets-errors.json,omitempty"`
Nodes []byte `json:"cluster-resources/nodes.json,omitempty"`
NodesErrors []byte `json:"cluster-resources/nodes-errors.json,omitempty"`
}
func ClusterResources(redact bool) error {
cfg, err := config.GetConfig()
func ClusterResources(ctx *Context) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
return nil, err
}
clusterResourcesOutput := &ClusterResourcesOutput{}
// namespaces
namespaces, namespaceList, nsErrors := namespaces(client)
clusterResourcesOutput.Namespaces = namespaces
clusterResourcesOutput.NamespacesErrors, err = marshalNonNil(nsErrors)
if err != nil {
return err
var namespaceNames []string
if ctx.Namespace == "" {
namespaces, namespaceList, namespaceErrors := namespaces(client)
clusterResourcesOutput.Namespaces = namespaces
clusterResourcesOutput.NamespacesErrors, err = marshalNonNil(namespaceErrors)
if err != nil {
return nil, err
}
if namespaceList != nil {
for _, namespace := range namespaceList.Items {
namespaceNames = append(namespaceNames, namespace.Name)
}
}
} else {
namespaces, namespaceErrors := getNamespace(client, ctx.Namespace)
clusterResourcesOutput.Namespaces = namespaces
clusterResourcesOutput.NamespacesErrors, err = marshalNonNil(namespaceErrors)
if err != nil {
return nil, err
}
namespaceNames = append(namespaceNames, ctx.Namespace)
}
namespaceNames := make([]string, 0, 0)
for _, namespace := range namespaceList.Items {
namespaceNames = append(namespaceNames, namespace.Name)
}
pods, podErrors := pods(client, namespaceNames)
clusterResourcesOutput.Pods = pods
clusterResourcesOutput.PodsErrors, err = marshalNonNil(podErrors)
if err != nil {
return err
return nil, err
}
// services
@@ -71,7 +76,7 @@ func ClusterResources(redact bool) error {
clusterResourcesOutput.Services = services
clusterResourcesOutput.ServicesErrors, err = marshalNonNil(servicesErrors)
if err != nil {
return err
return nil, err
}
// deployments
@@ -79,7 +84,7 @@ func ClusterResources(redact bool) error {
clusterResourcesOutput.Deployments = deployments
clusterResourcesOutput.DeploymentsErrors, err = marshalNonNil(deploymentsErrors)
if err != nil {
return err
return nil, err
}
// ingress
@@ -87,7 +92,7 @@ func ClusterResources(redact bool) error {
clusterResourcesOutput.Ingress = ingress
clusterResourcesOutput.IngressErrors, err = marshalNonNil(ingressErrors)
if err != nil {
return err
return nil, err
}
// storage classes
@@ -95,19 +100,19 @@ func ClusterResources(redact bool) error {
clusterResourcesOutput.StorageClasses = storageClasses
clusterResourcesOutput.StorageErrors, err = marshalNonNil(storageErrors)
if err != nil {
return err
return nil, err
}
// crds
crdClient, err := apiextensionsv1beta1clientset.NewForConfig(cfg)
crdClient, err := apiextensionsv1beta1clientset.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
return nil, err
}
customResourceDefinitions, crdErrors := crds(crdClient)
clusterResourcesOutput.CustomResourceDefinitions = customResourceDefinitions
clusterResourcesOutput.CustomResourceDefinitionsErrors, err = marshalNonNil(crdErrors)
if err != nil {
return err
return nil, err
}
// imagepullsecrets
@@ -115,24 +120,30 @@ func ClusterResources(redact bool) error {
clusterResourcesOutput.ImagePullSecrets = imagePullSecrets
clusterResourcesOutput.ImagePullSecretsErrors, err = marshalNonNil(pullSecretsErrors)
if err != nil {
return err
return nil, err
}
if redact {
// nodes
nodes, nodeErrors := nodes(client)
clusterResourcesOutput.Nodes = nodes
clusterResourcesOutput.NodesErrors, err = marshalNonNil(nodeErrors)
if err != nil {
return nil, err
}
if ctx.Redact {
clusterResourcesOutput, err = clusterResourcesOutput.Redact()
if err != nil {
return err
return nil, err
}
}
b, err := json.MarshalIndent(clusterResourcesOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func namespaces(client *kubernetes.Clientset) ([]byte, *corev1.NamespaceList, []string) {
@@ -149,6 +160,20 @@ func namespaces(client *kubernetes.Clientset) ([]byte, *corev1.NamespaceList, []
return b, namespaces, nil
}
func getNamespace(client *kubernetes.Clientset, namespace string) ([]byte, []string) {
namespaces, err := client.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{})
if err != nil {
return nil, []string{err.Error()}
}
b, err := json.MarshalIndent(namespaces, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
func pods(client *kubernetes.Clientset, namespaces []string) (map[string][]byte, map[string]string) {
podsByNamespace := make(map[string][]byte)
errorsByNamespace := make(map[string]string)
@@ -321,11 +346,29 @@ func imagePullSecrets(client *kubernetes.Clientset, namespaces []string) (map[st
return imagePullSecrets, errors
}
func nodes(client *kubernetes.Clientset) ([]byte, []string) {
nodes, err := client.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
return nil, []string{err.Error()}
}
b, err := json.MarshalIndent(nodes.Items, "", " ")
if err != nil {
return nil, []string{err.Error()}
}
return b, nil
}
func (c *ClusterResourcesOutput) Redact() (*ClusterResourcesOutput, error) {
namespaces, err := redact.Redact(c.Namespaces)
if err != nil {
return nil, err
}
nodes, err := redact.Redact(c.Nodes)
if err != nil {
return nil, err
}
pods, err := redactMap(c.Pods)
if err != nil {
return nil, err
@@ -353,6 +396,8 @@ func (c *ClusterResourcesOutput) Redact() (*ClusterResourcesOutput, error) {
return &ClusterResourcesOutput{
Namespaces: namespaces,
NamespacesErrors: c.NamespacesErrors,
Nodes: nodes,
NodesErrors: c.NodesErrors,
Pods: pods,
PodsErrors: c.PodsErrors,
Services: services,
+127 -35
View File
@@ -2,51 +2,143 @@ package collect
import (
"errors"
"fmt"
"strings"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"gopkg.in/yaml.v2"
"k8s.io/client-go/rest"
)
type Collector struct {
Spec string
Redact bool
Collect *troubleshootv1beta1.Collect
Redact bool
ClientConfig *rest.Config
Namespace string
}
func (c *Collector) RunCollectorSync() error {
collect, err := parseSpec(c.Spec)
if err != nil {
return err
}
if collect.ClusterInfo != nil {
return ClusterInfo()
}
if collect.ClusterResources != nil {
return ClusterResources(c.Redact)
}
if collect.Secret != nil {
return Secret(collect.Secret, c.Redact)
}
if collect.Logs != nil {
return Logs(collect.Logs, c.Redact)
}
if collect.Run != nil {
return Run(collect.Run, c.Redact)
}
if collect.Exec != nil {
return Exec(collect.Exec, c.Redact)
}
if collect.Copy != nil {
return Copy(collect.Copy, c.Redact)
}
if collect.HTTP != nil {
return HTTP(collect.HTTP, c.Redact)
}
return errors.New("no spec found to run")
type Context struct {
Redact bool
ClientConfig *rest.Config
Namespace string
}
func parseSpec(specContents string) (*troubleshootv1beta1.Collect, error) {
func (c *Collector) RunCollectorSync() ([]byte, error) {
if c.Collect.ClusterInfo != nil {
if c.Collect.ClusterInfo.Exclude {
return nil, nil
}
return ClusterInfo(c.GetContext())
}
if c.Collect.ClusterResources != nil {
if c.Collect.ClusterResources.Exclude {
return nil, nil
}
return ClusterResources(c.GetContext())
}
if c.Collect.Secret != nil {
if c.Collect.Secret.Exclude {
return nil, nil
}
return Secret(c.GetContext(), c.Collect.Secret)
}
if c.Collect.Logs != nil {
if c.Collect.Logs.Exclude {
return nil, nil
}
return Logs(c.GetContext(), c.Collect.Logs)
}
if c.Collect.Run != nil {
if c.Collect.Run.Exclude {
return nil, nil
}
return Run(c.GetContext(), c.Collect.Run)
}
if c.Collect.Exec != nil {
if c.Collect.Exec.Exclude {
return nil, nil
}
return Exec(c.GetContext(), c.Collect.Exec)
}
if c.Collect.Data != nil {
if c.Collect.Data.Exclude {
return nil, nil
}
return Data(c.GetContext(), c.Collect.Data)
}
if c.Collect.Copy != nil {
if c.Collect.Copy.Exclude {
return nil, nil
}
return Copy(c.GetContext(), c.Collect.Copy)
}
if c.Collect.HTTP != nil {
if c.Collect.HTTP.Exclude {
return nil, nil
}
return HTTP(c.GetContext(), c.Collect.HTTP)
}
return nil, errors.New("no spec found to run")
}
func (c *Collector) GetDisplayName() string {
var collector, name, selector string
if c.Collect.ClusterInfo != nil {
collector = "cluster-info"
}
if c.Collect.ClusterResources != nil {
collector = "cluster-resources"
}
if c.Collect.Secret != nil {
collector = "secret"
name = c.Collect.Secret.CollectorName
}
if c.Collect.Logs != nil {
collector = "logs"
name = c.Collect.Logs.CollectorName
selector = strings.Join(c.Collect.Logs.Selector, ",")
}
if c.Collect.Run != nil {
collector = "run"
name = c.Collect.Run.CollectorName
}
if c.Collect.Exec != nil {
collector = "exec"
name = c.Collect.Exec.CollectorName
selector = strings.Join(c.Collect.Exec.Selector, ",")
}
if c.Collect.Copy != nil {
collector = "copy"
name = c.Collect.Copy.CollectorName
selector = strings.Join(c.Collect.Copy.Selector, ",")
}
if c.Collect.HTTP != nil {
collector = "http"
name = c.Collect.HTTP.CollectorName
}
if collector == "" {
return "<none>"
}
if name != "" {
return fmt.Sprintf("%s/%s", collector, name)
}
if selector != "" {
return fmt.Sprintf("%s/%s", collector, selector)
}
return collector
}
func (c *Collector) GetContext() *Context {
return &Context{
Redact: c.Redact,
ClientConfig: c.ClientConfig,
Namespace: c.Namespace,
}
}
func ParseSpec(specContents string) (*troubleshootv1beta1.Collect, error) {
collect := troubleshootv1beta1.Collect{}
if err := yaml.Unmarshal([]byte(specContents), &collect); err != nil {
+1 -1
View File
@@ -26,7 +26,7 @@ func Test_ParseSpec(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := parseSpec(test.spec)
c, err := ParseSpec(test.spec)
if test.expectError {
assert.Error(t, err)
+53 -45
View File
@@ -4,83 +4,70 @@ import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/remotecommand"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type CopyOutput struct {
Files map[string][]byte `json:"copy/,omitempty"`
Errors map[string][]byte `json:"copy-errors/,omitempty"`
}
type CopyOutput map[string][]byte
func Copy(copyCollector *troubleshootv1beta1.Copy, redact bool) error {
cfg, err := config.GetConfig()
func Copy(ctx *Context, copyCollector *troubleshootv1beta1.Copy) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
return nil, err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
}
copyOutput := &CopyOutput{
Files: make(map[string][]byte),
Errors: make(map[string][]byte),
}
copyOutput := CopyOutput{}
pods, podsErrors := listPodsInSelectors(client, copyCollector.Namespace, copyCollector.Selector)
if len(podsErrors) > 0 {
errorBytes, err := marshalNonNil(podsErrors)
if err != nil {
return err
return nil, err
}
copyOutput.Errors[getCopyErrosFileName(copyCollector)] = errorBytes
copyOutput[getCopyErrosFileName(copyCollector)] = errorBytes
}
if len(pods) > 0 {
for _, pod := range pods {
files, copyErrors := copyFiles(client, pod, copyCollector)
bundlePath := filepath.Join(copyCollector.Name, pod.Namespace, pod.Name)
files, copyErrors := copyFiles(ctx, client, pod, copyCollector)
if len(copyErrors) > 0 {
key := fmt.Sprintf("%s/%s/%s-errors.json", pod.Namespace, pod.Name, copyCollector.ContainerPath)
copyOutput.Errors[key], err = marshalNonNil(copyErrors)
key := filepath.Join(bundlePath, copyCollector.ContainerPath+"-errors.json")
copyOutput[key], err = marshalNonNil(copyErrors)
if err != nil {
return err
return nil, err
}
continue
}
for k, v := range files {
copyOutput.Files[k] = v
copyOutput[filepath.Join(bundlePath, k)] = v
}
}
if redact {
// TODO
if ctx.Redact {
copyOutput, err = copyOutput.Redact()
if err != nil {
return nil, err
}
}
}
b, err := json.MarshalIndent(copyOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func copyFiles(client *kubernetes.Clientset, pod corev1.Pod, copyCollector *troubleshootv1beta1.Copy) (map[string][]byte, map[string]string) {
cfg, err := config.GetConfig()
if err != nil {
return nil, map[string]string{"error": err.Error()}
}
func copyFiles(ctx *Context, client *kubernetes.Clientset, pod corev1.Pod, copyCollector *troubleshootv1beta1.Copy) (map[string][]byte, map[string]string) {
container := pod.Spec.Containers[0].Name
if copyCollector.ContainerName != "" {
container = copyCollector.ContainerName
@@ -91,7 +78,9 @@ func copyFiles(client *kubernetes.Clientset, pod corev1.Pod, copyCollector *trou
req := client.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(pod.Namespace).SubResource("exec")
scheme := runtime.NewScheme()
if err := corev1.AddToScheme(scheme); err != nil {
return nil, map[string]string{"error": err.Error()}
return nil, map[string]string{
filepath.Join(copyCollector.ContainerPath, "error"): err.Error(),
}
}
parameterCodec := runtime.NewParameterCodec(scheme)
@@ -104,9 +93,11 @@ func copyFiles(client *kubernetes.Clientset, pod corev1.Pod, copyCollector *trou
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
exec, err := remotecommand.NewSPDYExecutor(ctx.ClientConfig, "POST", req.URL())
if err != nil {
return nil, map[string]string{"error": err.Error()}
return nil, map[string]string{
filepath.Join(copyCollector.ContainerPath, "error"): err.Error(),
}
}
output := new(bytes.Buffer)
@@ -118,21 +109,38 @@ func copyFiles(client *kubernetes.Clientset, pod corev1.Pod, copyCollector *trou
Tty: false,
})
if err != nil {
return nil, map[string]string{
"stdout": output.String(),
"stderr": stderr.String(),
"error": err.Error(),
errors := map[string]string{
filepath.Join(copyCollector.ContainerPath, "error"): err.Error(),
}
if s := output.String(); len(s) > 0 {
errors[filepath.Join(copyCollector.ContainerPath, "stdout")] = s
}
if s := stderr.String(); len(s) > 0 {
errors[filepath.Join(copyCollector.ContainerPath, "stderr")] = s
}
return nil, errors
}
return map[string][]byte{
fmt.Sprintf("%s/%s/%s", pod.Namespace, pod.Name, copyCollector.ContainerPath): output.Bytes(),
copyCollector.ContainerPath: output.Bytes(),
}, nil
}
func (c CopyOutput) Redact() (CopyOutput, error) {
results, err := redactMap(c)
if err != nil {
return nil, err
}
return results, nil
}
func getCopyErrosFileName(copyCollector *troubleshootv1beta1.Copy) string {
if len(copyCollector.Name) > 0 {
return fmt.Sprintf("%s-errors.json", copyCollector.Name)
}
if len(copyCollector.CollectorName) > 0 {
return fmt.Sprintf("%s.json", copyCollector.CollectorName)
return fmt.Sprintf("%s-errors.json", copyCollector.CollectorName)
}
// TODO: random part
return "errors.json"
+24
View File
@@ -0,0 +1,24 @@
package collect
import (
"encoding/json"
"path/filepath"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
)
type DataOutput map[string][]byte
func Data(ctx *Context, dataCollector *troubleshootv1beta1.Data) ([]byte, error) {
bundlePath := filepath.Join(dataCollector.Name, dataCollector.CollectorName)
dataOutput := DataOutput{
bundlePath: []byte(dataCollector.Data),
}
b, err := json.MarshalIndent(dataOutput, "", " ")
if err != nil {
return nil, err
}
return b, nil
}
+50 -52
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
@@ -12,101 +13,98 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/remotecommand"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type ExecOutput struct {
Results map[string][]byte `json:"exec/,omitempty"`
Errors map[string][]byte `json:"exec-errors/,omitempty"`
}
type ExecOutput map[string][]byte
func Exec(execCollector *troubleshootv1beta1.Exec, redact bool) error {
func Exec(ctx *Context, execCollector *troubleshootv1beta1.Exec) ([]byte, error) {
if execCollector.Timeout == "" {
return execWithoutTimeout(execCollector, redact)
return execWithoutTimeout(ctx, execCollector)
}
timeout, err := time.ParseDuration(execCollector.Timeout)
if err != nil {
return err
return nil, err
}
execChan := make(chan error, 1)
errCh := make(chan error, 1)
resultCh := make(chan []byte, 1)
go func() {
execChan <- execWithoutTimeout(execCollector, redact)
b, err := execWithoutTimeout(ctx, execCollector)
if err != nil {
errCh <- err
} else {
resultCh <- b
}
}()
select {
case <-time.After(timeout):
return errors.New("timeout")
case err := <-execChan:
return err
return nil, errors.New("timeout")
case result := <-resultCh:
return result, nil
case err := <-errCh:
return nil, err
}
}
func execWithoutTimeout(execCollector *troubleshootv1beta1.Exec, redact bool) error {
cfg, err := config.GetConfig()
func execWithoutTimeout(ctx *Context, execCollector *troubleshootv1beta1.Exec) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
return nil, err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
}
execOutput := &ExecOutput{
Results: make(map[string][]byte),
Errors: make(map[string][]byte),
}
execOutput := ExecOutput{}
pods, podsErrors := listPodsInSelectors(client, execCollector.Namespace, execCollector.Selector)
if len(podsErrors) > 0 {
errorBytes, err := marshalNonNil(podsErrors)
if err != nil {
return err
return nil, err
}
execOutput.Errors[getExecErrosFileName(execCollector)] = errorBytes
execOutput[getExecErrosFileName(execCollector)] = errorBytes
}
if len(pods) > 0 {
for _, pod := range pods {
stdout, stderr, execErrors := getExecOutputs(client, pod, execCollector, redact)
execOutput.Results[fmt.Sprintf("%s/%s/%s-stdout.txt", pod.Namespace, pod.Name, execCollector.CollectorName)] = stdout
execOutput.Results[fmt.Sprintf("%s/%s/%s-stderr.txt", pod.Namespace, pod.Name, execCollector.CollectorName)] = stderr
stdout, stderr, execErrors := getExecOutputs(ctx, client, pod, execCollector)
bundlePath := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
if len(stdout) > 0 {
execOutput[filepath.Join(bundlePath, execCollector.CollectorName+"-stdout.txt")] = stdout
}
if len(stderr) > 0 {
execOutput[filepath.Join(bundlePath, execCollector.CollectorName+"-stderr.txt")] = stderr
}
if len(execErrors) > 0 {
errorBytes, err := marshalNonNil(execErrors)
if err != nil {
return err
return nil, err
}
execOutput.Results[fmt.Sprintf("%s/%s/%s-errors.json", pod.Namespace, pod.Name, execCollector.CollectorName)] = errorBytes
execOutput[filepath.Join(bundlePath, execCollector.CollectorName+"-errors.json")] = errorBytes
continue
}
}
if redact {
if ctx.Redact {
execOutput, err = execOutput.Redact()
if err != nil {
return err
return nil, err
}
}
}
b, err := json.MarshalIndent(execOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func getExecOutputs(client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta1.Exec, doRedact bool) ([]byte, []byte, []string) {
cfg, err := config.GetConfig()
if err != nil {
return nil, nil, []string{err.Error()}
}
func getExecOutputs(ctx *Context, client *kubernetes.Clientset, pod corev1.Pod, execCollector *troubleshootv1beta1.Exec) ([]byte, []byte, []string) {
container := pod.Spec.Containers[0].Name
if execCollector.ContainerName != "" {
container = execCollector.ContainerName
@@ -128,7 +126,7 @@ func getExecOutputs(client *kubernetes.Clientset, pod corev1.Pod, execCollector
TTY: false,
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
exec, err := remotecommand.NewSPDYExecutor(ctx.ClientConfig, "POST", req.URL())
if err != nil {
return nil, nil, []string{err.Error()}
}
@@ -150,21 +148,21 @@ func getExecOutputs(client *kubernetes.Clientset, pod corev1.Pod, execCollector
return stdout.Bytes(), stderr.Bytes(), nil
}
func (r *ExecOutput) Redact() (*ExecOutput, error) {
results, err := redactMap(r.Results)
func (r ExecOutput) Redact() (ExecOutput, error) {
results, err := redactMap(r)
if err != nil {
return nil, err
}
return &ExecOutput{
Results: results,
Errors: r.Errors,
}, nil
return results, nil
}
func getExecErrosFileName(execCollector *troubleshootv1beta1.Exec) string {
if len(execCollector.Name) > 0 {
return fmt.Sprintf("%s-errors.json", execCollector.Name)
}
if len(execCollector.CollectorName) > 0 {
return fmt.Sprintf("%s.json", execCollector.CollectorName)
return fmt.Sprintf("%s-errors.json", execCollector.CollectorName)
}
// TODO: random part
return "errors.json"
+14 -16
View File
@@ -4,18 +4,16 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"strings"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/redact"
)
type HTTPOutput struct {
Response map[string][]byte `json:"http/,omitempty"`
}
type HTTPOutput map[string][]byte
type httpResponse struct {
Status int `json:"status"`
@@ -27,7 +25,7 @@ type httpError struct {
Message string `json:"message"`
}
func HTTP(httpCollector *troubleshootv1beta1.HTTP, redact bool) error {
func HTTP(ctx *Context, httpCollector *troubleshootv1beta1.HTTP) ([]byte, error) {
var response *http.Response
var err error
@@ -38,28 +36,28 @@ func HTTP(httpCollector *troubleshootv1beta1.HTTP, redact bool) error {
} else if httpCollector.Put != nil {
response, err = doPut(httpCollector.Put)
} else {
return errors.New("no supported http request type")
return nil, errors.New("no supported http request type")
}
output, err := responseToOutput(response, err, redact)
output, err := responseToOutput(response, err, ctx.Redact)
if err != nil {
return err
return nil, err
}
httpOutput := &HTTPOutput{
Response: map[string][]byte{
httpCollector.CollectorName + ".json": output,
},
fileName := "result.json"
if httpCollector.CollectorName != "" {
fileName = httpCollector.CollectorName + ".json"
}
httpOutput := HTTPOutput{
filepath.Join(httpCollector.Name, fileName): output,
}
b, err := json.MarshalIndent(httpOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func doGet(get *troubleshootv1beta1.Get) (*http.Response, error) {
+54 -46
View File
@@ -13,71 +13,74 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type LogsOutput struct {
PodLogs map[string][]byte `json:"logs/,omitempty"`
Errors map[string][]byte `json:"logs-errors/,omitempty"`
}
type LogsOutput map[string][]byte
func Logs(logsCollector *troubleshootv1beta1.Logs, redact bool) error {
cfg, err := config.GetConfig()
func Logs(ctx *Context, logsCollector *troubleshootv1beta1.Logs) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
return nil, err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
}
logsOutput := &LogsOutput{
PodLogs: make(map[string][]byte),
}
logsOutput := LogsOutput{}
pods, podsErrors := listPodsInSelectors(client, logsCollector.Namespace, logsCollector.Selector)
if len(podsErrors) > 0 {
errorBytes, err := marshalNonNil(podsErrors)
if err != nil {
return err
return nil, err
}
logsOutput.Errors[getLogsErrosFileName(logsCollector)] = errorBytes
logsOutput[getLogsErrorsFileName(logsCollector)] = errorBytes
}
if len(pods) > 0 {
for _, pod := range pods {
podLogs, err := getPodLogs(client, pod, logsCollector.Limits, false)
if err != nil {
key := fmt.Sprintf("%s/%s-errors.json", pod.Namespace, pod.Name)
logsOutput.Errors[key], err = marshalNonNil([]string{err.Error()})
if len(logsCollector.ContainerNames) == 0 {
podLogs, err := getPodLogs(client, pod, logsCollector.Name, "", logsCollector.Limits, false)
if err != nil {
return err
key := fmt.Sprintf("%s/%s-errors.json", logsCollector.Name, pod.Name)
logsOutput[key], err = marshalNonNil([]string{err.Error()})
if err != nil {
return nil, err
}
continue
}
for k, v := range podLogs {
logsOutput[k] = v
}
} else {
for _, container := range logsCollector.ContainerNames {
containerLogs, err := getPodLogs(client, pod, logsCollector.Name, container, logsCollector.Limits, false)
if err != nil {
key := fmt.Sprintf("%s/%s/%s-errors.json", logsCollector.Name, pod.Name, container)
logsOutput[key], err = marshalNonNil([]string{err.Error()})
if err != nil {
return nil, err
}
continue
}
for k, v := range containerLogs {
logsOutput[k] = v
}
}
continue
}
for k, v := range podLogs {
logsOutput.PodLogs[k] = v
}
}
if redact {
if ctx.Redact {
logsOutput, err = logsOutput.Redact()
if err != nil {
return err
return nil, err
}
}
}
b, err := json.MarshalIndent(logsOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func listPodsInSelectors(client *kubernetes.Clientset, namespace string, selector []string) ([]corev1.Pod, []string) {
@@ -95,9 +98,10 @@ func listPodsInSelectors(client *kubernetes.Clientset, namespace string, selecto
return pods.Items, nil
}
func getPodLogs(client *kubernetes.Clientset, pod corev1.Pod, limits *troubleshootv1beta1.LogLimits, follow bool) (map[string][]byte, error) {
func getPodLogs(client *kubernetes.Clientset, pod corev1.Pod, name, container string, limits *troubleshootv1beta1.LogLimits, follow bool) (map[string][]byte, error) {
podLogOpts := corev1.PodLogOptions{
Follow: follow,
Follow: follow,
Container: container,
}
defaultMaxLines := int64(10000)
@@ -133,26 +137,30 @@ func getPodLogs(client *kubernetes.Clientset, pod corev1.Pod, limits *troublesho
return nil, err
}
fileKey := fmt.Sprintf("%s/%s.txt", name, pod.Name)
if container != "" {
fileKey = fmt.Sprintf("%s/%s/%s.txt", name, pod.Name, container)
}
return map[string][]byte{
fmt.Sprintf("%s/%s.txt", pod.Namespace, pod.Name): buf.Bytes(),
fileKey: buf.Bytes(),
}, nil
}
func (l *LogsOutput) Redact() (*LogsOutput, error) {
podLogs, err := redactMap(l.PodLogs)
func (l LogsOutput) Redact() (LogsOutput, error) {
podLogs, err := redactMap(l)
if err != nil {
return nil, err
}
return &LogsOutput{
PodLogs: podLogs,
Errors: l.Errors,
}, nil
return podLogs, nil
}
func getLogsErrosFileName(logsCollector *troubleshootv1beta1.Logs) string {
if len(logsCollector.CollectorName) > 0 {
return fmt.Sprintf("%s.json", logsCollector.CollectorName)
func getLogsErrorsFileName(logsCollector *troubleshootv1beta1.Logs) string {
if len(logsCollector.Name) > 0 {
return fmt.Sprintf("%s/errors.json", logsCollector.Name)
} else if len(logsCollector.CollectorName) > 0 {
return fmt.Sprintf("%s/errors.json", logsCollector.CollectorName)
}
// TODO: random part
return "errors.json"
+37 -47
View File
@@ -3,7 +3,6 @@ package collect
import (
"encoding/json"
"errors"
"fmt"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
@@ -11,27 +10,19 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type RunOutput struct {
PodLogs map[string][]byte `json:"run/,omitempty"`
}
type RunOutput map[string][]byte
func Run(runCollector *troubleshootv1beta1.Run, redact bool) error {
cfg, err := config.GetConfig()
func Run(ctx *Context, runCollector *troubleshootv1beta1.Run) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
return nil, err
}
pod, err := runPod(client, runCollector)
if err != nil {
return err
return nil, err
}
defer func() {
@@ -41,77 +32,78 @@ func Run(runCollector *troubleshootv1beta1.Run, redact bool) error {
}()
if runCollector.Timeout == "" {
return runWithoutTimeout(pod, runCollector, redact)
return runWithoutTimeout(ctx, pod, runCollector)
}
timeout, err := time.ParseDuration(runCollector.Timeout)
if err != nil {
return err
return nil, err
}
runChan := make(chan error, 1)
errCh := make(chan error, 1)
resultCh := make(chan []byte, 1)
go func() {
runChan <- runWithoutTimeout(pod, runCollector, redact)
b, err := runWithoutTimeout(ctx, pod, runCollector)
if err != nil {
errCh <- err
} else {
resultCh <- b
}
}()
select {
case <-time.After(timeout):
return errors.New("timeout")
case err := <-runChan:
return err
return nil, errors.New("timeout")
case result := <-resultCh:
return result, nil
case err := <-errCh:
return nil, err
}
}
func runWithoutTimeout(pod *corev1.Pod, runCollector *troubleshootv1beta1.Run, redact bool) error {
cfg, err := config.GetConfig()
func runWithoutTimeout(ctx *Context, pod *corev1.Pod, runCollector *troubleshootv1beta1.Run) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
return nil, err
}
for {
status, err := client.CoreV1().Pods(pod.Namespace).Get(pod.Name, metav1.GetOptions{})
if err != nil {
return err
return nil, err
}
if status.Status.Phase == "Running" {
if status.Status.Phase == corev1.PodRunning ||
status.Status.Phase == corev1.PodFailed ||
status.Status.Phase == corev1.PodSucceeded {
break
}
time.Sleep(time.Second * 1)
}
runOutput := &RunOutput{
PodLogs: make(map[string][]byte),
}
runOutput := RunOutput{}
limits := troubleshootv1beta1.LogLimits{
MaxLines: 10000,
}
podLogs, err := getPodLogs(client, *pod, &limits, true)
podLogs, err := getPodLogs(client, *pod, runCollector.Name, "", &limits, true)
for k, v := range podLogs {
runOutput.PodLogs[k] = v
runOutput[k] = v
}
if redact {
if ctx.Redact {
runOutput, err = runOutput.Redact()
if err != nil {
return err
return nil, err
}
}
b, err := json.MarshalIndent(runOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func runPod(client *kubernetes.Clientset, runCollector *troubleshootv1beta1.Run) (*corev1.Pod, error) {
@@ -155,13 +147,11 @@ func runPod(client *kubernetes.Clientset, runCollector *troubleshootv1beta1.Run)
return created, nil
}
func (r *RunOutput) Redact() (*RunOutput, error) {
podLogs, err := redactMap(r.PodLogs)
func (r RunOutput) Redact() (RunOutput, error) {
podLogs, err := redactMap(r)
if err != nil {
return nil, err
}
return &RunOutput{
PodLogs: podLogs,
}, nil
return podLogs, nil
}
+12 -20
View File
@@ -7,7 +7,6 @@ import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type FoundSecret struct {
@@ -23,15 +22,10 @@ type SecretOutput struct {
Errors map[string][]byte `json:"secrets-errors/,omitempty"`
}
func Secret(secretCollector *troubleshootv1beta1.Secret, redact bool) error {
cfg, err := config.GetConfig()
func Secret(ctx *Context, secretCollector *troubleshootv1beta1.Secret) ([]byte, error) {
client, err := kubernetes.NewForConfig(ctx.ClientConfig)
if err != nil {
return err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return err
return nil, err
}
secretOutput := &SecretOutput{
@@ -43,36 +37,34 @@ func Secret(secretCollector *troubleshootv1beta1.Secret, redact bool) error {
if err != nil {
errorBytes, err := marshalNonNil([]string{err.Error()})
if err != nil {
return err
return nil, err
}
secretOutput.Errors[fmt.Sprintf("%s/%s.json", secret.Namespace, secret.Name)] = errorBytes
secretOutput.Errors[fmt.Sprintf("%s.json", secret.Name)] = errorBytes
}
if encoded != nil {
secretOutput.FoundSecret[fmt.Sprintf("%s/%s.json", secret.Namespace, secret.Name)] = encoded
if redact {
secretOutput.FoundSecret[fmt.Sprintf("%s.json", secret.Name)] = encoded
if ctx.Redact {
secretOutput, err = secretOutput.Redact()
if err != nil {
return err
return nil, err
}
}
}
b, err := json.MarshalIndent(secretOutput, "", " ")
if err != nil {
return err
return nil, err
}
fmt.Printf("%s\n", b)
return nil
return b, nil
}
func secret(client *kubernetes.Clientset, secretCollector *troubleshootv1beta1.Secret) (*FoundSecret, []byte, error) {
found, err := client.CoreV1().Secrets(secretCollector.Namespace).Get(secretCollector.Name, metav1.GetOptions{})
found, err := client.CoreV1().Secrets(secretCollector.Namespace).Get(secretCollector.SecretName, metav1.GetOptions{})
if err != nil {
missingSecret := FoundSecret{
Namespace: secretCollector.Namespace,
Name: secretCollector.Name,
Name: secretCollector.SecretName,
SecretExists: false,
}
+1 -1
View File
@@ -22,7 +22,7 @@ func DeterministicIDForCollector(collector *troubleshootv1beta1.Collect) string
}
if collector.Secret != nil {
unsafeID = fmt.Sprintf("secret-%s-%s", collector.Secret.Namespace, collector.Secret.Name)
unsafeID = fmt.Sprintf("secret-%s-%s", collector.Secret.Namespace, collector.Secret.SecretName)
}
if collector.Logs != nil {
+2 -2
View File
@@ -52,8 +52,8 @@ func Test_DeterministicIDForCollector(t *testing.T) {
name: "secret",
collector: &troubleshootv1beta1.Collect{
Secret: &troubleshootv1beta1.Secret{
Name: "secret-agent-woman",
Namespace: "top-secret",
SecretName: "secret-agent-woman",
Namespace: "top-secret",
},
},
expect: "secret-top-secret-secret-agent-woman",
@@ -25,7 +25,6 @@ import (
"math/rand"
"net/http"
"os"
"path/filepath"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
@@ -40,8 +39,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
@@ -61,7 +60,11 @@ func Add(mgr manager.Manager) error {
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcileCollectorJob{Client: mgr.GetClient(), scheme: mgr.GetScheme()}
return &ReconcileCollectorJob{
Client: mgr.GetClient(),
config: mgr.GetConfig(),
scheme: mgr.GetScheme(),
}
}
// add adds a new Controller to mgr with r as the reconcile.Reconciler
@@ -94,6 +97,7 @@ var _ reconcile.Reconciler = &ReconcileCollectorJob{}
// ReconcileCollectorJob reconciles a CollectorJob object
type ReconcileCollectorJob struct {
client.Client
config *rest.Config
scheme *runtime.Scheme
}
@@ -137,7 +141,7 @@ func (r *ReconcileCollectorJob) Reconcile(request reconcile.Request) (reconcile.
return reconcile.Result{}, err
}
for _, collector := range collectorSpec.Spec {
for _, collector := range collectorSpec.Spec.Collectors {
if err := r.reconileOneCollectorJob(instance, collector); err != nil {
return reconcile.Result{}, err
}
@@ -147,12 +151,7 @@ func (r *ReconcileCollectorJob) Reconcile(request reconcile.Request) (reconcile.
}
func (r *ReconcileCollectorJob) getCollectorSpec(namespace string, name string) (*troubleshootv1beta1.Collector, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(cfg)
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(r.config)
if err != nil {
return nil, err
}
@@ -190,12 +189,7 @@ func (r *ReconcileCollectorJob) reconileOneCollectorJob(instance *troubleshootv1
// Get the logs
podLogOpts := corev1.PodLogOptions{}
cfg, err := config.GetConfig()
if err != nil {
return err
}
k8sClient, err := kubernetes.NewForConfig(cfg)
k8sClient, err := kubernetes.NewForConfig(r.config)
if err != nil {
return err
}
@@ -223,15 +217,9 @@ func (r *ReconcileCollectorJob) reconileOneCollectorJob(instance *troubleshootv1
logger.Printf("setting up port forwarding because the manager is not running in the cluster\n")
// this isn't likely to be very solid
r := rand.New(rand.NewSource(time.Now().UnixNano()))
localPort := 3000 + r.Intn(999)
localPort := 3000 + rand.New(rand.NewSource(time.Now().UnixNano())).Intn(999)
homeDir := os.Getenv("HOME")
if homeDir == "" {
homeDir = os.Getenv("USERPROFILE")
}
kubeContext := filepath.Join(homeDir, ".kube", "config")
ch, err := k8sutil.PortForward(kubeContext, localPort, 8000, instance.Namespace, instance.Name+"-collector")
ch, err := k8sutil.PortForward(r.config, localPort, 8000, instance.Namespace, instance.Name+"-collector")
if err != nil {
return err
}
@@ -25,8 +25,8 @@ import (
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -45,7 +45,11 @@ func Add(mgr manager.Manager) error {
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcilePreflightJob{Client: mgr.GetClient(), scheme: mgr.GetScheme()}
return &ReconcilePreflightJob{
Client: mgr.GetClient(),
config: mgr.GetConfig(),
scheme: mgr.GetScheme(),
}
}
// add adds a new Controller to mgr with r as the reconcile.Reconciler
@@ -70,6 +74,7 @@ var _ reconcile.Reconciler = &ReconcilePreflightJob{}
// ReconcilePreflightJob reconciles a PreflightJob object
type ReconcilePreflightJob struct {
client.Client
config *rest.Config
scheme *runtime.Scheme
}
@@ -155,12 +160,7 @@ func (r *ReconcilePreflightJob) Reconcile(request reconcile.Request) (reconcile.
}
func (r *ReconcilePreflightJob) getPreflightSpec(namespace string, name string) (*troubleshootv1beta1.Preflight, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(cfg)
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(r.config)
if err != nil {
return nil, err
}
+3 -7
View File
@@ -8,21 +8,17 @@ import (
"strings"
"time"
"k8s.io/client-go/tools/clientcmd"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
func PortForward(kubeContext string, localPort int, remotePort int, namespace string, podName string) (chan struct{}, error) {
// port forward
config, err := clientcmd.BuildConfigFromFlags("", kubeContext)
if err != nil {
return nil, err
}
func PortForward(config *restclient.Config, localPort int, remotePort int, namespace string, podName string) (chan struct{}, error) {
roundTripper, upgrader, err := spdy.RoundTripperFor(config)
if err != nil {
return nil, err
}
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, podName)
hostIP := strings.TrimLeft(config.Host, "htps:/")
serverURL := url.URL{Scheme: "http", Path: path, Host: hostIP}
+7
View File
@@ -0,0 +1,7 @@
package version
// NOTE: these variables are injected at build time
var (
version, gitSHA, buildTime string
)
+12
View File
@@ -0,0 +1,12 @@
package version
import (
"time"
)
var RunAt time.Time
func init() {
RunAt = time.Now().UTC()
initBuild()
}
+72
View File
@@ -0,0 +1,72 @@
package version
import (
"runtime"
"time"
)
var (
build Build
)
// Build holds details about this build of the binary
type Build struct {
Version string `json:"version,omitempty"`
GitSHA string `json:"git,omitempty"`
BuildTime time.Time `json:"buildTime,omitempty"`
TimeFallback string `json:"buildTimeFallback,omitempty"`
GoInfo GoInfo `json:"go,omitempty"`
RunAt *time.Time `json:"runAt,omitempty"`
}
type GoInfo struct {
Version string `json:"version,omitempty"`
Compiler string `json:"compiler,omitempty"`
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
}
// initBuild sets up the version info from build args
func initBuild() {
build.Version = version
if len(gitSHA) >= 7 {
build.GitSHA = gitSHA[:7]
}
var err error
build.BuildTime, err = time.Parse(time.RFC3339, buildTime)
if err != nil {
build.TimeFallback = buildTime
}
build.GoInfo = getGoInfo()
build.RunAt = &RunAt
}
// GetBuild gets the build
func GetBuild() Build {
return build
}
// Version gets the version
func Version() string {
return build.Version
}
// GitSHA gets the gitsha
func GitSHA() string {
return build.GitSHA
}
// BuildTime gets the build time
func BuildTime() time.Time {
return build.BuildTime
}
func getGoInfo() GoInfo {
return GoInfo{
Version: runtime.Version(),
Compiler: runtime.Compiler,
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}
}