66 Commits

Author SHA1 Message Date
Andrei Pohilko
2221fb22a0 Release 0.2.1 2022-10-26 12:37:47 +01:00
Andrei Pohilko
9dc3e6a12d Merge branch 'main' of github.com:komodorio/helm-dashboard 2022-10-26 12:35:24 +01:00
Andrey Pokhilko
de0024cd03 Check for newer version available (#47)
* Add helm version requirement notes

* Check for newer version and offer upgrade

* fix lint
2022-10-26 12:35:07 +01:00
Andrei Pohilko
ed4e970194 Merge branch 'main' of github.com:komodorio/helm-dashboard 2022-10-26 11:09:43 +01:00
Andrey Pokhilko
b0067e31ba Improvements after release (#45)
* Add helm version requirement notes

* Create Help section, no charts placeholder

* Revive missed "user-defined" values

* Fix namespace undefined upon install

* cosmetics
2022-10-26 11:05:50 +01:00
Andrei Pohilko
7e8ba4709e Merge branch 'main' of github.com:komodorio/helm-dashboard 2022-10-26 08:59:09 +01:00
Andrei Pohilko
be7b2642fc Add helm version requirement notes 2022-10-25 16:58:01 +01:00
Andrey Pokhilko
896d9e3f72 Use different library for opening the browser (#22) 2022-10-25 16:53:33 +01:00
Lior Noy
be6666373b Add version information into UI (#35)
This commit adds the tool version into the UI in the top bar.
2022-10-25 14:08:28 +01:00
Dimas Yudha P
91df9392c0 adding unit test for GetQueryProps (utils). (#39)
* adding unit test for GetQueryProps (utils).

* add unit test chart and version
2022-10-25 14:02:48 +01:00
Dimas Yudha P
bd058ee912 removing else flow (#38) 2022-10-25 12:57:25 +01:00
Andrei Pohilko
997f951d0c Fix error when no scanners are present 2022-10-24 15:42:19 +01:00
Andrei Pohilko
09886ad933 Release 0.2.0 2022-10-23 13:46:18 +01:00
Andrey Pokhilko
0de0b5d0cb Repository-related functions (#19)
* Roadmap item

* Start building repo view

* Section switcher

* Show repo list

* Adding chart repo works

* Showing the pane

* Couple of buttons

* Listing items

* Styling

* Enriching repo view

* Navigate from repo to installed

* Tuning install popup

* Working on install

* Cosmetics
2022-10-23 13:41:45 +01:00
Andrey Pokhilko
0141eecef1 Update README.md 2022-10-20 12:34:42 +01:00
Andrey Pokhilko
65ecc20c90 Create CODE_OF_CONDUCT.md (#20) 2022-10-19 12:23:38 +01:00
Andrey Pokhilko
f86a4a93a7 Scanners Integration (#18)
* Research scanning

* Move files around

* Reports the list

* Scanner happens

* Commit

* Work on alternative

* refactorings

* Progress

* Save the state

* Commit

* Display trivy Results

* Checkov also reports

* Better display

* Correct trivy numbers

* Scan pre-install manifest

* Readme items

* Static checks
2022-10-17 13:41:08 +01:00
Andrei Pohilko
5cae4b5adf Release 0.1.1 2022-10-12 12:23:03 +01:00
Andrey Pokhilko
d8afa3861d Values editing (#17)
* Refactorings

* save

* REdesigning install dialog

* Display values editor

* Reconfigure flow

* Status handler and version

* error reporting
2022-10-07 17:01:19 +01:00
Andrei Pohilko
3c4d73665e Release 0.1.0 2022-10-04 09:12:14 +01:00
Andrei Pohilko
8b5f8e1031 Another test 2022-10-04 08:52:59 +01:00
Andrei Pohilko
44461bf5ab Test the workaround 2022-10-04 08:50:20 +01:00
Andrei Pohilko
061bd12f2f Fix release process 2022-10-04 08:45:13 +01:00
Andrei Pohilko
86c9f89acc Release 0.0.8 2022-10-04 08:40:48 +01:00
Itiel shwartz
890994d70d add datadog and heap (#16)
* add datadog

* also added heap
2022-10-04 08:32:41 +01:00
Itiel shwartz
35097fed45 Clean cluster name (#15)
* support GKE and AWS cluster name

* remove unused logs

Co-authored-by: Itiel Shwartz <itielshwartz@Itiels-MacBook-Pro.local>
2022-10-03 21:34:28 +01:00
Andrei Pohilko
11912e7b51 Use dry run instead of template 2022-10-02 16:06:31 +01:00
Andrei Pohilko
8e90c9f8d0 Update screenshot 2022-10-02 15:05:10 +01:00
Andrey Pokhilko
1e3a706698 Restyle fixes (#14)
* Applying style fixes

* Implement more notes

* Improvements

* Applying v2 design

* flyout restyle

* fix spec rev

* Status icons

* Restyle top line and footer

* Shorter err msg
2022-10-02 11:50:28 +01:00
Andrei Pohilko
1b6dc4159a Release 0.0.6 2022-09-29 09:01:05 +01:00
Andrei Pohilko
69609b1ee2 Fix wrapping and scrolls 2022-09-28 22:29:42 +01:00
Andrei Pohilko
bdd5b9b32e Workaround the wrap 2022-09-28 20:52:37 +01:00
Andrei Pohilko
870a1196f0 Make k8s context a URL param 2022-09-28 15:00:10 +01:00
Andrei Pohilko
c1732c86a5 Fix the binary path 2022-09-26 14:40:14 +01:00
Andrei Pohilko
388c330390 Fix unpacking 2022-09-26 14:37:53 +01:00
Andrei Pohilko
cb7c29de90 fix the name 2022-09-26 14:35:13 +01:00
Andrei Pohilko
fa6a38c50f Update install script 2022-09-26 14:33:10 +01:00
Andrei Pohilko
a4f4ddacb7 Plugin install script 2022-09-26 14:24:43 +01:00
Andrey Pokhilko
8fa2bcb87b Update README.md 2022-09-26 13:56:31 +01:00
Andrei Pohilko
7d9863ebed Update Readme 2022-09-26 13:45:25 +01:00
Andrei Pohilko
89be257ded Update screenshot 2022-09-26 11:23:28 +01:00
Andrey Pokhilko
7fd5fcc5b2 Restyle LAF (#12)
* Logos change

* Coding HTML

* Top bar

* Top bar is fine

* Restyling it

* progressing

* age line

* cosmetics

* Installed list is fine

* Save

* revision list display

* Split up files

* Rev list works

* Details

* Working on details

* Action buttons

* cosmetics

* Describe flyout

* Uninstall confirm flyout

* Working on flyouts

* Fixed the actions working

* Polishing it

* Cosmetics
2022-09-26 11:19:55 +01:00
Andrei Pohilko
ea6e4d55b0 Fix wrong deployemnt status 2022-09-20 12:41:06 +01:00
Andrei Pohilko
ccb2836791 Fix diff shown when no upgrade 2022-09-20 11:17:37 +01:00
Itiel shwartz
f4b753b19f easier local dev (#13)
* easier local dev

* rename and add build

* change pull command

Co-authored-by: Itiel Shwartz <itielshwartz@Itiels-MacBook-Pro.local>
2022-09-20 09:02:37 +01:00
Andrei Pohilko
927d507fd1 forgotten error report 2022-09-15 21:41:17 +01:00
Andrei Pohilko
d662849424 Right place for msg 2022-09-15 21:34:20 +01:00
Andrey Pokhilko
6b8d959491 Bugfixes (#11)
* Don't offer rollback for the first revision

* Fix the rollback bug

* Cosmetics

* Errors shown as alerts
2022-09-15 13:27:24 +01:00
Andrey Pokhilko
269895ae31 Repo version info and upgrade (#10)
* Repo block shows

* repository update

* API progress

* Fix problem of stopping after uninstall

* Upgrade preview backend

* Upgrade preview displays fine

* Install flow works

* Weird check for updates

* Update action

* Fix action

* Still trying

* Fix lint error

* Refactor out helm handlers

* refactor out kube handlers

* save

* Change icon collection

* Reworked upgrade check
2022-09-14 13:20:10 +01:00
Andrei Pohilko
47929785e7 Fix name of parameters 2022-09-14 10:15:05 +01:00
Andrei Pohilko
ab17544c96 Fix the panic 2022-09-14 10:00:40 +01:00
Andrey Pokhilko
5ea54f9257 Rollback action (#9)
* Show rollback confirm

* Implement rollback backend

* Refactoring

* Refactoring
2022-09-11 12:54:18 +01:00
Andrey Pokhilko
fa48cf5435 Allow uninstalling the chart (#8) 2022-09-09 14:50:50 +01:00
Andrei Pohilko
91fd3793c7 Don't offer to describe non-existing 2022-09-08 16:07:46 +01:00
Andrey Pokhilko
7b6e9f1748 Show describe modal (#7) 2022-09-08 15:56:05 +01:00
Andrei Pohilko
476a2a32d9 Cosmetics around poweroff 2022-09-08 13:00:29 +01:00
Andrei Pohilko
4bd510962a Add "close" button to shutdown the server 2022-09-08 12:11:21 +01:00
Andrei Pohilko
8fa3d9b960 Update the logo 2022-09-08 11:40:57 +01:00
Andrey Pokhilko
a3fb04fbae Resource status display (#6)
* Start experiments

* Going to get resources

* Start to show resources

* Working on statuses

* Displaying statuses

* Fix complexity?
2022-09-07 22:37:50 +01:00
Andrey Pokhilko
967d499742 Initial features 4 (#5)
* Select works fine

* Context switching works

* Going to expand fns

* Diff and value

* Progressing

* fix click events

* highlight code

* Add revision age info

* Values diff

* remove forgotten

* Refactor data layer, add flag

* UDV flag works

* Diff with prev works
2022-09-05 17:31:25 +01:00
Andrey Pokhilko
1580c2e9a0 Chart details draft (#4)
* Add logo

* Refactor out structs

* Data layer context-awareness

* Mod

* Data layer improvements

* Progress

* Progress

* Progress

* Figured the time format shorter

* Statuses colors

* Sticky URL

* Calculate some diffs inside

* Separate checks

* Scrap gofmt

* Skip custom test in GH

* Shows some colorful diff
2022-08-31 12:12:08 +01:00
Andrey Pokhilko
d9a88feb7b Initial features pt 2 (#3)
* Less logging when not in DEBUG

* Check helm is fine

* Display kube context switch

* Cosmetics

* Displays list of chartss

* Linter stuff

* Fix option name
2022-08-24 14:42:20 +03:00
Andrey Pokhilko
925cfa77dd Start working on web server (#2)
* Start working on web server

* Some restructure

* Serving static works

* Fix linter complaint

* Enable local dev

* Static files served fine

* Tidy
2022-08-23 13:46:11 +03:00
Nir Shtein
ee89611b81 Add git ignore (#1) 2022-08-23 10:26:06 +03:00
Andrey Pohilko
94839d7bd8 Add usage command 2022-08-23 10:04:01 +03:00
Andrey Pohilko
07afc6c6ef Document the minimal install 2022-08-23 10:01:14 +03:00
43 changed files with 4174 additions and 729 deletions

View File

@@ -16,17 +16,16 @@ jobs:
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
- name: Series of tests
- name: Unit tests
run: |
GO_FILES=$(find . -iname '*.go' -type f) # All the .go files, excluding vendor/
test -z $(gofmt -s -l $GO_FILES) # Fail if a .go file hasn't been formatted with gofmt
go test -v -race ./... # Run all the tests with the race detector enabled
- name: Static analysis
run: |
go vet ./... # go vet is the official Go static analyzer
- name: Cyclomatic complexity
run: |
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
/home/runner/go/bin/gocyclo -over 19 cmd pkg # forbid code with huge/complex functions
go build main.go
/home/runner/go/bin/gocyclo -over 19 main.go pkg # forbid code with huge/complex functions
- name: Dry Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
@@ -34,3 +33,11 @@ jobs:
args: release --snapshot --rm-dist
- 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
with:
# version: latest
# skip-go-installation: true
skip-pkg-cache: true
skip-build-cache: true
# args: --timeout=15m

View File

@@ -26,3 +26,5 @@ jobs:
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Test Binary Versions
run: "dist/helm-dashboard_linux_amd64_v1/helm-dashboard --help"

26
.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
/bin
/.idea/

View File

@@ -3,7 +3,7 @@
builds:
- main: ./main.go
binary: helm-dashboard
ldflags: -s -w -X github.com/komodorio/helm-dashboard/main.version={{.Version}} -X github.com/komodorio/helm-dashboard/main.commit={{.Commit}}
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
goos:
- windows
- darwin

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://komodorkommunity.slack.com/archives/C044U1B0265.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

9
Makefile Normal file
View File

@@ -0,0 +1,9 @@
pull:
git pull
build:
go build -o bin/dashboard .
debug:
DEBUG=1 ./bin/dashboard

130
README.md
View File

@@ -1,31 +1,117 @@
# Helm Dashboard
# <img src="pkg/dashboard/static/logo.png" height=30 style="height: 2rem"> Helm Dashboard
A simplified way of working with Helm.
<kbd>[<img src="screenshot.png" style="width: 100%; border: 1px solid silver;" border="1" alt="Screenshot">](screenshot.png)</kbd>
## Roadmap
## What it Does?
### Internal Milestone 1
- Helm Plugin Packaging
- CLI launcher
- Web Server with REST API
The _Helm Dashboard_ plugin offers a UI-driven way to view the installed Helm charts, see their revision history and
corresponding k8s resources. Also, you can perform simple actions like roll back to a revision or upgrade to newer
version.
This project is part of [Komodor's](https://komodor.com/?utm_campaign=Helm-Dash&utm_source=helm-dash-gh) vision of
helping Kubernetes users to navigate and troubleshoot their clusters.
Some of the key capabilities of the tool:
- See all installed charts and their revision history
- See manifest diff of the past revisions
- Browse k8s resources resulting from the chart
- Easy rollback or upgrade version with a clear and easy manifest diff
- Integration with popular problem scanners
- Easy switch between multiple clusters
## Installing
To install it, simply run Helm command:
```shell
helm plugin install https://github.com/komodorio/helm-dashboard.git
```
To update the plugin to the latest version, run:
```shell
helm plugin update dashboard
```
To uninstall, run:
```shell
helm plugin uninstall dashboard
```
Note: In case standard Helm plugin way did not work for you, you can just download the appropriate [release package](https://github.com/komodorio/helm-dashboard/releases) for your platform, unpack it and just run `dashboard` binary from it.
## Running
To use the plugin, your machine needs to have working `helm` and also `kubectl` commands. Helm version 3.4.0+ is required.
After installing, start the UI by running:
```shell
helm dashboard
```
The command above will launch the local Web server and will open the UI in new browser tab. The command will hang
waiting for you to terminate it in command-line or web UI.
By default, the web server is only available locally. You can change that by specifying `HD_BIND` environment variable
to the desired value. For example, `0.0.0.0` would bind to all IPv4 addresses or `[::0]` would be all IPv6 addresses.
If your port 8080 is busy, you can specify a different port to use via `HD_PORT` environment variable.
If you don't want browser tab to automatically open, set `HD_NOBROWSER=1` in your environment variables.
If you want to increase the logging verbosity and see all the debug info, set `DEBUG=1` environment variable.
## Scanner Integrations
Upon startup, Helm Dashboard detects the presence of [Trivy](https://github.com/aquasecurity/trivy)
and [Checkov](https://github.com/bridgecrewio/checkov) scanners. When available, these scanners are offered on k8s
resources page, as well as install/upgrade preview page.
You can request scanning of the specific k8s resource in your cluster:
![](screenshot_scan_resource.png)
If you want to validate the k8s manifest prior to installing/reconfiguring a Helm chart, look for "Scan for Problems"
button at the bottom of the dialog:
![](screenshot_scan_manifest.png)
## Support Channels
We have two main channels for supporting the Helm Dashboard
users: [Slack community](https://komodorkommunity.slack.com/archives/C044U1B0265) for general conversations
and [GitHub issues](https://github.com/komodorio/helm-dashboard/issues) for real bugs.
### First Public Version
Listing the installed applications
View k8s resources created by the application (describe, status)
Viewing revision history for application
View manifest diffs between revisions, also changelogs etc
Analytics reporting (telemetry)
## Local Dev Testing
### Further Ideas
Setting parameter values and installing
Installing new app from repo
Uninstalling the app completely
Reconfiguring the application
Rollback a revision
Prerequisites: `helm` and `kubectl` binaries installed and operational.
Validate manifests before deploy and get better errors
Switch clusters (?)
Browsing repositories
Adding new repository
There is a need to build binary for plugin to function, run:
```shell
go build -o bin/dashboard .
```
You can just run the `bin/dashboard` binary directly, it will just work.
To install, checkout the source code and run from source dir:
```shell
helm plugin install .
```
Local installation of plugin just creates a symlink, so making the changes and rebuilding the binary would not require
to
reinstall a plugin.
To use the plugin, run in your terminal:
```shell
helm dashboard
```
Then, use the web UI.

39
go.mod
View File

@@ -4,57 +4,42 @@ go 1.18
require (
github.com/gin-gonic/gin v1.8.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.4.0
github.com/toqueteos/webbrowser v1.2.0
helm.sh/helm/v3 v3.9.3
k8s.io/client-go v0.24.2
github.com/hashicorp/go-version v1.6.0
github.com/hexops/gotextdiff v1.0.3
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/sirupsen/logrus v1.9.0
gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.9.4
k8s.io/apimachinery v0.25.0-alpha.2
)
require (
cloud.google.com/go v0.99.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-logr/logr v1.2.3 // 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/goccy/go-json v0.9.11 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apimachinery v0.24.2 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/klog/v2 v2.70.0 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

672
go.sum
View File

@@ -1,134 +1,17 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
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 v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
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=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
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/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=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
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-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/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/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/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw=
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.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
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-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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
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-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-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=
@@ -141,111 +24,23 @@ 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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
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/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
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/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/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.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.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/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
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=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
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/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/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/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
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/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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/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/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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/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/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=
@@ -255,532 +50,113 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/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/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/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 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/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/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.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
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/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
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=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
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/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=
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/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/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=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
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/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=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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-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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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-20210503060351-7fd8e65b6420/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-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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-20200113162924-86b910548bc1/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=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/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-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-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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-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-20210616045830-e2b7044e8c71/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-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/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-20220209214540-3681064d5158/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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/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-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-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-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-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=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
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.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
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=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
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-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
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=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
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.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
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.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
helm.sh/helm/v3 v3.9.3 h1:etd4Qc45/bnIkBofZIRwrAzYuG3bNWR1EdMN4fsfzoE=
helm.sh/helm/v3 v3.9.3/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI=
k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM=
k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA=
k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
helm.sh/helm/v3 v3.9.4 h1:TCI1QhJUeLVOdccfdw+vnSEO3Td6gNqibptB04QtExY=
helm.sh/helm/v3 v3.9.4/go.mod h1:3eaWAIqzvlRSD06gR9MMwmp2KBKwlu9av1/1BZpjeWY=
k8s.io/apimachinery v0.25.0-alpha.2 h1:y6uTWaiqsPTPRewnXJ15IFyGmBo2qPt6enm4zszG8Z0=
k8s.io/apimachinery v0.25.0-alpha.2/go.mod h1:h34FtK3eCxige6ZIACdBSYExtDaKAUxoc7hVe2LOxzw=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk=
k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 h1:yEQKdMCjzAOvGeiTwG4hO/hNVNtDOuUFvMUZ0OlaIzs=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/klog/v2 v2.70.0 h1:GMmmjoFOrNepPN0ZeGCzvD2Gh5IKRwdFx8W5PBxVTQU=
k8s.io/klog/v2 v2.70.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
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-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y=
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

46
main.go
View File

@@ -1,38 +1,50 @@
package main
import (
"github.com/gin-gonic/gin"
"github.com/komodorio/helm-dashboard/pkg/dashboard"
"github.com/pkg/browser"
log "github.com/sirupsen/logrus"
"github.com/toqueteos/webbrowser"
_ "k8s.io/client-go/plugin/pkg/client/auth" //required for auth
"net/http"
"os"
)
var (
version = "dev"
commit = "dev"
commit = "none"
date = "unknown"
)
func main() {
log.Infof("Helm Dashboard by Komodor, version %s (%s)", version, commit)
setupLogging()
if len(os.Args) > 1 {
// TODO: proper command-line parsing
if len(os.Args) > 1 { // dirty thing to allow --help to work
os.Exit(0)
}
go func() {
// TODO: if it's already running - just open the tab, check that it's another instance of us via API
err := webbrowser.Open("http://localhost:8080")
address, webServerDone := dashboard.StartServer(version)
if os.Getenv("HD_NOBROWSER") == "" {
log.Infof("Opening web UI: %s", address)
err := browser.OpenURL(address)
if err != nil {
return
log.Warnf("Failed to open Web browser for URL: %s", err)
}
}()
panic(http.ListenAndServe(":8080", http.FileServer(http.Dir("/tmp"))))
/* v := cmd.NewRootCmd(os.Stdout, os.Args[1:])
if err := v.Execute(); err != nil {
os.Exit(1)
} else {
log.Infof("Access web UI at: %s", address)
}
*/
<-webServerDone
log.Infof("Done.")
}
func setupLogging() {
if os.Getenv("DEBUG") == "" {
log.SetLevel(log.InfoLevel)
gin.SetMode(gin.ReleaseMode)
} else {
log.SetLevel(log.DebugLevel)
gin.SetMode(gin.DebugMode)
}
log.Infof("Helm Dashboard by Komodor, version %s (%s @ %s)", version, commit, date)
}

145
pkg/dashboard/api.go Normal file
View File

@@ -0,0 +1,145 @@
package dashboard
import (
"embed"
"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"
"net/http"
"os"
"path"
)
//go:embed static/*
var staticFS embed.FS
func noCache(c *gin.Context) {
c.Header("Cache-Control", "no-cache")
c.Next()
}
func errorHandler(c *gin.Context) {
c.Next()
errs := ""
for _, err := range c.Errors {
log.Debugf("Error: %s", err)
errs += err.Error() + "\n"
}
if errs != "" {
c.String(http.StatusInternalServerError, errs)
}
}
func contextSetter(data *subproc.DataLayer) gin.HandlerFunc {
return func(c *gin.Context) {
if context, ok := c.Request.Header["X-Kubecontext"]; ok {
log.Debugf("Setting current context to: %s", context)
data.KubeContext = context[0]
}
c.Next()
}
}
func NewRouter(abortWeb utils.ControlChan, data *subproc.DataLayer) *gin.Engine {
var api *gin.Engine
if os.Getenv("DEBUG") == "" {
api = gin.New()
api.Use(gin.Recovery())
} else {
api = gin.Default()
}
api.Use(contextSetter(data))
api.Use(noCache)
api.Use(errorHandler)
configureStatic(api)
configureRoutes(abortWeb, data, api)
return api
}
func configureRoutes(abortWeb utils.ControlChan, data *subproc.DataLayer, api *gin.Engine) {
// server shutdown handler
api.DELETE("/", func(c *gin.Context) {
abortWeb <- struct{}{}
c.Status(http.StatusAccepted)
})
api.GET("/status", func(c *gin.Context) {
c.IndentedJSON(http.StatusOK, data.VersionInfo)
})
configureHelms(api.Group("/api/helm"), data)
configureKubectls(api.Group("/api/kube"), data)
configureScanners(api.Group("/api/scanners"), data)
}
func configureHelms(api *gin.RouterGroup, data *subproc.DataLayer) {
h := handlers.HelmHandler{Data: data}
api.GET("/charts", h.GetCharts)
api.DELETE("/charts", h.Uninstall)
api.GET("/charts/history", h.History)
api.GET("/charts/resources", h.Resources)
api.GET("/charts/:section", h.GetInfoSection)
api.POST("/charts/install", h.Install)
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)
}
func configureKubectls(api *gin.RouterGroup, data *subproc.DataLayer) {
h := handlers.KubeHandler{Data: data}
api.GET("/contexts", h.GetContexts)
api.GET("/resources/:kind", h.GetResourceInfo)
api.GET("/describe/:kind", h.Describe)
}
func configureStatic(api *gin.Engine) {
fs := http.FS(staticFS)
// local dev speed-up
localDevPath := "pkg/dashboard/static"
if _, err := os.Stat(localDevPath); err == nil {
log.Warnf("Using local development path to serve static files")
// the root page
api.GET("/", func(c *gin.Context) {
c.File(path.Join(localDevPath, "index.html"))
})
// serve a directory called static
api.GET("/static/*filepath", func(c *gin.Context) {
c.File(path.Join(localDevPath, c.Param("filepath")))
})
} else {
// the root page
api.GET("/", func(c *gin.Context) {
c.FileFromFS("/static/", fs)
})
// serve a directory called static
api.GET("/static/*filepath", func(c *gin.Context) {
c.FileFromFS(c.Request.URL.Path, fs)
})
}
}
func configureScanners(api *gin.RouterGroup, data *subproc.DataLayer) {
h := handlers.ScannersHandler{Data: data}
api.GET("", h.List)
api.POST("/manifests", h.ScanDraftManifest)
api.GET("/resource/:kind", h.ScanResource)
}

View File

@@ -0,0 +1,258 @@
package handlers
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
"net/http"
"strconv"
"strings"
)
type HelmHandler struct {
Data *subproc.DataLayer
}
func (h *HelmHandler) GetCharts(c *gin.Context) {
res, err := h.Data.ListInstalled()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, res)
}
// TODO: helm show chart komodorio/k8s-watcher to get the icon URL
func (h *HelmHandler) Uninstall(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
err = h.Data.ChartUninstall(qp.Namespace, qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Status(http.StatusAccepted)
}
func (h *HelmHandler) Rollback(c *gin.Context) {
qp, err := utils.GetQueryProps(c, true)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
err = h.Data.Revert(qp.Namespace, qp.Name, qp.Revision)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Status(http.StatusAccepted)
}
func (h *HelmHandler) History(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.ChartHistory(qp.Namespace, qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
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
}
res, err := h.Data.RevisionManifestsParsed(qp.Namespace, qp.Name, qp.Revision)
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)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.ChartRepoVersions(qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *HelmHandler) RepoCharts(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.ChartRepoCharts(qp.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)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
err = h.Data.ChartRepoUpdate(qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Status(http.StatusNoContent)
}
func (h *HelmHandler) Install(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
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)
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")
} else {
c.Header("Content-Type", "application/json")
}
c.String(http.StatusAccepted, out)
}
func (h *HelmHandler) GetInfoSection(c *gin.Context) {
qp, err := utils.GetQueryProps(c, true)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
flag := c.Query("flag") == "true"
rDiff := c.Query("revisionDiff")
res, err := handleGetSection(h.Data, c.Param("section"), rDiff, qp, flag)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, res)
}
func (h *HelmHandler) RepoValues(c *gin.Context) {
out, err := h.Data.ShowValues(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()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, out)
}
func (h *HelmHandler) RepoAdd(c *gin.Context) {
_, err := h.Data.ChartRepoAdd(c.PostForm("name"), c.PostForm("url"))
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Status(http.StatusNoContent)
}
func (h *HelmHandler) RepoDelete(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
_, err = h.Data.ChartRepoDelete(qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
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,
}
functor, found := sections[section]
if !found {
return "", errors.New("unsupported section: " + section)
}
if rDiff != "" {
cRevDiff, err := strconv.Atoi(rDiff)
if err != nil {
return "", err
}
ext := ".yaml"
if section == "notes" {
ext = ".txt"
}
res, err := subproc.RevisionDiff(functor, ext, qp.Namespace, qp.Name, cRevDiff, qp.Revision, flag)
if err != nil {
return "", err
}
return res, nil
}
res, err := functor(qp.Namespace, qp.Name, qp.Revision, flag)
if err != nil {
return "", err
}
return res, nil
}

View File

@@ -0,0 +1,71 @@
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1"
v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"net/http"
)
type KubeHandler struct {
Data *subproc.DataLayer
}
func (h *KubeHandler) GetContexts(c *gin.Context) {
res, err := h.Data.ListContexts()
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, res)
}
func (h *KubeHandler) GetResourceInfo(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
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 {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
if res.Status.Phase == "Active" || res.Status.Phase == "Error" {
_ = res.Name + ""
} else if res.Status.Phase == "" && len(res.Status.Conditions) > 0 {
res.Status.Phase = v12.CarpPhase(res.Status.Conditions[len(res.Status.Conditions)-1].Type)
res.Status.Message = res.Status.Conditions[len(res.Status.Conditions)-1].Message
res.Status.Reason = res.Status.Conditions[len(res.Status.Conditions)-1].Reason
if res.Status.Conditions[len(res.Status.Conditions)-1].Status == "False" {
res.Status.Phase = "Not" + res.Status.Phase
}
} 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)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
res, err := h.Data.DescribeResource(qp.Namespace, c.Param("kind"), qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, res)
}

View File

@@ -0,0 +1,69 @@
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
"net/http"
)
type ScannersHandler struct {
Data *subproc.DataLayer
}
func (h *ScannersHandler) List(c *gin.Context) {
res := make([]string, 0)
for _, scanner := range h.Data.Scanners {
res = append(res, scanner.Name())
}
c.JSON(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
}
reps := map[string]*subproc.ScanResults{}
for _, scanner := range h.Data.Scanners {
sr, err := scanner.ScanManifests(mnf)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
reps[scanner.Name()] = sr
}
c.IndentedJSON(http.StatusOK, reps)
}
func (h *ScannersHandler) ScanResource(c *gin.Context) {
qp, err := utils.GetQueryProps(c, false)
if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err)
return
}
reps := map[string]*subproc.ScanResults{}
for _, scanner := range h.Data.Scanners {
sr, err := scanner.ScanResource(qp.Namespace, c.Param("kind"), qp.Name)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
reps[scanner.Name()] = sr
}
c.IndentedJSON(http.StatusOK, reps)
}

View File

@@ -0,0 +1,107 @@
package scanners
import (
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
log "github.com/sirupsen/logrus"
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"strconv"
"strings"
)
type Checkov struct {
Data *subproc.DataLayer
}
func (c *Checkov) Name() string {
return "Checkov"
}
func (c *Checkov) Test() bool {
res, err := utils.RunCommand([]string{"checkov", "--version"}, nil)
if err != nil {
return false
}
log.Infof("Discovered Checkov version: %s", strings.TrimSpace(res))
return true
}
func (c *Checkov) ScanManifests(mnf string) (*subproc.ScanResults, error) {
fname, fclose, err := utils.TempFile(mnf)
if err != nil {
return nil, err
}
defer fclose()
cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "cli", "--file", fname}
out, err := utils.RunCommand(cmd, nil)
if err != nil {
return nil, err
}
res := &subproc.ScanResults{}
res.OrigReport = out
return res, nil
}
func (c *Checkov) ScanResource(ns string, kind string, name string) (*subproc.ScanResults, error) {
carp := v1.Carp{}
carp.Kind = kind
carp.Name = name
mnf, err := c.Data.GetResourceYAML(ns, &carp)
if err != nil {
return nil, err
}
fname, fclose, err := utils.TempFile(mnf)
if err != nil {
return nil, err
}
defer fclose()
cmd := []string{"checkov", "--quiet", "--soft-fail", "--framework", "kubernetes", "--output", "cli", "--file", fname}
out, err := utils.RunCommand(cmd, nil)
if err != nil {
return nil, err
}
res := subproc.ScanResults{}
_, out, _ = strings.Cut(out, "\n") // kubernetes scan results:
_, out, _ = strings.Cut(out, "\n") // empty line
line, out, found := strings.Cut(out, "\n") // status line
if found {
parts := strings.FieldsFunc(line, func(r rune) bool {
return r == ':' || r == ','
})
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
res.PassedCount = cnt
} else {
log.Warnf("Failed to parse Checkov output: %s", err)
}
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[3])); err == nil {
res.FailedCount = cnt
} else {
log.Warnf("Failed to parse Checkov output: %s", err)
}
} else {
log.Warnf("Failed to parse Checkov output")
}
res.OrigReport = strings.TrimSpace(out)
return &res, nil
}
type CheckovResults struct {
Summary CheckovSummary
}
type CheckovSummary struct {
Failed int `json:"failed"`
Passed int `json:"passed"`
ResourceCount int `json:"resource_count"`
// parsing errors?
// skipped ?
}

View File

@@ -0,0 +1,87 @@
package scanners
import (
"github.com/komodorio/helm-dashboard/pkg/dashboard/subproc"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
log "github.com/sirupsen/logrus"
"strconv"
"strings"
)
type Trivy struct {
Data *subproc.DataLayer
}
func (c *Trivy) Name() string {
return "Trivy"
}
func (c *Trivy) Test() bool {
res, err := utils.RunCommand([]string{"trivy", "--version"}, nil)
if err != nil {
return false
}
parts := strings.Split(res, "\n")
log.Infof("Discovered Trivy: %s", strings.TrimSpace(parts[0]))
return true
}
func (c *Trivy) ScanManifests(_ string) (*subproc.ScanResults, error) {
return nil, nil // Trivy is unable to scan manifests
}
func (c *Trivy) scanResource(ns string, kind string, name string) (string, error) {
cmd := []string{"trivy", "kubernetes", "--quiet", "--format", "table", "--report", "all", "--no-progress",
"--context", c.Data.KubeContext, "--namespace", ns, kind + "/" + name}
out, err := utils.RunCommand(cmd, nil)
if err != nil {
return "", err
}
return out, nil
}
func (c *Trivy) ScanResource(ns string, kind string, name string) (*subproc.ScanResults, error) {
res := subproc.ScanResults{}
resource, err := c.scanResource(ns, kind, name)
if err != nil {
return nil, err
}
for _, line := range strings.Split(resource, "\n") {
if strings.HasPrefix(line, "Tests:") {
parts := strings.FieldsFunc(line, func(r rune) bool {
return r == ':' || r == ',' || r == ')'
})
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[2])); err == nil {
res.PassedCount += cnt
} else {
log.Warnf("Failed to parse Trivy output: %s", err)
}
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[4])); err == nil {
res.FailedCount += cnt
} else {
log.Warnf("Failed to parse Trivy output: %s", err)
}
}
if strings.HasPrefix(line, "Total:") {
parts := strings.FieldsFunc(line, func(r rune) bool {
return r == ':' || r == ',' || r == '('
})
if cnt, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
res.FailedCount += cnt
} else {
log.Warnf("Failed to parse Trivy output: %s", err)
}
}
}
res.OrigReport = resource
return &res, nil
}

124
pkg/dashboard/server.go Normal file
View File

@@ -0,0 +1,124 @@
package dashboard
import (
"context"
"encoding/json"
"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"
"net/http"
"os"
"time"
)
func StartServer(version string) (string, utils.ControlChan) {
data := subproc.DataLayer{}
err := data.CheckConnectivity()
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?
}
data.VersionInfo = &subproc.VersionInfo{CurVer: version}
go checkUpgrade(data.VersionInfo)
discoverScanners(&data)
address := os.Getenv("HD_BIND")
if address == "" {
address = "localhost"
}
if os.Getenv("HD_PORT") == "" {
address += ":8080" // TODO: better default port to clash less?
} else {
address += ":" + os.Getenv("HD_PORT")
}
abort := make(utils.ControlChan)
api := NewRouter(abort, &data)
done := startBackgroundServer(address, api, abort)
return "http://" + address, done
}
func startBackgroundServer(addr string, routes *gin.Engine, abort utils.ControlChan) utils.ControlChan {
done := make(utils.ControlChan)
server := &http.Server{Addr: addr, Handler: routes}
go func() {
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
panic(err) // TODO: in case of "port busy", check that it's another instance of us and just open browser
}
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 discoverScanners(data *subproc.DataLayer) {
potential := []subproc.Scanner{
&scanners.Checkov{Data: data},
&scanners.Trivy{Data: data},
}
data.Scanners = []subproc.Scanner{}
for _, scanner := range potential {
if scanner.Test() {
data.Scanners = append(data.Scanners, scanner)
}
}
}
func checkUpgrade(d *subproc.VersionInfo) {
url := "https://api.github.com/repos/komodorio/helm-dashboard/releases/latest"
type GHRelease struct {
Name string `json:"name"`
}
var myClient = &http.Client{Timeout: 5 * time.Second}
r, err := myClient.Get(url)
if err != nil {
log.Warnf("Failed to check for new version: %s", err)
return
}
defer r.Body.Close()
target := new(GHRelease)
err = json.NewDecoder(r.Body).Decode(target)
if err != nil {
log.Warnf("Failed to decode new release version: %s", err)
return
}
d.LatestVer = target.Name
v1, err := version.NewVersion(d.CurVer)
if err != nil {
log.Warnf("Failed to parse version: %s", err)
v1 = &version.Version{}
}
v2, err := version.NewVersion(d.LatestVer)
if err != nil {
log.Warnf("Failed to parse version: %s", err)
} else {
if v1.LessThan(v2) {
log.Warnf("Newer Helm Dashboard version is available: %s", d.LatestVer)
log.Warnf("Upgrade instructions: https://github.com/komodorio/helm-dashboard#installing")
} else {
log.Debugf("Got latest version from GH: %s", d.LatestVer)
}
}
}

View File

@@ -0,0 +1,332 @@
$("#btnUpgradeCheck").click(function () {
const self = $(this)
self.find(".bi-repeat").hide()
self.find(".spinner-border").show()
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) {
reportError("Failed to update chart repo", xhr)
}).done(function () {
self.find(".spinner-border").hide()
self.find(".bi-repeat").show()
checkUpgradeable(self.data("chart"))
$("#btnUpgradeCheck").prop("disabled", true)
})
})
function checkUpgradeable(name) {
$.getJSON("/api/helm/repo/search?name=" + name).fail(function (xhr) {
reportError("Failed to find chart in repo", xhr)
}).done(function (data) {
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)
return
}
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())
const canUpgrade = isNewerVersion(verCur, elm.version);
$("#btnUpgradeCheck").prop("disabled", false)
if (canUpgrade) {
$("#btnUpgrade span").text("Upgrade to " + elm.version)
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-arrow-up")
} else {
$("#btnUpgrade span").text("Reconfigure")
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-pencil")
}
$("#btnUpgrade").off("click").click(function () {
popUpUpgrade(elm, getHashParam("namespace"), getHashParam("chart"), verCur, $("#specRev").data("last-rev"))
})
})
}
function popUpUpgrade(elm, ns, name, verCur, lastRev) {
$("#upgradeModal .btn-confirm").prop("disabled", true)
$('#upgradeModal').data("chart", elm.name).data("initial", !verCur)
$("#upgradeModalLabel .name").text(elm.name)
if (verCur) {
$("#upgradeModal .ver-old").show().find("span").text(verCur)
$("#upgradeModal .rel-name").prop("disabled", true).val(name)
$("#upgradeModal .rel-ns").prop("disabled", true).val(ns)
} else {
$("#upgradeModal .ver-old").hide()
$("#upgradeModal .rel-name").prop("disabled", false).val(elm.name.split("/").pop())
$("#upgradeModal .rel-ns").prop("disabled", false).val("")
}
$.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 = $("<option value='" + vers[i].version + "'></option>");
if (vers[i].version === verCur) {
opt.html(vers[i].version + " &middot;")
} else {
opt.html(vers[i].version)
}
$('#upgradeModal select').append(opt)
}
$('#upgradeModal select').val(elm.version).trigger("change")
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)
}
})
}
$("#upgradeModal .btn-confirm").click(function () {
const btnConfirm = $("#upgradeModal .btn-confirm")
btnConfirm.prop("disabled", true).prepend('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.ajax({
type: 'POST',
url: "/api/helm/charts/install" + upgradeModalQstr() + "&flag=true",
data: $("#upgradeModal textarea").data("dirty") ? $("#upgradeModal form").serialize() : null,
}).fail(function (xhr) {
reportError("Failed to upgrade the chart", xhr)
}).done(function (data) {
if (data.version) {
setHashParam("section", null)
const ns = $("#upgradeModal .rel-ns").val();
setHashParam("namespace", ns ? ns : "default")
setHashParam("chart", $("#upgradeModal .rel-name").val())
setHashParam("revision", data.version)
window.location.reload()
} else {
reportError("Failed to get new revision number")
}
})
})
let reconfigTimeout = null;
function changeTimer() {
const self = $(this);
self.data("dirty", true)
if (reconfigTimeout) {
window.clearTimeout(reconfigTimeout)
}
reconfigTimeout = window.setTimeout(function () {
requestChangeDiff()
}, 500)
}
$("#upgradeModal textarea").keyup(changeTimer)
$("#upgradeModal .rel-name").keyup(changeTimer)
$("#upgradeModal .rel-ns").keyup(changeTimer)
$('#upgradeModal select').change(function () {
const self = $(this)
requestChangeDiff()
// fill reference values
$("#upgradeModal .ref-vals").html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.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)
})
})
$('#upgradeModal .btn-scan').click(function () {
const self = $(this)
self.prop("disabled", true).prepend('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.ajax({
type: "POST",
url: "/api/scanners/manifests" + upgradeModalQstr(),
data: $("#upgradeModal form").serialize(),
}).fail(function (xhr) {
reportError("Failed to scan the manifest", xhr)
}).done(function (data) {
self.prop("disabled", false).find(".spinner-border").hide()
const container = $("<div></div>")
for (let name in data) {
const res = data[name]
if (!res) {
continue
}
const pre = $("<pre></pre>").text(res.OrigReport)
container.append("<h2>" + name + " Scan Results</h2>")
container.append(pre)
}
const tab = window.open('about:blank', '_blank');
tab.document.write(container.prop('outerHTML')); // where 'html' is a variable containing your HTML
tab.document.close(); // to finish loading the page
})
})
function requestChangeDiff() {
const self = $('#upgradeModal select');
const diffBody = $("#upgradeModalBody");
diffBody.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Calculating diff...')
$("#upgradeModal .btn-confirm").prop("disabled", true)
let values = null;
if ($("#upgradeModal textarea").data("dirty")) {
$("#upgradeModal .invalid-feedback").hide()
values = $("#upgradeModal form").serialize()
try {
jsyaml.load($("#upgradeModal textarea").val())
} catch (e) {
$("#upgradeModal .invalid-feedback").text("YAML parse error: " + e.message).show()
$("#upgradeModalBody").html("Invalid values YAML")
return
}
}
$.ajax({
type: "POST",
url: "/api/helm/charts/install" + upgradeModalQstr(),
data: values,
}).fail(function (xhr) {
$("#upgradeModalBody").html("<p class='text-danger'>Failed to get upgrade info: " + xhr.responseText + "</p>")
}).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()
if ($("#upgradeModal").data("initial")) {
qstr += "&initial=true"
}
return qstr
}
const btnConfirm = $("#confirmModal .btn-confirm");
$("#btnUninstall").click(function () {
const chart = getHashParam('chart');
const namespace = getHashParam('namespace');
const revision = $("#specRev").data("last-rev")
$("#confirmModalLabel").html("Uninstall <b class='text-danger'>" + chart + "</b> from namespace <b class='text-danger'>" + namespace + "</b>")
$("#confirmModalBody").empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
btnConfirm.prop("disabled", true).off('click').click(function () {
btnConfirm.prop("disabled", true).append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
const url = "/api/helm/charts?namespace=" + namespace + "&name=" + chart;
$.ajax({
url: url,
type: 'DELETE',
}).fail(function (xhr) {
reportError("Failed to delete the chart", xhr)
}).done(function () {
window.location.href = "/"
})
})
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
$.getJSON(url).fail(function (xhr) {
reportError("Failed to get list of resources", xhr)
}).done(function (data) {
$("#confirmModalBody").empty().append("<p>Following resources will be deleted from the cluster:</p>");
btnConfirm.prop("disabled", false)
for (let i = 0; i < data.length; i++) {
const res = data[i]
$("#confirmModalBody").append("<p class='row'><i class='col-sm-3 text-end'>" + res.kind + "</i><b class='col-sm-9'>" + res.metadata.name + "</b></p>")
}
})
})
$("#btnRollback").click(function () {
const chart = getHashParam('chart');
const namespace = getHashParam('namespace');
const revisionNew = $("#btnRollback").data("rev")
const revisionCur = $("#specRev").data("last-rev")
$("#confirmModalLabel").html("Rollback <b class='text-danger'>" + chart + "</b> from revision " + revisionCur + " to " + revisionNew)
$("#confirmModalBody").empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
btnConfirm.prop("disabled", true).off('click').click(function () {
btnConfirm.prop("disabled", true).append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
const url = "/api/helm/charts/rollback?namespace=" + namespace + "&name=" + chart + "&revision=" + revisionNew;
$.ajax({
url: url,
type: 'POST',
}).fail(function (xhr) {
reportError("Failed to rollback the chart", xhr)
}).done(function () {
window.location.reload()
})
})
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"
url += "?" + qstr
$.get(url).fail(function (xhr) {
reportError("Failed to get list of resources", xhr)
}).done(function (data) {
$("#confirmModalBody").empty();
$("#confirmModal .btn-confirm").prop("disabled", false)
const targetElement = document.getElementById('confirmModalBody');
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) {
$("#confirmModalBody").prepend("<p>Following changes will happen to cluster:</p>")
} else {
$("#confirmModalBody").html("<p>No changes will happen to cluster</p>")
}
})
})

View File

@@ -0,0 +1,26 @@
(function(h,o,u,n,d) {
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
d=o.createElement(u);d.async=1;d.src=n
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
})(window,document,'script','https://www.datadoghq-browser-agent.com/datadog-rum-v4.js','DD_RUM')
DD_RUM.onReady(function() {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
const version = JSON.parse(xhr.responseText).VerCur;
if (xhr.readyState === XMLHttpRequest.DONE && version!=="dev") {
DD_RUM.init({
clientToken: 'pub16d64cd1c00cf073ce85af914333bf72',
applicationId: 'e75439e5-e1b3-46ba-a9e9-a2e58579a2e2',
site: 'datadoghq.com',
service: 'helm-dashboard',
version: version,
trackInteractions: true,
trackResources: true,
trackLongTasks: true,
defaultPrivacyLevel: 'mask'
})
}
}
xhr.open('GET', '/status', true);
xhr.send(null);
})

View File

@@ -0,0 +1,261 @@
function revisionClicked(namespace, name, self) {
let active = "active border-primary border-1 bg-white";
let inactive = "border-secondary bg-secondary";
revRow.find(".active").removeClass(active).addClass(inactive)
self.removeClass(inactive).addClass(active)
const elm = self.data("elm")
setHashParam("revision", elm.revision)
$("#sectionDetails span.rev").text("#" + elm.revision)
statusStyle(elm.status, $("#none"), $("#sectionDetails .rev-details .rev-status"))
const rdate = luxon.DateTime.fromISO(elm.updated);
$("#sectionDetails .rev-date").text(rdate.toJSDate().toLocaleString())
$("#sectionDetails .rev-tags .rev-chart").text(elm.chart)
$("#sectionDetails .rev-tags .rev-app").text(elm.app_version)
$("#sectionDetails .rev-tags .rev-ns").text(getHashParam("namespace"))
$("#sectionDetails .rev-tags .rev-cluster").text(getHashParam("context"))
$("#revDescr").text(elm.description).removeClass("text-danger")
if (elm.status === "failed") {
$("#revDescr").addClass("text-danger")
}
const rev = $("#specRev").data("last-rev") == elm.revision ? elm.revision - 1 : elm.revision
if (!rev || getHashParam("revision") === $("#specRev").data("first-rev")) {
$("#btnRollback").hide()
} else {
$("#btnRollback").show().data("rev", rev).find("span").text("Rollback to #" + rev)
}
const tab = getHashParam("tab")
if (!tab) {
$("#nav-tab [data-tab=resources]").click()
} else {
$("#nav-tab [data-tab=" + tab + "]").click()
}
}
function loadContentWrapper() {
let revDiff = 0
const revision = parseInt(getHashParam("revision"));
if (revision === $("#specRev").data("first-rev")) {
revDiff = 0
} else if (getHashParam("mode") === "diff-prev") {
revDiff = revision - 1
} else if (getHashParam("mode") === "diff-rev") {
revDiff = $("#specRev").val()
}
const flag = $("#userDefinedVals").prop("checked");
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
if (revDiff) {
qstr += "&revisionDiff=" + revDiff
}
if (flag) {
qstr += "&flag=" + flag
}
let url = "/api/helm/charts/" + mode
url += "?" + qstr
const diffDisplay = $("#manifestText");
diffDisplay.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.get(url).fail(function (xhr) {
reportError("Failed to get diff of " + mode, xhr)
}).done(function (data) {
diffDisplay.empty();
if (data === "") {
diffDisplay.text("No differences to display")
} else {
if (revDiff) {
const targetElement = document.getElementById('manifestText');
const configuration = {
inputFormat: 'diff', outputFormat: 'side-by-side',
drawFileList: false, showFiles: false, highlight: true, //matching: 'lines',
};
const diff2htmlUi = new Diff2HtmlUI(targetElement, data, configuration);
diff2htmlUi.draw()
} else {
data = hljs.highlight(data, {language: 'yaml'}).value
const code = $("#manifestText").empty().append("<pre class='bg-white rounded p-3'></pre>").find("pre");
code.html(data)
}
}
})
}
$('#specRev').keyup(function (event) {
let keycode = (event.keyCode ? event.keyCode : event.which);
if (keycode == '13') {
$("#diffModeRev").click()
}
});
$("form").submit(function (e) {
e.preventDefault();
});
$("#userDefinedVals").change(function () {
const self = $(this)
const flag = $("#userDefinedVals").prop("checked");
setHashParam("udv", flag)
loadContentWrapper()
})
$("#modePanel [data-mode]").click(function () {
const self = $(this)
const mode = self.data("mode")
setHashParam("mode", mode)
loadContentWrapper()
})
$("#nav-tab [data-tab]").click(function () {
const self = $(this)
setHashParam("tab", self.data("tab"))
if (self.data("tab") === "values") {
$("#userDefinedVals").parent().show()
} else {
$("#userDefinedVals").parent().hide()
}
const flag = getHashParam("udv") === "true";
$("#userDefinedVals").prop("checked", flag)
if (self.data("tab") === "resources") {
showResources(getHashParam("namespace"), getHashParam("chart"), getHashParam("revision"))
} else {
const mode = getHashParam("mode")
if (!mode) {
$("#modePanel [data-mode=view]").trigger('click')
} else {
$("#modePanel [data-mode=" + mode + "]").trigger('click')
}
}
})
function showResources(namespace, chart, revision) {
const resBody = $("#nav-resources .body");
resBody.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>');
let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revision
let url = "/api/helm/charts/resources"
url += "?" + qstr
$.getJSON(url).fail(function (xhr) {
reportError("Failed to get list of resources", xhr)
}).done(function (data) {
resBody.empty();
for (let i = 0; i < data.length; i++) {
const res = data[i]
const resBlock = $(`
<div class="row px-3 py-2 mb-3 bg-white rounded">
<div class="col-2 res-kind text-break"></div>
<div class="col-3 res-name text-break fw-bold"></div>
<div class="col-1 res-status overflow-hidden"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></div>
<div class="col-4 res-statusmsg"><span class="text-muted small">Getting status...</span></div>
<div class="col-2 res-actions"></div>
</div>
`)
resBlock.find(".res-kind").text(res.kind)
resBlock.find(".res-name").text(res.metadata.name)
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 () {
//reportError("Failed to get list of resources")
}).done(function (data) {
const badge = $("<span class='badge me-2 fw-normal'></span>").text(data.status.phase);
if (["Available", "Active", "Established"].includes(data.status.phase)) {
badge.addClass("bg-success text-dark")
} else if (["Exists"].includes(data.status.phase)) {
badge.addClass("bg-success text-dark bg-opacity-50")
} else if (["Progressing"].includes(data.status.phase)) {
badge.addClass("bg-warning")
} else {
badge.addClass("bg-danger")
}
const statusBlock = resBlock.find(".res-status");
statusBlock.empty().append(badge).attr("title", data.status.phase)
resBlock.find(".res-statusmsg").html("<span class='text-muted small'>" + (data.status.message ? data.status.message : '') + "</span>")
if (badge.text() !== "NotFound") {
resBlock.find(".res-actions")
const btn = $("<button class=\"btn btn-sm btn-white border-secondary\">Describe</button>");
resBlock.find(".res-actions").append(btn)
btn.click(function () {
showDescribe(ns, res.kind, res.metadata.name, badge.clone())
})
const btn2 = $("<button class='btn btn-sm btn-white border-secondary ms-2'>Scan</button>");
resBlock.find(".res-actions").append(btn2)
btn2.click(function () {
scanResource(ns, res.kind, res.metadata.name, badge.clone())
})
}
})
}
})
}
function showDescribe(ns, kind, name, badge) {
$("#describeModal .offcanvas-header p").text(kind)
$("#describeModalLabel").text(name).append(badge.addClass("ms-3 small fw-normal"))
$("#describeModalBody").empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal'));
myModal.show()
$.get("/api/kube/describe/" + kind.toLowerCase() + "?name=" + name + "&namespace=" + ns).fail(function (xhr) {
reportError("Failed to describe resource", xhr)
}).done(function (data) {
data = hljs.highlight(data, {language: 'yaml'}).value
$("#describeModalBody").empty().append("<pre class='bg-white rounded p-3'></pre>").find("pre").html(data)
})
}
function scanResource(ns, kind, name, badge) {
$("#describeModal .offcanvas-header p").text(kind)
$("#describeModalLabel").text(name).append(badge.addClass("ms-3 small fw-normal"))
const body = $("#describeModalBody");
body.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Scanning...')
const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal'));
myModal.show()
$.get("/api/scanners/resource/" + kind.toLowerCase() + "?name=" + name + "&namespace=" + ns).fail(function (xhr) {
reportError("Failed to scan resource", xhr)
}).done(function (data) {
body.empty()
if ($.isEmptyObject(data)) {
body.append("No information from scanners. Make sure you have installed some and scanned object is supported.")
}
for (let name in data) {
const res = data[name]
if (!res.OrigReport) continue
const hdr = $("<h3>" + name + " Scan Results</h3>");
if (res.FailedCount) {
hdr.append("<span class='badge bg-danger ms-3'>" + res.FailedCount + " failed</span>")
}
if (res.PassedCount) {
hdr.append("<span class='badge bg-info ms-3'>" + res.PassedCount + " passed</span>")
}
body.append(hdr)
const hl = hljs.highlight(res.OrigReport, {language: 'yaml'}).value
const pre = $("<pre class='bg-white rounded p-3' style='font-size: inherit; overflow: unset'></pre>").html(hl)
body.append(pre)
}
})
}

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
version="1.1"
id="svg886"
sodipodi:docname="helm-gray-50.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs890" />
<sodipodi:namedview
id="namedview888"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="64.928571"
inkscape:cx="13.992299"
inkscape:cy="14"
inkscape:window-width="3840"
inkscape:window-height="2059"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg886" />
<path
d="M7.64558 6.78334C7.61355 6.75296 7.57868 6.72027 7.54422 6.68716C6.83768 6.00837 6.29089 5.22352 5.9606 4.29585C5.86816 4.03621 5.79837 3.7714 5.81075 3.49176C5.81193 3.46522 5.81185 3.4386 5.81368 3.41212C5.8386 3.05114 6.08019 2.86874 6.43295 2.95422C6.54422 2.98304 6.65188 3.0243 6.75391 3.07723C7.13976 3.27073 7.45426 3.55679 7.74346 3.87051C8.25713 4.41715 8.66907 5.05112 8.95989 5.74257C8.96624 5.75904 8.97352 5.77514 8.9817 5.79078C8.98569 5.79798 8.99415 5.8027 9.01302 5.81984C10.3898 4.99388 11.9471 4.51602 13.5501 4.42764C13.5403 4.37858 13.5344 4.34108 13.5251 4.30444C13.3615 3.62754 13.3113 2.9282 13.3765 2.23488C13.4052 1.81941 13.4889 1.40957 13.6254 1.01611C13.6914 0.808874 13.7954 0.615724 13.9321 0.446513C13.9837 0.386073 14.0433 0.33292 14.1092 0.288509C14.1749 0.242074 14.2532 0.216879 14.3337 0.216318C14.4141 0.215757 14.4927 0.239858 14.5591 0.285373C14.6992 0.380274 14.8119 0.510259 14.8861 0.662357C15.0243 0.920312 15.1236 1.19726 15.1808 1.48424C15.3112 2.09109 15.3513 2.71388 15.2997 3.33244C15.2741 3.70997 15.2086 4.08373 15.1042 4.44745C15.503 4.52092 15.8999 4.57785 16.2884 4.67017C16.6761 4.76031 17.0583 4.87256 17.4331 5.00635C17.811 5.14505 18.1807 5.30527 18.5402 5.48623C18.8956 5.66344 19.2338 5.87491 19.5884 6.07638C19.5999 6.05213 19.6167 6.02319 19.628 5.99226C19.9973 4.97054 20.6335 4.06633 21.4704 3.37357C21.6658 3.20585 21.8901 3.07522 22.1324 2.98812C22.1992 2.96486 22.2682 2.94899 22.3384 2.9408C22.6892 2.90063 22.8365 3.12136 22.8624 3.38498C22.8818 3.57931 22.8672 3.77555 22.8191 3.96484C22.6909 4.45379 22.4883 4.92009 22.2182 5.34735C21.8379 5.96177 21.3866 6.51522 20.813 6.96185C20.796 6.97503 20.7812 6.99087 20.7525 7.01731C21.3135 7.53479 21.8132 8.11507 22.2417 8.74672C22.2106 8.75474 22.179 8.76031 22.1471 8.76339C21.5538 8.76423 20.9604 8.76237 20.3671 8.76597C20.3321 8.76513 20.2979 8.75614 20.267 8.73971C20.2362 8.72327 20.2096 8.69986 20.1894 8.67133C18.8949 7.25696 17.1496 6.33565 15.2515 6.06462C14.6902 5.98295 14.1218 5.961 13.5558 5.99914C11.8683 6.10218 10.2539 6.72438 8.93373 7.78053C8.59283 8.04942 8.27485 8.34616 7.98306 8.66767C7.95717 8.6998 7.92412 8.72543 7.88656 8.74252C7.84899 8.75961 7.80796 8.76768 7.76672 8.76609C7.19997 8.76215 6.63318 8.76413 6.0664 8.76413H5.94588C5.98049 8.62928 6.32893 8.15161 6.72336 7.72518C7.01749 7.40716 7.32909 7.10529 7.64558 6.78334Z"
fill="#3B3D45"
id="path874"
style="opacity:1;fill:#3b3d45;fill-opacity:0.5" />
<path
d="M22.0936 19.4833C21.6995 20.035 21.2496 20.5447 20.7511 21.0044C20.7909 21.0375 20.8231 21.0644 20.8554 21.0913C21.7206 21.799 22.3734 22.7321 22.7417 23.7874C22.8397 24.0448 22.8817 24.3201 22.865 24.595C22.8598 24.6654 22.8458 24.7348 22.8231 24.8017C22.7946 24.8964 22.7324 24.9774 22.6482 25.0292C22.564 25.0811 22.4636 25.1002 22.3663 25.083C22.2348 25.0659 22.107 25.028 21.9875 24.9706C21.8051 24.8806 21.6327 24.7715 21.4734 24.645C20.634 23.9554 19.9967 23.0516 19.629 22.0294C19.6185 22.0007 19.607 21.9723 19.5885 21.9243C19.1415 22.2198 18.6734 22.482 18.1878 22.7087C17.7046 22.929 17.205 23.1111 16.6934 23.2533C16.1735 23.3944 15.6436 23.4952 15.1083 23.555C15.1177 23.6021 15.1231 23.6396 15.1328 23.6759C15.3026 24.342 15.3588 25.032 15.2992 25.7167C15.2768 26.146 15.1934 26.5698 15.0515 26.9755C14.9839 27.1434 14.906 27.3069 14.8183 27.4652C14.7827 27.5266 14.7386 27.5826 14.6873 27.6315C14.4644 27.8617 14.1983 27.8636 13.9811 27.6274C13.8952 27.5322 13.8217 27.4265 13.7623 27.3128C13.59 26.9894 13.5013 26.6377 13.438 26.2791C13.3565 25.7895 13.331 25.2923 13.3619 24.797C13.3787 24.4343 13.4328 24.0743 13.5234 23.7226C13.5312 23.6928 13.5384 23.6627 13.5442 23.6325C13.5457 23.6248 13.5406 23.6158 13.5346 23.5911C11.9318 23.5005 10.3754 23.0201 9.0004 22.1915C8.97745 22.2424 8.95773 22.2853 8.93871 22.3284C8.55337 23.2275 7.96033 24.0225 7.20825 24.648C7.00915 24.8181 6.78047 24.9502 6.53361 25.0377C6.41792 25.0841 6.29167 25.0977 6.16876 25.0769C6.10089 25.0647 6.03733 25.0352 5.9843 24.9911C5.93126 24.9471 5.89056 24.89 5.86618 24.8255C5.78687 24.6338 5.80092 24.4343 5.82785 24.2366C5.87203 23.9609 5.95317 23.6925 6.06908 23.4385C6.41254 22.6386 6.91804 21.9186 7.55371 21.3238C7.57941 21.2995 7.60578 21.2758 7.63103 21.251C7.63906 21.2395 7.64591 21.2272 7.65149 21.2143C7.05256 20.6949 6.51739 20.1062 6.05723 19.4606C6.11237 19.4561 6.14925 19.4505 6.18615 19.4505C6.77495 19.4499 7.36377 19.452 7.95254 19.448C7.99171 19.4469 8.03065 19.4544 8.06652 19.4702C8.1024 19.4859 8.13431 19.5095 8.15994 19.5391C8.80004 20.1976 9.54586 20.7444 10.3665 21.1567C11.2347 21.6034 12.1787 21.884 13.1499 21.984C15.7886 22.2405 18.0585 21.4405 19.9595 19.5841C20.002 19.5382 20.0541 19.5021 20.1121 19.4785C20.1701 19.4548 20.2325 19.4442 20.2951 19.4473C20.844 19.4541 21.393 19.4501 21.9419 19.4501H22.0838L22.0936 19.4833Z"
fill="#3B3D45"
id="path876"
style="opacity:1;fill:#3b3d45;fill-opacity:0.5" />
<path
d="M19.6412 11.0745C19.7972 11.0745 19.9475 11.0851 20.0956 11.0717C20.2633 11.0565 20.3834 11.1165 20.5057 11.2292C21.212 11.88 21.9257 12.5229 22.637 13.1683C22.6728 13.2008 22.7093 13.2324 22.7552 13.273C22.798 13.2362 22.8381 13.2034 22.8764 13.1686C23.6096 12.5012 24.3422 11.8331 25.0743 11.1645C25.1047 11.1332 25.1414 11.1088 25.182 11.0929C25.2226 11.077 25.2662 11.07 25.3097 11.0724C25.49 11.0797 25.6707 11.0745 25.8608 11.0745V16.9751C25.7643 17.0033 24.4678 17.0089 24.313 16.9785V13.9903L24.283 13.976C23.7784 14.4362 23.2738 14.8964 22.7577 15.3671C22.241 14.9017 21.7305 14.4418 21.22 13.9819L21.1906 13.9927C21.1893 14.2421 21.1902 14.4915 21.19 14.7409C21.1899 14.9888 21.1899 15.2367 21.19 15.4846V16.9894H19.654C19.6253 16.8901 19.6119 11.4083 19.6412 11.0745Z"
fill="#3B3D45"
id="path878"
style="opacity:1;fill:#3b3d45;fill-opacity:0.5" />
<path
d="M5.46747 11.0815H6.99421C7.02504 11.1797 7.03107 16.8479 6.99951 16.9909H5.47141C5.46299 16.6155 5.46875 16.2414 5.4677 15.8675C5.46665 15.4966 5.46747 15.1258 5.46747 14.7453H3.57536V16.9708C3.46003 17.0052 2.15667 17.0085 2.0271 16.9777V11.0822H3.56924V13.1648C3.67944 13.1966 5.30094 13.2025 5.46607 13.172C5.46653 13.0053 5.46719 12.8346 5.46742 12.6638C5.46765 12.4867 5.46767 12.3096 5.46747 12.1325C5.46747 11.9599 5.46747 11.7872 5.46747 11.6145C5.46747 11.4422 5.46747 11.2698 5.46747 11.0815Z"
fill="#3B3D45"
id="path880"
style="opacity:1;fill:#3b3d45;fill-opacity:0.5" />
<path
d="M8.82422 16.9889V11.0991C8.91477 11.0695 12.2707 11.0579 12.4901 11.0877V12.3429C12.4409 12.3464 12.3901 12.3531 12.3393 12.3532C11.7417 12.354 11.144 12.3541 10.5463 12.3537H10.3801V13.33H12.2476V14.6287H10.3968C10.3659 14.7399 10.3574 15.5145 10.3825 15.7289C10.4298 15.7321 10.4805 15.7384 10.5312 15.7384C11.1289 15.7391 11.7265 15.7393 12.3242 15.7389H12.4905V16.9889H8.82422Z"
fill="#3B3D45"
id="path882"
style="opacity:1;fill:#3b3d45;fill-opacity:0.5" />
<path
d="M14.2399 16.991C14.2118 16.833 14.2175 11.1893 14.2453 11.082H15.7664V15.4368C15.832 15.4403 15.8835 15.4452 15.935 15.4452C16.5371 15.4458 17.1392 15.4459 17.7413 15.4456C17.7932 15.4456 17.845 15.4456 17.9042 15.4456V16.991L14.2399 16.991Z"
fill="#3B3D45"
id="path884"
style="opacity:1;fill:#3b3d45;fill-opacity:0.5" />
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -0,0 +1,8 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.64558 6.78334C7.61355 6.75296 7.57868 6.72027 7.54422 6.68716C6.83768 6.00837 6.29089 5.22352 5.9606 4.29585C5.86816 4.03621 5.79837 3.7714 5.81075 3.49176C5.81193 3.46522 5.81185 3.4386 5.81368 3.41212C5.8386 3.05114 6.08019 2.86874 6.43295 2.95422C6.54422 2.98304 6.65188 3.0243 6.75391 3.07723C7.13976 3.27073 7.45426 3.55679 7.74346 3.87051C8.25713 4.41715 8.66907 5.05112 8.95989 5.74257C8.96624 5.75904 8.97352 5.77514 8.9817 5.79078C8.98569 5.79798 8.99415 5.8027 9.01302 5.81984C10.3898 4.99388 11.9471 4.51602 13.5501 4.42764C13.5403 4.37858 13.5344 4.34108 13.5251 4.30444C13.3615 3.62754 13.3113 2.9282 13.3765 2.23488C13.4052 1.81941 13.4889 1.40957 13.6254 1.01611C13.6914 0.808874 13.7954 0.615724 13.9321 0.446513C13.9837 0.386073 14.0433 0.33292 14.1092 0.288509C14.1749 0.242074 14.2532 0.216879 14.3337 0.216318C14.4141 0.215757 14.4927 0.239858 14.5591 0.285373C14.6992 0.380274 14.8119 0.510259 14.8861 0.662357C15.0243 0.920312 15.1236 1.19726 15.1808 1.48424C15.3112 2.09109 15.3513 2.71388 15.2997 3.33244C15.2741 3.70997 15.2086 4.08373 15.1042 4.44745C15.503 4.52092 15.8999 4.57785 16.2884 4.67017C16.6761 4.76031 17.0583 4.87256 17.4331 5.00635C17.811 5.14505 18.1807 5.30527 18.5402 5.48623C18.8956 5.66344 19.2338 5.87491 19.5884 6.07638C19.5999 6.05213 19.6167 6.02319 19.628 5.99226C19.9973 4.97054 20.6335 4.06633 21.4704 3.37357C21.6658 3.20585 21.8901 3.07522 22.1324 2.98812C22.1992 2.96486 22.2682 2.94899 22.3384 2.9408C22.6892 2.90063 22.8365 3.12136 22.8624 3.38498C22.8818 3.57931 22.8672 3.77555 22.8191 3.96484C22.6909 4.45379 22.4883 4.92009 22.2182 5.34735C21.8379 5.96177 21.3866 6.51522 20.813 6.96185C20.796 6.97503 20.7812 6.99087 20.7525 7.01731C21.3135 7.53479 21.8132 8.11507 22.2417 8.74672C22.2106 8.75474 22.179 8.76031 22.1471 8.76339C21.5538 8.76423 20.9604 8.76237 20.3671 8.76597C20.3321 8.76513 20.2979 8.75614 20.267 8.73971C20.2362 8.72327 20.2096 8.69986 20.1894 8.67133C18.8949 7.25696 17.1496 6.33565 15.2515 6.06462C14.6902 5.98295 14.1218 5.961 13.5558 5.99914C11.8683 6.10218 10.2539 6.72438 8.93373 7.78053C8.59283 8.04942 8.27485 8.34616 7.98306 8.66767C7.95717 8.6998 7.92412 8.72543 7.88656 8.74252C7.84899 8.75961 7.80796 8.76768 7.76672 8.76609C7.19997 8.76215 6.63318 8.76413 6.0664 8.76413H5.94588C5.98049 8.62928 6.32893 8.15161 6.72336 7.72518C7.01749 7.40716 7.32909 7.10529 7.64558 6.78334Z" fill="#3B3D45"/>
<path d="M22.0936 19.4833C21.6995 20.035 21.2496 20.5447 20.7511 21.0044C20.7909 21.0375 20.8231 21.0644 20.8554 21.0913C21.7206 21.799 22.3734 22.7321 22.7417 23.7874C22.8397 24.0448 22.8817 24.3201 22.865 24.595C22.8598 24.6654 22.8458 24.7348 22.8231 24.8017C22.7946 24.8964 22.7324 24.9774 22.6482 25.0292C22.564 25.0811 22.4636 25.1002 22.3663 25.083C22.2348 25.0659 22.107 25.028 21.9875 24.9706C21.8051 24.8806 21.6327 24.7715 21.4734 24.645C20.634 23.9554 19.9967 23.0516 19.629 22.0294C19.6185 22.0007 19.607 21.9723 19.5885 21.9243C19.1415 22.2198 18.6734 22.482 18.1878 22.7087C17.7046 22.929 17.205 23.1111 16.6934 23.2533C16.1735 23.3944 15.6436 23.4952 15.1083 23.555C15.1177 23.6021 15.1231 23.6396 15.1328 23.6759C15.3026 24.342 15.3588 25.032 15.2992 25.7167C15.2768 26.146 15.1934 26.5698 15.0515 26.9755C14.9839 27.1434 14.906 27.3069 14.8183 27.4652C14.7827 27.5266 14.7386 27.5826 14.6873 27.6315C14.4644 27.8617 14.1983 27.8636 13.9811 27.6274C13.8952 27.5322 13.8217 27.4265 13.7623 27.3128C13.59 26.9894 13.5013 26.6377 13.438 26.2791C13.3565 25.7895 13.331 25.2923 13.3619 24.797C13.3787 24.4343 13.4328 24.0743 13.5234 23.7226C13.5312 23.6928 13.5384 23.6627 13.5442 23.6325C13.5457 23.6248 13.5406 23.6158 13.5346 23.5911C11.9318 23.5005 10.3754 23.0201 9.0004 22.1915C8.97745 22.2424 8.95773 22.2853 8.93871 22.3284C8.55337 23.2275 7.96033 24.0225 7.20825 24.648C7.00915 24.8181 6.78047 24.9502 6.53361 25.0377C6.41792 25.0841 6.29167 25.0977 6.16876 25.0769C6.10089 25.0647 6.03733 25.0352 5.9843 24.9911C5.93126 24.9471 5.89056 24.89 5.86618 24.8255C5.78687 24.6338 5.80092 24.4343 5.82785 24.2366C5.87203 23.9609 5.95317 23.6925 6.06908 23.4385C6.41254 22.6386 6.91804 21.9186 7.55371 21.3238C7.57941 21.2995 7.60578 21.2758 7.63103 21.251C7.63906 21.2395 7.64591 21.2272 7.65149 21.2143C7.05256 20.6949 6.51739 20.1062 6.05723 19.4606C6.11237 19.4561 6.14925 19.4505 6.18615 19.4505C6.77495 19.4499 7.36377 19.452 7.95254 19.448C7.99171 19.4469 8.03065 19.4544 8.06652 19.4702C8.1024 19.4859 8.13431 19.5095 8.15994 19.5391C8.80004 20.1976 9.54586 20.7444 10.3665 21.1567C11.2347 21.6034 12.1787 21.884 13.1499 21.984C15.7886 22.2405 18.0585 21.4405 19.9595 19.5841C20.002 19.5382 20.0541 19.5021 20.1121 19.4785C20.1701 19.4548 20.2325 19.4442 20.2951 19.4473C20.844 19.4541 21.393 19.4501 21.9419 19.4501H22.0838L22.0936 19.4833Z" fill="#3B3D45"/>
<path d="M19.6412 11.0745C19.7972 11.0745 19.9475 11.0851 20.0956 11.0717C20.2633 11.0565 20.3834 11.1165 20.5057 11.2292C21.212 11.88 21.9257 12.5229 22.637 13.1683C22.6728 13.2008 22.7093 13.2324 22.7552 13.273C22.798 13.2362 22.8381 13.2034 22.8764 13.1686C23.6096 12.5012 24.3422 11.8331 25.0743 11.1645C25.1047 11.1332 25.1414 11.1088 25.182 11.0929C25.2226 11.077 25.2662 11.07 25.3097 11.0724C25.49 11.0797 25.6707 11.0745 25.8608 11.0745V16.9751C25.7643 17.0033 24.4678 17.0089 24.313 16.9785V13.9903L24.283 13.976C23.7784 14.4362 23.2738 14.8964 22.7577 15.3671C22.241 14.9017 21.7305 14.4418 21.22 13.9819L21.1906 13.9927C21.1893 14.2421 21.1902 14.4915 21.19 14.7409C21.1899 14.9888 21.1899 15.2367 21.19 15.4846V16.9894H19.654C19.6253 16.8901 19.6119 11.4083 19.6412 11.0745Z" fill="#3B3D45"/>
<path d="M5.46747 11.0815H6.99421C7.02504 11.1797 7.03107 16.8479 6.99951 16.9909H5.47141C5.46299 16.6155 5.46875 16.2414 5.4677 15.8675C5.46665 15.4966 5.46747 15.1258 5.46747 14.7453H3.57536V16.9708C3.46003 17.0052 2.15667 17.0085 2.0271 16.9777V11.0822H3.56924V13.1648C3.67944 13.1966 5.30094 13.2025 5.46607 13.172C5.46653 13.0053 5.46719 12.8346 5.46742 12.6638C5.46765 12.4867 5.46767 12.3096 5.46747 12.1325C5.46747 11.9599 5.46747 11.7872 5.46747 11.6145C5.46747 11.4422 5.46747 11.2698 5.46747 11.0815Z" fill="#3B3D45"/>
<path d="M8.82422 16.9889V11.0991C8.91477 11.0695 12.2707 11.0579 12.4901 11.0877V12.3429C12.4409 12.3464 12.3901 12.3531 12.3393 12.3532C11.7417 12.354 11.144 12.3541 10.5463 12.3537H10.3801V13.33H12.2476V14.6287H10.3968C10.3659 14.7399 10.3574 15.5145 10.3825 15.7289C10.4298 15.7321 10.4805 15.7384 10.5312 15.7384C11.1289 15.7391 11.7265 15.7393 12.3242 15.7389H12.4905V16.9889H8.82422Z" fill="#3B3D45"/>
<path d="M14.2399 16.991C14.2118 16.833 14.2175 11.1893 14.2453 11.082H15.7664V15.4368C15.832 15.4403 15.8835 15.4452 15.935 15.4452C16.5371 15.4458 17.1392 15.4459 17.7413 15.4456C17.7932 15.4456 17.845 15.4456 17.9042 15.4456V16.991L14.2399 16.991Z" fill="#3B3D45"/>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,435 @@
<!doctype html>
<html lang="en">
<head>
<link rel="icon" href="static/logo.png"/>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Helm Dashboard</title>
<script src="static/datadog.js"></script>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto&family=Inter&family=Poppins:wght@600&family=Poppins:wght@500&family=Inter:wght@500&family=Roboto+Slab:wght@400&family=Roboto+Slab:wght@700&family=Roboto:wght@700&family=Roboto:wght@500"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/lightfair.min.css"/>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"/>
<link href="static/styles.css" rel="stylesheet">
<script type="text/javascript">
window.heap = window.heap || [], heap.load = function (e, t) {
window.heap.appid = e, window.heap.config = t = t || {};
var r = document.createElement("script");
r.type = "text/javascript", r.async = !0, r.src = "https://cdn.heapanalytics.com/js/heap-" + e + ".js";
var a = document.getElementsByTagName("script")[0];
a.parentNode.insertBefore(r, a);
for (var n = function (e) {
return function () {
heap.push([e].concat(Array.prototype.slice.call(arguments, 0)))
}
}, p = ["addEventProperties", "addUserProperties", "clearEventProperties", "identify", "resetIdentity", "removeEventProperty", "setEventProperties", "track", "unsetEventProperty"], o = 0; o < p.length; o++) heap[p[o]] = n(p[o])
};
heap.load("3615793373");
</script>
</head>
<body>
<div class="container-fluid px-0">
<!-- TOP BAR -->
<nav class="navbar navbar-expand bg-white mb-0 p-0 b-shadow" id="topNav">
<div class="container-fluid m-0 p-0">
<div class="navbar-brand">
<a href="/"><img src="static/logo.png" alt="Logo"></a>
<div>
<h1><a href="/">Helm Dashboard</a></h1>
</div>
</div>
<div class="separator-vertical mx-3"><span></span></div>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item mx-2">
<a class="nav-link px-3 section-installed">Installed</a>
</li>
<li class="nav-item mx-2">
<a class="nav-link px-3 section-repo">Repository</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown"
aria-expanded="false">
<span class="position-absolute top-50 start-0 translate-middle p-1 bg-danger border border-light rounded-circle new-version-pill display-none">
<span class="visually-hidden">New version</span>
</span>
Help
</a>
<ul class="dropdown-menu fs-80">
<li><a class="dropdown-item" href="https://komodorkommunity.slack.com/archives/C044U1B0265"
target="_blank"><i class="bi-slack"></i> Support Chat</a></li>
<li><a class="dropdown-item" href="https://github.com/komodorio/helm-dashboard" target="_blank"><i
class="bi-github"></i> Project Page</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item disabled" href="#">Version <span id="toolVersion"></span></a></li>
<li class="">
<a class="dropdown-item position-relative" href="https://github.com/komodorio/helm-dashboard#installing" target="_blank">
<span class="position-absolute top-50 start-0 translate-middle p-1 bg-danger border border-light rounded-circle new-version-pill display-none">
<span class="visually-hidden">New version</span>
</span>
Upgrade to <span id="toolVersionUpgrade"></span>
</a></li>
</ul>
</li>
</ul>
<div>
<a class="btn" href="https://komodor.com/?utm_campaign=Helm-Dash&utm_source=helm-dash"><img
src="static/komodor-logo.svg" alt="komodor.io"
style="height: 1.2rem; vertical-align: text-bottom; filter: grayscale(00%);"></a>
</div>
<div class="separator-vertical"><span></span></div>
<i class="btn bi-power text-muted p-2 m-1 mx-2" title="Shut down the Helm Dashboard application"></i>
</div>
</nav>
<!-- /TOP BAR -->
<!--REPO SECTION-->
<div class="row mt-3 pt-3 me-5 section" id="sectionRepo" style="display: none">
<div class="col-3 ps-4 repo-list">
<div class="p-2 bg-white rounded-1 b-shadow">
<h4 class="fs-6">Repositories</h4>
<ul class="list-unstyled p-2">
</ul>
<button class="btn btn-sm border-secondary text-muted">
<i class="bi-plus-lg"></i> Add Repository
</button>
</div>
</div>
<div class="col-9 repo-details bg-white b-shadow pt-4 px-5 overflow-auto rounded">
<div class="float-end">
<button class="me-2 btn btn-sm btn-light bg-white border border-secondary btn-update">
<i class="bi-arrow-repeat"></i> Update
</button>
<button class="btn btn-sm btn-light bg-white border border-secondary btn-remove">
<i class="bi-trash3"></i> Remove
</button>
</div>
<div><span class="text-muted small fw-bold me-3">REPOSITORY</span></div>
<h2 class="mb-3">name-of-repo</h2>
<div class="mb-5">
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">URL: <span class="url fw-bold">http://somerepo/somepath</span></span>
</div>
<div class="float-end">
<!-- TODO <input class="form-control form-control-sm" type="text" placeholder="Filter..."> -->
</div>
<div class="row bg-secondary rounded px-3 py-2 mb-3 fw-bold small"
style="text-transform: uppercase">
<div class="col-3">Chart Name</div>
<div class="col">Description</div>
<div class="col-1">Version</div>
<div class="col-1"></div>
</div>
<ul class="list-unstyled mt-4"></ul>
</div>
</div>
<div class="row mt-3 pt-3 me-5 section" id="sectionList" style="display: none">
<div class="col-2 ms-3">
<!-- FILTER BLOCK -->
<div class="p-2 ps-2 bg-white rounded-1 b-shadow" id="filters">
<form>
<h4>Clusters</h4>
<ul class="list-unstyled" id="cluster">
</ul>
<!-- TODO
<h4 class="mt-4">Namespaces</h4>
<ul class="list-unstyled" id="namespaces">
</ul>
-->
</form>
</div>
<!-- /FILTER BLOCK -->
</div>
<!-- INSTALLED LIST -->
<div class="col ms-2" id="installedList">
<div class="col rounded rounded-1 b-shadow header">
<div class="bg-white rounded-top m-0">
<h2 class="m-0 p-1"><img class="m-2 mx-3 me-2" src="static/helm-gray.svg" alt="Installed Charts">Installed
Charts (<span></span>)</h2>
</div>
<div class="bg-secondary rounded-bottom m-0 row p-2">
<div class="col-4 hdr-name">Name</div>
<div class="col-3">Chart Status</div>
<div class="col-2">Chart</div>
<div class="col-1">Revision</div>
<div class="col-1">Namespace</div>
<div class="col-1">Updated</div>
</div>
</div>
<div class="body"></div>
<div class="bg-white rounded shadow p-3 display-none no-charts">Looks like you don't have any charts
installed. "Repository" section may be a good place to start.
</div>
</div>
<!-- /INSTALLED LIST -->
</div>
<div class="row flex-nowrap pt-0 mx-0 section" id="sectionDetails" style="display: none">
<div class="col-2 px-4 py-4 pe-3 rev-list">
<h3 class="fw-bold small">Revisions</h3>
<ul class="list-unstyled">
</ul>
</div>
<div class="col-10 rev-details bg-white b-shadow pt-4 px-5 overflow-auto">
<div><span class="rev-status fw-bold me-3"></span></div>
<div>
<h1 class="name float-start">Name</h1>
<div id="actionButtons" class="float-end">
<span><button id="btnUpgrade"
class="opacity-10 btn btn-sm btn-light bg-white me-2 border-secondary">
<i class="icon bi-hourglass-split"></i> <span></span>
</button></span>
<button id="btnRollback" class="btn btn-sm btn-light bg-white border border-secondary me-2"
title="Rollback to this revision"><i class="bi-arrow-repeat"></i> <span>Rollback</span>
</button>
<button id="btnUninstall" class="btn btn-sm btn-light bg-white border border-secondary"
title="Uninstall the chart"><i class="bi-trash3"></i> Uninstall
</button>
<br/>
<a class="link small" id="btnUpgradeCheck">Check for new version
<span class="spinner-border spinner-border-sm" style="display: none" role="status"
aria-hidden="true"></span>
</a>
</div>
<div class="fs-2">&nbsp;</div>
</div>
<div>
Revision <span class="rev fw-bold me-4"></span>
<span class="rev-date"></span>
</div>
<div class="rev-tags mt-3">
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">chart version: <span
class="rev-chart fw-bold"></span></span>
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">app version: <span
class="rev-app fw-bold"></span></span>
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">namespace: <span
class="rev-ns fw-bold"></span></span>
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">cluster: <span
class="rev-cluster fw-bold"></span></span>
</div>
<div id="revDescr" class="mt-3 mb-4"></div>
<nav class="mt-2">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-resources" data-tab="resources"
type="button" role="tab" aria-controls="nav-resources" aria-selected="false" tabindex="-1">
Resources
</button>
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="manifests"
type="button" role="tab" aria-controls="nav-manifest" aria-selected="false"
tabindex="-1">Manifests
</button>
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="values"
type="button" role="tab" aria-controls="nav-manifest" aria-selected="false" tabindex="-1">
Values
</button>
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="notes"
type="button" role="tab" aria-controls="nav-manifest" aria-selected="false" tabindex="-1">
Notes
</button>
</div>
</nav>
<div class="tab-content">
<div class="tab-pane p-3 container-fluid" id="nav-resources" role="tabpanel">
<div class="row bg-secondary rounded px-3 py-2 mb-3 fw-bold small"
style="text-transform: uppercase">
<div class="col-2">Resource Type</div>
<div class="col-3">Name</div>
<div class="col-1">Status</div>
<div class="col-5">Status Message</div>
<div class="col-1"></div>
</div>
<div class="body"></div>
</div>
<div class="tab-pane" id="nav-manifest" role="tabpanel">
<nav class="navbar bg-white rounded border border-secondary">
<form class="container-fluid" id="modePanel">
<label class="form-check-label" for="diffModeNone">
<input class="form-check-input" type="radio" name="diffMode" id="diffModeNone"
data-mode="view">
View
</label>
<label class="form-check-label" for="diffModePrev">
<input class="form-check-input" type="radio" name="diffMode" id="diffModePrev"
data-mode="diff-prev">
Diff with previous
</label>
<label class="form-check-label" for="diffModeRev">
<input class="form-check-input" type="radio" name="diffMode" id="diffModeRev"
data-mode="diff-rev">
Diff with specific revision: <input class="form-input" size="3" id="specRev">
</label>
<label class="form-check-label" for="userDefinedVals">
<input class="form-check-input" type="checkbox" id="userDefinedVals"> User-defined only
</label>
</form>
</nav>
<div id="manifestText" class="mt-2 bg-white"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
<div id="errorAlert" style="z-index: 2000; max-width: 95%; overflow: auto"
class="display-none alert alert-sm alert-danger alert-dismissible position-absolute position-absolute top-0 start-50 translate-middle-x mt-3 border-danger"
role="alert">
<h4 class="alert-heading"><i class="bi-exclamation-triangle-fill"></i> <span></span></h4>
<hr>
<p style="white-space: pre-wrap"></p>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="describeModal"
aria-labelledby="describeModalLabel" style="overflow-x: auto">
<div class="offcanvas-header border-bottom p-4">
<div>
<h5 id="describeModalLabel"></h5>
<p class="m-0 mt-4">ResourceType</p>
</div>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body p-2 ps-4" id="describeModalBody">
</div>
</div>
<div class="modal" id="confirmModal" tabindex="-1" aria-labelledby="describeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmModalLabel"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="confirmModalBody">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-confirm">Confirm</button>
</div>
</div>
</div>
</div>
<div class="modal" id="repoAddModal" tabindex="-1">
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Chart Repository</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form enctype="application/x-www-form-urlencoded">
<label class="form-label">Name: <input class="form-control" name="name"></label>
<label class="form-label">URL: <input class="form-control" name="url"></label>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-confirm">Add Repository</button>
</div>
</div>
</div>
</div>
<div class="modal" id="upgradeModal" tabindex="-1">
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="upgradeModalLabel">
Install <b class='text-success name'></b>
</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="modal-body border-bottom fs-5" enctype="multipart/form-data">
<div class="input-group mb-3 text-muted">
<label class="form-label me-4 text-dark">Version to install: <select
class='fw-bold text-success ver-new'></select></label> <span class="ver-old">(current version is <span
class='text-success ms-1'>0.0.0</span>)</span>
</div>
<div class="input-group mb-3 text-muted">
<label class="form-label me-4 text-dark">
Release Name: <input class="form-control rel-name">
</label>
<label class="form-label me-4 text-dark">
Namespace (optional): <input class="form-control rel-ns">
</label>
</div>
<div class="row">
<div class="col-6 pe-3">
<label class="form-label">User-Defined Values:</label>
</div>
<div class="col-6 ps-3">
<label class="form-label">Chart Values Reference:</label>
</div>
</div>
<div class="row">
<div class="col-6 pe-3">
<textarea name="values" class="form-control w-100 h-100" rows="5"
style="font-family: monospace"></textarea>
</div>
<div class="col-6 ps-3">
<pre class="ref-vals fs-6 w-100 bg-secondary p-2 rounded" style="max-height: 20rem"></pre>
</div>
</div>
<div class="row">
<div class="col-6 pe-3">
<span class="invalid-feedback small mb-3"> (wrong YAML)</span>
</div>
</div>
<label class="form-label mt-4">Manifest changes:</label>
<div id="upgradeModalBody" class="small"></div>
</form>
<div class="modal-footer d-flex">
<button type="button" class="btn btn-scan bg-white border-secondary">Scan for Problems</button>
<button type="button" class="btn btn-primary btn-confirm">Confirm</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa"
crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"
integrity="sha512-gU7kztaQEl7SHJyraPfZLQCNnrKdaQi5ndOyt4L4UPL/FHDd/uB9Je6KDARIqwnNNE27hnqoWLBq+Kpe4iHfeQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3.0.3/build/global/luxon.min.js"
integrity="sha256-RH4TKnKcKyde0s2jc5BW3pXZl/5annY3fcZI9VrV5WQ=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"
integrity="sha512-CSBhVREyzHAjAFfBlIBakjoRUKp5h7VSweP0InR/pAJyptH7peuhCsqAI/snV+TwZmXZqoUklpXp6R6wMnYf5Q=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="static/repo.js"></script>
<script src="static/list-view.js"></script>
<script src="static/revisions-view.js"></script>
<script src="static/details-view.js"></script>
<script src="static/actions.js"></script>
<script src="static/scripts.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
<svg width="77" height="19" viewBox="0 0 77 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_231_1524)">
<path d="M8.51786 7.17108H5.90895L1.96859 10.9915V3.16663H0V16.0875H1.96859V13.0592L3.52824 11.6745L6.64123 16.0875H9.06309L4.93573 10.4021L8.51786 7.17108Z" fill="#1347FF"/>
<path d="M28.2007 7.17127L27.2085 8.16924L26.2321 7.17127H22.6848L21.5024 8.39069V7.17127H19.6448V16.0877H21.6165V9.84712L22.6658 8.79613H24.9356L25.4808 9.33257V16.0877H27.4526V9.49785L28.1627 8.79613H30.7716L31.3168 9.33257V16.0877H33.2854V8.75871L31.7099 7.17127H28.2007Z" fill="#1347FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.38939 8.75871L10.9649 7.17127H16.3508L17.9263 8.75871V14.5003L16.3508 16.0877H10.9649L9.38939 14.5003V8.75871ZM15.4124 14.4628L15.9387 13.9264H15.9418V9.33257L15.4156 8.79613H11.9064L11.3802 9.33257V13.9264L11.9033 14.4628H15.4124Z" fill="#1347FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8772 7.17127L61.3017 8.75871V14.5003L62.8772 16.0877H68.2631L69.8385 14.5003V8.75871L68.2631 7.17127H62.8772ZM67.8513 13.9264L67.3249 14.4628H63.8158L63.2895 13.9264V9.33257L63.8158 8.79613H67.3249L67.8513 9.33257V13.9264Z" fill="#1347FF"/>
<path d="M73.4148 8.49984L74.7271 7.17127H77.0003V9.01758H74.5785L73.5289 10.053V16.0877H71.5571V7.17127H73.4148V8.49984Z" fill="#1347FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.4523 7.17127L34.8768 8.75871V14.5003L36.4523 16.0877H48.0926L49.668 14.5003V8.75871L48.0926 7.17127H36.4523ZM47.6776 13.9264L47.1512 14.4628H37.3875L36.8613 13.9264V9.33257L37.3875 8.79613H47.1512L47.6776 9.33257V13.9264Z" fill="#1347FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.5078 7.17108L57.5955 8.27823V3.16663H59.5637V16.0875H57.5955V14.9991L56.5078 16.0875H52.7546L51.1792 14.5001V8.7585L52.7546 7.17108H56.5078ZM56.546 14.4627L57.5955 13.4303V9.84695L56.546 8.79592H53.6933L53.1478 9.33236V13.9262L53.6933 14.4627H56.546Z" fill="#1347FF"/>
</g>
<defs>
<clipPath id="clip0_231_1524">
<rect width="77" height="19" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,54 @@
function loadChartsList() {
$("body").removeClass("bg-variant1 bg-variant2").addClass("bg-variant1")
$("#sectionList").show()
const chartsCards = $("#installedList .body")
chartsCards.empty().append("<div><span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span> Loading...</div>")
$.getJSON("/api/helm/charts").fail(function (xhr) {
reportError("Failed to get list of charts", xhr)
}).done(function (data) {
chartsCards.empty()
$("#installedList .header h2 span").text(data.length)
data.forEach(function (elm) {
let card = buildChartCard(elm);
chartsCards.append(card)
})
if (!data.length) {
$("#installedList .no-charts").show()
}
})
}
function buildChartCard(elm) {
const card = $(`<div class="row m-0 py-3 bg-white rounded-1 b-shadow border-4 border-start">
<div class="col-4 rel-name"><span class="link">release-name</span><div></div></div>
<div class="col-3 rel-status"><span></span><div></div></div>
<div class="col-2 rel-chart text-nowrap"><span></span><div>Chart Version</div></div>
<div class="col-1 rel-rev"><span>#0</span><div>Revision</div></div>
<div class="col-1 rel-ns text-nowrap"><span>default</span><div>Namespace</div></div>
<div class="col-1 rel-date text-nowrap"><span>today</span><div>Updated</div></div>
</div>`)
card.find(".rel-name span").text(elm.name)
card.find(".rel-rev span").text("#" + elm.revision)
card.find(".rel-ns span").text(elm.namespace)
card.find(".rel-chart span").text(elm.chart)
card.find(".rel-date span").text(getAge(elm))
statusStyle(elm.status, card, card.find(".rel-status span"))
card.find("a").attr("href", '#context=' + getHashParam('context') + '&namespace=' + elm.namespace + '&name=' + elm.name)
card.find(".rel-name span").data("chart", elm).click(function () {
const self = $(this)
$("#sectionList").hide()
let chart = self.data("chart");
setHashParam("namespace", chart.namespace)
setHashParam("chart", chart.name)
loadChartHistory(chart.namespace, chart.name, elm.chart_name)
})
return card;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="32"
height="37"
viewBox="0 0 32 37"
fill="none"
version="1.1"
id="svg4"
sodipodi:docname="logo.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="34.810811"
inkscape:cx="13.228649"
inkscape:cy="17.552019"
inkscape:window-width="3840"
inkscape:window-height="2059"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<rect
style="opacity:0.455635;fill:#ffffff;stroke-width:18.0094;fill-opacity:1"
id="rect1031"
width="17.224331"
height="10.929513"
x="7.3572245"
y="12.500892"
ry="0" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18 5V2.29761C18 1.02617 17.1002 0 15.9958 0C14.8915 0 14 1.03576 14 2.29761V5H18ZM28.8285 11.364L30.7394 9.45308C31.6384 8.55404 31.7278 7.19222 30.9469 6.4113C30.166 5.63038 28.8032 5.73239 27.9109 6.62466L26.0001 8.53553L28.8285 11.364ZM1.62465 9.45308L3.53553 11.364L6.36396 8.53553L4.45308 6.62466C3.56081 5.73239 2.19804 5.63038 1.41712 6.4113C0.636208 7.19222 0.72561 8.55404 1.62465 9.45308ZM6 12.8164L7.78385 11H24.2163L26 12.8085V23.1915L24.2163 25H7.78372L6 23.1915V12.8164ZM8.94831 12.977L8.25128 13.6683V22.3438L8.94831 23.0351H23.0733L23.7446 22.3426V13.6694L23.0733 12.977H8.94831ZM20.1736 17.4485C20.1736 18.7679 19.4222 19.8375 18.4953 19.8375C17.5684 19.8375 16.817 18.7679 16.817 17.4485C16.817 16.1291 17.5684 15.0595 18.4953 15.0595C19.4222 15.0595 20.1736 16.1291 20.1736 17.4485ZM13.5045 19.8375C14.4314 19.8375 15.1828 18.7679 15.1828 17.4485C15.1828 16.1291 14.4314 15.0595 13.5045 15.0595C12.5776 15.0595 11.8262 16.1291 11.8262 17.4485C11.8262 18.7679 12.5776 19.8375 13.5045 19.8375ZM14.3641 34.0662V31.3638H18.3641V34.0662C18.3641 35.328 17.4726 36.3638 16.3682 36.3638C15.2638 36.3638 14.3641 35.3376 14.3641 34.0662ZM1.62465 26.9107L3.53553 24.9998L6.36396 27.8282L4.45308 29.7391C3.56081 30.6314 2.19804 30.7334 1.41712 29.9525C0.636204 29.1716 0.725608 27.8097 1.62465 26.9107ZM28.8285 24.9998L30.7394 26.9107C31.6384 27.8097 31.7278 29.1716 30.9469 29.9525C30.166 30.7334 28.8032 30.6314 27.9109 29.7391L26.0001 27.8282L28.8285 24.9998Z"
fill="#1347FF"
id="path2" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,120 @@
function loadRepoView() {
$("#sectionRepo .repo-details").hide()
$("#sectionRepo").show()
$.getJSON("/api/helm/repo").fail(function (xhr) {
reportError("Failed to get list of repositories", xhr)
}).done(function (data) {
const items = $("#sectionRepo .repo-list ul").empty()
data.forEach(function (elm) {
let opt = $('<li class="mb-2"><label><input type="radio" name="cluster" class="me-2"/><span></span></label></li>');
opt.attr('title', elm.url)
opt.find("input").val(elm.name).text(elm.name).data("item", elm)
opt.find("span").text(elm.name)
items.append(opt)
})
if (!data.length) {
items.text("No repositories found, try adding one")
}
items.find("input").click(function () {
const self = $(this)
const elm = self.data("item");
setHashParam("repo", elm.name)
$("#sectionRepo .repo-details").show()
$("#sectionRepo .repo-details h2").text(elm.name)
$("#sectionRepo .repo-details .url").text(elm.url)
$("#sectionRepo .repo-details ul").html('<span class="spinner-border spinner-border-sm mx-1" role="status" aria-hidden="true"></span>')
$.getJSON("/api/helm/repo/charts?name=" + elm.name).fail(function (xhr) {
reportError("Failed to get list of charts in repo", xhr)
}).done(function (data) {
$("#sectionRepo .repo-details ul").empty()
data.forEach(function (elm) {
const li = $(`<li class="row p-2 rounded">
<h6 class="col-3 py-2">` + elm.name.split('/').pop() + `</h6>
<div class="col py-2">` + elm.description + `</div>
<div class="col-1 py-2">` + elm.version + `</div>
<div class="col-1 action text-nowrap"><button class="btn btn-sm border-secondary bg-white">Install</button></div>
</li>`)
li.data("item", elm)
if (elm.installed_namespace) {
li.find("button").text("View").addClass("btn-success").removeClass("bg-white")
li.find(".action").prepend("<i class='bi-check-circle-fill me-1 text-success' title='Already installed'></i>")
}
li.click(repoChartClicked)
$("#sectionRepo .repo-details ul").append(li)
})
})
})
if (getHashParam("repo")) {
items.find("input[value='" + getHashParam("repo") + "']").click()
} else {
items.find("input").first().click()
}
})
}
$("#sectionRepo .repo-list .btn").click(function () {
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
myModal.show()
})
$("#repoAddModal .btn-confirm").click(function () {
$("#repoAddModal .btn-confirm").prop("disabled", true).prepend('<span class="spinner-border spinner-border-sm mx-1" role="status" aria-hidden="true"></span>')
$.ajax({
type: 'POST',
url: "/api/helm/repo",
data: $("#repoAddModal form").serialize(),
}).fail(function (xhr) {
reportError("Failed to add repo", xhr)
}).done(function () {
setHashParam("repo", $("#repoAddModal form input[name=name]").val())
window.location.reload()
})
})
$("#sectionRepo .btn-remove").click(function () {
if (confirm("Confirm removing repository?")) {
$.ajax({
type: 'DELETE',
url: "/api/helm/repo?name=" + $("#sectionRepo .repo-details h2").text(),
}).fail(function (xhr) {
reportError("Failed to add repo", xhr)
}).done(function () {
setHashParam("repo", null)
window.location.reload()
})
}
})
$("#sectionRepo .btn-update").click(function () {
$("#sectionRepo .btn-update i").removeClass("bi-arrow-repeat").append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.ajax({
type: 'POST',
url: "/api/helm/repo/update?name=" + $("#sectionRepo .repo-details h2").text(),
}).fail(function (xhr) {
reportError("Failed to add repo", xhr)
}).done(function () {
window.location.reload()
})
})
function repoChartClicked() {
const self = $(this)
const elm = self.data("item")
if (elm.installed_namespace) {
setHashParam("section", null)
setHashParam("namespace", elm.installed_namespace)
setHashParam("chart", elm.installed_name)
window.location.reload()
} else {
popUpUpgrade(elm)
}
}

View File

@@ -0,0 +1,69 @@
const revRow = $("#sectionDetails .rev-list ul");
function loadChartHistory(namespace, name) {
$("body").removeClass("bg-variant1 bg-variant2").addClass("bg-variant2")
$("#sectionDetails").show()
$("#sectionDetails .name").text(name)
revRow.empty().append("<li><span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span></li>")
$.getJSON("/api/helm/charts/history?name=" + name + "&namespace=" + namespace).fail(function (xhr) {
reportError("Failed to get chart details", xhr)
}).done(function (data) {
fillChartHistory(data, namespace, name);
checkUpgradeable(data[data.length - 1].chart_name)
const rev = getHashParam("revision")
if (rev) {
revRow.find(".rev-" + rev).click()
} else {
revRow.find("li:first-child").click()
}
})
}
function fillChartHistory(data, namespace, name) {
revRow.empty()
data.reverse()
for (let x = 0; x < data.length; x++) {
const elm = data[x]
$("#specRev").data("first-rev", elm.revision)
if (!x) {
$("#specRev").val(elm.revision).data("last-rev", elm.revision).data("last-chart-ver", elm.chart_ver)
}
const rev = $(`<li class="px-2 pt-5 pb-4 mb-2 rounded border border-secondary bg-secondary position-relative">
<div class="rev-status position-absolute top-0 m-2 mb-5 start-0 fw-bold"></div>
<div class="rev-number position-absolute top-0 m-2 mb-5 end-0 fw-bold fs-6"></div>
<div class="rev-changes position-absolute bottom-0 start-0 m-2 text-muted small"></div>
<div class="position-absolute bottom-0 end-0 m-2 text-muted small">AGE: <span class="rev-age"></span></div>
</li>`)
rev.find(".rev-number").text("#" + elm.revision)
//rev.find(".app-ver").text(elm.app_version)
//rev.find(".chart-ver").text(elm.chart_ver)
rev.find(".rev-date").text(elm.updated.replace("T", " "))
rev.find(".rev-age").text(getAge(elm, data[x - 1])).parent().attr("title", elm.updated)
statusStyle(elm.status, rev.find(".rev-status"), rev.find(".rev-status"))
if (elm.description.startsWith("Rollback to ")) {
//rev.find(".rev-status").append(" <span class='small fw-normal text-lowercase'>(rollback)</span>")
rev.find(".rev-status").append(" <i class='bi-arrow-counterclockwise text-muted' title='"+elm.description+"'></i>")
}
const nxt = data[x + 1];
if (nxt && isNewerVersion(elm.chart_ver, nxt.chart_ver)) {
rev.find(".rev-changes").html("<span class='strike'>" + nxt.chart_ver + "</span> <i class='text-danger bi-arrow-down-right'></i> " + elm.chart_ver)
} else if (nxt && isNewerVersion(nxt.chart_ver, elm.chart_ver)) {
rev.find(".rev-changes").html("<span class='strike'>" + nxt.chart_ver + "</span> <i class='text-success bi-arrow-up-right'></i> " + elm.chart_ver)
}
rev.data("elm", elm)
rev.addClass("rev-" + elm.revision)
rev.click(function () {
revisionClicked(namespace, name, $(this))
})
revRow.append(rev)
}
}

View File

@@ -0,0 +1,222 @@
$(function () {
const clusterSelect = $("#cluster");
clusterSelect.change(function () {
window.location.href = "/#context=" + clusterSelect.find("input:radio:checked").val()
window.location.reload()
})
$.getJSON("/api/kube/contexts").fail(function (xhr) {
reportError("Failed to get list of clusters", xhr)
}).done(function (data) {
const context = getHashParam("context")
fillClusterList(data, context);
initView(); // can only do it after loading cluster list
})
$.getJSON("/api/scanners").fail(function (xhr) {
reportError("Failed to get list of scanners", xhr)
}).done(function (data) {
if (!data.length) {
$("#upgradeModal .btn-scan").hide()
}
})
$.getJSON("/status").fail(function (xhr) {
reportError("Failed to get tool version", xhr)
}).done(function (data) {
fillToolVersion(data)
})
})
function initView() {
$(".section").hide()
const section = getHashParam("section")
if (section === "repository") {
$("#topNav ul a.section-repo").addClass("active")
loadRepoView()
} else {
$("#topNav ul a.section-installed").addClass("active")
const namespace = getHashParam("namespace")
const chart = getHashParam("chart")
if (!chart) {
loadChartsList()
} else {
loadChartHistory(namespace, chart)
}
}
}
$("#topNav ul a").click(function () {
const self = $(this)
if (self.hasClass("section-repo")) {
setHashParam("section", "repository")
} else if (self.hasClass("section-installed")) {
setHashParam("section", null)
} else {
return
}
$("#topNav ul a").removeClass("active")
const ctx = getHashParam("context")
setHashParam(null, null)
setHashParam("context", ctx)
initView()
})
const myAlert = document.getElementById('errorAlert')
myAlert.addEventListener('close.bs.alert', event => {
event.preventDefault()
$("#errorAlert").hide()
})
function reportError(err, xhr) {
$("#errorAlert h4 span").text(err)
if (xhr) {
$("#errorAlert p").text(xhr.responseText)
}
$("#errorAlert").show()
}
function getHashParam(name) {
const params = new URLSearchParams(window.location.hash.substring(1))
return params.get(name)
}
function setHashParam(name, val) {
let params = new URLSearchParams(window.location.hash.substring(1))
if (!name) {
params = new URLSearchParams()
} else if (!val) {
params.delete(name)
} else {
params.set(name, val)
}
window.location.hash = new URLSearchParams(params).toString()
}
function statusStyle(status, card, txt) {
txt.addClass("text-uppercase")
txt.html("<span class='fs-6'>●</span> " + status)
txt.removeClass("text-failed text-deployed text-pending text-other")
if (status === "failed") {
card.addClass("border-failed")
txt.addClass("text-failed")
// TODO: add failure description here
} else if (status === "deployed") {
card.addClass("border-deployed")
txt.addClass("text-deployed")
} else if (status.startsWith("pending-")) {
card.addClass("border-pending")
txt.addClass("text-pending")
} else {
card.addClass("border-other")
txt.addClass("text-other")
}
}
function getCleanClusterName(rawClusterName) {
if (rawClusterName.indexOf('arn') === 0) {
// AWS cluster
const clusterSplit = rawClusterName.split(':')
const clusterName = clusterSplit.at(-1).split("/").at(-1)
const region = clusterSplit.at(-3)
return region + "/" + clusterName + ' [AWS]'
}
if (rawClusterName.indexOf('gke') === 0) {
// GKE cluster
return rawClusterName.split('_').at(-2) + '/' + rawClusterName.split('_').at(-1) + ' [GKE]'
}
return rawClusterName
}
function fillClusterList(data, context) {
data.forEach(function (elm) {
let label = getCleanClusterName(elm.Name)
let opt = $('<li><label><input type="radio" name="cluster" class="me-2"/><span></span></label></li>');
opt.attr('title', elm.Name)
opt.find("input").val(elm.Name).text(label)
opt.find("span").text(label)
if (elm.IsCurrent && !context) {
opt.find("input").prop("checked", true)
setCurrentContext(elm.Name)
} else if (context && elm.Name === context) {
opt.find("input").prop("checked", true)
setCurrentContext(elm.Name)
}
$("#cluster").append(opt)
})
}
function setCurrentContext(ctx) {
setHashParam("context", ctx)
$.ajaxSetup({
headers: {
'x-kubecontext': ctx
}
});
}
function getAge(obj1, obj2) {
const date = luxon.DateTime.fromISO(obj1.updated);
let dateNext = luxon.DateTime.now()
if (obj2) {
dateNext = luxon.DateTime.fromISO(obj2.updated);
}
const diff = dateNext.diff(date);
const map = {
"years": "yr", "months": "mo", "days": "d", "hours": "h", "minutes": "m", "seconds": "s", "milliseconds": "ms"
}
for (let unit of ["years", "months", "days", "hours", "minutes", "seconds", "milliseconds"]) {
const val = diff.as(unit);
if (val >= 1) {
return Math.round(val) + map[unit]
}
}
return "n/a"
}
$(".bi-power").click(function () {
$(".bi-power").attr("disabled", "disabled").removeClass(".bi-power").append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.ajax({
url: "/",
type: 'DELETE',
}).done(function () {
window.close();
})
})
function isNewerVersion(oldVer, newVer) {
if (oldVer && oldVer[0] === 'v') {
oldVer = oldVer.substring(1)
}
if (newVer && newVer[0] === 'v') {
newVer = newVer.substring(1)
}
const oldParts = oldVer.split('.')
const newParts = newVer.split('.')
for (let i = 0; i < newParts.length; i++) {
const a = ~~newParts[i] // parse int
const b = ~~oldParts[i] // parse int
if (a > b) return true
if (a < b) return false
}
return false
}
function fillToolVersion(data) {
$("#toolVersion").text(data.CurVer)
if (isNewerVersion(data.CurVer, data.LatestVer)) {
$("#toolVersionUpgrade").text(data.LatestVer)
$(".new-version-pill").show()
}
}

View File

@@ -0,0 +1,372 @@
.link, .nav-link {
cursor: pointer;
}
.strike {
text-decoration: line-through;
}
.text-muted {
color: #707583 !important;
}
.border-other {
border-color: #9195A1 !important;
}
.text-other {
color: #9195A1 !important;
}
.border-failed {
border-color: #FC1683 !important;
}
.text-failed {
color: #FC1683 !important;
}
.border-deployed {
border-color: #1BE99A !important;
}
.text-deployed {
color: #1FA470 !important;
}
.border-pending {
border-color: #5AB0FF !important;
}
.text-pending {
color: #5AB0FF !important;
}
.bg-tag {
background-color: #D6EFFE;
}
.bg-tag.text-dark {
color: #333333 !important;
}
.bg-danger {
background-color: #FC1683 !important;
}
.bg-success {
background-color: #A4F8D7 !important;
}
.btn {
font-weight: 500;
}
.btn-primary {
background-color: #1347FF;
}
.text-uppercase {
text-transform: uppercase;
}
.offcanvas {
width: auto !important;
max-width: 90%;
min-width: 60%;
}
.fs-80 {
font-size: 0.8rem!important;
}
html {
min-height: 100%;
}
body {
font-size: 14px;
background-color: #F4F7FA;
font-family: Roboto, serif;
color: #3D4048;
}
body.bg-variant1 {
background-color: #F4F7FA;
background-image: url("topographic.svg");
background-repeat: no-repeat;
background-position: bottom left;
background-size: auto 100%;
}
body.bg-variant2 {
background-color: #E8EDF2;
}
body > .container-fluid {
min-height: 100% !important;
}
#topNav.navbar {
}
.navbar-brand {
font-family: Poppins, serif;
font-size: 0.6rem !important;
color: #707583;
font-weight: 500;
text-decoration: none;
}
.navbar-brand > a > img {
vertical-align: middle;
height: 2.5rem;
display: inline-block;
margin: 0.5rem 0.8rem
}
.navbar-brand > div {
display: inline-block;
vertical-align: top;
}
.navbar-brand h1 a {
font-size: 1.2rem !important;
color: #0023A3 !important;
text-decoration: none;
vertical-align: middle;
}
#topNav .navbar i.btn {
font-size: 1.5rem;
}
.d2h-file-collapse, .d2h-tag {
opacity: 0; /* trollface */
}
.display-none {
display: none;
}
.separator-vertical span {
font-size: 2rem;
border-left: 1px solid #DCDDDF;
margin: 0.25rem 0;
}
#topNav .nav-link {
color: #3B3D45 !important;
}
#topNav .nav-link.active {
background: #EBEFFF;
border-radius: 2px;
color: #1347FF !important;
}
.b-shadow {
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
}
#filters h4 {
font-size: 1rem;
font-weight: bold;
margin-top: 0;
}
#filters {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.8rem;
line-height: 175%;
}
#cluster input, #cluster span {
vertical-align: middle;
}
#installedList > div, #installedList .body > div {
margin-bottom: 0.75rem !important;
}
#installedList h2 {
font-family: Inter, serif;
font-weight: 700;
font-size: 1rem;
}
.bg-secondary {
background: #ECEFF2 !important;
}
.border-secondary {
border-color: #DCDDDF !important;
}
#installedList .header {
font-family: Roboto, serif;
font-weight: 600;
font-size: 0.6rem;
color: #3B3D45;
}
#installedList .header .row {
text-transform: uppercase;
}
#installedList .hdr-name {
padding-left: 5.5rem
}
#installedList .body {
font-size: 0.75rem;
}
#installedList .body .row div div {
margin-top: 0.75rem;
}
span.link {
cursor: pointer;
}
#installedList .body .row:hover span.link {
text-decoration: underline;
}
#installedList .rel-name {
padding-left: 5.5rem;
background-image: url("helm-gray-50.svg");
background-repeat: no-repeat;
background-position: center left;
background-position-x: 1.1rem;
background-size: 3rem;
}
#installedList .rel-name span {
font-family: Roboto Slab, sans-serif;
font-weight: 700;
font-size: 1.25rem;
}
#sectionDetails h1 {
font-family: Roboto Slab, sans-serif;
font-weight: 400;
font-size: 2rem;
}
#installedList .rel-name div {
height: 2rem;
min-height: 2rem;
max-height: 2rem;
}
#installedList .rel-status span {
text-transform: uppercase;
font-weight: 700;
}
#installedList .rel-chart span, #installedList .rel-rev span, #installedList .rel-ns span, #installedList .rel-date span {
font-weight: 700;
}
#installedList .rel-chart div, #installedList .rel-rev div, #installedList .rel-ns div, #installedList .rel-date div {
text-transform: uppercase;
color: #707583;
}
#actionButtons .link {
cursor: pointer;
}
#actionButtons button > * {
vertical-align: bottom;
}
.d2h-code-side-linenumber {
position: static;
}
.nav-tabs {
border: none;
margin-bottom: 1rem;
}
.nav-tabs .nav-link {
padding-bottom: 0.25rem;
color: #3B3D45;
}
.nav-tabs .nav-link.active {
border: none;
border-bottom: 3px solid #3B3D45;
background-color: transparent;
}
#installedList .b-shadow:hover {
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.18);
}
#btnUpgradeCheck {
color: #3B3D45;
}
#btnUpgrade {
min-width: 9rem;
}
#sectionDetails > .bg-white {
background-color: #F4F7FA !important;
}
#sectionDetails .list-unstyled .bg-secondary {
background-color: #F4F7FA !important;
}
#nav-resources .badge {
font-size: inherit;
}
#nav-resources .bg-secondary {
background-color: #E6E7EB !important;
}
.res-actions .btn-sm {
font-size: 0.75rem;
}
.offcanvas-header h5 {
font-family: Poppins, serif;
font-weight: 500;
}
.offcanvas-header h5 .badge {
font-family: Roboto, serif;
font-weight: normal;
font-size: 0.8rem;
}
.offcanvas-header p {
font-family: Inter, serif;
}
#describeModalBody pre {
font-size: 1rem;
}
#sectionRepo .repo-details ul .row .btn {
visibility: hidden;
}
#sectionRepo .repo-details ul .row:hover {
background-color: #F4F7FA !important;
}
#sectionRepo .repo-details ul .row:hover .btn {
visibility: visible;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -0,0 +1,487 @@
package subproc
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"helm.sh/helm/v3/pkg/release"
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
type DataLayer struct {
KubeContext string
Helm string
Kubectl string
Scanners []Scanner
VersionInfo *VersionInfo
}
type VersionInfo struct {
CurVer string
LatestVer string
}
func (d *DataLayer) runCommand(cmd ...string) (string, error) {
log.Debugf("Starting command: %s", cmd)
return utils.RunCommand(cmd, map[string]string{"HELM_KUBECONTEXT": d.KubeContext})
}
func (d *DataLayer) runCommandHelm(cmd ...string) (string, error) {
if d.Helm == "" {
d.Helm = "helm"
}
cmd = append([]string{d.Helm}, cmd...)
if d.KubeContext != "" {
cmd = append(cmd, "--kube-context", d.KubeContext)
}
return d.runCommand(cmd...)
}
func (d *DataLayer) runCommandKubectl(cmd ...string) (string, error) {
// TODO: migrate into using kubectl "k8s.io/kubectl/pkg/cmd" and kube API
if d.Kubectl == "" {
d.Kubectl = "kubectl"
}
cmd = append([]string{d.Kubectl}, cmd...)
if d.KubeContext != "" {
cmd = append(cmd, "--context", d.KubeContext)
}
return d.runCommand(cmd...)
}
func (d *DataLayer) CheckConnectivity() error {
contexts, err := d.ListContexts()
if err != nil {
return err
}
if len(contexts) < 1 {
return errors.New("did not find any kubectl contexts configured")
}
_, err = d.runCommandHelm("--help") // no point in doing is, since the default context may be invalid
if err != nil {
return err
}
return nil
}
type KubeContext struct {
IsCurrent bool
Name string
Cluster string
AuthInfo string
Namespace string
}
func (d *DataLayer) ListContexts() (res []KubeContext, err error) {
out, err := d.runCommandKubectl("config", "get-contexts")
if err != nil {
return nil, err
}
// kubectl has no JSON output for it, we'll have to do custom text parsing
lines := strings.Split(out, "\n")
// find field positions
fields := regexp.MustCompile(`(\w+\s+)`).FindAllString(lines[0], -1)
cur := len(fields[0])
name := cur + len(fields[1])
cluster := name + len(fields[2])
auth := cluster + len(fields[3])
// read items
for _, line := range lines[1:] {
if strings.TrimSpace(line) == "" {
continue
}
res = append(res, KubeContext{
IsCurrent: strings.TrimSpace(line[0:cur]) == "*",
Name: strings.TrimSpace(line[cur:name]),
Cluster: strings.TrimSpace(line[name:cluster]),
AuthInfo: strings.TrimSpace(line[cluster:auth]),
Namespace: strings.TrimSpace(line[auth:]),
})
}
return res, nil
}
func (d *DataLayer) ListInstalled() (res []ReleaseElement, err error) {
// TODO: filter by namespace
out, err := d.runCommandHelm("ls", "--all", "--all-namespaces", "--output", "json", "--time-format", time.RFC3339)
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
return res, nil
}
func (d *DataLayer) ChartHistory(namespace string, chartName string) (res []*HistoryElement, err error) {
// TODO: there is `max` but there is no `offset`
out, err := d.runCommandHelm("history", chartName, "--namespace", namespace, "--output", "json", "--max", "18")
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
for _, elm := range res {
chartRepoName, curVer, err := utils.ChartAndVersion(elm.Chart)
if err != nil {
return nil, err
}
elm.ChartName = chartRepoName
elm.ChartVer = curVer
elm.Updated.Time = elm.Updated.Time.Round(time.Second)
}
return res, nil
}
func (d *DataLayer) ChartRepoVersions(chartName string) (res []*RepoChartElement, err error) {
search := "/" + chartName + "\v"
if strings.Contains(chartName, "/") {
search = "\v" + chartName + "\v"
}
cmd := []string{"search", "repo", "--regexp", search, "--versions", "--output", "json"}
out, err := d.runCommandHelm(cmd...)
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
return res, nil
}
func (d *DataLayer) ChartRepoCharts(repoName string) (res []*RepoChartElement, err error) {
cmd := []string{"search", "repo", "--regexp", "\v" + repoName + "/", "--output", "json"}
out, err := d.runCommandHelm(cmd...)
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
ins, err := d.ListInstalled()
if err != nil {
return nil, err
}
enrichRepoChartsWithInstalled(res, ins)
return res, nil
}
func enrichRepoChartsWithInstalled(charts []*RepoChartElement, installed []ReleaseElement) {
for _, chart := range charts {
for _, rel := range installed {
c, _, err := utils.ChartAndVersion(rel.Chart)
if err != nil {
log.Warnf("Failed to parse chart: %s", err)
continue
}
pieces := strings.Split(chart.Name, "/")
if pieces[1] == c {
chart.InstalledNamespace = rel.Namespace
chart.InstalledName = rel.Name
}
}
}
}
type SectionFn = func(string, string, int, bool) (string, error) // TODO: rework it into struct-based argument?
func (d *DataLayer) RevisionManifests(namespace string, chartName string, revision int, _ bool) (res string, err error) {
cmd := []string{"get", "manifest", chartName, "--namespace", namespace}
if revision > 0 {
cmd = append(cmd, "--revision", strconv.Itoa(revision))
}
out, err := d.runCommandHelm(cmd...)
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) RevisionManifestsParsed(namespace string, chartName string, revision int) ([]*v1.Carp, error) {
out, err := d.RevisionManifests(namespace, chartName, revision, false)
if err != nil {
return nil, err
}
dec := yaml.NewDecoder(bytes.NewReader([]byte(out)))
res := make([]*v1.Carp, 0)
var tmp interface{}
for dec.Decode(&tmp) == nil {
// k8s libs uses only JSON tags defined, say hello to https://github.com/go-yaml/yaml/issues/424
// bug 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) RevisionNotes(namespace string, chartName string, revision int, _ bool) (res string, err error) {
out, err := d.runCommandHelm("get", "notes", chartName, "--namespace", namespace, "--revision", strconv.Itoa(revision))
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) RevisionValues(namespace string, chartName string, revision int, onlyUserDefined bool) (res string, err error) {
cmd := []string{"get", "values", chartName, "--namespace", namespace, "--output", "yaml"}
if revision > 0 {
cmd = append(cmd, "--revision", strconv.Itoa(revision))
}
if !onlyUserDefined {
cmd = append(cmd, "--all")
}
out, err := d.runCommandHelm(cmd...)
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) GetResource(namespace string, def *v1.Carp) (*v1.Carp, error) {
out, err := d.runCommandKubectl("get", strings.ToLower(def.Kind), def.Name, "--namespace", namespace, "--output", "json")
if err != nil {
if strings.HasSuffix(strings.TrimSpace(err.Error()), " not found") {
return &v1.Carp{
Status: v1.CarpStatus{
Phase: "NotFound",
Message: err.Error(),
Reason: "not found",
},
}, nil
} else {
return nil, err
}
}
var res v1.Carp
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
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 (d *DataLayer) GetResourceYAML(namespace string, def *v1.Carp) (string, error) {
out, err := d.runCommandKubectl("get", strings.ToLower(def.Kind), def.Name, "--namespace", namespace, "--output", "yaml")
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) DescribeResource(namespace string, kind string, name string) (string, error) {
out, err := d.runCommandKubectl("describe", strings.ToLower(kind), name, "--namespace", namespace)
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) ChartUninstall(namespace string, name string) error {
_, err := d.runCommandHelm("uninstall", name, "--namespace", namespace)
if err != nil {
return err
}
return nil
}
func (d *DataLayer) Revert(namespace string, name string, rev int) error {
_, err := d.runCommandHelm("rollback", name, strconv.Itoa(rev), "--namespace", namespace)
if err != nil {
return err
}
return nil
}
func (d *DataLayer) ChartRepoUpdate(name string) error {
cmd := []string{"repo", "update"}
if name != "" {
cmd = append(cmd, name)
}
_, err := d.runCommandHelm(cmd...)
if err != nil {
return err
}
return nil
}
func (d *DataLayer) ChartInstall(namespace string, name string, repoChart string, version string, justTemplate bool, values string, reuseVals bool) (string, error) {
if values == "" && reuseVals {
oldVals, err := d.RevisionValues(namespace, name, 0, true)
if err != nil {
return "", err
}
values = oldVals
}
valsFile, close1, err := utils.TempFile(values)
defer close1()
if err != nil {
return "", err
}
cmd := []string{"upgrade", "--install", "--create-namespace", name, repoChart, "--version", version, "--namespace", namespace, "--values", valsFile, "--output", "json"}
if justTemplate {
cmd = append(cmd, "--dry-run")
}
out, err := d.runCommandHelm(cmd...)
if err != nil {
return "", err
}
res := release.Release{}
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return "", err
}
if justTemplate {
out = strings.TrimSpace(res.Manifest)
}
return out, nil
}
func (d *DataLayer) ShowValues(chart string, ver string) (string, error) {
return d.runCommandHelm("show", "values", chart, "--version", ver)
}
func (d *DataLayer) ChartRepoList() (res []RepositoryElement, err error) {
out, err := d.runCommandHelm("repo", "list", "--output", "json")
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(out), &res)
if err != nil {
return nil, err
}
return res, nil
}
func (d *DataLayer) ChartRepoAdd(name string, url string) (string, error) {
out, err := d.runCommandHelm("repo", "add", "--force-update", name, url)
if err != nil {
return "", err
}
return out, nil
}
func (d *DataLayer) ChartRepoDelete(name string) (string, error) {
out, err := d.runCommandHelm("repo", "remove", name)
if err != nil {
return "", err
}
return out, nil
}
func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
if revision1 == 0 || revision2 == 0 {
log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
return "", nil
}
manifest1, err := functor(namespace, name, revision1, flag)
if err != nil {
return "", err
}
manifest2, err := functor(namespace, name, revision2, flag)
if err != nil {
return "", err
}
diff := GetDiff(manifest1, manifest2, strconv.Itoa(revision1)+ext, strconv.Itoa(revision2)+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
}

View File

@@ -0,0 +1,88 @@
package subproc
import (
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
log "github.com/sirupsen/logrus"
"helm.sh/helm/v3/pkg/release"
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"sync"
"testing"
)
func TestFlow(t *testing.T) {
log.SetLevel(log.DebugLevel)
var _ release.Status
data := DataLayer{}
err := data.CheckConnectivity()
if err != nil {
if err.Error() == "did not find any kubectl contexts configured" {
t.Skip()
} else {
t.Fatal(err)
}
}
ctxses, err := data.ListContexts()
if err != nil {
t.Fatal(err)
}
for _, ctx := range ctxses {
if ctx.IsCurrent {
data.KubeContext = ctx.Name
}
}
installed, err := data.ListInstalled()
if err != nil {
t.Fatal(err)
}
chart := installed[1]
history, err := data.ChartHistory(chart.Namespace, chart.Name)
if err != nil {
t.Fatal(err)
}
_ = history
chartRepoName, curVer, err := utils.ChartAndVersion(chart.Chart)
if err != nil {
t.Fatal(err)
}
_ = curVer
upgrade, err := data.ChartRepoVersions(chartRepoName)
if err != nil {
t.Fatal(err)
}
_ = upgrade
manifests, err := data.RevisionManifestsParsed(chart.Namespace, chart.Name, history[len(history)-1].Revision)
if err != nil {
t.Fatal(err)
}
_ = manifests
var wg sync.WaitGroup
res := make([]*v1.Carp, 0)
for _, m := range manifests {
wg.Add(1)
mc := m // fix the clojure
func() {
defer wg.Done()
lst, err := data.GetResource(chart.Namespace, mc)
if err != nil {
t.Fatal(err)
}
res = append(res, lst)
}()
}
wg.Wait()
diff, err := RevisionDiff(data.RevisionManifests, ".yaml", chart.Namespace, chart.Name, history[len(history)-1].Revision, history[len(history)-2].Revision, true)
if err != nil {
t.Fatal(err)
}
_ = diff
}

View File

@@ -0,0 +1,44 @@
package subproc
import (
"helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
)
// unpleasant copy from Helm sources, where they have it non-public
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"`
}
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"`
ChartVer string `json:"chart_ver"`
}
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
}
type RepositoryElement struct {
Name string `json:"name"`
URL string `json:"url"`
}

View File

@@ -0,0 +1,15 @@
package subproc
type Scanner interface {
Name() string // returns string label for the scanner
Test() bool // test if the scanner is available
ScanManifests(mnf string) (*ScanResults, error) // run the scanner on manifests
ScanResource(ns string, kind string, name string) (*ScanResults, error) // run the scanner on k8s resource
}
type ScanResults struct {
PassedCount int
FailedCount int
OrigReport interface{}
Error error
}

View File

@@ -0,0 +1,115 @@
package utils
import (
"bytes"
"errors"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io/ioutil"
"os"
"os/exec"
"strconv"
"strings"
)
type ControlChan = chan struct{}
func ChartAndVersion(x string) (string, string, error) {
lastInd := strings.LastIndex(x, "-")
if lastInd < 0 {
return "", "", errors.New("can't parse chart version string")
}
return x[:lastInd], x[lastInd+1:], nil
}
func TempFile(txt string) (string, func(), error) {
file, err := ioutil.TempFile("", "helm_dahsboard_*.yaml")
if err != nil {
return "", nil, err
}
err = ioutil.WriteFile(file.Name(), []byte(txt), 0600)
if err != nil {
return "", nil, err
}
return file.Name(), func() { os.Remove(file.Name()) }, nil
}
type CmdError struct {
Command []string
OrigError error
StdErr string
}
func (e CmdError) Error() string {
//return fmt.Sprintf("failed to run command %s:\nError: %s\nSTDERR:%s", e.Command, e.OrigError, e.StdErr)
return string(e.StdErr)
}
func RunCommand(cmd []string, env map[string]string) (string, error) {
prog := exec.Command(cmd[0], cmd[1:]...)
prog.Env = os.Environ()
for k, v := range env {
prog.Env = append(prog.Env, k+"="+v)
}
var stdout bytes.Buffer
prog.Stdout = &stdout
var stderr bytes.Buffer
prog.Stderr = &stderr
if err := prog.Run(); err != nil {
log.Warnf("Failed command: %s", cmd)
serr := stderr.Bytes()
if serr != nil {
log.Warnf("STDERR:\n%s", serr)
}
if eerr, ok := err.(*exec.ExitError); ok {
return "", CmdError{
Command: cmd,
StdErr: string(serr),
OrigError: eerr,
}
}
return "", CmdError{
Command: cmd,
StdErr: string(serr),
OrigError: err,
}
}
sout := stdout.Bytes()
serr := stderr.Bytes()
log.Debugf("Command STDOUT:\n%s", sout)
log.Debugf("Command STDERR:\n%s", serr)
return string(sout), nil
}
type QueryProps struct {
Namespace string
Name string
Revision int
}
func GetQueryProps(c *gin.Context, revRequired bool) (*QueryProps, error) {
qp := QueryProps{}
qp.Namespace = c.Query("namespace")
qp.Name = c.Query("name")
if qp.Name == "" {
return nil, errors.New("missing required query string parameter: name")
}
cRev, err := strconv.Atoi(c.Query("revision"))
if err != nil && revRequired {
return nil, err
}
qp.Revision = cRev
return &qp, nil
}

View File

@@ -0,0 +1,96 @@
package utils
import (
"github.com/gin-gonic/gin"
"net/http/httptest"
"testing"
)
func TestGetQueryProps(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []struct {
name string
endpoint string
revRequired bool
wantErr bool
}{
{
name: "Get query props - all set with revRequired true",
wantErr: false,
revRequired: true,
endpoint: "/api/v1/namespaces/komodorio/charts?name=testing&namespace=testing&revision=1",
},
{
name: "Get query props - no revision with revRequired true",
wantErr: true,
revRequired: true,
endpoint: "/api/v1/namespaces/komodorio/charts?name=testing&namespace=testing",
},
{
name: "Get query props - no namespace with revRequired true",
wantErr: false,
revRequired: true,
endpoint: "/api/v1/namespaces/komodorio/charts?name=testing&revision=1",
},
{
name: "Get query props - no name with revRequired true",
wantErr: true,
revRequired: true,
endpoint: "/api/v1/namespaces/komodorio/charts?namespace=testing&revision=1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", tt.endpoint, nil)
_, err := GetQueryProps(c, tt.revRequired)
if (err != nil) != tt.wantErr {
t.Errorf("GetQueryProps() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestChartAndVersion(t *testing.T) {
tests := []struct {
name string
params string
wantChart string
wantVer string
wantError bool
}{
{
name: "Chart and version - successfully parsing chart and version",
params: "chart-1.0.0",
wantChart: "chart",
wantVer: "1.0.0",
wantError: false,
},
{
name: "Chart and version - parsing chart without version",
params: "chart",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a, b, err := ChartAndVersion(tt.params)
if (err != nil) != tt.wantError {
t.Errorf("ChartAndVersion() error = %v, wantErr %v", err, tt.wantError)
return
}
if a != tt.wantChart {
t.Errorf("ChartAndVersion() got = %v, want %v", a, tt.wantChart)
}
if b != tt.wantVer {
t.Errorf("ChartAndVersion() got1 = %v, want %v", b, tt.wantVer)
}
})
}
}

View File

@@ -1,8 +1,8 @@
name: "dashboard"
version: "0.0.0"
version: "0.2.1"
usage: "A simplified way of working with Helm"
description: "View HELM situation in nice web UI"
command: "$HELM_PLUGIN_DIR/bin/dashboard"
#hooks:
# install: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
# update: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
command: "$HELM_PLUGIN_DIR/bin/helm-dashboard"
hooks:
install: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"
update: "cd $HELM_PLUGIN_DIR; scripts/install_plugin.sh"

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

64
scripts/install_plugin.sh Executable file
View File

@@ -0,0 +1,64 @@
#!/bin/sh -e
# Copied w/ love from the chartmuseum/helm-push :)
name="helm-dashboard"
repo="https://github.com/komodorio/${name}"
if [ -n "${HELM_PUSH_PLUGIN_NO_INSTALL_HOOK}" ]; then
echo "Development mode: not downloading versioned release."
exit 0
fi
version="$(cat plugin.yaml | grep "version" | cut -d '"' -f 2)"
echo "Downloading and installing ${name} v${version} ..."
url=""
# convert architecture of the target system to a compatible GOARCH value.
# Otherwise failes to download of the plugin from github, because the provided
# architecture by `uname -m` is not part of the github release.
arch=""
case $(uname -m) in
x86_64)
arch="x86_64"
;;
armv6*)
arch="armv6"
;;
# match every arm processor version like armv7h, armv7l and so on.
armv7*)
arch="armv7"
;;
aarch64 | arm64)
arch="arm64"
;;
*)
echo "Failed to detect target architecture"
exit 1
;;
esac
if [ "$(uname)" = "Darwin" ]; then
url="${repo}/releases/download/v${version}/${name}_${version}_Darwin_${arch}.tar.gz"
elif [ "$(uname)" = "Linux" ] ; then
url="${repo}/releases/download/v${version}/${name}_${version}_Linux_${arch}.tar.gz"
else
url="${repo}/releases/download/v${version}/${name}_${version}_windows_${arch}.tar.gz"
fi
echo $url
mkdir -p "bin"
mkdir -p "releases/v${version}"
# Download with curl if possible.
if [ -x "$(which curl 2>/dev/null)" ]; then
curl --fail -sSL "${url}" -o "releases/v${version}.tar.gz"
else
wget -q "${url}" -O "releases/v${version}.tar.gz"
fi
tar xzf "releases/v${version}.tar.gz" -C "releases/v${version}"
mv "releases/v${version}/${name}" "bin/${name}" || \
mv "releases/v${version}/${name}.exe" "bin/${name}"