diff --git a/.github/labeler.yml b/.github/labeler.yml index 6c77ea8..5aa8ab9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -60,5 +60,9 @@ release: scanners: - pkg/dashboard/scanners/* +tests: + - pkg/dashboard/**/*_test.go + - pkg/dashboard/objects/testdata/* + frontend: - - pkg/dashboard/static/* + - pkg/dashboard/static/* \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6d0b12..ad7ac36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: push: branches: main pull_request: - branches: main + branches: "*" jobs: build: @@ -18,7 +18,7 @@ jobs: go-version: 1.18 - name: Unit tests run: | - go test -v -race ./... # Run all the tests with the race detector enabled + go test -v -race ./... -covermode=atomic # Run all the tests with the race detector enabled - name: Static analysis run: | go vet ./... # go vet is the official Go static analyzer @@ -34,7 +34,7 @@ jobs: - name: Test Binary is Runnable run: "dist/helm-dashboard_linux_amd64_v1/helm-dashboard --help" - name: golangci-lint - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v3.3.1 with: # version: latest # skip-go-installation: true diff --git a/Dockerfile b/Dockerfile index 04424c0..6f13e7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,13 +23,11 @@ WORKDIR /build/src RUN make build # Stage - runner -FROM alpine/helm +FROM alpine +EXPOSE 8080 # Python -RUN apk add --update --no-cache python3 && python3 -m ensurepip && pip3 install --upgrade pip setuptools - -# kubectl -RUN curl -o /bin/kubectl -vf -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && chmod +x /bin/kubectl && kubectl --help +RUN apk add --update --no-cache python3 curl && python3 -m ensurepip && pip3 install --upgrade pip setuptools # Trivy RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.18.3 @@ -40,6 +38,6 @@ RUN pip3 install checkov packaging==21.3 && checkov --version COPY --from=builder /build/src/bin/dashboard /bin/helm-dashboard -ENTRYPOINT ["/bin/helm-dashboard", "--no-browser", "--bind=0.0.0.0"] +ENTRYPOINT ["/bin/helm-dashboard", "--no-browser", "--bind=0.0.0.0", "--port=8080"] # docker build . -t komodorio/helm-dashboard:0.0.0 && kind load docker-image komodorio/helm-dashboard:0.0.0 \ No newline at end of file diff --git a/Makefile b/Makefile index b34eaf2..3929173 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VERSION ?= $(git describe --tags --always --dirty --match=v* 2> /dev/null || \ .PHONY: test test: ; $(info $(M) start unit testing...) @ - @go test $$(go list ./... | grep -v /mocks/) --race -v -short -coverprofile=profile.cov + @go test $$(go list ./... | grep -v /mocks/) --race -v -short -coverpkg=./... -coverprofile=profile.cov @echo "\n*****************************" @echo "** TOTAL COVERAGE: $$(go tool cover -func profile.cov | grep total | grep -Eo '[0-9]+\.[0-9]+')% **" @echo "*****************************\n" diff --git a/README.md b/README.md index af747d2..5038f6d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,15 @@ Some of the key capabilities of the tool: ## Setup +### Standalone Binary + +Since version 1.0, the recommended install method is to just use standalone binary. It does not require Helm or kubectl to be installed. + +Download the appropriate [release package](https://github.com/komodorio/helm-dashboard/releases) for your platform, unpack it and just run `dashboard` binary from it. + ### Using Helm plugin manager -To install the plugin, simply run Helm command: +To install dashboard as Helm plugin, simply run Helm command: ```shell helm plugin install https://github.com/komodorio/helm-dashboard.git @@ -72,7 +78,7 @@ This can also be specified using flag `--bind `, for example `--bind=0.0.0 If your port 8080 is busy, you can specify a different port to use via `--port ` command-line flag. -If you need to limit the operations to a specific namespace, please use `--namespace=...` in your command-line. +If you need to limit the operations to a specific namespace, please use `--namespace=...` in your command-line. You can specify multiple namespaces, separated by commas. If you don't want browser tab to automatically open, add `--no-browser` flag in your command line. @@ -84,9 +90,6 @@ If you want to increase the logging verbosity and see all the debug info, use th The official helm chart is [available here](https://github.com/komodorio/helm-charts/blob/master/charts/helm-dashboard) -### Manual Installation - -Download the appropriate [release package](https://github.com/komodorio/helm-dashboard/releases) for your platform, unpack it and just run `dashboard` binary from it. ## Execute Helm tests diff --git a/charts/helm-dashboard/README.md b/charts/helm-dashboard/README.md index 363d106..a9970f8 100644 --- a/charts/helm-dashboard/README.md +++ b/charts/helm-dashboard/README.md @@ -12,6 +12,8 @@ helm upgrade --install my-release komodorio/helm-dashboard This chart bootstraps a Helm Dashboard deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. +While installed inside cluster, Helm Dashboard will run some additional backgroud actions, for example, will automatically update Helm repositories. To enable that behavior locally, set `HD_CLUSTER_MODE` env variable. + ## Prerequisites - Kubernetes 1.16+ diff --git a/go.mod b/go.mod index 464d5aa..f7eff0d 100644 --- a/go.mod +++ b/go.mod @@ -3,68 +3,169 @@ module github.com/komodorio/helm-dashboard go 1.18 require ( - github.com/eko/gocache/v3 v3.1.1 + github.com/eko/gocache/v3 v3.1.2 github.com/gin-gonic/gin v1.8.1 github.com/hashicorp/go-version v1.6.0 github.com/hexops/gotextdiff v1.0.3 github.com/jessevdk/go-flags v1.5.0 + github.com/joomcode/errorx v1.1.0 github.com/olekukonko/tablewriter v0.0.5 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/pkg/errors v0.9.1 + github.com/rogpeppe/go-internal v1.8.0 github.com/sirupsen/logrus v1.9.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.10.3 - k8s.io/apimachinery v0.25.2 + k8s.io/api v0.26.0 + k8s.io/apimachinery v0.26.0 + k8s.io/cli-runtime v0.26.0 + k8s.io/client-go v0.26.0 + k8s.io/kubectl v0.26.0 ) require ( + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.1 // indirect +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/BurntSushi/toml v1.1.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/Masterminds/squirrel v1.5.3 // indirect github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect + github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect + github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/containerd/containerd v1.6.6 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/docker v20.10.17+incompatible // indirect + github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/camelcase v1.0.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fvbommel/sortorder v1.0.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-errors/errors v1.0.1 // indirect + github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.9.11 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.2.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lib/pq v1.10.6 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect github.com/pelletier/go-toml/v2 v2.0.3 // indirect - github.com/prometheus/client_golang v1.12.2 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.33.0 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/rubenv/sql-migrate v1.1.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/ugorji/go/codec v1.2.7 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.4 // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect - golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect - golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/exp v0.0.0-20221110155412-d0897a79cd37 // indirect + golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + gotest.tools/v3 v3.4.0 + k8s.io/apiextensions-apiserver v0.25.2 // indirect + k8s.io/apiserver v0.25.2 // indirect + k8s.io/component-base v0.26.0 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect + oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/kustomize/api v0.12.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index fe015a9..cb3140c 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,11 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -21,6 +26,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -31,14 +37,32 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10= github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -46,13 +70,26 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= +github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= -github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 h1:hjXJeBcAMS1WGENGqDpzvmgS43oECTx8UXq31UBu0Jw= +github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -60,44 +97,109 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= +github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= +github.com/coocood/freecache v1.2.3 h1:lcBwpZrwBZRZyLk/8EMyQVXRiFl663cCuMOrjCALeto= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= +github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +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-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +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/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/eko/gocache/v3 v3.1.1 h1:r3CBwLnqPkcK56h9Do2CWw1kZ4TeKK0wDE1Oo/YZnhs= -github.com/eko/gocache/v3 v3.1.1/go.mod h1:UpP/LyHAioP/a/dizgl0MpgZ3A3CkS4NbG/mWkGTQ9M= +github.com/eko/gocache/v3 v3.1.2 h1:tBAn5kBScEmRXWHJl0iJgJU7TsMeOjySwHDZ/92riqg= +github.com/eko/gocache/v3 v3.1.2/go.mod h1:92prWCVTLxRkRlZuxDkLkwwUfitZ60zKNi6kn3qiDNU= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= +github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -111,9 +213,17 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -124,19 +234,35 @@ github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2B github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 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= @@ -144,6 +270,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -161,10 +288,16 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -172,15 +305,20 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 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/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -188,25 +326,79 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0 h1:fWY+zXdWhvWndXqnMj4SyC/vi8sK508OjhGCtMzsA9M= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joomcode/errorx v1.1.0 h1:dizuSG6yHzlvXOOGHW00gwsmM4Sb9x/yWEfdtPztqcs= +github.com/joomcode/errorx v1.1.0/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -215,15 +407,23 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -231,16 +431,73 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +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/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -249,10 +506,17 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= @@ -260,15 +524,25 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4= github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s= github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +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/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -276,82 +550,154 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.33.0 h1:rHgav/0a6+uYgGdNt3jwz8FNSesO/Hsang3O0T9A5SE= -github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= +github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 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.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -365,8 +711,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw= -golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys= +golang.org/x/exp v0.0.0-20221110155412-d0897a79cd37 h1:wKMvZzBFHbOCGvF2OmxR5Fqv/jDlkt7slnPz5ejEU8A= +golang.org/x/exp v0.0.0-20221110155412-d0897a79cd37/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -379,6 +725,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -387,11 +735,16 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -420,18 +773,34 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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= @@ -443,12 +812,15 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -459,13 +831,18 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -480,35 +857,57 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= -golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -517,15 +916,18 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/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-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -552,7 +954,15 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -573,6 +983,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -604,12 +1020,27 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= 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.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -622,6 +1053,17 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -634,18 +1076,22 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -654,6 +1100,7 @@ gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -661,9 +1108,13 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= helm.sh/helm/v3 v3.10.3 h1:wL7IUZ7Zyukm5Kz0OUmIFZgKHuAgByCrUcJBtY0kDyw= helm.sh/helm/v3 v3.10.3/go.mod h1:CXOcs02AYvrlPMWARNYNRgf2rNP7gLJQsi/Ubd4EDrI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -673,25 +1124,47 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= +k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= +k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= +k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8= k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= -k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/apiserver v0.25.2 h1:YePimobk187IMIdnmsMxsfIbC5p4eX3WSOrS9x6FEYw= +k8s.io/apiserver v0.25.2/go.mod h1:30r7xyQTREWCkG2uSjgjhQcKVvAAlqoD+YyrqR6Cn+I= +k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= +k8s.io/cli-runtime v0.26.0/go.mod h1:o+4KmwHzO/UK0wepE1qpRk6l3o60/txUZ1fEXWGIKTY= +k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= +k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= +k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= +k8s.io/kubectl v0.26.0/go.mod h1:eInP0b+U9XUJWSYeU9XZnTA+cVYuWyl3iYPGtru0qhQ= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= +oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index f26d483..175c303 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,12 @@ package main import ( + "context" "fmt" "os" + "os/signal" + "strings" + "syscall" "github.com/gin-gonic/gin" "github.com/jessevdk/go-flags" @@ -23,8 +27,8 @@ type options struct { NoBrowser bool `short:"b" long:"no-browser" description:"Do not attempt to open Web browser upon start"` NoTracking bool `long:"no-analytics" description:"Disable user analytics (GA, DataDog etc.)"` BindHost string `long:"bind" description:"Host binding to start server (default: localhost)"` // default should be printed but not assigned as the precedence: flag > env > default - Port uint `short:"p" long:"port" description:"Port to start server on" default:"8080"` // TODO: better default port to clash less? - Namespace string `short:"n" long:"namespace" description:"Limit operations to a specific namespace"` + Port uint `short:"p" long:"port" description:"Port to start server on" default:"8080"` + Namespace string `short:"n" long:"namespace" description:"Namespace for HELM operations"` } func main() { @@ -42,12 +46,26 @@ func main() { server := dashboard.Server{ Version: version, - Namespace: opts.Namespace, + Namespaces: strings.Split(opts.Namespace, ","), Address: fmt.Sprintf("%s:%d", opts.BindHost, opts.Port), Debug: opts.Verbose, NoTracking: opts.NoTracking, } - address, webServerDone := server.StartServer() + + ctx, cancel := context.WithCancel(context.Background()) + + osSignal := make(chan os.Signal, 1) + signal.Notify(osSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + go func() { + oscall := <-osSignal + log.Warnf("Stopping on signal: %s\n", oscall) + cancel() + }() + + address, webServerDone, err := server.StartServer(ctx, cancel) + if err != nil { + log.Fatalf("Failed to start Helm Dashboard: %+v", err) + } if !opts.NoTracking { log.Infof("User analytics is collected to improve the quality, disable it with --no-analytics") @@ -69,7 +87,7 @@ func main() { func parseFlags() options { ns := os.Getenv("HELM_NAMESPACE") - if ns == "default" { + if ns == "default" { // it's how Helm passes to plugin the empty NS, we have to reset it back ns = "" } @@ -92,7 +110,8 @@ func parseFlags() options { } if len(args) > 0 { - panic("The program does not take argumants, see --help for usage") + fmt.Println("The program does not take arguments, see --help for usage") + os.Exit(1) } return opts } diff --git a/pkg/dashboard/api.go b/pkg/dashboard/api.go index 5f2917c..dbd487d 100644 --- a/pkg/dashboard/api.go +++ b/pkg/dashboard/api.go @@ -1,23 +1,25 @@ package dashboard import ( + "context" "embed" + "github.com/gin-gonic/gin" + "github.com/komodorio/helm-dashboard/pkg/dashboard/handlers" + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" + log "github.com/sirupsen/logrus" + "html" "net/http" "os" "path" - - "github.com/gin-gonic/gin" - "github.com/komodorio/helm-dashboard/pkg/dashboard/handlers" - "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" - "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" - log "github.com/sirupsen/logrus" ) //go:embed static/* var staticFS embed.FS func noCache(c *gin.Context) { - c.Header("Cache-Control", "no-cache") + if c.GetHeader("Cache-Control") == "" { // default policy is not to cache + c.Header("Cache-Control", "no-cache") + } c.Next() } @@ -26,33 +28,39 @@ func errorHandler(c *gin.Context) { errs := "" for _, err := range c.Errors { - log.Debugf("Error: %s", err) + log.Debugf("Error: %+v", err) errs += err.Error() + "\n" } if errs != "" { - c.String(http.StatusInternalServerError, errs) + c.String(http.StatusInternalServerError, html.EscapeString(errs)) } } -func contextSetter(data *subproc.DataLayer) gin.HandlerFunc { +func contextSetter(data *objects.DataLayer) gin.HandlerFunc { return func(c *gin.Context) { + ctxName := "" if ctx, ok := c.Request.Header["X-Kubecontext"]; ok { - log.Debugf("Setting current context to: %s", ctx) - if data.KubeContext != ctx[0] { - err := data.Cache.Clear() - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } + ctxName = ctx[0] + if err := data.SetContext(ctxName); err != nil { + c.String(http.StatusInternalServerError, err.Error()) + return } - data.KubeContext = ctx[0] } + + app, err := data.AppForCtx(ctxName) + if err != nil { + c.String(http.StatusInternalServerError, err.Error()) + return + } + + c.Set(handlers.APP, app) + c.Next() } } -func NewRouter(abortWeb utils.ControlChan, data *subproc.DataLayer, debug bool) *gin.Engine { +func NewRouter(abortWeb context.CancelFunc, data *objects.DataLayer, debug bool) *gin.Engine { var api *gin.Engine if debug { api = gin.New() @@ -71,10 +79,10 @@ func NewRouter(abortWeb utils.ControlChan, data *subproc.DataLayer, debug bool) return api } -func configureRoutes(abortWeb utils.ControlChan, data *subproc.DataLayer, api *gin.Engine) { +func configureRoutes(abortWeb context.CancelFunc, data *objects.DataLayer, api *gin.Engine) { // server shutdown handler api.DELETE("/", func(c *gin.Context) { - abortWeb <- struct{}{} + abortWeb() c.Status(http.StatusAccepted) }) @@ -83,11 +91,11 @@ func configureRoutes(abortWeb utils.ControlChan, data *subproc.DataLayer, api *g c.IndentedJSON(http.StatusOK, data.GetStatus()) }) - api.GET("/api/cache", func(c *gin.Context) { + api.GET("/api/cache", func(c *gin.Context) { // TODO: included into OpenAPI or not? c.IndentedJSON(http.StatusOK, data.Cache) }) - api.DELETE("/api/cache", func(c *gin.Context) { + api.DELETE("/api/cache", func(c *gin.Context) { // TODO: included into OpenAPI or not? err := data.Cache.Clear() if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) @@ -96,40 +104,63 @@ func configureRoutes(abortWeb utils.ControlChan, data *subproc.DataLayer, api *g c.Status(http.StatusAccepted) }) + api.POST("/diff", func(c *gin.Context) { // TODO: included into OpenAPI or not? + a := c.PostForm("a") + b := c.PostForm("b") + + out := handlers.GetDiff(a, b, "current.yaml", "upgraded.yaml") + c.Header("Content-Type", "text/plain") + c.String(http.StatusOK, out) + }) + + api.GET("/api-docs", func(c *gin.Context) { // https://github.com/OAI/OpenAPI-Specification/search?q=api-docs + c.Redirect(http.StatusFound, "static/api-docs.html") + }) + configureHelms(api.Group("/api/helm"), data) - configureKubectls(api.Group("/api/kube"), data) + configureKubectls(api.Group("/api/k8s"), data) configureScanners(api.Group("/api/scanners"), data) } -func configureHelms(api *gin.RouterGroup, data *subproc.DataLayer) { - h := handlers.HelmHandler{Data: data} +func configureHelms(api *gin.RouterGroup, data *objects.DataLayer) { + h := handlers.HelmHandler{ + Contexted: &handlers.Contexted{ + Data: data, + }, + } - api.GET("/charts", h.GetCharts) - api.DELETE("/charts", h.Uninstall) + rels := api.Group("/releases") + rels.GET("", h.GetReleases) + rels.POST(":ns", h.Install) + rels.POST(":ns/:name", h.Upgrade) + rels.DELETE(":ns/:name", h.Uninstall) + rels.GET(":ns/:name/history", h.History) + rels.GET(":ns/:name/:section", h.GetInfoSection) + rels.GET(":ns/:name/resources", h.Resources) + rels.POST(":ns/:name/rollback", h.Rollback) + rels.POST(":ns/:name/test", h.RunTests) - api.GET("/charts/history", h.History) - api.GET("/charts/resources", h.Resources) - api.GET("/charts/:section", h.GetInfoSection) - api.GET("/charts/show", h.Show) - api.POST("/charts/install", h.Install) - api.POST("/charts/tests", h.Tests) - api.POST("/charts/rollback", h.Rollback) - - api.GET("/repo", h.RepoList) - api.POST("/repo", h.RepoAdd) - api.DELETE("/repo", h.RepoDelete) - api.GET("/repo/charts", h.RepoCharts) - api.GET("/repo/search", h.RepoSearch) - api.POST("/repo/update", h.RepoUpdate) - api.GET("/repo/values", h.RepoValues) + repos := api.Group("/repositories") + repos.GET("", h.RepoList) + repos.POST("", h.RepoAdd) + repos.GET("/:name", h.RepoCharts) + repos.POST("/:name", h.RepoUpdate) + repos.DELETE("/:name", h.RepoDelete) + repos.GET("/latestver", h.RepoLatestVer) // TODO: use /versions in client insted and remove this? + repos.GET("/versions", h.RepoVersions) + repos.GET("/values", h.RepoValues) } -func configureKubectls(api *gin.RouterGroup, data *subproc.DataLayer) { - h := handlers.KubeHandler{Data: data} +func configureKubectls(api *gin.RouterGroup, data *objects.DataLayer) { + h := handlers.KubeHandler{ + Contexted: &handlers.Contexted{ + Data: data, + }, + } api.GET("/contexts", h.GetContexts) - api.GET("/resources/:kind", h.GetResourceInfo) - api.GET("/describe/:kind", h.Describe) - api.GET("/namespaces", h.GetNameSpaces) + api.GET("/:kind/get", h.GetResourceInfo) + api.GET("/:kind/describe", h.Describe) + api.GET("/:kind/list", h.GetNameSpaces) } func configureStatic(api *gin.Engine) { @@ -162,9 +193,13 @@ func configureStatic(api *gin.Engine) { } } -func configureScanners(api *gin.RouterGroup, data *subproc.DataLayer) { - h := handlers.ScannersHandler{Data: data} +func configureScanners(api *gin.RouterGroup, data *objects.DataLayer) { + h := handlers.ScannersHandler{ + Contexted: &handlers.Contexted{ + Data: data, + }, + } api.GET("", h.List) - api.POST("/manifests", h.ScanDraftManifest) + api.POST("/manifests", h.ScanManifest) api.GET("/resource/:kind", h.ScanResource) } diff --git a/pkg/dashboard/api_test.go b/pkg/dashboard/api_test.go new file mode 100644 index 0000000..e2cb3de --- /dev/null +++ b/pkg/dashboard/api_test.go @@ -0,0 +1,412 @@ +package dashboard + +import ( + "github.com/gin-gonic/gin" + "github.com/komodorio/helm-dashboard/pkg/dashboard/handlers" + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" + log "github.com/sirupsen/logrus" + "gotest.tools/v3/assert" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli" + kubefake "helm.sh/helm/v3/pkg/kube/fake" + "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/storage" + "helm.sh/helm/v3/pkg/storage/driver" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" +) + +var inMemStorage *storage.Storage +var repoFile string + +func TestMain(m *testing.M) { // fixture to set logging level via env variable + if os.Getenv("DEBUG") != "" { + log.SetLevel(log.DebugLevel) + log.Debugf("Set logging level") + } + + inMemStorage = storage.Init(driver.NewMemory()) + d, err := ioutil.TempDir("", "helm") + if err != nil { + panic(err) + } + repoFile = filepath.Join(d, "repositories.yaml") + + m.Run() + inMemStorage = nil + repoFile = "" +} + +func GetTestGinContext(w *httptest.ResponseRecorder) *gin.Context { + gin.SetMode(gin.TestMode) + + ctx, _ := gin.CreateTestContext(w) + ctx.Request = &http.Request{ + Header: make(http.Header), + } + + return ctx +} + +func TestNoCacheMiddleware(t *testing.T) { + w := httptest.NewRecorder() + con := GetTestGinContext(w) + noCache(con) + assert.Equal(t, w.Header().Get("Cache-Control"), "no-cache") +} + +func TestEnableCacheControl(t *testing.T) { + w := httptest.NewRecorder() + con := GetTestGinContext(w) + + // Sets deafault policy to `no-cache` + noCache(con) + + h := handlers.HelmHandler{ + Contexted: &handlers.Contexted{ + Data: &objects.DataLayer{}, + }, + } + h.EnableClientCache(con) + assert.Equal(t, w.Header().Get("Cache-Control"), "max-age=43200") +} + +func TestConfigureStatic(t *testing.T) { + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + // Create an API Engine + api := gin.Default() + + // Configure static routes + configureStatic(api) + + // Start the server + api.ServeHTTP(w, req) + + assert.Equal(t, w.Code, http.StatusOK) +} + +func TestConfigureRoutes(t *testing.T) { + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/status", nil) + if err != nil { + t.Fatal(err) + } + + // Create a API Engine + api := gin.Default() + + // Required arguements for route configuration + abortWeb := func() {} + data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig) + + if err != nil { + t.Fatal(err) + } + + // Configure routes to API engine + configureRoutes(abortWeb, data, api) + + // Start the server + api.ServeHTTP(w, req) + + assert.Equal(t, w.Code, http.StatusOK) +} + +func TestContextSetter(t *testing.T) { + w := httptest.NewRecorder() + con := GetTestGinContext(w) + + // Required arguements + data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig) + + if err != nil { + t.Fatal(err) + } + + // Set the context + ctxHandler := contextSetter(data) + ctxHandler(con) + + appName, exists := con.Get("app") + + if !exists { + t.Fatal("Value app doesn't exist in context") + } + + tmp := handlers.Contexted{Data: data} + + assert.Equal(t, appName, tmp.GetApp(con)) +} + +func TestNewRouter(t *testing.T) { + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/status", nil) + if err != nil { + t.Fatal(err) + } + + // Required arguemnets + abortWeb := func() {} + data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig) + + if err != nil { + t.Fatal(err) + } + + // Create a new router with the function + newRouter := NewRouter(abortWeb, data, false) + + newRouter.ServeHTTP(w, req) + + assert.Equal(t, w.Code, http.StatusOK) +} + +func TestConfigureScanners(t *testing.T) { + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/scanners", nil) + if err != nil { + t.Fatal(err) + } + + // Required arguemnets + data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig) + + if err != nil { + t.Fatal(err) + } + + apiEngine := gin.Default() + + configureScanners(apiEngine.Group("/api/scanners"), data) + + apiEngine.ServeHTTP(w, req) + + assert.Equal(t, w.Code, http.StatusOK) +} + +func TestConfigureKubectls(t *testing.T) { + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/kube/contexts", nil) + if err != nil { + t.Fatal(err) + } + + // Required arguemnets + data, err := objects.NewDataLayer([]string{"TestSpace"}, "T-1", NewHelmConfig) + + if err != nil { + t.Fatal(err) + } + + apiEngine := gin.Default() + + // Required middleware for kubectl api configuration + apiEngine.Use(contextSetter(data)) + + configureKubectls(apiEngine.Group("/api/kube"), data) + + apiEngine.ServeHTTP(w, req) + + assert.Equal(t, w.Code, http.StatusOK) +} + +func TestE2E(t *testing.T) { + // Initialize data layer + data, err := objects.NewDataLayer([]string{""}, "0.0.0-test", getFakeHelmConfig) + assert.NilError(t, err) + + // Create a new router with the function + abortWeb := func() {} + newRouter := NewRouter(abortWeb, data, false) + + // initially, we don't have any releases + w := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/api/helm/releases", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + assert.Equal(t, w.Body.String(), "[]") + + // initially, we don't have any repositories + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/repositories", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + assert.Equal(t, w.Body.String(), "[]") + + // then we add one repository + w = httptest.NewRecorder() + form := url.Values{} + form.Add("name", "komodorio") + form.Add("url", "https://helm-charts.komodor.io") + req, err = http.NewRequest("POST", "/api/helm/repositories", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusNoContent) + assert.Equal(t, w.Body.String(), "") + + // now, we have one repo + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/repositories", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + assert.Equal(t, w.Body.String(), `[ + { + "name": "komodorio", + "url": "https://helm-charts.komodor.io" + } +]`) + + // what's the latest version of that chart + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/repositories/latestver?name=helm-dashboard", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + + // generate template for potential release + w = httptest.NewRecorder() + form = url.Values{} + form.Add("preview", "true") + form.Add("name", "release1") + form.Add("chart", "komodorio/helm-dashboard") + req, err = http.NewRequest("POST", "/api/helm/releases/test1", strings.NewReader(form.Encode())) + assert.NilError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + + // install the release + w = httptest.NewRecorder() + form = url.Values{} + form.Add("name", "release1") + form.Add("chart", "komodorio/helm-dashboard") + req, err = http.NewRequest("POST", "/api/helm/releases/test1", strings.NewReader(form.Encode())) + assert.NilError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusAccepted) + + // get list of releases + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/releases", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + t.Logf("Release: %s", w.Body.String()) + //assert.Equal(t, w.Body.String(), "[]") + + // upgrade/reconfigure release + w = httptest.NewRecorder() + form = url.Values{} + form.Add("chart", "komodorio/helm-dashboard") + form.Add("values", "dashboard:\n allowWriteActions: true\n") + req, err = http.NewRequest("POST", "/api/helm/releases/test1/release1", strings.NewReader(form.Encode())) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + assert.Equal(t, w.Code, http.StatusAccepted) + + // get history of revisions for release + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/releases/test1/release1/history", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + t.Logf("Revs: %s", w.Body.String()) + //assert.Equal(t, w.Body.String(), "[]") + + // get values for revision + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/releases/test1/release1/values?revision=2&userDefined=true", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + //assert.Equal(t, w.Body.String(), "[]") + + // rollback + w = httptest.NewRecorder() + form = url.Values{} + form.Add("revision", "1") + req, err = http.NewRequest("POST", "/api/helm/releases/test1/release1/rollback", strings.NewReader(form.Encode())) + assert.NilError(t, err) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusAccepted) + + // get manifest diff for release + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/releases/test1/release1/manifests?revision=1&revisionDiff=2", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + //assert.Equal(t, w.Body.String(), "[]") + + // delete repo + w = httptest.NewRecorder() + req, err = http.NewRequest("DELETE", "/api/helm/repositories/komodorio", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusNoContent) + + // reconfigure release without repo connection + w = httptest.NewRecorder() + form = url.Values{} + form.Add("chart", "komodorio/helm-dashboard") + form.Add("values", "dashboard:\n allowWriteActions: false\n") + req, err = http.NewRequest("POST", "/api/helm/releases/test1/release1", strings.NewReader(form.Encode())) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + assert.Equal(t, w.Code, http.StatusAccepted) + t.Logf("Upgraded: %s", w.Body.String()) + + // uninstall + w = httptest.NewRecorder() + req, err = http.NewRequest("DELETE", "/api/helm/releases/test1/release1", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusAccepted) + + // check we don't have releases again + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/api/helm/releases", nil) + assert.NilError(t, err) + newRouter.ServeHTTP(w, req) + assert.Equal(t, w.Code, http.StatusOK) + assert.Equal(t, w.Body.String(), "[]") +} + +func getFakeHelmConfig(settings *cli.EnvSettings, _ string) (*action.Configuration, error) { + settings.RepositoryConfig = repoFile + + registryClient, err := registry.NewClient() + if err != nil { + return nil, err + } + + return &action.Configuration{ + Releases: inMemStorage, + KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: os.Stderr}}, + Capabilities: chartutil.DefaultCapabilities, + RegistryClient: registryClient, + Log: log.Infof, + }, nil +} diff --git a/pkg/dashboard/handlers/common.go b/pkg/dashboard/handlers/common.go new file mode 100644 index 0000000..d1cb0f3 --- /dev/null +++ b/pkg/dashboard/handlers/common.go @@ -0,0 +1,31 @@ +package handlers + +import ( + "github.com/gin-gonic/gin" + "github.com/joomcode/errorx" + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" + "net/http" +) + +const APP = "app" + +type Contexted struct { + Data *objects.DataLayer +} + +func (h *Contexted) GetApp(c *gin.Context) *objects.Application { + var app *objects.Application + if a, ok := c.Get(APP); ok { + app = a.(*objects.Application) + } else { + err := errorx.IllegalState.New("No application context found") + _ = c.AbortWithError(http.StatusBadRequest, err) + return nil + } + + return app +} + +func (h *Contexted) EnableClientCache(c *gin.Context) { + c.Header("Cache-Control", "max-age=43200") +} diff --git a/pkg/dashboard/handlers/helmHandlers.go b/pkg/dashboard/handlers/helmHandlers.go index 4a2c209..54325a3 100644 --- a/pkg/dashboard/handlers/helmHandlers.go +++ b/pkg/dashboard/handlers/helmHandlers.go @@ -2,35 +2,72 @@ package handlers import ( "errors" + "fmt" + "github.com/hexops/gotextdiff" + "github.com/hexops/gotextdiff/myers" + "github.com/hexops/gotextdiff/span" + "github.com/joomcode/errorx" + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" + "github.com/rogpeppe/go-internal/semver" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/repo" + helmtime "helm.sh/helm/v3/pkg/time" "net/http" + "sort" "strconv" - "strings" "github.com/gin-gonic/gin" - "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" ) type HelmHandler struct { - Data *subproc.DataLayer + *Contexted } -func (h *HelmHandler) GetCharts(c *gin.Context) { - res, err := h.Data.ListInstalled() +func (h *HelmHandler) getRelease(c *gin.Context) *objects.Release { + app := h.GetApp(c) + if app == nil { + return nil + } + + rel, err := app.Releases.ByName(c.Param("ns"), c.Param("name")) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return nil + } + return rel +} + +func (h *HelmHandler) GetReleases(c *gin.Context) { + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + rels, err := app.Releases.List() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + + res := []*ReleaseElement{} + for _, r := range rels { + res = append(res, HReleaseToJSON(r.Orig)) + } + c.IndentedJSON(http.StatusOK, res) } func (h *HelmHandler) Uninstall(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return + rel := h.getRelease(c) + if rel == nil { + return // error state is set inside } - err = h.Data.ReleaseUninstall(qp.Namespace, qp.Name) + + err := rel.Uninstall() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -39,13 +76,18 @@ func (h *HelmHandler) Uninstall(c *gin.Context) { } func (h *HelmHandler) Rollback(c *gin.Context) { - qp, err := utils.GetQueryProps(c, true) + rel := h.getRelease(c) + if rel == nil { + return // error state is set inside + } + + revn, err := strconv.Atoi(c.PostForm("revision")) if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) + _ = c.AbortWithError(http.StatusInternalServerError, err) return } - err = h.Data.Rollback(qp.Namespace, qp.Name, qp.Revision) + err = rel.Rollback(revn) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -54,73 +96,178 @@ func (h *HelmHandler) Rollback(c *gin.Context) { } func (h *HelmHandler) History(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return + rel := h.getRelease(c) + if rel == nil { + return // error state is set inside } - res, err := h.Data.ReleaseHistory(qp.Namespace, qp.Name) + revs, err := rel.History() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + + res := []*HistoryElement{} + for _, r := range revs { + res = append(res, HReleaseToHistElem(r.Orig)) + } + + sort.Slice(res, func(i, j int) bool { + return res[i].Revision < res[j].Revision + }) + c.IndentedJSON(http.StatusOK, res) } func (h *HelmHandler) Resources(c *gin.Context) { - qp, err := utils.GetQueryProps(c, true) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return + h.EnableClientCache(c) + + rel := h.getRelease(c) + if rel == nil { + return // error state is set inside } - res, err := h.Data.RevisionManifestsParsed(qp.Namespace, qp.Name, qp.Revision) + res, err := objects.ParseManifests(rel.Orig.Manifest) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + c.IndentedJSON(http.StatusOK, res) } -func (h *HelmHandler) RepoSearch(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) +func (h *HelmHandler) RepoVersions(c *gin.Context) { + qp, err := utils.GetQueryProps(c) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return } - res, err := h.Data.ChartRepoVersions(qp.Name) + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + repos, err := app.Repositories.Containing(qp.Name) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + + res := []*RepoChartElement{} + for _, r := range repos { + res = append(res, &RepoChartElement{ + Name: r.Name, + Version: r.Version, + AppVersion: r.AppVersion, + Description: r.Description, + Repository: r.Annotations[objects.AnnRepo], + }) + } + c.IndentedJSON(http.StatusOK, res) } +func (h *HelmHandler) RepoLatestVer(c *gin.Context) { + qp, err := utils.GetQueryProps(c) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + rep, err := app.Repositories.Containing(qp.Name) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + res := []*RepoChartElement{} + for _, r := range rep { + res = append(res, &RepoChartElement{ + Name: r.Name, + Version: r.Version, + AppVersion: r.AppVersion, + Description: r.Description, + Repository: r.Annotations[objects.AnnRepo], + }) + } + + sort.Slice(res, func(i, j int) bool { + return semver.Compare(res[i].Version, res[j].Version) > 0 + }) + + if len(res) > 0 { + c.IndentedJSON(http.StatusOK, res[:1]) + } else { + c.Status(http.StatusNoContent) + } +} + func (h *HelmHandler) RepoCharts(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return + app := h.GetApp(c) + if app == nil { + return // sets error inside } - res, err := h.Data.ChartRepoCharts(qp.Name) + rep, err := app.Repositories.Get(c.Param("name")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } - c.IndentedJSON(http.StatusOK, res) -} -func (h *HelmHandler) RepoUpdate(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) + charts, err := rep.Charts() if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) + _ = c.AbortWithError(http.StatusInternalServerError, err) return } - err = h.Data.ChartRepoUpdate(qp.Name) + installed, err := app.Releases.List() + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + // TODO: enrich with installed + enrichRepoChartsWithInstalled(charts, installed) + + sort.Slice(charts, func(i, j int) bool { + return charts[i].Name < charts[j].Name + }) + + c.IndentedJSON(http.StatusOK, charts) +} + +func enrichRepoChartsWithInstalled(charts []*repo.ChartVersion, installed []*objects.Release) { + for _, rchart := range charts { + for _, rel := range installed { + if rchart.Metadata.Name == rel.Orig.Chart.Name() { + log.Debugf("Matched") // TODO: restore implementation + // TODO: there can be more than one + //rchart.InstalledNamespace = rel.Orig.Namespace + //rchart.InstalledName = rel.Orig.Name + } + } + } +} + +func (h *HelmHandler) RepoUpdate(c *gin.Context) { + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + rep, err := app.Repositories.Get(c.Param("name")) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + err = rep.Update() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -128,80 +275,125 @@ func (h *HelmHandler) RepoUpdate(c *gin.Context) { c.Status(http.StatusNoContent) } -func (h *HelmHandler) Show(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return +func (h *HelmHandler) Install(c *gin.Context) { + app := h.GetApp(c) + if app == nil { + return // sets error inside } - res, err := h.Data.ShowChart(qp.Name) + values := map[string]interface{}{} + err := yaml.Unmarshal([]byte(c.PostForm("values")), &values) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } - c.IndentedJSON(http.StatusOK, res) -} - -func (h *HelmHandler) Install(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return + justTemplate := c.PostForm("preview") == "true" + ns := c.Param("ns") + if ns == "[empty]" { + ns = "" } - - justTemplate := c.Query("flag") != "true" - isInitial := c.Query("initial") != "true" - out, err := h.Data.ChartInstall(qp.Namespace, qp.Name, c.Query("chart"), c.Query("version"), justTemplate, c.PostForm("values"), isInitial) + rel, err := app.Releases.Install(ns, c.PostForm("name"), c.PostForm("chart"), c.PostForm("version"), justTemplate, values) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } if justTemplate { - manifests := "" - if isInitial { - manifests, err = h.Data.RevisionManifests(qp.Namespace, qp.Name, 0, false) - if err != nil { - _ = c.AbortWithError(http.StatusInternalServerError, err) - return - } - } - out = subproc.GetDiff(strings.TrimSpace(manifests), out, "current.yaml", "upgraded.yaml") + c.IndentedJSON(http.StatusOK, rel) } else { - c.Header("Content-Type", "application/json") + c.IndentedJSON(http.StatusAccepted, rel) } - - c.String(http.StatusAccepted, out) } -func (h *HelmHandler) Tests(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return +func (h *HelmHandler) Upgrade(c *gin.Context) { + app := h.GetApp(c) + if app == nil { + return // sets error inside } - out, err := h.Data.RunTests(qp.Namespace, qp.Name) + existing, err := app.Releases.ByName(c.Param("ns"), c.Param("name")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + values := map[string]interface{}{} + err = yaml.Unmarshal([]byte(c.PostForm("values")), &values) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + justTemplate := c.PostForm("preview") == "true" + rel, err := existing.Upgrade(c.PostForm("chart"), c.PostForm("version"), justTemplate, values) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if justTemplate { + c.IndentedJSON(http.StatusOK, rel) + } else { + c.IndentedJSON(http.StatusAccepted, rel) + } +} + +func (h *HelmHandler) RunTests(c *gin.Context) { + rel := h.getRelease(c) + if rel == nil { + return // error state is set inside + } + + out, err := rel.RunTests() + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } c.String(http.StatusOK, out) } func (h *HelmHandler) GetInfoSection(c *gin.Context) { - qp, err := utils.GetQueryProps(c, true) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) + if c.Query("revision") != "" { // don't cache if latest is requested + h.EnableClientCache(c) + } + + rel := h.getRelease(c) + if rel == nil { + return // error state is set inside + } + + revn, err := strconv.Atoi(c.Query("revision")) + if c.Query("revision") != "" && err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) return } - flag := c.Query("flag") == "true" - rDiff := c.Query("revisionDiff") - res, err := handleGetSection(h.Data, c.Param("section"), rDiff, qp, flag) + rev, err := rel.GetRev(revn) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + var revDiff *objects.Release + revS := c.Query("revisionDiff") + if revS != "" { + revN, err := strconv.Atoi(revS) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + revDiff, err = rel.GetRev(revN) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + } + + flag := c.Query("userDefined") == "true" + + res, err := h.handleGetSection(rev, c.Param("section"), revDiff, flag) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -210,25 +402,53 @@ func (h *HelmHandler) GetInfoSection(c *gin.Context) { } func (h *HelmHandler) RepoValues(c *gin.Context) { - out, err := h.Data.ShowValues(c.Query("chart"), c.Query("version")) + h.EnableClientCache(c) + + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + out, err := app.Repositories.GetChartValues(c.Query("chart"), c.Query("version")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + c.String(http.StatusOK, out) } func (h *HelmHandler) RepoList(c *gin.Context) { - out, err := h.Data.ChartRepoList() + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + repos, err := app.Repositories.List() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + + out := []RepositoryElement{} + for _, r := range repos { + out = append(out, RepositoryElement{ + Name: r.Orig.Name, + URL: r.Orig.URL, + }) + } + c.IndentedJSON(http.StatusOK, out) } func (h *HelmHandler) RepoAdd(c *gin.Context) { - _, err := h.Data.ChartRepoAdd(c.PostForm("name"), c.PostForm("url")) + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + // TODO: more repo options to accept + err := app.Repositories.Add(c.PostForm("name"), c.PostForm("url")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -237,13 +457,12 @@ func (h *HelmHandler) RepoAdd(c *gin.Context) { } func (h *HelmHandler) RepoDelete(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return + app := h.GetApp(c) + if app == nil { + return // sets error inside } - _, err = h.Data.ChartRepoDelete(qp.Name) + err := app.Repositories.Delete(c.Param("name")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -251,11 +470,30 @@ func (h *HelmHandler) RepoDelete(c *gin.Context) { c.Status(http.StatusNoContent) } -func handleGetSection(data *subproc.DataLayer, section string, rDiff string, qp *utils.QueryProps, flag bool) (string, error) { - sections := map[string]subproc.SectionFn{ - "manifests": data.RevisionManifests, - "values": data.RevisionValues, - "notes": data.RevisionNotes, +func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDiff *objects.Release, flag bool) (string, error) { + sections := map[string]objects.SectionFn{ + "manifests": func(qp *release.Release, b bool) (string, error) { return qp.Manifest, nil }, + "notes": func(qp *release.Release, b bool) (string, error) { return qp.Info.Notes, nil }, + "values": func(qp *release.Release, b bool) (string, error) { + allVals := qp.Config + + if !b { + merged, err := chartutil.CoalesceValues(qp.Chart, qp.Config) + if err != nil { + return "", errorx.Decorate(err, "failed to merge chart vals with user defined") + } + allVals = merged + } + + if len(allVals) > 0 { + data, err := yaml.Marshal(allVals) + if err != nil { + return "", errorx.Decorate(err, "failed to serialize values into YAML") + } + return string(data), nil + } + return "", nil + }, } functor, found := sections[section] @@ -263,27 +501,130 @@ func handleGetSection(data *subproc.DataLayer, section string, rDiff string, qp return "", errors.New("unsupported section: " + section) } - if rDiff != "" { - cRevDiff, err := strconv.Atoi(rDiff) - if err != nil { - return "", err - } - + if rDiff != nil { ext := ".yaml" if section == "notes" { ext = ".txt" } - res, err := subproc.RevisionDiff(functor, ext, qp.Namespace, qp.Name, cRevDiff, qp.Revision, flag) + res, err := RevisionDiff(functor, ext, rDiff.Orig, rel.Orig, flag) if err != nil { return "", err } return res, nil } - res, err := functor(qp.Namespace, qp.Name, qp.Revision, flag) + res, err := functor(rel.Orig, flag) if err != nil { - return "", err + return "", errorx.Decorate(err, "failed to get section info") } return res, nil } + +type RepoChartElement struct { + Name string `json:"name"` + Version string `json:"version"` + AppVersion string `json:"app_version"` + Description string `json:"description"` + + InstalledNamespace string `json:"installed_namespace"` // custom addition on top of Helm + InstalledName string `json:"installed_name"` // custom addition on top of Helm + Repository string `json:"repository"` +} + +func HReleaseToJSON(o *release.Release) *ReleaseElement { + return &ReleaseElement{ + Name: o.Name, + Namespace: o.Namespace, + Revision: strconv.Itoa(o.Version), + Updated: o.Info.LastDeployed, + Status: o.Info.Status, + Chart: fmt.Sprintf("%s-%s", o.Chart.Name(), o.Chart.Metadata.Version), + AppVersion: o.Chart.AppVersion(), + Icon: o.Chart.Metadata.Icon, + Description: o.Chart.Metadata.Description, + } +} + +type ReleaseElement struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Revision string `json:"revision"` + Updated helmtime.Time `json:"updated"` + Status release.Status `json:"status"` + Chart string `json:"chart"` + AppVersion string `json:"app_version"` + Icon string `json:"icon"` + Description string `json:"description"` +} + +type RepositoryElement struct { + Name string `json:"name"` + URL string `json:"url"` +} + +type HistoryElement struct { + Revision int `json:"revision"` + Updated helmtime.Time `json:"updated"` + Status release.Status `json:"status"` + Chart string `json:"chart"` + AppVersion string `json:"app_version"` + Description string `json:"description"` + + ChartName string `json:"chart_name"` // custom addition on top of Helm + ChartVer string `json:"chart_ver"` // custom addition on top of Helm + HasTests bool `json:"has_tests"` +} + +func HReleaseToHistElem(o *release.Release) *HistoryElement { + return &HistoryElement{ + Revision: o.Version, + Updated: o.Info.LastDeployed, + Status: o.Info.Status, + Chart: fmt.Sprintf("%s-%s", o.Chart.Name(), o.Chart.Metadata.Version), + AppVersion: o.Chart.AppVersion(), + Description: o.Info.Description, + ChartName: o.Chart.Name(), + ChartVer: o.Chart.Metadata.Version, + HasTests: releaseHasTests(o), + } +} + +func RevisionDiff(functor objects.SectionFn, ext string, revision1 *release.Release, revision2 *release.Release, flag bool) (string, error) { + if revision1 == nil || revision2 == nil { + log.Debugf("One of revisions is nil: %v %v", revision1, revision2) + return "", nil + } + + manifest1, err := functor(revision1, flag) + if err != nil { + return "", err + } + + manifest2, err := functor(revision2, flag) + if err != nil { + return "", err + } + + diff := GetDiff(manifest1, manifest2, strconv.Itoa(revision1.Version)+ext, strconv.Itoa(revision2.Version)+ext) + return diff, nil +} + +func GetDiff(text1 string, text2 string, name1 string, name2 string) string { + edits := myers.ComputeEdits(span.URIFromPath(""), text1, text2) + unified := gotextdiff.ToUnified(name1, name2, text1, edits) + diff := fmt.Sprint(unified) + log.Debugf("The diff is: %s", diff) + return diff +} + +func releaseHasTests(o *release.Release) bool { + for _, h := range o.Hooks { + for _, e := range h.Events { + if e == release.HookTest { + return true + } + } + } + return false +} diff --git a/pkg/dashboard/handlers/kubeHandlers.go b/pkg/dashboard/handlers/kubeHandlers.go index 918b7b8..60bc28e 100644 --- a/pkg/dashboard/handlers/kubeHandlers.go +++ b/pkg/dashboard/handlers/kubeHandlers.go @@ -1,20 +1,25 @@ package handlers import ( + "github.com/joomcode/errorx" + "k8s.io/apimachinery/pkg/api/errors" "net/http" "github.com/gin-gonic/gin" - "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" ) type KubeHandler struct { - Data *subproc.DataLayer + *Contexted } func (h *KubeHandler) GetContexts(c *gin.Context) { + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + res, err := h.Data.ListContexts() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) @@ -24,21 +29,33 @@ func (h *KubeHandler) GetContexts(c *gin.Context) { } func (h *KubeHandler) GetResourceInfo(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) + qp, err := utils.GetQueryProps(c) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return } - res, err := h.Data.GetResource(qp.Namespace, &v12.Carp{ - TypeMeta: v1.TypeMeta{Kind: c.Param("kind")}, - ObjectMeta: v1.ObjectMeta{Name: qp.Name}, - }) - if err != nil { + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + res, err := app.K8s.GetResourceInfo(c.Param("kind"), qp.Namespace, qp.Name) + if errors.IsNotFound(err) { + res = &v12.Carp{Status: v12.CarpStatus{Phase: "NotFound", Message: err.Error()}} + //_ = c.AbortWithError(http.StatusNotFound, err) + //return + } else if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } + EnhanceStatus(res) + + c.IndentedJSON(http.StatusOK, res) +} + +func EnhanceStatus(res *v12.Carp) { // custom logic to provide most meaningful status for the resource if res.Status.Phase == "Active" || res.Status.Phase == "Error" { _ = res.Name + "" @@ -52,18 +69,21 @@ func (h *KubeHandler) GetResourceInfo(c *gin.Context) { } else if res.Status.Phase == "" { res.Status.Phase = "Exists" } - - c.IndentedJSON(http.StatusOK, res) } func (h *KubeHandler) Describe(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) + qp, err := utils.GetQueryProps(c) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return } - res, err := h.Data.DescribeResource(qp.Namespace, c.Param("kind"), qp.Name) + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + res, err := app.K8s.DescribeResource(c.Param("kind"), qp.Namespace, qp.Name) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -73,11 +93,21 @@ func (h *KubeHandler) Describe(c *gin.Context) { } func (h *KubeHandler) GetNameSpaces(c *gin.Context) { - res, err := h.Data.GetNameSpaces() + if c.Param("kind") != "namespaces" { + _ = c.AbortWithError(http.StatusBadRequest, errorx.AssertionFailed.New("Only 'namespaces' kind is allowed for listing")) + return + } + + app := h.GetApp(c) + if app == nil { + return // sets error inside + } + + res, err := app.K8s.GetNameSpaces() if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return } - c.JSON(http.StatusOK, res) + c.IndentedJSON(http.StatusOK, res) } diff --git a/pkg/dashboard/handlers/scannerHandlers.go b/pkg/dashboard/handlers/scannerHandlers.go index c59654b..d37e214 100644 --- a/pkg/dashboard/handlers/scannerHandlers.go +++ b/pkg/dashboard/handlers/scannerHandlers.go @@ -8,7 +8,7 @@ import ( ) type ScannersHandler struct { - Data *subproc.DataLayer + *Contexted } func (h *ScannersHandler) List(c *gin.Context) { @@ -26,23 +26,10 @@ func (h *ScannersHandler) List(c *gin.Context) { c.IndentedJSON(http.StatusOK, res) } -func (h *ScannersHandler) ScanDraftManifest(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } - - reuseVals := c.Query("initial") != "true" - mnf, err := h.Data.ChartInstall(qp.Namespace, qp.Name, c.Query("chart"), c.Query("version"), true, c.PostForm("values"), reuseVals) - if err != nil { - _ = c.AbortWithError(http.StatusInternalServerError, err) - return - } - +func (h *ScannersHandler) ScanManifest(c *gin.Context) { reps := map[string]*subproc.ScanResults{} for _, scanner := range h.Data.Scanners { - sr, err := scanner.ScanManifests(mnf) + sr, err := scanner.ScanManifests(c.PostForm("manifest")) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -55,7 +42,7 @@ func (h *ScannersHandler) ScanDraftManifest(c *gin.Context) { } func (h *ScannersHandler) ScanResource(c *gin.Context) { - qp, err := utils.GetQueryProps(c, false) + qp, err := utils.GetQueryProps(c) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return diff --git a/pkg/dashboard/objects/app.go b/pkg/dashboard/objects/app.go new file mode 100644 index 0000000..edf3e2d --- /dev/null +++ b/pkg/dashboard/objects/app.go @@ -0,0 +1,49 @@ +package objects + +import ( + "github.com/joomcode/errorx" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + // Import to initialize client auth plugins. + // From https://github.com/kubernetes/client-go/issues/242 + _ "k8s.io/client-go/plugin/pkg/client/auth" +) + +type HelmConfigGetter = func(sett *cli.EnvSettings, ns string) (*action.Configuration, error) +type HelmNSConfigGetter = func(ns string) (*action.Configuration, error) + +type Application struct { + Settings *cli.EnvSettings + HelmConfig HelmNSConfigGetter + + K8s *K8s + + Releases *Releases + Repositories *Repositories +} + +func NewApplication(settings *cli.EnvSettings, helmConfig HelmNSConfigGetter, namespaces []string) (*Application, error) { + hc, err := helmConfig(settings.Namespace()) + if err != nil { + return nil, errorx.Decorate(err, "failed to get helm config for namespace '%s'", "") + } + + k8s, err := NewK8s(hc, namespaces) + if err != nil { + return nil, errorx.Decorate(err, "failed to get k8s client") + } + + return &Application{ + HelmConfig: helmConfig, + K8s: k8s, + Releases: &Releases{ + Namespaces: namespaces, + Settings: settings, + HelmConfig: helmConfig, + }, + Repositories: &Repositories{ + Settings: settings, + HelmConfig: hc, + }, + }, nil +} diff --git a/pkg/dashboard/subproc/cache.go b/pkg/dashboard/objects/cache.go similarity index 66% rename from pkg/dashboard/subproc/cache.go rename to pkg/dashboard/objects/cache.go index 8bc0069..afe4dd8 100644 --- a/pkg/dashboard/subproc/cache.go +++ b/pkg/dashboard/objects/cache.go @@ -1,4 +1,4 @@ -package subproc +package objects import ( "context" @@ -12,15 +12,6 @@ import ( type CacheKey = string -const CacheKeyRelList CacheKey = "installed-releases-list" -const CacheKeyShowChart CacheKey = "show-chart" -const CacheKeyRelHistory CacheKey = "release-history" -const CacheKeyRevManifests CacheKey = "rev-manifests" -const CacheKeyRevNotes CacheKey = "rev-notes" -const CacheKeyRevValues CacheKey = "rev-values" -const CacheKeyRepoChartValues CacheKey = "chart-values" -const CacheKeyAllRepos CacheKey = "all-repos" - type Cache struct { Marshaler *marshaler.Marshaler `json:"-"` HitCount int @@ -83,18 +74,3 @@ func (c *Cache) Clear() error { c.MissCount = 0 return c.Marshaler.Clear(context.Background()) } - -func cacheTagRelease(namespace string, name string) CacheKey { - return "release" + "\v" + namespace + "\v" + name -} -func cacheTagRepoVers(chartName string) CacheKey { - return "repo-versions" + "\v" + chartName -} - -func cacheTagRepoCharts(name string) CacheKey { - return "repo-charts" + "\v" + name -} - -func cacheTagRepoName(name string) CacheKey { - return "repo-name" + "\v" + name -} diff --git a/pkg/dashboard/objects/data.go b/pkg/dashboard/objects/data.go new file mode 100644 index 0000000..8f19cbf --- /dev/null +++ b/pkg/dashboard/objects/data.go @@ -0,0 +1,232 @@ +package objects + +import ( + "bytes" + "context" + "encoding/json" + "sync" + "time" + + "github.com/joomcode/errorx" + "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/release" + "io" + v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/client-go/tools/clientcmd" +) + +type DataLayer struct { + KubeContext string + Scanners []subproc.Scanner + StatusInfo *StatusInfo + Namespaces []string + Cache *Cache + + ConfGen HelmConfigGetter + appPerContext map[string]*Application + appPerContextMx *sync.Mutex +} + +type StatusInfo struct { + CurVer string + LatestVer string + Analytics bool + CacheHitRatio float64 + ClusterMode bool +} + +func NewDataLayer(ns []string, ver string, cg HelmConfigGetter) (*DataLayer, error) { + if cg == nil { + return nil, errors.New("HelmConfigGetter can't be nil") + } + + return &DataLayer{ + Namespaces: ns, + Cache: NewCache(), + StatusInfo: &StatusInfo{ + CurVer: ver, + Analytics: false, + }, + + ConfGen: cg, + appPerContext: map[string]*Application{}, + appPerContextMx: new(sync.Mutex), + }, nil +} + +func (d *DataLayer) ListContexts() ([]KubeContext, error) { + res := []KubeContext{} + + if d.StatusInfo.ClusterMode { + return res, nil + } + + cfg, err := clientcmd.NewDefaultPathOptions().GetStartingConfig() + if err != nil { + return nil, errorx.Decorate(err, "failed to get kubectl config") + } + + for name, ctx := range cfg.Contexts { + res = append(res, KubeContext{ + IsCurrent: cfg.CurrentContext == name, + Name: name, + Cluster: ctx.Cluster, + AuthInfo: ctx.AuthInfo, + Namespace: ctx.Namespace, + }) + } + + return res, nil +} + +func (d *DataLayer) GetStatus() *StatusInfo { + sum := float64(d.Cache.HitCount + d.Cache.MissCount) + if sum > 0 { + d.StatusInfo.CacheHitRatio = float64(d.Cache.HitCount) / sum + } else { + d.StatusInfo.CacheHitRatio = 0 + } + return d.StatusInfo +} + +type SectionFn = func(*release.Release, bool) (string, error) + +func ParseManifests(out string) ([]*v1.Carp, error) { + dec := yaml.NewDecoder(bytes.NewReader([]byte(out))) + + res := make([]*v1.Carp, 0) + var tmp interface{} + for { + err := dec.Decode(&tmp) + if err == io.EOF { + break + } + + if err != nil { + return nil, errorx.Decorate(err, "failed to parse manifest document #%d", len(res)+1) + } + + // k8s libs uses only JSON tags defined, say hello to https://github.com/go-yaml/yaml/issues/424 + // we can juggle it + jsoned, err := json.Marshal(tmp) + if err != nil { + return nil, err + } + + var doc v1.Carp + err = json.Unmarshal(jsoned, &doc) + if err != nil { + return nil, err + } + + if doc.Kind == "" { + log.Warnf("Manifest piece is not k8s resource: %s", jsoned) + continue + } + + res = append(res, &doc) + } + return res, nil +} + +func (d *DataLayer) SetContext(ctx string) error { + if d.KubeContext != ctx { + err := d.Cache.Clear() + if err != nil { + return errors.Wrap(err, "failed to set context") + } + } + + d.KubeContext = ctx + + return nil +} + +func (d *DataLayer) AppForCtx(ctx string) (*Application, error) { + d.appPerContextMx.Lock() + defer d.appPerContextMx.Unlock() + + app, ok := d.appPerContext[ctx] + if !ok { + settings := cli.New() + settings.KubeContext = ctx + + settings.SetNamespace(d.nsForCtx(ctx)) + + cfgGetter := func(ns string) (*action.Configuration, error) { + return d.ConfGen(settings, ns) + } + + a, err := NewApplication(settings, cfgGetter, d.Namespaces) + if err != nil { + return nil, errorx.Decorate(err, "Failed to create application for context '%s'", ctx) + } + + app = a + d.appPerContext[ctx] = app + } + return app, nil +} + +func (d *DataLayer) nsForCtx(ctx string) string { + lst, err := d.ListContexts() + if err != nil { + log.Debugf("Failed to get contexts for NS lookup: %+v", err) + } + for _, c := range lst { + if c.Name == ctx { + return c.Namespace + } + } + log.Debugf("Strange: no context found for '%s'", ctx) + return "" +} + +func (d *DataLayer) PeriodicTasks(ctx context.Context) { + if !d.StatusInfo.ClusterMode { // TODO: maybe have a separate flag for that? + log.Debugf("Not in cluster mode, not starting background tasks") + return + } + + // auto-update repos + go d.loopUpdateRepos(ctx, 10*time.Minute) // TODO: parameterize interval? + + // auto-scan +} + +func (d *DataLayer) loopUpdateRepos(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + for { + app, err := d.AppForCtx("") + if err != nil { + log.Warnf("Failed to get app object while in background repo update: %v", err) + break // no point in retrying + } else { + repos, err := app.Repositories.List() + if err != nil { + log.Warnf("Failed to get list of repos while in background update: %v", err) + } + + for _, repo := range repos { + err := repo.Update() + if err != nil { + log.Warnf("Failed to update repo %s: %v", repo.Orig.Name, err) + } + } + } + + select { + case <-ctx.Done(): + ticker.Stop() + return + case <-ticker.C: + continue + } + } + log.Debugf("Update repo loop done.") +} diff --git a/pkg/dashboard/objects/data_test.go b/pkg/dashboard/objects/data_test.go new file mode 100644 index 0000000..868534d --- /dev/null +++ b/pkg/dashboard/objects/data_test.go @@ -0,0 +1,58 @@ +package objects + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" +) + +func TestNewDataLayer(t *testing.T) { + testCases := []struct { + name string + namespaces []string + version string + helmConfig HelmConfigGetter + errorExpected bool + }{ + { + name: "should return error when helm config is nil", + namespaces: []string{"namespace1", "namespace2"}, + version: "1.0.0", + helmConfig: nil, + errorExpected: true, + }, + { + name: "should return data layer when all parameters are correct", + namespaces: []string{ + "namespace1", + "namespace2", + }, + version: "1.0.0", + helmConfig: func(sett *cli.EnvSettings, ns string) (*action.Configuration, error) { + return &action.Configuration{}, nil + }, + errorExpected: false, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + dl, err := NewDataLayer(tt.namespaces, tt.version, tt.helmConfig) + if tt.errorExpected { + assert.Error(t, err, "Expected error but got nil") + } else { + assert.Nil(t, err, "NewDataLayer returned an error: %v", err) + assert.NotNil(t, dl, "NewDataLayer returned nil") + assert.Equal(t, tt.namespaces, dl.Namespaces, "NewDataLayer returned incorrect namespaces: %v", dl.Namespaces) + assert.NotNil(t, dl.Cache, "NewDataLayer returned nil cache") + assert.Equal(t, tt.version, dl.StatusInfo.CurVer, "NewDataLayer returned incorrect version: %v", dl.StatusInfo.CurVer) + assert.False(t, dl.StatusInfo.Analytics, "NewDataLayer returned incorrect version: %v", dl.StatusInfo.CurVer) + assert.NotNil(t, dl.appPerContext, "NewDataLayer returned nil appPerContext") + assert.NotNil(t, dl.ConfGen, "NewDataLayer returned nil ConfGen") + + } + }) + } +} diff --git a/pkg/dashboard/objects/kubectl.go b/pkg/dashboard/objects/kubectl.go new file mode 100644 index 0000000..937e385 --- /dev/null +++ b/pkg/dashboard/objects/kubectl.go @@ -0,0 +1,198 @@ +package objects + +import ( + "context" + "encoding/json" + "github.com/joomcode/errorx" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/kube" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testapiv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + describecmd "k8s.io/kubectl/pkg/cmd/describe" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/describe" + "k8s.io/utils/strings/slices" + "sort" +) + +type KubeContext struct { + IsCurrent bool + Name string + Cluster string + AuthInfo string + Namespace string +} + +// maps action.RESTClientGetter into genericclioptions.RESTClientGetter +type cfgProxyObject struct { + Impl action.RESTClientGetter +} + +func (p *cfgProxyObject) ToRESTConfig() (*rest.Config, error) { + return p.Impl.ToRESTConfig() +} + +func (p *cfgProxyObject) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + return p.Impl.ToDiscoveryClient() +} + +func (p *cfgProxyObject) ToRESTMapper() (meta.RESTMapper, error) { + return p.Impl.ToRESTMapper() +} + +func (p *cfgProxyObject) ToRawKubeConfigLoader() clientcmd.ClientConfig { + panic("Not implemented, stub") +} + +type K8s struct { + Namespaces []string + Factory kube.Factory + RestClientGetter genericclioptions.RESTClientGetter +} + +func NewK8s(helmConfig *action.Configuration, namespaces []string) (*K8s, error) { + factory := cmdutil.NewFactory(&cfgProxyObject{Impl: helmConfig.RESTClientGetter}) + + return &K8s{ + Namespaces: namespaces, + Factory: factory, + RestClientGetter: factory, + }, nil +} + +func (k *K8s) GetNameSpaces() (res *corev1.NamespaceList, err error) { + clientset, err := k.Factory.KubernetesClientSet() + if err != nil { + return nil, errors.Wrap(err, "failed to get KubernetesClientSet") + } + + lst, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to get list of namespaces") + } + + if !slices.Contains(k.Namespaces, "") { + filtered := []corev1.Namespace{} + for _, ns := range lst.Items { + if slices.Contains(k.Namespaces, ns.Name) { + filtered = append(filtered, ns) + } + } + lst.Items = filtered + } + + return lst, nil +} + +func (k *K8s) DescribeResource(kind string, ns string, name string) (string, error) { + log.Debugf("Describing resource: %s %s in %s", kind, name, ns) + streams, _, out, errout := genericclioptions.NewTestIOStreams() + o := &describecmd.DescribeOptions{ + Describer: func(mapping *meta.RESTMapping) (describe.ResourceDescriber, error) { + return describe.DescriberFn(k.RestClientGetter, mapping) + }, + FilenameOptions: &resource.FilenameOptions{}, + DescriberSettings: &describe.DescriberSettings{ + ShowEvents: true, + ChunkSize: cmdutil.DefaultChunkSize, + }, + + IOStreams: streams, + + NewBuilder: k.Factory.NewBuilder, + } + + o.Namespace = ns + o.BuilderArgs = []string{kind, name} + + err := o.Run() + if err != nil { + return "", errorx.Decorate(err, "Failed to run describe command: %s", errout.String()) + } + + return out.String(), nil +} + +func (k *K8s) GetResource(kind string, namespace string, name string) (*runtime.Object, error) { + builder := k.Factory.NewBuilder() + resp := builder.Unstructured().NamespaceParam(namespace).Flatten().ResourceNames(kind, name).Do() + if resp.Err() != nil { + return nil, errorx.Decorate(resp.Err(), "failed to get k8s resource") + } + + obj, err := resp.Object() + if err != nil { + return nil, errorx.Decorate(err, "failed to get k8s resulting object") + } + return &obj, nil +} + +func (k *K8s) GetResourceInfo(kind string, namespace string, name string) (*testapiv1.Carp, error) { + obj, err := k.GetResource(kind, namespace, name) + if err != nil { + return nil, errorx.Decorate(err, "failed to get k8s object") + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, errorx.Decorate(err, "failed to marshal k8s object into JSON") + } + + res := new(testapiv1.Carp) + err = json.Unmarshal(data, &res) + if err != nil { + return nil, errorx.Decorate(err, "failed to decode k8s object from JSON") + } + + sort.Slice(res.Status.Conditions, func(i, j int) bool { + // some condition types always bubble up + if res.Status.Conditions[i].Type == "Available" { + return false + } + + if res.Status.Conditions[j].Type == "Available" { + return true + } + + t1 := res.Status.Conditions[i].LastTransitionTime + t2 := res.Status.Conditions[j].LastTransitionTime + return t1.Time.Before(t2.Time) + }) + + return res, nil +} + +func (k *K8s) GetResourceYAML(kind string, namespace string, name string) (string, error) { + obj, err := k.GetResource(kind, namespace, name) + if err != nil { + return "", errorx.Decorate(err, "failed to get k8s object") + } + + data, err := json.Marshal(obj) + if err != nil { + return "", errorx.Decorate(err, "failed to marshal k8s object into JSON") + } + + res := map[string]interface{}{} + err = json.Unmarshal(data, &res) + if err != nil { + return "", errorx.Decorate(err, "failed to decode k8s object from JSON") + } + + ydata, err := yaml.Marshal(res) + if err != nil { + return "", errorx.Decorate(err, "failed to marshal k8s object into JSON") + } + return string(ydata), nil +} diff --git a/pkg/dashboard/objects/releases.go b/pkg/dashboard/objects/releases.go new file mode 100644 index 0000000..a631236 --- /dev/null +++ b/pkg/dashboard/objects/releases.go @@ -0,0 +1,399 @@ +package objects + +import ( + "bytes" + "fmt" + "gopkg.in/yaml.v3" + "io/ioutil" + "os" + "path" + "sync" + + "github.com/joomcode/errorx" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/release" + v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" +) + +type Releases struct { + Namespaces []string + HelmConfig HelmNSConfigGetter + Settings *cli.EnvSettings + mx sync.Mutex +} + +func (a *Releases) List() ([]*Release, error) { + a.mx.Lock() + defer a.mx.Unlock() + + releases := []*Release{} + for _, ns := range a.Namespaces { + log.Debugf("Listing releases in namespace: %s", ns) + hc, err := a.HelmConfig(ns) + if err != nil { + return nil, errorx.Decorate(err, "failed to get helm config for namespace '%s'", "") + } + + client := action.NewList(hc) + client.All = true + client.AllNamespaces = true + client.Limit = 0 + rels, err := client.Run() + if err != nil { + return nil, errorx.Decorate(err, "failed to get list of releases") + } + for _, r := range rels { + releases = append(releases, &Release{HelmConfig: a.HelmConfig, Orig: r, Settings: a.Settings}) + } + } + return releases, nil +} + +func (a *Releases) ByName(namespace string, name string) (*Release, error) { + rels, err := a.List() + if err != nil { + return nil, errorx.Decorate(err, "failed to get list of releases") + } + + for _, r := range rels { + if r.Orig.Namespace == namespace && r.Orig.Name == name { + return r, nil + } + } + + return nil, errorx.DataUnavailable.New(fmt.Sprintf("release '%s' is not found in namespace '%s'", name, namespace)) +} + +func (a *Releases) Install(namespace string, name string, repoChart string, version string, justTemplate bool, values map[string]interface{}) (*release.Release, error) { + a.mx.Lock() + defer a.mx.Unlock() + + if namespace == "" { + namespace = a.Settings.Namespace() + } + + hc, err := a.HelmConfig(namespace) + if err != nil { + return nil, errorx.Decorate(err, "failed to get helm config for namespace '%s'", "") + } + + cmd := action.NewInstall(hc) + + cmd.ReleaseName = name + cmd.CreateNamespace = true + cmd.Namespace = namespace + cmd.Version = version + + cmd.DryRun = justTemplate + + chrt, err := locateChart(cmd.ChartPathOptions, repoChart, a.Settings) + if err != nil { + return nil, err + } + + res, err := cmd.Run(chrt, values) + if err != nil { + return nil, err + } + + if !justTemplate { + log.Infof("Installed new release: %s/%s", namespace, name) + } + + return res, nil +} + +func locateChart(pathOpts action.ChartPathOptions, chart string, settings *cli.EnvSettings) (*chart.Chart, error) { + // from cmd/helm/install.go and cmd/helm/upgrade.go + cp, err := pathOpts.LocateChart(chart, settings) + if err != nil { + return nil, err + } + + log.Debugf("Located chart %s: %s\n", chart, cp) + + p := getter.All(settings) + + // Check chart dependencies to make sure all are present in /charts + chartRequested, err := loader.Load(cp) + if err != nil { + return nil, err + } + + if err := checkIfInstallable(chartRequested); err != nil { + return nil, err + } + + if req := chartRequested.Metadata.Dependencies; req != nil { + // If CheckDependencies returns an error, we have unfulfilled dependencies. + // As of Helm 2.4.0, this is treated as a stopping condition: + // https://github.com/helm/helm/issues/2209 + if err := action.CheckDependencies(chartRequested, req); err != nil { + err = errorx.Decorate(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies") + if true { // client.DependencyUpdate + man := &downloader.Manager{ + Out: ioutil.Discard, + ChartPath: cp, + Keyring: pathOpts.Keyring, + SkipUpdate: false, + Getters: p, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + Debug: settings.Debug, + } + if err := man.Update(); err != nil { + return nil, err + } + // Reload the chart with the updated Chart.lock file. + if chartRequested, err = loader.Load(cp); err != nil { + return nil, errorx.Decorate(err, "failed reloading chart after repo update") + } + } else { + return nil, err + } + } + } + + return chartRequested, nil +} + +type Release struct { + Settings *cli.EnvSettings + HelmConfig HelmNSConfigGetter + Orig *release.Release + revisions []*Release + mx sync.Mutex + restoredChartPath string +} + +func (r *Release) History() ([]*Release, error) { + r.mx.Lock() + defer r.mx.Unlock() + + hc, err := r.HelmConfig(r.Orig.Namespace) + if err != nil { + return nil, errorx.Decorate(err, "failed to get helm config for namespace '%s'", "") + } + + client := action.NewHistory(hc) + revs, err := client.Run(r.Orig.Name) + if err != nil { + return nil, errorx.Decorate(err, "failed to get revisions of release") + } + + r.revisions = []*Release{} + for _, rev := range revs { + r.revisions = append(r.revisions, &Release{HelmConfig: r.HelmConfig, Orig: rev, Settings: r.Settings}) + } + + return r.revisions, nil +} + +func (r *Release) Uninstall() error { + r.mx.Lock() + defer r.mx.Unlock() + + hc, err := r.HelmConfig(r.Orig.Namespace) + if err != nil { + return errorx.Decorate(err, "failed to get helm config for namespace '%s'", "") + } + + client := action.NewUninstall(hc) + _, err = client.Run(r.Orig.Name) + if err != nil { + return errorx.Decorate(err, "failed to uninstall release") + } + return nil +} + +func (r *Release) Rollback(toRevision int) error { + r.mx.Lock() + defer r.mx.Unlock() + + hc, err := r.HelmConfig(r.Orig.Namespace) + if err != nil { + return errorx.Decorate(err, "failed to get helm config for namespace '%s'", "") + } + + client := action.NewRollback(hc) + client.Version = toRevision + err = client.Run(r.Orig.Name) + if err != nil { + return errorx.Decorate(err, "failed to rollback the release") + } + log.Infof("Rolled back %s/%s to %d=>%d", r.Orig.Namespace, r.Orig.Name, r.Orig.Version, toRevision) + return nil +} + +func (r *Release) RunTests() (string, error) { + r.mx.Lock() + defer r.mx.Unlock() + + hc, err := r.HelmConfig(r.Orig.Namespace) + if err != nil { + return "", errorx.Decorate(err, "failed to get helm config for namespace '%s'", r.Orig.Namespace) + } + + client := action.NewReleaseTesting(hc) + client.Namespace = r.Orig.Namespace + + rel, err := client.Run(r.Orig.Name) + if err != nil { + return "", errorx.Decorate(err, "failed to execute 'helm test' for release '%s'", r.Orig.Name) + } + + var buf bytes.Buffer + if err := client.GetPodLogs(&buf, rel); err != nil { + return "", errorx.Decorate(err, "failed to fetch logs for 'helm test' command") + } + return buf.String(), nil +} + +func (r *Release) ParsedManifests() ([]*v1.Carp, error) { + carps, err := ParseManifests(r.Orig.Manifest) + if err != nil { + return nil, err + } + + for _, carp := range carps { + if carp.Namespace == "" { + carp.Namespace = r.Orig.Namespace + } + } + + return carps, err +} + +func (r *Release) GetRev(revNo int) (*Release, error) { + if revNo == 0 { + revNo = r.Orig.Version + } + + hist, err := r.History() + if err != nil { + return nil, errorx.Decorate(err, "failed to get history") + } + + for _, rev := range hist { + if rev.Orig.Version == revNo { + return rev, nil + } + } + + return nil, errorx.InternalError.New("No revision found for number %d", revNo) +} + +func (r *Release) Upgrade(repoChart string, version string, justTemplate bool, values map[string]interface{}) (*release.Release, error) { + r.mx.Lock() + defer r.mx.Unlock() + + // if repo chart is not passed, let's try to restore it from secret + if repoChart == "" { + var err error + repoChart, err = r.restoreChart() + if err != nil { + return nil, errorx.Decorate(err, "failed to revive chart for release") + } + } + + ns := r.Settings.Namespace() + if r.Orig != nil { + ns = r.Orig.Namespace + } + + hc, err := r.HelmConfig(ns) + if err != nil { + return nil, errorx.Decorate(err, "failed to get helm config for namespace '%s'", ns) + } + + cmd := action.NewUpgrade(hc) + + cmd.Namespace = r.Settings.Namespace() + cmd.Version = version + + cmd.DryRun = justTemplate + + chrt, err := locateChart(cmd.ChartPathOptions, repoChart, r.Settings) + if err != nil { + return nil, err + } + + res, err := cmd.Run(r.Orig.Name, chrt, values) + if err != nil { + return nil, err + } + + if !justTemplate { + log.Infof("Upgraded release: %s/%s#%d", res.Namespace, res.Name, res.Version) + } + + return res, nil +} + +func (r *Release) restoreChart() (string, error) { + if r.restoredChartPath != "" { + return r.restoredChartPath, nil + } + + // we're unlikely to have the original chart, let's try the cheesy thing... + + log.Infof("Attempting to restore the chart for %s", r.Orig.Name) + dir, err := ioutil.TempDir("", "khd-*") + if err != nil { + return "", errorx.Decorate(err, "failed to get temporary directory") + } + + //restore Chart.yaml + cdata, err := yaml.Marshal(r.Orig.Chart.Metadata) + if err != nil { + return "", errorx.Decorate(err, "failed to restore Chart.yaml") + } + err = ioutil.WriteFile(path.Join(dir, "Chart.yaml"), cdata, 0644) + if err != nil { + return "", errorx.Decorate(err, "failed to write file Chart.yaml") + } + + //restore known values + vdata, err := yaml.Marshal(r.Orig.Chart.Values) + if err != nil { + return "", errorx.Decorate(err, "failed to restore values.yaml") + } + err = ioutil.WriteFile(path.Join(dir, "values.yaml"), vdata, 0644) + if err != nil { + return "", errorx.Decorate(err, "failed to write file values.yaml") + } + + // if possible, overwrite files with better alternatives + for _, f := range append(r.Orig.Chart.Raw, r.Orig.Chart.Templates...) { + fname := path.Join(dir, f.Name) + log.Debugf("Restoring file: %s", fname) + err := os.MkdirAll(path.Dir(fname), 0755) + if err != nil { + return "", errorx.Decorate(err, "failed to create directory for file: %s", fname) + } + + err = ioutil.WriteFile(fname, f.Data, 0644) + if err != nil { + return "", errorx.Decorate(err, "failed to write file to restore chart: %s", fname) + } + } + + r.restoredChartPath = dir + + return dir, nil +} + +func checkIfInstallable(ch *chart.Chart) error { + switch ch.Metadata.Type { + case "", "application": + return nil + } + return errors.Errorf("%s charts are not installable", ch.Metadata.Type) +} diff --git a/pkg/dashboard/objects/releases_test.go b/pkg/dashboard/objects/releases_test.go new file mode 100644 index 0000000..29da545 --- /dev/null +++ b/pkg/dashboard/objects/releases_test.go @@ -0,0 +1,80 @@ +package objects + +import ( + "sync" + "testing" + + "gotest.tools/v3/assert" + "helm.sh/helm/v3/pkg/action" + kubefake "helm.sh/helm/v3/pkg/kube/fake" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage" + "helm.sh/helm/v3/pkg/storage/driver" +) + +var ( + fakeKubeClient *kubefake.PrintingKubeClient + fakeStorage *storage.Storage +) + +func fakeHelmNSConfigGetter(ns string) (*action.Configuration, error) { + return &action.Configuration{ + KubeClient: fakeKubeClient, + Releases: fakeStorage, + }, nil +} + +func TestListReleases(t *testing.T) { + fakeStorage = storage.Init(driver.NewMemory()) + err := fakeStorage.Create(&release.Release{ + Name: "release1", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + }) + assert.NilError(t, err) + err = fakeStorage.Create(&release.Release{ + Name: "release2", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + }) + assert.NilError(t, err) + err = fakeStorage.Create(&release.Release{ + Name: "release3", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + }) + assert.NilError(t, err) + err = fakeStorage.Create(&release.Release{ + Name: "release4", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + }) + assert.NilError(t, err) + err = fakeStorage.Create(&release.Release{ + Name: "release5", + Info: &release.Info{ + Status: release.StatusDeployed, + }, + }) + assert.NilError(t, err) + + releases := &Releases{ + Namespaces: []string{"testNamespace"}, + HelmConfig: fakeHelmNSConfigGetter, + mx: sync.Mutex{}, + } + + res, err := releases.List() + assert.NilError(t, err) + + assert.Equal(t, len(res), 5) + assert.Equal(t, res[0].Orig.Name, "release1") + assert.Equal(t, res[1].Orig.Name, "release2") + assert.Equal(t, res[2].Orig.Name, "release3") + assert.Equal(t, res[3].Orig.Name, "release4") + assert.Equal(t, res[4].Orig.Name, "release5") +} diff --git a/pkg/dashboard/objects/repos.go b/pkg/dashboard/objects/repos.go new file mode 100644 index 0000000..f25394e --- /dev/null +++ b/pkg/dashboard/objects/repos.go @@ -0,0 +1,312 @@ +package objects + +import ( + "github.com/joomcode/errorx" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v3/pkg/repo" + "os" + "path/filepath" + "strings" + "sync" +) + +const AnnRepo = "helm-dashboard/repository-name" + +type Repositories struct { + Settings *cli.EnvSettings + HelmConfig *action.Configuration + mx sync.Mutex +} + +func (r *Repositories) Load() (*repo.File, error) { + r.mx.Lock() + defer r.mx.Unlock() + + // copied from cmd/helm/repo_list.go + f, err := repo.LoadFile(r.Settings.RepositoryConfig) + if err != nil && !isNotExist(err) { + return nil, errorx.Decorate(err, "failed to load repository list") + } + return f, nil +} + +func (r *Repositories) List() ([]*Repository, error) { + f, err := r.Load() + if err != nil { + return nil, errorx.Decorate(err, "failed to load repo information") + } + + res := []*Repository{} + for _, item := range f.Repositories { + res = append(res, &Repository{ + Settings: r.Settings, + Orig: item, + }) + } + + return res, nil +} + +func (r *Repositories) Add(name string, url string) error { + if name == "" || url == "" { + return errors.New("Name and URL are required parameters to add the repository") + } + + // copied from cmd/helm/repo_add.go + repoFile := r.Settings.RepositoryConfig + + // Ensure the file directory exists as it is required for file locking + err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm) + if err != nil && !os.IsExist(err) { + return err + } + + f, err := r.Load() + if err != nil { + return errorx.Decorate(err, "Failed to load repo config") + } + + r.mx.Lock() + defer r.mx.Unlock() + + c := repo.Entry{ + Name: name, + URL: url, + //Username: o.username, + //Password: o.password, + //PassCredentialsAll: o.passCredentialsAll, + //CertFile: o.certFile, + //KeyFile: o.keyFile, + //CAFile: o.caFile, + //InsecureSkipTLSverify: o.insecureSkipTLSverify, + } + + // Check if the repo name is legal + if strings.Contains(c.Name, "/") { + return errors.Errorf("repository name (%s) contains '/', please specify a different name without '/'", c.Name) + } + + rep, err := repo.NewChartRepository(&c, getter.All(r.Settings)) + if err != nil { + return err + } + + if _, err := rep.DownloadIndexFile(); err != nil { + return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url) + } + + f.Update(&c) + + if err := f.WriteFile(repoFile, 0644); err != nil { + return err + } + return nil +} + +func (r *Repositories) Delete(name string) error { + f, err := r.Load() + if err != nil { + return errorx.Decorate(err, "failed to load repo information") + } + + r.mx.Lock() + defer r.mx.Unlock() + + // copied from cmd/helm/repo_remove.go + if !f.Remove(name) { + return errors.Errorf("no repo named %q found", name) + } + if err := f.WriteFile(r.Settings.RepositoryConfig, 0644); err != nil { + return err + } + + if err := removeRepoCache(r.Settings.RepositoryCache, name); err != nil { + return err + } + return nil +} + +func (r *Repositories) Get(name string) (*Repository, error) { + f, err := r.Load() + if err != nil { + return nil, errorx.Decorate(err, "failed to load repo information") + } + + for _, entry := range f.Repositories { + if entry.Name == name { + return &Repository{ + Settings: r.Settings, + Orig: entry, + }, nil + } + } + + return nil, errorx.DataUnavailable.New("Could not find reposiroty '%s'", name) +} + +func (r *Repositories) Containing(name string) (repo.ChartVersions, error) { + list, err := r.List() + if err != nil { + return nil, errorx.Decorate(err, "failed to get list of repos") + } + + res := repo.ChartVersions{} + for _, rep := range list { + vers, err := rep.ByName(name) + if err != nil { + log.Warnf("Failed to get data from repo '%s', updating it might help", rep.Orig.Name) + log.Debugf("The error was: %v", err) + continue + } + + for _, v := range vers { + // just using annotations here to attach a bit of information to the object + // it has nothing to do with k8s annotations and should not get into manifests + if v.Annotations == nil { + v.Annotations = map[string]string{} + } + + v.Annotations[AnnRepo] = rep.Orig.Name + } + + res = append(res, vers...) // TODO filter dev versions here, relates to #139 + } + return res, nil +} + +func (r *Repositories) GetChart(chart string, ver string) (*chart.Chart, error) { + // TODO: unused method? + client := action.NewShowWithConfig(action.ShowAll, r.HelmConfig) + client.Version = ver + + cp, err := client.ChartPathOptions.LocateChart(chart, r.Settings) + if err != nil { + return nil, errorx.Decorate(err, "failed to locate chart '%s'", chart) + } + + chrt, err := loader.Load(cp) + if err != nil { + return nil, errorx.Decorate(err, "failed to load chart from '%s'", cp) + } + + return chrt, nil +} + +func (r *Repositories) GetChartValues(chart string, ver string) (string, error) { + // comes from cmd/helm/show.go + client := action.NewShowWithConfig(action.ShowValues, r.HelmConfig) + client.Version = ver + + cp, err := client.ChartPathOptions.LocateChart(chart, r.Settings) + if err != nil { + return "", err + } + + out, err := client.Run(cp) + if err != nil { + return "", errorx.Decorate(err, "failed to get values for chart '%s'", chart) + } + return out, nil +} + +type Repository struct { + Settings *cli.EnvSettings + Orig *repo.Entry + mx sync.Mutex +} + +func (r *Repository) indexFileName() string { + return filepath.Join(r.Settings.RepositoryCache, helmpath.CacheIndexFile(r.Orig.Name)) +} + +func (r *Repository) getIndex() (*repo.IndexFile, error) { + r.mx.Lock() + defer r.mx.Unlock() + + f := r.indexFileName() + ind, err := repo.LoadIndexFile(f) + if err != nil { + return nil, errorx.Decorate(err, "Repo index is corrupt or missing. Try updating repo") + } + + ind.SortEntries() + return ind, nil +} + +func (r *Repository) Charts() ([]*repo.ChartVersion, error) { + ind, err := r.getIndex() + if err != nil { + return nil, errorx.Decorate(err, "failed to get repo index") + } + + res := []*repo.ChartVersion{} + for _, v := range ind.Entries { + if len(v) > 0 { // TODO filter dev versions here, relates to #139 + res = append(res, v[0]) + } + } + + return res, nil +} + +func (r *Repository) ByName(name string) (repo.ChartVersions, error) { + ind, err := r.getIndex() + if err != nil { + return nil, errorx.Decorate(err, "failed to get repo index") + } + + nx, ok := ind.Entries[name] + if ok { + return nx, nil + } + return repo.ChartVersions{}, nil +} + +func (r *Repository) Update() error { + r.mx.Lock() + defer r.mx.Unlock() + log.Infof("Updating repository: %s", r.Orig.Name) + + // from cmd/helm/repo_update.go + + // TODO: make this object to be an `Orig`? + rep, err := repo.NewChartRepository(r.Orig, getter.All(r.Settings)) + if err != nil { + return errorx.Decorate(err, "could not create repository object") + } + rep.CachePath = r.Settings.RepositoryCache + + _, err = rep.DownloadIndexFile() + if err != nil { + return errorx.Decorate(err, "failed to download repo index file") + } + return nil +} + +// copied from cmd/helm/repo.go +func isNotExist(err error) bool { + return os.IsNotExist(errors.Cause(err)) +} + +// copied from cmd/helm/repo_remove.go +func removeRepoCache(root, name string) error { + idx := filepath.Join(root, helmpath.CacheChartsFile(name)) + if _, err := os.Stat(idx); err == nil { + _ = os.Remove(idx) + } + + idx = filepath.Join(root, helmpath.CacheIndexFile(name)) + if _, err := os.Stat(idx); os.IsNotExist(err) { + return nil + } else if err != nil { + return errors.Wrapf(err, "can't remove index file %s", idx) + } + return os.Remove(idx) +} diff --git a/pkg/dashboard/objects/repos_test.go b/pkg/dashboard/objects/repos_test.go new file mode 100644 index 0000000..8fe90a8 --- /dev/null +++ b/pkg/dashboard/objects/repos_test.go @@ -0,0 +1,149 @@ +package objects + +import ( + "helm.sh/helm/v3/pkg/action" + "testing" + + "gotest.tools/v3/assert" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/repo" +) + +var filePath = "./testdata/repositories.yaml" + +func initRepository(t *testing.T, filePath string) *Repositories { + t.Helper() + + settings := cli.New() + + // Sets the repository file path + settings.RepositoryConfig = filePath + + testRepository := &Repositories{ + Settings: settings, + HelmConfig: &action.Configuration{}, // maybe use copy of getFakeHelmConfig from api_test.go + } + + return testRepository +} + +func TestLoadRepo(t *testing.T) { + + res, err := repo.LoadFile(filePath) + if err != nil { + t.Fatal(err) + } + + testRepository := initRepository(t, filePath) + + file, err := testRepository.Load() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, file.Generated, res.Generated) +} + +func TestList(t *testing.T) { + res, err := repo.LoadFile(filePath) + if err != nil { + t.Fatal(err) + } + + testRepository := initRepository(t, filePath) + + repos, err := testRepository.List() + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(repos), len(res.Repositories)) +} + +func TestAdd(t *testing.T) { + testRepoName := "TEST" + testRepoUrl := "https://helm.github.io/examples" + + res, err := repo.LoadFile(filePath) + if err != nil { + t.Fatal(err) + } + + // Delete the repository if already exist + res.Remove(testRepoName) + + testRepository := initRepository(t, filePath) + + err = testRepository.Add(testRepoName, testRepoUrl) + + if err != nil { + t.Fatal(err, "Failed to add repo") + } + + // Reload the file + res, err = repo.LoadFile(filePath) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, res.Has(testRepoName), true) + + // Removes test repository which is added for testing + t.Cleanup(func() { + removed := res.Remove(testRepoName) + if removed != true { + t.Log("Failed to clean the test repository file") + } + err = res.WriteFile(filePath, 0644) + if err != nil { + t.Log("Failed to write the file while cleaning test repo") + } + }) +} + +func TestDelete(t *testing.T) { + testRepoName := "TEST DELETE" + testRepoUrl := "https://helm.github.io/examples" + + res, err := repo.LoadFile(filePath) + if err != nil { + t.Fatal(err) + } + + // Add a test entry + res.Add(&repo.Entry{Name: testRepoName, URL: testRepoUrl}) + err = res.WriteFile(filePath, 0644) + if err != nil { + t.Fatal("Failed to write the file while creating test repo") + } + + testRepository := initRepository(t, filePath) + + err = testRepository.Delete(testRepoName) + if err != nil { + t.Fatal(err, "Failed to delete the repo") + } + + // Reload the file + res, err = repo.LoadFile(filePath) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, res.Has(testRepoName), false) +} + +func TestGet(t *testing.T) { + // Initial repositiry name in test file + repoName := "charts" + + testRepository := initRepository(t, filePath) + + repo, err := testRepository.Get(repoName) + if err != nil { + t.Fatal(err, "Failed to get th repo") + } + + assert.Equal(t, repo.Orig.Name, repoName) +} diff --git a/pkg/dashboard/objects/testdata/repositories.yaml b/pkg/dashboard/objects/testdata/repositories.yaml new file mode 100644 index 0000000..4b40604 --- /dev/null +++ b/pkg/dashboard/objects/testdata/repositories.yaml @@ -0,0 +1,30 @@ +apiVersion: "" +generated: "0001-01-01T00:00:00Z" +repositories: +- caFile: "" + certFile: "" + insecure_skip_tls_verify: false + keyFile: "" + name: charts + pass_credentials_all: false + password: "" + url: https://charts.helm.sh/stable + username: "" +- caFile: "" + certFile: "" + insecure_skip_tls_verify: false + keyFile: "" + name: firstexample + pass_credentials_all: false + password: "" + url: http://firstexample.com + username: "" +- caFile: "" + certFile: "" + insecure_skip_tls_verify: false + keyFile: "" + name: secondexample + pass_credentials_all: false + password: "" + url: http://secondexample.com + username: "" diff --git a/pkg/dashboard/scanners/checkov.go b/pkg/dashboard/scanners/checkov.go index ab01530..b8eeb93 100644 --- a/pkg/dashboard/scanners/checkov.go +++ b/pkg/dashboard/scanners/checkov.go @@ -2,6 +2,8 @@ package scanners import ( "encoding/json" + "github.com/joomcode/errorx" + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" "github.com/olekukonko/tablewriter" @@ -11,7 +13,7 @@ import ( ) type Checkov struct { - Data *subproc.DataLayer + Data *objects.DataLayer } func (c *Checkov) ManifestIsScannable() bool { @@ -77,7 +79,7 @@ func (c *Checkov) ScanManifests(mnf string) (*subproc.ScanResults, error) { res := &subproc.ScanResults{} - err = json.Unmarshal([]byte(out), res.OrigReport) + err = json.Unmarshal([]byte(out), &res.OrigReport) if err != nil { return nil, err } @@ -89,14 +91,19 @@ func (c *Checkov) ScanResource(ns string, kind string, name string) (*subproc.Sc carp := v1.Carp{} carp.Kind = kind carp.Name = name - mnf, err := c.Data.GetResourceYAML(ns, &carp) + app, err := c.Data.AppForCtx(c.Data.KubeContext) if err != nil { - return nil, err + return nil, errorx.Decorate(err, "failed to get app for context") + } + + mnf, err := app.K8s.GetResourceYAML(kind, ns, name) + if err != nil { + return nil, errorx.Decorate(err, "failed to get YAML for resource") } fname, fclose, err := utils.TempFile(mnf) if err != nil { - return nil, err + return nil, errorx.Decorate(err, "failed to create temporary file") } defer fclose() diff --git a/pkg/dashboard/scanners/trivy.go b/pkg/dashboard/scanners/trivy.go index ff0a19e..d0d9b57 100644 --- a/pkg/dashboard/scanners/trivy.go +++ b/pkg/dashboard/scanners/trivy.go @@ -1,6 +1,7 @@ package scanners import ( + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" log "github.com/sirupsen/logrus" @@ -9,7 +10,7 @@ import ( ) type Trivy struct { - Data *subproc.DataLayer + Data *objects.DataLayer } func (c *Trivy) ManifestIsScannable() bool { diff --git a/pkg/dashboard/server.go b/pkg/dashboard/server.go index 4ba68ab..2c8e8b1 100644 --- a/pkg/dashboard/server.go +++ b/pkg/dashboard/server.go @@ -4,6 +4,12 @@ import ( "context" "encoding/json" "fmt" + "github.com/joomcode/errorx" + "github.com/komodorio/helm-dashboard/pkg/dashboard/objects" + "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/registry" "net/http" "os" "strings" @@ -12,54 +18,87 @@ import ( "github.com/gin-gonic/gin" "github.com/hashicorp/go-version" "github.com/komodorio/helm-dashboard/pkg/dashboard/scanners" - "github.com/komodorio/helm-dashboard/pkg/dashboard/subproc" "github.com/komodorio/helm-dashboard/pkg/dashboard/utils" log "github.com/sirupsen/logrus" ) type Server struct { Version string - Namespace string + Namespaces []string Address string Debug bool NoTracking bool } -func (s Server) StartServer() (string, utils.ControlChan) { - data := subproc.DataLayer{ - Namespace: s.Namespace, - Cache: subproc.NewCache(), - StatusInfo: &subproc.StatusInfo{ - CurVer: s.Version, - Analytics: false, - LimitedToNamespace: s.Namespace, - }, - } - err := data.CheckConnectivity() +func (s *Server) StartServer(ctx context.Context, cancel context.CancelFunc) (string, utils.ControlChan, error) { + data, err := objects.NewDataLayer(s.Namespaces, s.Version, NewHelmConfig) if err != nil { - log.Errorf("Failed to check that Helm is operational, cannot continue. The error was: %s", err) - os.Exit(1) // TODO: propagate error instead? + return "", nil, errorx.Decorate(err, "Failed to create data layer") } + isDevModeWithAnalytics := os.Getenv("HD_DEV_ANALYTICS") == "true" data.StatusInfo.Analytics = (!s.NoTracking && s.Version != "0.0.0") || isDevModeWithAnalytics + + err = s.detectClusterMode(data) + if err != nil { + return "", nil, err + } + go checkUpgrade(data.StatusInfo) - discoverScanners(&data) + discoverScanners(data) - abort := make(utils.ControlChan) - api := NewRouter(abort, &data, s.Debug) - done := s.startBackgroundServer(api, abort) + go data.PeriodicTasks(ctx) - return "http://" + s.Address, done + api := NewRouter(cancel, data, s.Debug) + done := s.startBackgroundServer(api, ctx) + + return "http://" + s.Address, done, nil } -func (s Server) startBackgroundServer(routes *gin.Engine, abort utils.ControlChan) utils.ControlChan { +func (s *Server) detectClusterMode(data *objects.DataLayer) error { + data.StatusInfo.ClusterMode = os.Getenv("HD_CLUSTER_MODE") != "" + if data.StatusInfo.ClusterMode { + return nil + } + + ctxs, err := data.ListContexts() + if err != nil { + return err + } + + if len(ctxs) == 0 { + log.Infof("Got no kubectl config contexts, will attempt to detect if we're inside cluster...") + app, err := data.AppForCtx("") + if err != nil { + return err + } + ns, err := app.K8s.GetNameSpaces() + if err != nil { // no point in continuing without kubectl context and k8s connection + return err + } + log.Debugf("Got %d namespaces listed", len(ns.Items)) + data.StatusInfo.ClusterMode = true + } + return err +} + +func (s *Server) startBackgroundServer(routes *gin.Engine, ctx context.Context) utils.ControlChan { done := make(utils.ControlChan) server := &http.Server{ Addr: s.Address, Handler: routes, } + go func() { + <-ctx.Done() + err := server.Shutdown(context.Background()) + if err != nil { + log.Warnf("Had problems shutting down the server: %s", err) + } + log.Infof("Web server has been shut down.") + }() + go func() { err := server.ListenAndServe() if err != nil && err != http.ErrServerClosed { @@ -73,18 +112,10 @@ func (s Server) startBackgroundServer(routes *gin.Engine, abort utils.ControlCha done <- struct{}{} }() - go func() { - <-abort - err := server.Shutdown(context.Background()) - if err != nil { - log.Warnf("Had problems shutting down the server: %s", err) - } - }() - return done } -func (s Server) itIsUs() bool { +func (s *Server) itIsUs() bool { url := fmt.Sprintf("http://%s/status", s.Address) var myClient = &http.Client{ Timeout: 5 * time.Second, @@ -99,7 +130,7 @@ func (s Server) itIsUs() bool { return strings.HasPrefix(r.Header.Get("X-Application-Name"), "Helm Dashboard") } -func discoverScanners(data *subproc.DataLayer) { +func discoverScanners(data *objects.DataLayer) { potential := []subproc.Scanner{ &scanners.Checkov{Data: data}, &scanners.Trivy{Data: data}, @@ -113,7 +144,7 @@ func discoverScanners(data *subproc.DataLayer) { } } -func checkUpgrade(d *subproc.StatusInfo) { // TODO: check it once an hour +func checkUpgrade(d *objects.StatusInfo) { // TODO: check it once an hour url := "https://api.github.com/repos/komodorio/helm-dashboard/releases/latest" type GHRelease struct { Name string `json:"name"` @@ -143,7 +174,7 @@ func checkUpgrade(d *subproc.StatusInfo) { // TODO: check it once an hour v2, err := version.NewVersion(d.LatestVer) if err != nil { - log.Warnf("Failed to parse LatestVer: %s", err) + log.Warnf("Failed to parse RepoLatestVer: %s", err) } else { if v1.LessThan(v2) { log.Warnf("Newer Helm Dashboard version is available: %s", d.LatestVer) @@ -153,3 +184,34 @@ func checkUpgrade(d *subproc.StatusInfo) { // TODO: check it once an hour } } } + +func NewHelmConfig(origSettings *cli.EnvSettings, ns string) (*action.Configuration, error) { + // TODO: cache it into map + // TODO: I feel there should be more elegant way to organize this code + actionConfig := new(action.Configuration) + + settings := cli.New() + settings.KubeContext = origSettings.KubeContext + settings.SetNamespace(ns) // important for RESTClientGetter to have correct namespace + + registryClient, err := registry.NewClient( + registry.ClientOptDebug(false), + registry.ClientOptEnableCache(true), + //registry.ClientOptWriter(out), + registry.ClientOptCredentialsFile(settings.RegistryConfig), + ) + if err != nil { + return nil, errorx.Decorate(err, "failed to crete helm config object") + } + actionConfig.RegistryClient = registryClient + + helmDriver := os.Getenv("HELM_DRIVER") + if err := actionConfig.Init( + settings.RESTClientGetter(), + ns, + helmDriver, log.Debugf); err != nil { + return nil, errorx.Decorate(err, "failed to init Helm action config") + } + + return actionConfig, nil +} diff --git a/pkg/dashboard/static/actions.js b/pkg/dashboard/static/actions.js index f0492b9..43cddc4 100644 --- a/pkg/dashboard/static/actions.js +++ b/pkg/dashboard/static/actions.js @@ -5,7 +5,7 @@ $("#btnUpgradeCheck").click(function () { const repoName = self.data("repo") $("#btnUpgrade span").text("Checking...") $("#btnUpgrade .icon").removeClass("bi-arrow-up bi-pencil").addClass("bi-hourglass-split") - $.post("/api/helm/repo/update?name=" + repoName).fail(function (xhr) { + $.post("/api/helm/repositories/" + repoName).fail(function (xhr) { reportError("Failed to update chart repo", xhr) }).done(function () { self.find(".spinner-border").hide() @@ -16,31 +16,29 @@ $("#btnUpgradeCheck").click(function () { }) }) - function checkUpgradeable(name) { - $.getJSON("/api/helm/repo/search?name=" + name).fail(function (xhr) { + $.getJSON("/api/helm/repositories/latestver?name=" + name).fail(function (xhr) { reportError("Failed to find chart in repo", xhr) }).done(function (data) { + let elm = {name: "", version: "0"} + const btnUpgradeCheck = $("#btnUpgradeCheck"); if (!data || !data.length) { - $("#btnUpgrade span").text("No upgrades") - $("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-x-octagon") - $("#btnUpgrade").prop("disabled", true) - $("#btnUpgradeCheck").prop("disabled", true) + btnUpgradeCheck.prop("disabled", true) + btnUpgradeCheck.text("") $("#btnAddRepository").text("Add repository for it") - $("#btnUpgradeCheck").text("") - return + } else { + $("#btnAddRepository").text("") + btnUpgradeCheck.text("Check for new version") + elm = data[0] } - $("#btnUpgrade .icon").removeClass("bi-x-octagon").addClass("bi-hourglass-split") - $("#btnAddRepository").text("") - $("#btnUpgradeCheck").text("Check for new version") + $("#btnUpgrade .icon").removeClass("bi-arrow-up bi-pencil").addClass("bi-hourglass-split") const verCur = $("#specRev").data("last-chart-ver"); - const elm = data[0] - $("#btnUpgradeCheck").data("repo", elm.name.split('/').shift()) - $("#btnUpgradeCheck").data("chart", elm.name.split('/').pop()) + btnUpgradeCheck.data("repo", elm.repository) + btnUpgradeCheck.data("chart", elm.name) const canUpgrade = isNewerVersion(verCur, elm.version); - $("#btnUpgradeCheck").prop("disabled", false) + btnUpgradeCheck.prop("disabled", false) if (canUpgrade) { $("#btnUpgrade span").text("Upgrade to " + elm.version) $("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-arrow-up") @@ -58,7 +56,14 @@ function checkUpgradeable(name) { function popUpUpgrade(elm, ns, name, verCur, lastRev) { $("#upgradeModal .btn-confirm").prop("disabled", true) - $('#upgradeModal').data("chart", elm.name).data("initial", !verCur) + let chart = elm.repository + "/" + elm.name; + if (!elm.name) { + chart = "" + } + + $('#upgradeModal').data("chart", chart).data("initial", !verCur) + $('#upgradeModal form .chart-name').val(chart) + $('#upgradeModal').data("newManifest", "") $("#upgradeModalLabel .name").text(elm.name) @@ -69,53 +74,70 @@ function popUpUpgrade(elm, ns, name, verCur, lastRev) { $("#upgradeModal .ver-old").show().find("span").text(verCur) $("#upgradeModal .rel-name").prop("disabled", true).val(name) $("#upgradeModal .rel-ns").prop("disabled", true).val(ns) + + $.get("/api/helm/releases/" + ns + "/" + name + "/manifests").fail(function (xhr) { + reportError("Failed to get current manifest", xhr) + }).done(function (text) { + $('#upgradeModal').data("curManifest", text) + }) + } else { $("#upgradeModalLabel .type").text("Install") $("#upgradeModal .ver-old").hide() $("#upgradeModal .rel-name").prop("disabled", false).val(elm.name.split("/").pop()) $("#upgradeModal .rel-ns").prop("disabled", false).val(ns) + $('#upgradeModal').data("curManifest", "") } - $.getJSON("/api/helm/repo/search?name=" + elm.name).fail(function (xhr) { - reportError("Failed to find chart in repo", xhr) - }).done(function (vers) { - // fill versions - $('#upgradeModal select').empty() - for (let i = 0; i < vers.length; i++) { - const opt = $(""); - if (vers[i].version === verCur) { - opt.html(vers[i].version + " ·") - } else { - opt.html(vers[i].version) + if (elm.name) { + $.getJSON("/api/helm/repositories/versions?name=" + elm.name).fail(function (xhr) { + reportError("Failed to find chart in repo", xhr) + }).done(function (vers) { + // fill versions + $('#upgradeModal select').empty() + for (let i = 0; i < vers.length; i++) { + const opt = $(""); + if (vers[i].version === verCur) { + opt.html(vers[i].version + " ·") + } else { + opt.html(vers[i].version) + } + $('#upgradeModal select').append(opt) } - $('#upgradeModal select').append(opt) - } - $('#upgradeModal select').val(elm.version).trigger("change") + $('#upgradeModal select').val(elm.version).trigger("change").parent().show() + upgrPopUpCommon(verCur, ns, lastRev, name) + }) + } else { // chart without repo reconfigure + $('#upgradeModal select').empty().trigger("change").parent().hide() + upgrPopUpCommon(verCur, ns, lastRev, name) + } +} - const myModal = new bootstrap.Modal(document.getElementById('upgradeModal'), {}); - myModal.show() +function upgrPopUpCommon(verCur, ns, lastRev, name) { + const myModal = new bootstrap.Modal(document.getElementById('upgradeModal'), {}); + myModal.show() - if (verCur) { - // fill current values - $.get("/api/helm/charts/values?namespace=" + ns + "&revision=" + lastRev + "&name=" + name + "&flag=true").fail(function (xhr) { - reportError("Failed to get charts values info", xhr) - }).done(function (data) { - $("#upgradeModal textarea").val(data).data("dirty", false) - }) - } else { - $("#upgradeModal textarea").val("").data("dirty", true) - } - }) + if (verCur) { + // fill current values + $.get("/api/helm/releases/" + ns + "/" + name + "/values?userDefined=true&revision=" + lastRev).fail(function (xhr) { + reportError("Failed to get charts values info", xhr) + }).done(function (data) { + $("#upgradeModal textarea").val(data).data("dirty", false) + }) + } else { + $("#upgradeModal textarea").val("").data("dirty", true) + } } $("#upgradeModal .btn-confirm").click(function () { const btnConfirm = $("#upgradeModal .btn-confirm") btnConfirm.prop("disabled", true).prepend('') + $('#upgradeModal form .preview-mode').val("false") $.ajax({ type: 'POST', - url: "/api/helm/charts/install" + upgradeModalQstr() + "&flag=true", - data: $("#upgradeModal textarea").data("dirty") ? $("#upgradeModal form").serialize() : null, + url: upgradeModalURL(), + data: $("#upgradeModal form").serialize(), }).fail(function (xhr) { reportError("Failed to upgrade the chart", xhr) }).done(function (data) { @@ -156,22 +178,33 @@ $('#upgradeModal select').change(function () { // fill reference values $("#upgradeModal .ref-vals").html('') - $.get("/api/helm/repo/values?chart=" + $("#upgradeModal").data("chart") + "&version=" + self.val()).fail(function (xhr) { - reportError("Failed to get upgrade info", xhr) - }).done(function (data) { - data = hljs.highlight(data, {language: 'yaml'}).value - $("#upgradeModal .ref-vals").html(data) - }) + const chart = $("#upgradeModal").data("chart"); + // TODO: if chart is empty, query different URL that will restore values without repo + if (chart) { + $.get("/api/helm/repositories/values?chart=" + chart + "&version=" + self.val()).fail(function (xhr) { + reportError("Failed to get upgrade info", xhr) + }).done(function (data) { + data = hljs.highlight(data, {language: 'yaml'}).value + $("#upgradeModal .ref-vals").html(data) + }) + } else { + $("#upgradeModal .ref-vals").html("No original values information found") + } }) $('#upgradeModal .btn-scan').click(function () { const self = $(this) self.prop("disabled", true).prepend('') + + const form = new FormData(); + form.append('manifest', $('#upgradeModal').data("newManifest")); $.ajax({ type: "POST", - url: "/api/scanners/manifests" + upgradeModalQstr(), - data: $("#upgradeModal form").serialize(), + url: "/api/scanners/manifests", + processData: false, + contentType: false, + data: form, }).fail(function (xhr) { reportError("Failed to scan the manifest", xhr) }).done(function (data) { @@ -185,7 +218,7 @@ $('#upgradeModal .btn-scan').click(function () { continue } - const pre = $("
").text(res.OrigReport)
+            const pre = $("
").text(JSON.stringify(res.OrigReport, null, 2))
 
             container.append("

" + name + " Scan Results

") container.append(pre) @@ -203,10 +236,10 @@ function requestChangeDiff() { diffBody.empty().append(' Calculating diff...') $("#upgradeModal .btn-confirm").prop("disabled", true) - let values = null; + $('#upgradeModal form .preview-mode').val("true") + let form = $("#upgradeModal form").serialize(); if ($("#upgradeModal textarea").data("dirty")) { $("#upgradeModal .invalid-feedback").hide() - values = $("#upgradeModal form").serialize() try { jsyaml.load($("#upgradeModal textarea").val()) @@ -219,36 +252,52 @@ function requestChangeDiff() { $.ajax({ type: "POST", - url: "/api/helm/charts/install" + upgradeModalQstr(), - data: values, + url: upgradeModalURL(), + data: form, }).fail(function (xhr) { - $("#upgradeModalBody").html("

Failed to get upgrade info: " + xhr.responseText + "

") + $("#upgradeModalBody").html("

Failed to get upgrade info: " + xhr.responseText + "

") }).done(function (data) { - diffBody.empty(); - $("#upgradeModal .btn-confirm").prop("disabled", false) + $('#upgradeModal').data("newManifest", data.manifest) - const targetElement = document.getElementById('upgradeModalBody'); - const configuration = { - inputFormat: 'diff', outputFormat: 'side-by-side', - drawFileList: false, showFiles: false, highlight: true, - }; - const diff2htmlUi = new Diff2HtmlUI(targetElement, data, configuration); - diff2htmlUi.draw() - if (!data) { - diffBody.html("No changes will happen to the cluster") - } + const form = new FormData(); + form.append('a', $('#upgradeModal').data("curManifest")); + form.append('b', data.manifest); + + $.ajax({ + type: "POST", + url: "/diff", + processData: false, + contentType: false, + data: form, + }).fail(function (xhr) { + $("#upgradeModalBody").html("

Failed to get upgrade info: " + xhr.responseText + "

") + }).done(function (data) { + diffBody.empty(); + $("#upgradeModal .btn-confirm").prop("disabled", false) + + const targetElement = document.getElementById('upgradeModalBody'); + const configuration = { + inputFormat: 'diff', outputFormat: 'side-by-side', + drawFileList: false, showFiles: false, highlight: true, + }; + const diff2htmlUi = new Diff2HtmlUI(targetElement, data, configuration); + diff2htmlUi.draw() + if (!data) { + diffBody.html("No changes will happen to the cluster") + } + }) }) } -function upgradeModalQstr() { - let qstr = "?" + - "namespace=" + $("#upgradeModal .rel-ns").val() + - "&name=" + $("#upgradeModal .rel-name").val() + - "&chart=" + $("#upgradeModal").data("chart") + - "&version=" + $('#upgradeModal select').val() +function upgradeModalURL() { + let ns = $("#upgradeModal .rel-ns").val(); + if (!ns) { + ns = "[empty]" + } - if ($("#upgradeModal").data("initial")) { - qstr += "&initial=true" + let qstr = "/api/helm/releases/" + ns; + if (!$("#upgradeModal").data("initial")) { + qstr += "/" + $("#upgradeModal .rel-name").val() } return qstr @@ -263,7 +312,7 @@ $("#btnUninstall").click(function () { $("#confirmModalBody").empty().append('') btnConfirm.prop("disabled", true).off('click').click(function () { btnConfirm.prop("disabled", true).append('') - const url = "/api/helm/charts?namespace=" + namespace + "&name=" + chart; + const url = "/api/helm/releases/" + namespace + "/" + chart; $.ajax({ url: url, type: 'DELETE', @@ -277,9 +326,7 @@ $("#btnUninstall").click(function () { const myModal = new bootstrap.Modal(document.getElementById('confirmModal')); myModal.show() - let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revision - let url = "/api/helm/charts/resources" - url += "?" + qstr + let url = "/api/helm/releases/" + namespace + "/" + chart + "/resources" $.getJSON(url).fail(function (xhr) { reportError("Failed to get list of resources", xhr) }).done(function (data) { @@ -301,10 +348,13 @@ $("#btnRollback").click(function () { $("#confirmModalBody").empty().append('') btnConfirm.prop("disabled", true).off('click').click(function () { btnConfirm.prop("disabled", true).append('') - const url = "/api/helm/charts/rollback?namespace=" + namespace + "&name=" + chart + "&revision=" + revisionNew; + const url = "/api/helm/releases/" + namespace + "/" + chart + "/rollback"; $.ajax({ url: url, type: 'POST', + data: { + revision: revisionNew + } }).fail(function (xhr) { reportError("Failed to rollback the chart", xhr) }).done(function () { @@ -315,8 +365,8 @@ $("#btnRollback").click(function () { const myModal = new bootstrap.Modal(document.getElementById('confirmModal'), {}); myModal.show() - let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revisionNew + "&revisionDiff=" + revisionCur - let url = "/api/helm/charts/manifests" + let qstr = "revision=" + revisionNew + "&revisionDiff=" + revisionCur + let url = "/api/helm/releases/" + namespace + "/" + chart + "/manifests" url += "?" + qstr $.get(url).fail(function (xhr) { reportError("Failed to get list of resources", xhr) @@ -345,16 +395,23 @@ $("#btnAddRepository").click(function () { }) $("#btnTest").click(function() { - $("#testModal .test-result").empty().prepend('') + const myModal = new bootstrap.Modal(document.getElementById('testModal'), {}); + $("#testModal .test-result").empty().prepend(' Waiting for completion...') + myModal.show() $.ajax({ type: 'POST', - url: "/api/helm/charts/tests" + "?namespace=" + getHashParam("namespace") + "&name=" + getHashParam("chart") + url: "/api/helm/releases/" + getHashParam("namespace") + "/" + getHashParam("chart") + "/test" }).fail(function (xhr) { reportError("Failed to execute test for chart", xhr) + myModal.hide() }).done(function (data) { - $("#testModal .test-result").empty().html(data.replaceAll("\n", "
")) + var output; + if(data.length == 0 || data == null || data == "") { + output = "
Tests executed successfully

Empty response from API
" + } else { + output = data.replaceAll("\n", "
") + } + $("#testModal .test-result").empty().html(output) + myModal.show() }) - - const myModal = new bootstrap.Modal(document.getElementById('testModal'), {}); - myModal.show() }) \ No newline at end of file diff --git a/pkg/dashboard/static/api-docs.html b/pkg/dashboard/static/api-docs.html new file mode 100644 index 0000000..eaeac8b --- /dev/null +++ b/pkg/dashboard/static/api-docs.html @@ -0,0 +1,70 @@ + + + + + + Helm Dashboard API + + + + + +
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/pkg/dashboard/static/details-view.js b/pkg/dashboard/static/details-view.js index 82e1c30..b1c07ef 100644 --- a/pkg/dashboard/static/details-view.js +++ b/pkg/dashboard/static/details-view.js @@ -55,17 +55,17 @@ function loadContentWrapper() { loadContent(getHashParam("tab"), getHashParam("namespace"), getHashParam("chart"), revision, revDiff, flag) } -function loadContent(mode, namespace, name, revision, revDiff, flag) { - let qstr = "name=" + name + "&namespace=" + namespace + "&revision=" + revision +function loadContent(mode, namespace, name, revision, revDiff, userDefined) { + let qstr = "revision=" + revision if (revDiff) { qstr += "&revisionDiff=" + revDiff } - if (flag) { - qstr += "&flag=" + flag + if (userDefined) { + qstr += "&userDefined=" + userDefined } - let url = "/api/helm/charts/" + mode + let url = "/api/helm/releases/" + namespace + "/" + name + "/" + mode url += "?" + qstr const diffDisplay = $("#manifestText"); diffDisplay.empty().append('') @@ -149,9 +149,7 @@ function showResources(namespace, chart, revision) { const resBody = $("#nav-resources .body"); const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"]; resBody.empty().append(''); - let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revision - let url = "/api/helm/charts/resources" - url += "?" + qstr + let url = "/api/helm/releases/" + namespace + "/" + chart + "/resources" $.getJSON(url).fail(function (xhr) { reportError("Failed to get list of resources", xhr) }).done(function (data) { @@ -182,7 +180,7 @@ function showResources(namespace, chart, revision) { resBody.append(resBlock) let ns = res.metadata.namespace ? res.metadata.namespace : namespace - $.getJSON("/api/kube/resources/" + res.kind.toLowerCase() + "?name=" + res.metadata.name + "&namespace=" + ns).fail(function () { + $.getJSON("/api/k8s/" + res.kind.toLowerCase() + "/get?name=" + res.metadata.name + "&namespace=" + ns).fail(function () { //reportError("Failed to get list of resources") }).done(function (data) { const badge = $("").text(data.status.phase); @@ -228,7 +226,7 @@ function getStatusMessage(status) { } if (status.conditions) { return status.conditions[0].message || status.conditions[0].reason - } + } return status.message || status.reason } @@ -239,7 +237,7 @@ function showDescribe(ns, kind, name, badge) { const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal')); myModal.show() - $.get("/api/kube/describe/" + kind.toLowerCase() + "?name=" + name + "&namespace=" + ns).fail(function (xhr) { + $.get("/api/k8s/" + kind.toLowerCase() + "/describe?name=" + name + "&namespace=" + ns).fail(function (xhr) { reportError("Failed to describe resource", xhr) }).done(function (data) { data = hljs.highlight(data, {language: 'yaml'}).value diff --git a/pkg/dashboard/static/index.html b/pkg/dashboard/static/index.html index 8448582..0d6aa37 100644 --- a/pkg/dashboard/static/index.html +++ b/pkg/dashboard/static/index.html @@ -54,7 +54,8 @@ - +
  • REST API
  • @@ -194,7 +195,7 @@ -