├── .github
├── CODEOWNERS
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── boring-cyborg.yml
└── workflows
│ ├── build.yml
│ └── release.yml
├── .gitignore
├── .go-version
├── .goreleaser.yml
├── .mergify.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── assets
└── img
│ └── gopher.png
├── config.dist.json
├── config.toml
├── core
├── cmd
│ ├── license.go
│ ├── root.go
│ ├── serve.go
│ └── version.go
├── controller
│ ├── debug.go
│ ├── health.go
│ ├── home.go
│ ├── metrics.go
│ └── mock.go
├── middleware
│ ├── correlation.go
│ ├── cors.go
│ ├── log.go
│ └── metric.go
├── model
│ ├── request.go
│ └── route.go
├── module
│ ├── faker.go
│ └── file_system.go
└── util
│ ├── helpers.go
│ └── helpers_test.go
├── deployment
├── .gitkeep
├── advanced
│ └── docker-compose
│ │ ├── configs
│ │ ├── config.prod.json
│ │ └── service2.getItem.response.json
│ │ ├── docker-compose.yml
│ │ └── prometheus
│ │ └── prometheus.yml
└── basic
│ └── docker-compose
│ ├── configs
│ ├── config.prod.json
│ └── service2.getItem.response.json
│ └── docker-compose.yml
├── go.mod
├── go.sum
├── pkg
└── expect.go
├── renovate.json
├── rhino.go
└── web
└── app.html
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Docs: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
2 | * @clivern
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: # clivern
2 | custom: clivern.com/sponsor/
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **Development or production environment**
11 | - OS: [e.g. Ubuntu 18.04]
12 | - Go 1.13
13 |
14 | **Additional context**
15 | Add any other context about the problem here.
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Additional context**
14 | Add any other context or screenshots about the feature request here.
15 |
--------------------------------------------------------------------------------
/.github/boring-cyborg.yml:
--------------------------------------------------------------------------------
1 | ---
2 | firstIssueWelcomeComment: "Thanks for opening your first issue here! Be sure to follow the issue template!"
3 | firstPRMergeComment: "Awesome work, congrats on your first merged pull request!"
4 | firstPRWelcomeComment: "Thanks for opening this pull request! Please check out our contributing guidelines."
5 |
6 | labelPRBasedOnFilePath:
7 | "🚧 CI":
8 | - .github/workflows/*
9 |
10 | "🚧 CSS":
11 | - "**/*.css"
12 |
13 | "🚧 Configuration":
14 | - .github/*
15 |
16 | "🚧 Dependencies":
17 | - Dockerfile*
18 | - composer.*
19 | - package.json
20 | - package-lock.json
21 | - yarn.lock
22 | - go.mod
23 | - go.sum
24 | - build.gradle
25 | - Cargo.toml
26 | - Cargo.lock
27 | - Gemfile.lock
28 | - Gemfile
29 |
30 | "🚧 Docker":
31 | - Dockerfile*
32 | - .docker/**/*
33 |
34 | "🚧 Documentation":
35 | - README.md
36 | - CONTRIBUTING.md
37 |
38 | "🚧 Go":
39 | - "**/*.go"
40 |
41 | "🚧 Rust":
42 | - "**/*.rs"
43 |
44 | "🚧 Java":
45 | - "**/*.java"
46 |
47 | "🚧 Ruby":
48 | - "**/*.rb"
49 |
50 | "🚧 HTML":
51 | - "**/*.htm"
52 | - "**/*.html"
53 |
54 | "🚧 Image":
55 | - "**/*.gif"
56 | - "**/*.jpg"
57 | - "**/*.jpeg"
58 | - "**/*.png"
59 | - "**/*.webp"
60 |
61 | "🚧 JSON":
62 | - "**/*.json"
63 |
64 | "🚧 JavaScript":
65 | - "**/*.js"
66 | - package.json
67 | - package-lock.json
68 | - yarn.lock
69 |
70 | "🚧 MarkDown":
71 | - "**/*.md"
72 |
73 | "🚧 PHP":
74 | - "**/*.php"
75 | - composer.*
76 |
77 | "🚧 Source":
78 | - src/**/*
79 |
80 | "🚧 TOML":
81 | - "**/*.toml"
82 |
83 | "🚧 Templates":
84 | - "**/*.twig"
85 | - "**/*.tpl"
86 |
87 | "🚧 Tests":
88 | - tests/**/*
89 |
90 | "🚧 YAML":
91 | - "**/*.yml"
92 | - "**/*.yaml"
93 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | go: ["1.19", "1.20.4", "1.21.3"]
11 | name: Go ${{ matrix.go }} run
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: Setup go
15 | uses: actions/setup-go@v5
16 | with:
17 | go-version: ${{ matrix.go }}
18 |
19 | - name: Get dependencies
20 | run: |
21 | export PATH=${PATH}:`go env GOPATH`/bin
22 | make install_revive
23 |
24 | - name: Run make ci
25 | run: |
26 | export PATH=${PATH}:`go env GOPATH`/bin
27 | make ci
28 | git status
29 | git diff > diff.log
30 | cat diff.log
31 | git clean -fd
32 | git reset --hard
33 | make verify
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | jobs:
9 | goreleaser:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0
16 | - name: Set up Go
17 | uses: actions/setup-go@v5
18 | with:
19 | go-version: 1.20.4
20 | - name: Run GoReleaser
21 | uses: goreleaser/goreleaser-action@v4
22 | with:
23 | version: latest
24 | args: release --clean
25 | env:
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | /config.prod.json
--------------------------------------------------------------------------------
/.go-version:
--------------------------------------------------------------------------------
1 | 1.20.4
2 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | # This is an example .goreleaser.yml file with some sensible defaults.
2 | # Make sure to check the documentation at https://goreleaser.com
3 | before:
4 | hooks:
5 | - "go mod download"
6 | - "go mod tidy"
7 | - "go generate ./..."
8 | builds:
9 | - env:
10 | - CGO_ENABLED=0
11 | goarch:
12 | - amd64
13 | goos:
14 | - linux
15 | - windows
16 | - darwin
17 | archives:
18 | - format: tar.gz
19 | # this name template makes the OS and Arch compatible with the results of uname.
20 | name_template: >-
21 | {{ .ProjectName }}_
22 | {{- title .Os }}_
23 | {{- if eq .Arch "amd64" }}x86_64
24 | {{- else if eq .Arch "386" }}i386
25 | {{- else }}{{ .Arch }}{{ end }}
26 | {{- if .Arm }}v{{ .Arm }}{{ end }}
27 | # use zip for windows archives
28 | format_overrides:
29 | - goos: windows
30 | format: zip
31 | files:
32 | - LICENSE
33 | - README.md
34 | - config.dist.json
35 | checksum:
36 | name_template: 'checksums.txt'
37 | snapshot:
38 | name_template: "{{ incpatch .Version }}-next"
39 | changelog:
40 | sort: asc
41 | filters:
42 | exclude:
43 | - '^docs:'
44 | - '^test:'
45 | project_name: rhino
46 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | ---
2 | pull_request_rules:
3 | -
4 | actions:
5 | merge:
6 | method: squash
7 | conditions:
8 | - author!=Clivern
9 | - approved-reviews-by=Clivern
10 | - label=merge
11 | - status-success=Travis CI - Pull Request
12 | - status-success=Travis CI - Branch
13 | name: "Automatic Merge 🚀"
14 | -
15 | actions:
16 | merge:
17 | method: merge
18 | conditions:
19 | - author=Clivern
20 | - label=merge
21 | name: "Automatic Merge 🚀"
22 | -
23 | actions:
24 | merge:
25 | method: squash
26 | conditions:
27 | - "author=renovate[bot]"
28 | - label=merge
29 | - status-success=Travis CI - Pull Request
30 | - status-success=Travis CI - Branch
31 | name: "Automatic Merge for Renovate PRs 🚀"
32 | -
33 | actions:
34 | comment:
35 | message: "Nice! PR successfully merged."
36 | conditions:
37 | - merged
38 | name: "Merge Done 🚀"
39 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at hello@clivern.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | - With issues:
4 | - Use the search tool before opening a new issue.
5 | - Please provide source code and commit sha if you found a bug.
6 | - Review existing issues and provide feedback or react to them.
7 |
8 | - With pull requests:
9 | - Open your pull request against `master`
10 | - Your pull request should have no more than two commits, if not you should squash them.
11 | - It should pass all tests in the available continuous integrations systems such as TravisCI.
12 | - You should add/modify tests to cover your proposed code changes.
13 | - If your pull request contains a new feature, please document it on the README.
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.21.6
2 |
3 | ARG RHINO_VERSION=1.6.2
4 |
5 | ENV GO111MODULE=on
6 |
7 | RUN mkdir -p /app/configs
8 | RUN apt-get update
9 |
10 | WORKDIR /app
11 |
12 | RUN curl -sL https://github.com/Clivern/Rhino/releases/download/${RHINO_VERSION}/Rhino_${RHINO_VERSION}_Linux_x86_64.tar.gz | tar xz
13 |
14 | RUN rm LICENSE
15 | RUN rm README.md
16 | RUN mv Rhino rhino
17 |
18 | COPY ./config.dist.json /app/configs/
19 |
20 | RUN ./rhino version
21 |
22 | EXPOSE 8080
23 |
24 | VOLUME /app/configs
25 |
26 | CMD ["./rhino", "serve", "-c", "/app/configs/config.dist.json"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Clivern
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GO ?= go
2 | GOFMT ?= $(GO)fmt
3 | pkgs = ./...
4 | NPM ?= npm
5 | NG ?= ng
6 |
7 |
8 | help: Makefile
9 | @echo
10 | @echo " Choose a command run in Rhino:"
11 | @echo
12 | @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
13 | @echo
14 |
15 |
16 | ## install_revive: Install revive for linting.
17 | .PHONY: install_revive
18 | install_revive:
19 | @echo ">> ============= Install Revive ============= <<"
20 | $(GO) install github.com/mgechev/revive@latest
21 |
22 |
23 | ## style: Check code style.
24 | .PHONY: style
25 | style:
26 | @echo ">> ============= Checking Code Style ============= <<"
27 | @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
28 | if [ -n "$${fmtRes}" ]; then \
29 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
30 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \
31 | exit 1; \
32 | fi
33 |
34 |
35 | ## check_license: Check if license header on all files.
36 | .PHONY: check_license
37 | check_license:
38 | @echo ">> ============= Checking License Header ============= <<"
39 | @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
40 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
41 | done); \
42 | if [ -n "$${licRes}" ]; then \
43 | echo "license header checking failed:"; echo "$${licRes}"; \
44 | exit 1; \
45 | fi
46 |
47 |
48 | ## test_short: Run test cases with short flag.
49 | .PHONY: test_short
50 | test_short:
51 | @echo ">> ============= Running Short Tests ============= <<"
52 | $(GO) test -short $(pkgs)
53 |
54 |
55 | ## test: Run test cases.
56 | .PHONY: test
57 | test:
58 | @echo ">> ============= Running All Tests ============= <<"
59 | $(GO) test -v -cover $(pkgs)
60 |
61 |
62 | ## lint: Lint the code.
63 | .PHONY: lint
64 | lint:
65 | @echo ">> ============= Lint All Files ============= <<"
66 | revive -config config.toml -exclude vendor/... -formatter friendly ./...
67 |
68 |
69 | ## verify: Verify dependencies
70 | .PHONY: verify
71 | verify:
72 | @echo ">> ============= List Dependencies ============= <<"
73 | $(GO) list -m all
74 | @echo ">> ============= Verify Dependencies ============= <<"
75 | $(GO) mod verify
76 |
77 |
78 | ## format: Format the code.
79 | .PHONY: format
80 | format:
81 | @echo ">> ============= Formatting Code ============= <<"
82 | $(GO) fmt $(pkgs)
83 |
84 |
85 | ## vet: Examines source code and reports suspicious constructs.
86 | .PHONY: vet
87 | vet:
88 | @echo ">> ============= Vetting Code ============= <<"
89 | $(GO) vet $(pkgs)
90 |
91 |
92 | ## coverage: Create HTML coverage report
93 | .PHONY: coverage
94 | coverage:
95 | @echo ">> ============= Coverage ============= <<"
96 | rm -f coverage.html cover.out
97 | $(GO) test -coverprofile=cover.out $(pkgs)
98 | go tool cover -html=cover.out -o coverage.html
99 |
100 |
101 | ## ci: Run all CI tests.
102 | .PHONY: ci
103 | ci: style check_license test vet lint
104 | @echo "\n==> All quality checks passed"
105 |
106 |
107 | ## run: Run the service
108 | .PHONY: run
109 | run:
110 | -cp -n config.dist.json config.prod.json
111 | $(GO) run rhino.go serve -c config.prod.json
112 |
113 |
114 | .PHONY: help
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Rhino
4 | HTTP Mocking & Debugging Service
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Rhino is an HTTP Mocking & Debugging Service. It enables easy mocking of any HTTP web service for testing and debugging purposes. Also it can simulate high latencies and failures to make sure your services have the capability to withstand and recover from failures. It supports cross-origin resource sharing (CORS) so it can be used as a backend for single page applications.
16 |
17 |
18 | ## Documentation
19 |
20 | ### Usage
21 |
22 | Get [the latest binary.](https://github.com/Clivern/Rhino/releases)
23 |
24 | ```zsh
25 | $ curl -sL https://github.com/Clivern/Rhino/releases/download/x.x.x/Rhino_x.x.x_OS_x86_64.tar.gz | tar xz
26 | ```
27 |
28 | Create the config file `config.prod.json`
29 |
30 | ```json
31 | {
32 | "app": {
33 | "mode": "prod or dev",
34 | "port": "8080",
35 | "domain": "http://127.0.0.1:8080",
36 | "tls": {
37 | "status": "off",
38 | "pemPath": "/cert/server.pem",
39 | "keyPath": "/cert/server.key"
40 | }
41 | },
42 | "mock": [
43 | {
44 | "path": "/api/v2/service1/mock/:id",
45 | "request": {
46 | "method": "get"
47 | },
48 | "response": {
49 | "statusCode": 200,
50 | "headers": [
51 | {"key": "Content-Type", "value": "application/json"}
52 | ],
53 | "body": "{\"id\": \":id\"}"
54 | },
55 | "chaos": {
56 | "latency": "0s",
57 | "failRate": "0%"
58 | }
59 | },
60 | {
61 | "path": "/api/v2/service2/mock/:id",
62 | "request": {
63 | "method": "get",
64 | "parameters": {
65 | "var_param": ":var_param",
66 | "fixed_param": 10
67 | }
68 | },
69 | "response": {
70 | "statusCode": 200,
71 | "headers": [
72 | {"key": "Content-Type", "value": "application/json"}
73 | ],
74 | "body": "@json:@config_dir/route.response.json"
75 | },
76 | "chaos": {
77 | "latency": "0s",
78 | "failRate": "0%"
79 | }
80 | }
81 | ],
82 | "debug": [
83 | {
84 | "path": "/api/v2/service/debug",
85 | "chaos": {
86 | "latency": "0s",
87 | "failRate": "0%"
88 | }
89 | }
90 | ],
91 | "log": {
92 | "level": "info",
93 | "output": "stdout or /var/log/rhino.log",
94 | "format": "text or json"
95 | }
96 | }
97 | ```
98 |
99 | Run Rhino with that config file
100 |
101 | ```zsh
102 | $ ./rhino serve -c /custom/path/config.prod.json
103 | ```
104 |
105 | Check the release.
106 |
107 | ```zsh
108 | $ ./rhino version
109 | ```
110 |
111 | Test it.
112 |
113 | ```zsh
114 | $ curl http://127.0.0.1:8080/_health
115 | ```
116 |
117 | You can use fake data flags inside response body and rhino will auto generate them. Here is the full list of supported types:
118 |
119 | ```bash
120 | AnyOf: @fake(:anyof[A||B||C||D])
121 | Latitude: @fake(:lat)
122 | Longitude: @fake(:long)
123 | CreditCardNumber: @fake(:cc_number)
124 | CreditCardType: @fake(:cc_type)
125 | Email: @fake(:email)
126 | DomainName: @fake(:domain_name)
127 | IPV4: @fake(:ipv4)
128 | IPV6: @fake(:ipv6)
129 | Password: @fake(:password)
130 | PhoneNumber: @fake(:phone_number)
131 | MacAddress: @fake(:mac_address)
132 | URL: @fake(:url)
133 | UserName: @fake(:username)
134 | TollFreeNumber: @fake(:toll_free_number)
135 | E164PhoneNumber: @fake(:e_164_phone_number)
136 | TitleMale: @fake(:title_male)
137 | TitleFemale: @fake(:title_female)
138 | FirstName: @fake(:first_name)
139 | FirstNameMale: @fake(:first_name_male)
140 | FirstNameFemale: @fake(:first_name_female)
141 | LastName: @fake(:last_name)
142 | Name: @fake(:name)
143 | UnixTime: @fake(:unix_time)
144 | Date: @fake(:date)
145 | Time: @fake(:time)
146 | MonthName: @fake(:month_name)
147 | Year: @fake(:year)
148 | DayOfWeek: @fake(:day_of_week)
149 | DayOfMonth: @fake(:day_of_month)
150 | Timestamp: @fake(:timestamp)
151 | Century: @fake(:century)
152 | TimeZone: @fake(:timezone)
153 | TimePeriod: @fake(:time_period)
154 | Word: @fake(:word)
155 | Sentence: @fake(:sentence)
156 | Paragraph: @fake(:paragraph)
157 | Currency: @fake(:currency)
158 | Amount: @fake(:amount)
159 | AmountWithCurrency: @fake(:amount_with_currency)
160 | UUIDHypenated: @fake(:uuid_hyphenated)
161 | UUID: @fake(:uuid_digit)
162 | ```
163 |
164 |
165 | ### Docker
166 |
167 | Clone and then run docker containers.
168 |
169 | ```zsh
170 | # Simple setup
171 | $ git clone https://github.com/Clivern/Rhino.git
172 | $ cd Rhino/deployment/basic/docker-compose
173 | $ docker-compose up -d
174 |
175 | # In case you want to visualize incoming requests with grafana
176 | $ git clone https://github.com/Clivern/Rhino.git
177 | $ cd Rhino/deployment/advanced/docker-compose
178 | $ docker-compose up -d
179 | ```
180 |
181 |
182 | ## Versioning
183 |
184 | For transparency into our release cycle and in striving to maintain backward compatibility, Rhino is maintained under the [Semantic Versioning guidelines](https://semver.org/) and release process is predictable and business-friendly.
185 |
186 | See the [Releases section of our GitHub project](https://github.com/clivern/rhino/releases) for changelogs for each release version of Rhino. It contains summaries of the most noteworthy changes made in each release.
187 |
188 |
189 | ## Bug tracker
190 |
191 | If you have any suggestions, bug reports, or annoyances please report them to our issue tracker at https://github.com/clivern/rhino/issues
192 |
193 |
194 | ## Security Issues
195 |
196 | If you discover a security vulnerability within Rhino, please send an email to [hello@clivern.com](mailto:hello@clivern.com)
197 |
198 |
199 | ## Contributing
200 |
201 | We are an open source, community-driven project so please feel free to join us. see the [contributing guidelines](CONTRIBUTING.md) for more details.
202 |
203 |
204 | ## License
205 |
206 | © 2020, Clivern. Released under [MIT License](https://opensource.org/licenses/mit-license.php).
207 |
208 | **Rhino** is authored and maintained by [@clivern](http://github.com/clivern).
209 |
--------------------------------------------------------------------------------
/assets/img/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Rhino/fb1513d5f8d2c6a0710e10c352297a27f02c4c3c/assets/img/gopher.png
--------------------------------------------------------------------------------
/config.dist.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "mode": "dev",
4 | "port": "8080",
5 | "domain": "http://127.0.0.1:8080",
6 | "tls": {
7 | "status": "off",
8 | "pemPath": "/cert/server.pem",
9 | "keyPath": "/cert/server.key"
10 | }
11 | },
12 | "mock": [
13 | {
14 | "path": "/api/v2/service1/mock/:id",
15 | "request": {
16 | "method": "get",
17 | "parameters": {}
18 | },
19 | "response": {
20 | "statusCode": 200,
21 | "headers": [
22 | {"key": "Content-Type", "value": "application/json"}
23 | ],
24 | "body": "{\"id\": \":id\"}"
25 | },
26 | "chaos": {
27 | "latency": "0s",
28 | "failRate": "0%"
29 | }
30 | },
31 | {
32 | "path": "/api/v2/service2/mock/:id",
33 | "request": {
34 | "method": "get",
35 | "parameters": {
36 | "var_param": ":var_param",
37 | "fixed_param": 10
38 | }
39 | },
40 | "response": {
41 | "statusCode": 200,
42 | "headers": [
43 | {"key": "Content-Type", "value": "application/json"}
44 | ],
45 | "body": "@json:@config_dir/route.response.json"
46 | },
47 | "chaos": {
48 | "latency": "0s",
49 | "failRate": "0%"
50 | }
51 | },
52 | {
53 | "path": "/_fake_data",
54 | "request": {
55 | "method": "get",
56 | "parameters": {}
57 | },
58 | "response": {
59 | "statusCode": 200,
60 | "headers": [
61 | {"key": "Content-Type", "value": "application/json"}
62 | ],
63 | "body": "{\"AnyOf\":\"@fake(:anyof[A||B||C||D])\",\"Latitude\":\"@fake(:lat)\",\"Longitude\":\"@fake(:long)\",\"CreditCardNumber\":\"@fake(:cc_number)\",\"CreditCardType\":\"@fake(:cc_type)\",\"Email\":\"@fake(:email)\",\"DomainName\":\"@fake(:domain_name)\",\"IPV4\":\"@fake(:ipv4)\",\"IPV6\":\"@fake(:ipv6)\",\"Password\":\"@fake(:password)\",\"PhoneNumber\":\"@fake(:phone_number)\",\"MacAddress\":\"@fake(:mac_address)\",\"URL\":\"@fake(:url)\",\"UserName\":\"@fake(:username)\",\"TollFreeNumber\":\"@fake(:toll_free_number)\",\"E164PhoneNumber\":\"@fake(:e_164_phone_number)\",\"TitleMale\":\"@fake(:title_male)\",\"TitleFemale\":\"@fake(:title_female)\",\"FirstName\":\"@fake(:first_name)\",\"FirstNameMale\":\"@fake(:first_name_male)\",\"FirstNameFemale\":\"@fake(:first_name_female)\",\"LastName\":\"@fake(:last_name)\",\"Name\":\"@fake(:name)\",\"UnixTime\":\"@fake(:unix_time)\",\"Date\":\"@fake(:date)\",\"Time\":\"@fake(:time)\",\"MonthName\":\"@fake(:month_name)\",\"Year\":\"@fake(:year)\",\"DayOfWeek\":\"@fake(:day_of_week)\",\"DayOfMonth\":\"@fake(:day_of_month)\",\"Timestamp\":\"@fake(:timestamp)\",\"Century\":\"@fake(:century)\",\"TimeZone\":\"@fake(:timezone)\",\"TimePeriod\":\"@fake(:time_period)\",\"Word\":\"@fake(:word)\",\"Sentence\":\"@fake(:sentence)\",\"Paragraph\":\"@fake(:paragraph)\",\"Currency\":\"@fake(:currency)\",\"Amount\":\"@fake(:amount)\",\"AmountWithCurrency\":\"@fake(:amount_with_currency)\",\"UUIDHypenated\":\"@fake(:uuid_hyphenated)\",\"UUID\":\"@fake(:uuid_digit)\"}"
64 | },
65 | "chaos": {
66 | "latency": "0s",
67 | "failRate": "0%"
68 | }
69 | },
70 | {
71 | "path": "/api/singleEndpoint",
72 | "request": {
73 | "method": "get",
74 | "parameters": {
75 | "action": "actionA"
76 | }
77 | },
78 | "response": {
79 | "statusCode": 200,
80 | "headers": [
81 | {"key": "Content-Type", "value": "application/json"}
82 | ],
83 | "body": "{\"action\": \"actionA\"}"
84 | },
85 | "chaos": {
86 | "latency": "0s",
87 | "failRate": "0%"
88 | }
89 | },
90 | {
91 | "path": "/api/singleEndpoint",
92 | "request": {
93 | "method": "get",
94 | "parameters": {
95 | "action": "actionB"
96 | }
97 | },
98 | "response": {
99 | "statusCode": 200,
100 | "headers": [
101 | {"key": "Content-Type", "value": "application/json"}
102 | ],
103 | "body": "{\"action\": \"actionB\"}"
104 | },
105 | "chaos": {
106 | "latency": "0s",
107 | "failRate": "0%"
108 | }
109 | },
110 | {
111 | "path": "/api/singleEndpoint",
112 | "request": {
113 | "method": "get",
114 | "parameters": {
115 | "action": ":action"
116 | }
117 | },
118 | "response": {
119 | "statusCode": 200,
120 | "headers": [
121 | {"key": "Content-Type", "value": "application/json"}
122 | ],
123 | "body": "{\"action\": \":action\"}"
124 | },
125 | "chaos": {
126 | "latency": "0s",
127 | "failRate": "0%"
128 | }
129 | }
130 | ],
131 | "debug": [
132 | {
133 | "path": "/api/v2/service/debug",
134 | "chaos": {
135 | "latency": "0s",
136 | "failRate": "0%"
137 | }
138 | }
139 | ],
140 | "log": {
141 | "level": "info",
142 | "output": "stdout",
143 | "format": "json"
144 | }
145 | }
--------------------------------------------------------------------------------
/config.toml:
--------------------------------------------------------------------------------
1 | ignoreGeneratedHeader = false
2 | severity = "warning"
3 | confidence = 0.8
4 | errorCode = 0
5 | warningCode = 0
6 |
7 | [rule.blank-imports]
8 | [rule.context-as-argument]
9 | [rule.context-keys-type]
10 | [rule.dot-imports]
11 | [rule.error-return]
12 | [rule.error-strings]
13 | [rule.error-naming]
14 | [rule.exported]
15 | [rule.if-return]
16 | [rule.increment-decrement]
17 | [rule.var-naming]
18 | [rule.var-declaration]
19 | [rule.package-comments]
20 | [rule.range]
21 | [rule.receiver-naming]
22 | [rule.time-naming]
23 | [rule.unexported-return]
24 | [rule.indent-error-flow]
25 | [rule.errorf]
26 | [rule.empty-block]
27 | [rule.superfluous-else]
28 | [rule.unused-parameter]
29 | [rule.unreachable-code]
30 | [rule.redefines-builtin-id]
--------------------------------------------------------------------------------
/core/cmd/license.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "fmt"
9 |
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | var licenseCmd = &cobra.Command{
14 | Use: "license",
15 | Short: "Get License",
16 | Run: func(cmd *cobra.Command, args []string) {
17 | fmt.Println(`MIT License
18 |
19 | Copyright (c) 2020 Clivern
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.`)
38 | },
39 | }
40 |
41 | func init() {
42 | rootCmd.AddCommand(licenseCmd)
43 | }
44 |
--------------------------------------------------------------------------------
/core/cmd/root.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "fmt"
9 | "os"
10 |
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | var rootCmd = &cobra.Command{
15 | Use: "rhino",
16 | Short: `Work seamlessly with Rhino from the command line.
17 |
18 | We'd love to hear your feedback at `,
19 | }
20 |
21 | // Execute runs cmd tool
22 | func Execute() {
23 | if err := rootCmd.Execute(); err != nil {
24 | fmt.Println(err)
25 | os.Exit(1)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/cmd/serve.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "net/http"
13 | "os"
14 | "path/filepath"
15 | "strconv"
16 | "strings"
17 |
18 | "github.com/clivern/rhino/core/controller"
19 | "github.com/clivern/rhino/core/middleware"
20 | "github.com/clivern/rhino/core/model"
21 | "github.com/clivern/rhino/core/module"
22 | "github.com/clivern/rhino/core/util"
23 |
24 | "github.com/drone/envsubst"
25 | "github.com/gin-gonic/gin"
26 | log "github.com/sirupsen/logrus"
27 | "github.com/spf13/cobra"
28 | "github.com/spf13/viper"
29 | )
30 |
31 | var config string
32 |
33 | var serveCmd = &cobra.Command{
34 | Use: "serve",
35 | Short: "Start Rhino Server",
36 | Run: func(cmd *cobra.Command, args []string) {
37 | configUnparsed, err := ioutil.ReadFile(config)
38 |
39 | if err != nil {
40 | panic(fmt.Sprintf(
41 | "Error while reading config file [%s]: %s",
42 | config,
43 | err.Error(),
44 | ))
45 | }
46 |
47 | configParsed, err := envsubst.EvalEnv(string(configUnparsed))
48 |
49 | if err != nil {
50 | panic(fmt.Sprintf(
51 | "Error while parsing config file [%s]: %s",
52 | config,
53 | err.Error(),
54 | ))
55 | }
56 |
57 | viper.SetConfigType("json")
58 | err = viper.ReadConfig(bytes.NewBuffer([]byte(configParsed)))
59 |
60 | if err != nil {
61 | panic(fmt.Sprintf(
62 | "Error while loading configs [%s]: %s",
63 | config,
64 | err.Error(),
65 | ))
66 | }
67 |
68 | viper.SetDefault("configPath", filepath.Dir(config))
69 |
70 | if viper.GetString("log.output") != "stdout" {
71 | fs := module.FileSystem{}
72 | dir, _ := filepath.Split(viper.GetString("log.output"))
73 |
74 | if !fs.DirExists(dir) {
75 | if _, err := fs.EnsureDir(dir, 777); err != nil {
76 | panic(fmt.Sprintf(
77 | "Directory [%s] creation failed with error: %s",
78 | dir,
79 | err.Error(),
80 | ))
81 | }
82 | }
83 |
84 | if !fs.FileExists(viper.GetString("log.output")) {
85 | f, err := os.Create(viper.GetString("log.output"))
86 | if err != nil {
87 | panic(fmt.Sprintf(
88 | "Error while creating log file [%s]: %s",
89 | viper.GetString("log.output"),
90 | err.Error(),
91 | ))
92 | }
93 | defer f.Close()
94 | }
95 | }
96 |
97 | if viper.GetString("log.output") == "stdout" {
98 | gin.DefaultWriter = os.Stdout
99 | log.SetOutput(os.Stdout)
100 | } else {
101 | f, _ := os.Create(viper.GetString("log.output"))
102 | gin.DefaultWriter = io.MultiWriter(f)
103 | log.SetOutput(f)
104 | }
105 |
106 | lvl := strings.ToLower(viper.GetString("log.level"))
107 | level, err := log.ParseLevel(lvl)
108 |
109 | if err != nil {
110 | level = log.InfoLevel
111 | }
112 |
113 | log.SetLevel(level)
114 |
115 | if viper.GetString("app.mode") == "prod" {
116 | gin.SetMode(gin.ReleaseMode)
117 | gin.DefaultWriter = ioutil.Discard
118 | gin.DisableConsoleColor()
119 | }
120 |
121 | if viper.GetString("log.format") == "json" {
122 | log.SetFormatter(&log.JSONFormatter{})
123 | } else {
124 | log.SetFormatter(&log.TextFormatter{})
125 | }
126 |
127 | r := gin.Default()
128 |
129 | r.Use(middleware.Cors())
130 | r.Use(middleware.Correlation())
131 | r.Use(middleware.Logger())
132 | r.Use(middleware.Metric())
133 |
134 | r.GET("/favicon.ico", func(c *gin.Context) {
135 | c.String(http.StatusNoContent, "")
136 | })
137 |
138 | r.GET("/", controller.Index)
139 | r.GET("/_health", controller.Health)
140 | r.GET("/_metrics", gin.WrapH(controller.Metrics()))
141 |
142 | debugRoutes, err := model.GetDebugRoutes()
143 |
144 | if err != nil {
145 | panic(fmt.Sprintf(
146 | "Error while building debug routes from config file: %s",
147 | err.Error(),
148 | ))
149 | }
150 |
151 | for _, route := range debugRoutes {
152 | r.Any(route.Path, controller.Debug)
153 | }
154 |
155 | mockRoutes, err := model.GetMockRoutes()
156 |
157 | if err != nil {
158 | panic(fmt.Sprintf(
159 | "Error while building mock routes from config file: %s",
160 | err.Error(),
161 | ))
162 | }
163 |
164 | routes := []string{}
165 |
166 | for _, route := range mockRoutes {
167 | uri := fmt.Sprintf(
168 | "%s:%s",
169 | strings.ToLower(route.Request.Method),
170 | route.Path,
171 | )
172 |
173 | if util.InArray(uri, routes) {
174 | continue
175 | }
176 |
177 | if strings.ToLower(route.Request.Method) == "get" {
178 | r.GET(route.Path, controller.Mock)
179 | } else if strings.ToLower(route.Request.Method) == "post" {
180 | r.POST(route.Path, controller.Mock)
181 | } else if strings.ToLower(route.Request.Method) == "put" {
182 | r.PUT(route.Path, controller.Mock)
183 | } else if strings.ToLower(route.Request.Method) == "delete" {
184 | r.DELETE(route.Path, controller.Mock)
185 | } else if strings.ToLower(route.Request.Method) == "patch" {
186 | r.PATCH(route.Path, controller.Mock)
187 | } else if strings.ToLower(route.Request.Method) == "head" {
188 | r.HEAD(route.Path, controller.Mock)
189 | } else if strings.ToLower(route.Request.Method) == "options" {
190 | r.OPTIONS(route.Path, controller.Mock)
191 | }
192 |
193 | routes = append(routes, uri)
194 | }
195 |
196 | var runerr error
197 |
198 | if viper.GetBool("app.tls.status") {
199 | runerr = r.RunTLS(
200 | fmt.Sprintf(":%s", strconv.Itoa(viper.GetInt("app.port"))),
201 | viper.GetString("app.tls.pemPath"),
202 | viper.GetString("app.tls.keyPath"),
203 | )
204 | } else {
205 | runerr = r.Run(
206 | fmt.Sprintf(":%s", strconv.Itoa(viper.GetInt("app.port"))),
207 | )
208 | }
209 |
210 | if runerr != nil {
211 | panic(runerr.Error())
212 | }
213 | },
214 | }
215 |
216 | func init() {
217 | serveCmd.Flags().StringVarP(&config, "config", "c", "config.prod.yml", "Absolute path to config file (required)")
218 | serveCmd.MarkFlagRequired("config")
219 | rootCmd.AddCommand(serveCmd)
220 | }
221 |
--------------------------------------------------------------------------------
/core/cmd/version.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmd
6 |
7 | import (
8 | "fmt"
9 |
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | var (
14 | // Version buildinfo item
15 | Version = "dev"
16 | // Commit buildinfo item
17 | Commit = "none"
18 | // Date buildinfo item
19 | Date = "unknown"
20 | // BuiltBy buildinfo item
21 | BuiltBy = "unknown"
22 | )
23 |
24 | var versionCmd = &cobra.Command{
25 | Use: "version",
26 | Short: "Get current version",
27 | Run: func(cmd *cobra.Command, args []string) {
28 | fmt.Println(
29 | fmt.Sprintf(
30 | `Current Rhino Version %v Commit %v, Built @%v By %v.`,
31 | Version,
32 | Commit,
33 | Date,
34 | BuiltBy,
35 | ),
36 | )
37 | },
38 | }
39 |
40 | func init() {
41 | rootCmd.AddCommand(versionCmd)
42 | }
43 |
--------------------------------------------------------------------------------
/core/controller/debug.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "bytes"
9 | "io/ioutil"
10 | "math/rand"
11 | "net/http"
12 | "strconv"
13 | "strings"
14 | "time"
15 |
16 | "github.com/clivern/rhino/core/model"
17 |
18 | "github.com/gin-gonic/gin"
19 | log "github.com/sirupsen/logrus"
20 | )
21 |
22 | // Debug controller
23 | func Debug(c *gin.Context) {
24 | var bodyBytes []byte
25 |
26 | // Workaround for issue https://github.com/gin-gonic/gin/issues/1651
27 | if c.Request.Body != nil {
28 | bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
29 | }
30 |
31 | c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
32 |
33 | parameters := make(map[string]string)
34 | headers := make(map[string]string)
35 |
36 | for k, v := range c.Request.URL.Query() {
37 | parameters[k] = strings.Join(v, ", ")
38 | }
39 |
40 | for k, v := range c.Request.Header {
41 | headers[k] = strings.Join(v, ", ")
42 | }
43 |
44 | route := model.GetRoute(c.FullPath(), "", parameters)
45 |
46 | rand.Seed(time.Now().UnixNano())
47 |
48 | failCount, _ := strconv.Atoi(strings.Replace(route.Chaos.FailRate, "%", "", -1))
49 |
50 | if rand.Intn(100) < failCount {
51 | log.WithFields(log.Fields{
52 | "method": c.Request.Method,
53 | "url": c.Request.URL.Path,
54 | "headers": headers,
55 | "parameters": parameters,
56 | "body": string(bodyBytes),
57 | }).Info("Failed Request")
58 |
59 | c.Status(http.StatusInternalServerError)
60 | return
61 | }
62 |
63 | latencySeconds, _ := strconv.Atoi(strings.Replace(route.Chaos.Latency, "s", "", -1))
64 |
65 | time.Sleep(time.Duration(latencySeconds) * time.Second)
66 |
67 | log.WithFields(log.Fields{
68 | "method": c.Request.Method,
69 | "url": c.Request.URL.Path,
70 | "headers": headers,
71 | "parameters": parameters,
72 | "body": string(bodyBytes),
73 | }).Info("Request Success")
74 |
75 | c.JSON(http.StatusOK, gin.H{
76 | "status": "ok",
77 | })
78 | }
79 |
--------------------------------------------------------------------------------
/core/controller/health.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // Health controller
14 | func Health(c *gin.Context) {
15 | c.JSON(http.StatusOK, gin.H{
16 | "status": "ok",
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/core/controller/home.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // Index controller
14 | func Index(c *gin.Context) {
15 |
16 | homeTpl := `
17 |
18 |
19 |
20 |
21 | Rhino
22 |
23 |
24 |
25 |
26 |
27 |
228 |
229 |
230 |
231 |
242 |
249 |
250 |
251 | Contributing
252 | Want to contribute? Follow these Recommendations.
253 |
254 |
259 |
260 |
261 | `
262 | c.Writer.WriteHeader(http.StatusOK)
263 | c.Writer.Write([]byte(homeTpl))
264 | return
265 | }
266 |
--------------------------------------------------------------------------------
/core/controller/metrics.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/prometheus/client_golang/prometheus/promhttp"
11 | )
12 |
13 | // Metrics controller
14 | func Metrics() http.Handler {
15 | return promhttp.Handler()
16 | }
17 |
--------------------------------------------------------------------------------
/core/controller/mock.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package controller
6 |
7 | import (
8 | "bytes"
9 | "io/ioutil"
10 | "math/rand"
11 | "net/http"
12 | "strconv"
13 | "strings"
14 | "time"
15 |
16 | "github.com/clivern/rhino/core/model"
17 | "github.com/clivern/rhino/core/module"
18 |
19 | "github.com/gin-gonic/gin"
20 | log "github.com/sirupsen/logrus"
21 | "github.com/spf13/viper"
22 | )
23 |
24 | // Mock controller
25 | func Mock(c *gin.Context) {
26 | var bodyBytes []byte
27 |
28 | // Workaround for issue https://github.com/gin-gonic/gin/issues/1651
29 | if c.Request.Body != nil {
30 | bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
31 | }
32 |
33 | c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
34 |
35 | parameters := make(map[string]string)
36 | headers := make(map[string]string)
37 |
38 | for k, v := range c.Request.URL.Query() {
39 | parameters[k] = strings.Join(v, ", ")
40 | }
41 |
42 | for k, v := range c.Request.Header {
43 | headers[k] = strings.Join(v, ", ")
44 | }
45 |
46 | route := model.GetRoute(c.FullPath(), c.Request.Method, parameters)
47 |
48 | rand.Seed(time.Now().UnixNano())
49 |
50 | failCount, _ := strconv.Atoi(strings.Replace(route.Chaos.FailRate, "%", "", -1))
51 |
52 | if rand.Intn(100) < failCount {
53 | log.WithFields(log.Fields{
54 | "method": c.Request.Method,
55 | "url": c.Request.URL.Path,
56 | "headers": headers,
57 | "parameters": parameters,
58 | "body": string(bodyBytes),
59 | }).Info("Failed Request")
60 |
61 | c.Status(http.StatusInternalServerError)
62 | return
63 | }
64 |
65 | latencySeconds, _ := strconv.Atoi(strings.Replace(route.Chaos.Latency, "s", "", -1))
66 |
67 | time.Sleep(time.Duration(latencySeconds) * time.Second)
68 |
69 | log.WithFields(log.Fields{
70 | "method": c.Request.Method,
71 | "url": c.Request.URL.Path,
72 | "headers": headers,
73 | "parameters": parameters,
74 | "body": string(bodyBytes),
75 | }).Info("Request Success")
76 |
77 | for _, header := range route.Response.Headers {
78 | c.Header(header.Key, header.Value)
79 | }
80 |
81 | if strings.Contains(route.Response.Body, "@json:") {
82 | path := strings.Replace(route.Response.Body, "@json:", "", -1)
83 | path = strings.Replace(path, "@config_dir", viper.GetString("configPath"), -1)
84 | content, err := ioutil.ReadFile(path)
85 |
86 | if err != nil {
87 | panic(err)
88 | }
89 |
90 | route.Response.Body = string(content)
91 | }
92 |
93 | for _, param := range c.Params {
94 | route.Response.Body = strings.Replace(route.Response.Body, ":"+param.Key, param.Value, -1)
95 | }
96 |
97 | for key, value := range route.Request.Parameters {
98 |
99 | if !strings.HasPrefix(value, ":") {
100 | continue
101 | }
102 |
103 | route.Response.Body = strings.Replace(route.Response.Body, value, parameters[key], -1)
104 | }
105 |
106 | faker := &module.Faker{}
107 | var err error
108 | route.Response.Body, err = faker.Transform(route.Response.Body)
109 |
110 | if err != nil {
111 | panic(err)
112 | }
113 |
114 | c.String(route.Response.StatusCode, route.Response.Body)
115 | }
116 |
--------------------------------------------------------------------------------
/core/middleware/correlation.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "strings"
9 |
10 | "github.com/clivern/rhino/core/util"
11 |
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | // Correlation middleware
16 | func Correlation() gin.HandlerFunc {
17 | return func(c *gin.Context) {
18 | corralationID := c.Request.Header.Get("X-Correlation-ID")
19 |
20 | if strings.TrimSpace(corralationID) == "" {
21 | c.Request.Header.Add("X-Correlation-ID", util.GenerateUUID4())
22 | }
23 | c.Next()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/middleware/cors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | // Cors middleware
14 | func Cors() gin.HandlerFunc {
15 | return func(c *gin.Context) {
16 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
17 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
18 | c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
19 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
20 |
21 | if c.Request.Method == "OPTIONS" {
22 | c.AbortWithStatus(http.StatusOK)
23 | return
24 | }
25 |
26 | c.Next()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/middleware/log.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "bytes"
9 | "io/ioutil"
10 |
11 | "github.com/gin-gonic/gin"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | // Logger middleware
16 | func Logger() gin.HandlerFunc {
17 | return func(c *gin.Context) {
18 | // before request
19 | var bodyBytes []byte
20 |
21 | // Workaround for issue https://github.com/gin-gonic/gin/issues/1651
22 | if c.Request.Body != nil {
23 | bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
24 | }
25 |
26 | c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
27 |
28 | log.WithFields(log.Fields{
29 | "correlation_id": c.Request.Header.Get("X-Correlation-ID"),
30 | "http_method": c.Request.Method,
31 | "http_path": c.Request.URL.Path,
32 | "request_body": string(bodyBytes),
33 | }).Info("Request started")
34 |
35 | c.Next()
36 |
37 | // after request
38 | status := c.Writer.Status()
39 | size := c.Writer.Size()
40 |
41 | log.WithFields(log.Fields{
42 | "correlation_id": c.Request.Header.Get("X-Correlation-ID"),
43 | "http_status": status,
44 | "response_size": size,
45 | }).Info(`Request finished`)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/core/middleware/metric.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package middleware
6 |
7 | import (
8 | "strconv"
9 | "time"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/prometheus/client_golang/prometheus"
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | var (
17 | httpRequests = prometheus.NewCounterVec(
18 | prometheus.CounterOpts{
19 | Namespace: "rhino",
20 | Name: "total_http_requests",
21 | Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
22 | }, []string{"code", "method", "handler", "host", "url"})
23 |
24 | requestDuration = prometheus.NewHistogramVec(
25 | prometheus.HistogramOpts{
26 | Subsystem: "rhino",
27 | Name: "request_duration_seconds",
28 | Help: "The HTTP request latencies in seconds.",
29 | },
30 | []string{"code", "method", "url"},
31 | )
32 |
33 | responseSize = prometheus.NewSummary(
34 | prometheus.SummaryOpts{
35 | Namespace: "rhino",
36 | Name: "response_size_bytes",
37 | Help: "The HTTP response sizes in bytes.",
38 | },
39 | )
40 | )
41 |
42 | func init() {
43 | prometheus.MustRegister(httpRequests)
44 | prometheus.MustRegister(requestDuration)
45 | prometheus.MustRegister(responseSize)
46 | }
47 |
48 | // Metric middleware
49 | func Metric() gin.HandlerFunc {
50 | return func(c *gin.Context) {
51 | // before request
52 | start := time.Now()
53 |
54 | c.Next()
55 |
56 | // after request
57 | elapsed := float64(time.Since(start)) / float64(time.Second)
58 |
59 | log.WithFields(log.Fields{
60 | "correlation_id": c.Request.Header.Get("X-Correlation-ID"),
61 | }).Info(`Collecting metrics`)
62 |
63 | // Collect Metrics
64 | httpRequests.WithLabelValues(
65 | strconv.Itoa(c.Writer.Status()),
66 | c.Request.Method,
67 | c.HandlerName(),
68 | c.Request.Host,
69 | c.Request.URL.Path,
70 | ).Inc()
71 |
72 | requestDuration.WithLabelValues(
73 | strconv.Itoa(c.Writer.Status()),
74 | c.Request.Method,
75 | c.Request.URL.Path,
76 | ).Observe(elapsed)
77 |
78 | responseSize.Observe(float64(c.Writer.Size()))
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/core/model/request.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import (
8 | "encoding/json"
9 | "time"
10 | )
11 |
12 | // Header struct
13 | type Header struct {
14 | Key string `json:"key"`
15 | Value string `json:"value"`
16 | }
17 |
18 | // Request struct
19 | type Request struct {
20 | Route string `json:"route"`
21 | URI string `json:"uri"`
22 | Method string `json:"method"`
23 | StatusCode int `json:"statusCode"`
24 | Headers []Header `json:"headers"`
25 | Status string `json:"status"`
26 | Body string `json:"body"`
27 | Time time.Time `json:"time"`
28 | }
29 |
30 | // LoadFromJSON update object from json
31 | func (p *Request) LoadFromJSON(data []byte) (bool, error) {
32 | err := json.Unmarshal(data, &p)
33 | if err != nil {
34 | return false, err
35 | }
36 | return true, nil
37 | }
38 |
39 | // ConvertToJSON convert object to json
40 | func (p *Request) ConvertToJSON() (string, error) {
41 | data, err := json.Marshal(&p)
42 | if err != nil {
43 | return "", err
44 | }
45 | return string(data), nil
46 | }
47 |
--------------------------------------------------------------------------------
/core/model/route.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package model
6 |
7 | import (
8 | "strings"
9 |
10 | "github.com/spf13/viper"
11 | )
12 |
13 | // Route struct
14 | type Route struct {
15 | Path string `mapstructure:"path"`
16 | Request struct {
17 | Method string `mapstructure:"method"`
18 | Parameters map[string]string `mapstructure:"parameters"`
19 | } `mapstructure:"request"`
20 | Response struct {
21 | StatusCode int `mapstructure:"statusCode"`
22 | Headers []struct {
23 | Key string `mapstructure:"key"`
24 | Value string `mapstructure:"value"`
25 | } `mapstructure:"headers"`
26 | Body string `mapstructure:"body"`
27 | } `mapstructure:"response"`
28 | Chaos struct {
29 | Latency string `mapstructure:"latency"`
30 | FailRate string `mapstructure:"failRate"`
31 | } `mapstructure:"chaos"`
32 | }
33 |
34 | // GetDebugRoutes get a list of debug routes
35 | func GetDebugRoutes() ([]Route, error) {
36 | var routes []Route
37 |
38 | err := viper.UnmarshalKey("debug", &routes)
39 |
40 | if err != nil {
41 | return routes, err
42 | }
43 |
44 | return routes, nil
45 | }
46 |
47 | // GetMockRoutes get a list of mock routes
48 | func GetMockRoutes() ([]Route, error) {
49 | var routes []Route
50 |
51 | err := viper.UnmarshalKey("mock", &routes)
52 |
53 | if err != nil {
54 | return routes, err
55 | }
56 |
57 | return routes, nil
58 | }
59 |
60 | // GetRoute get route object with path
61 | func GetRoute(path string, method string, parameters map[string]string) Route {
62 | debugRoutes, _ := GetDebugRoutes()
63 |
64 | for _, route := range debugRoutes {
65 | if path != route.Path {
66 | continue
67 | }
68 |
69 | // Match parameters
70 | if len(route.Request.Parameters) != 0 {
71 | status := true
72 |
73 | for key, value := range route.Request.Parameters {
74 | if _, ok := parameters[key]; !ok {
75 | status = false
76 | }
77 |
78 | if !strings.HasPrefix(value, ":") && value != parameters[key] {
79 | status = false
80 | }
81 | }
82 |
83 | if !status {
84 | continue
85 | }
86 | }
87 |
88 | return route
89 | }
90 |
91 | mockRoutes, _ := GetMockRoutes()
92 |
93 | for _, route := range mockRoutes {
94 | if path != route.Path || strings.ToLower(method) != strings.ToLower(route.Request.Method) {
95 | continue
96 | }
97 |
98 | // Match parameters
99 | if len(route.Request.Parameters) == len(parameters) {
100 | status := true
101 |
102 | for key, value := range route.Request.Parameters {
103 | if _, ok := parameters[key]; !ok {
104 | status = false
105 | }
106 |
107 | if !strings.HasPrefix(value, ":") && value != parameters[key] {
108 | status = false
109 | }
110 | }
111 |
112 | if !status {
113 | continue
114 | }
115 | } else {
116 | continue
117 | }
118 |
119 | return route
120 | }
121 |
122 | return Route{}
123 | }
124 |
--------------------------------------------------------------------------------
/core/module/faker.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package module
6 |
7 | import (
8 | "fmt"
9 | "math/rand"
10 | "regexp"
11 | "strings"
12 |
13 | "github.com/bxcodec/faker/v3"
14 | )
15 |
16 | // Faker type
17 | type Faker struct {
18 | Latitude float32 `faker:"lat"`
19 | Longitude float32 `faker:"long"`
20 | CreditCardNumber string `faker:"cc_number"`
21 | CreditCardType string `faker:"cc_type"`
22 | Email string `faker:"email"`
23 | DomainName string `faker:"domain_name"`
24 | IPV4 string `faker:"ipv4"`
25 | IPV6 string `faker:"ipv6"`
26 | Password string `faker:"password"`
27 | PhoneNumber string `faker:"phone_number"`
28 | MacAddress string `faker:"mac_address"`
29 | URL string `faker:"url"`
30 | UserName string `faker:"username"`
31 | TollFreeNumber string `faker:"toll_free_number"`
32 | E164PhoneNumber string `faker:"e_164_phone_number"`
33 | TitleMale string `faker:"title_male"`
34 | TitleFemale string `faker:"title_female"`
35 | FirstName string `faker:"first_name"`
36 | FirstNameMale string `faker:"first_name_male"`
37 | FirstNameFemale string `faker:"first_name_female"`
38 | LastName string `faker:"last_name"`
39 | Name string `faker:"name"`
40 | UnixTime int64 `faker:"unix_time"`
41 | Date string `faker:"date"`
42 | Time string `faker:"time"`
43 | MonthName string `faker:"month_name"`
44 | Year string `faker:"year"`
45 | DayOfWeek string `faker:"day_of_week"`
46 | DayOfMonth string `faker:"day_of_month"`
47 | Timestamp string `faker:"timestamp"`
48 | Century string `faker:"century"`
49 | TimeZone string `faker:"timezone"`
50 | TimePeriod string `faker:"time_period"`
51 | Word string `faker:"word"`
52 | Sentence string `faker:"sentence"`
53 | Paragraph string `faker:"paragraph"`
54 | Currency string `faker:"currency"`
55 | Amount float64 `faker:"amount"`
56 | AmountWithCurrency string `faker:"amount_with_currency"`
57 | UUIDHypenated string `faker:"uuid_hyphenated"`
58 | UUID string `faker:"uuid_digit"`
59 | }
60 |
61 | // Transform populate faked data
62 | func (f *Faker) Transform(data string) (string, error) {
63 | types := f.GetTypesFound(data)
64 |
65 | err := faker.FakeData(f)
66 |
67 | if err != nil {
68 | return data, err
69 | }
70 |
71 | flags := f.GetFakeData()
72 |
73 | for i := 0; i < len(types); i++ {
74 | if strings.HasPrefix(types[i], "@fake(:anyof[") {
75 | item := strings.TrimPrefix(types[i], "@fake(:anyof[")
76 | item = strings.TrimSuffix(item, "])")
77 | items := strings.Split(item, "||")
78 | data = strings.Replace(
79 | data,
80 | types[i],
81 | items[rand.Intn(len(items))],
82 | -1,
83 | )
84 | } else {
85 | if val, ok := flags[types[i]]; ok {
86 | data = strings.Replace(data, types[i], val, -1)
87 | }
88 | }
89 | }
90 |
91 | return data, nil
92 | }
93 |
94 | // GetTypesFound grep all fake data tags
95 | func (f *Faker) GetTypesFound(data string) []string {
96 | result := []string{}
97 | r := regexp.MustCompile(`@fake\((.)+?\)`)
98 | matches := r.FindAllStringIndex(data, -1)
99 |
100 | for n := 0; n < len(matches); n++ {
101 | result = append(
102 | result,
103 | data[matches[n][0]:matches[n][1]],
104 | )
105 | }
106 |
107 | return result
108 | }
109 |
110 | // GetFakeData gets a map of fake data
111 | func (f *Faker) GetFakeData() map[string]string {
112 | flags := make(map[string]string)
113 |
114 | flags["@fake(:lat)"] = fmt.Sprintf("%f", f.Latitude)
115 | flags["@fake(:long)"] = fmt.Sprintf("%f", f.Longitude)
116 | flags["@fake(:amount)"] = fmt.Sprintf("%f", f.Amount)
117 | flags["@fake(:cc_number)"] = f.CreditCardNumber
118 | flags["@fake(:cc_type)"] = f.CreditCardType
119 | flags["@fake(:email)"] = f.Email
120 | flags["@fake(:domain_name)"] = f.DomainName
121 | flags["@fake(:ipv4)"] = f.IPV4
122 | flags["@fake(:ipv6)"] = f.IPV6
123 | flags["@fake(:password)"] = f.Password
124 | flags["@fake(:phone_number)"] = f.PhoneNumber
125 | flags["@fake(:mac_address)"] = f.MacAddress
126 | flags["@fake(:url)"] = f.URL
127 | flags["@fake(:username)"] = f.UserName
128 | flags["@fake(:toll_free_number)"] = f.TollFreeNumber
129 | flags["@fake(:e_164_phone_number)"] = f.E164PhoneNumber
130 | flags["@fake(:title_male)"] = f.TitleMale
131 | flags["@fake(:title_female)"] = f.TitleFemale
132 | flags["@fake(:first_name)"] = f.FirstName
133 | flags["@fake(:first_name_male)"] = f.FirstNameMale
134 | flags["@fake(:first_name_female)"] = f.FirstNameFemale
135 | flags["@fake(:last_name)"] = f.LastName
136 | flags["@fake(:name)"] = f.Name
137 | flags["@fake(:unix_time)"] = fmt.Sprintf("%d", f.UnixTime)
138 | flags["@fake(:date)"] = f.Date
139 | flags["@fake(:time)"] = f.Time
140 | flags["@fake(:month_name)"] = f.MonthName
141 | flags["@fake(:year)"] = f.Year
142 | flags["@fake(:day_of_week)"] = f.DayOfWeek
143 | flags["@fake(:day_of_month)"] = f.DayOfMonth
144 | flags["@fake(:timestamp)"] = f.Timestamp
145 | flags["@fake(:century)"] = f.Century
146 | flags["@fake(:timezone)"] = f.TimeZone
147 | flags["@fake(:time_period)"] = f.TimePeriod
148 | flags["@fake(:word)"] = f.Word
149 | flags["@fake(:sentence)"] = f.Sentence
150 | flags["@fake(:paragraph)"] = f.Paragraph
151 | flags["@fake(:currency)"] = f.Currency
152 | flags["@fake(:amount_with_currency)"] = f.AmountWithCurrency
153 | flags["@fake(:uuid_hyphenated)"] = f.UUIDHypenated
154 | flags["@fake(:uuid_digit)"] = f.UUID
155 |
156 | return flags
157 | }
158 |
--------------------------------------------------------------------------------
/core/module/file_system.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package module
6 |
7 | import (
8 | "os"
9 | )
10 |
11 | // FileSystem struct
12 | type FileSystem struct{}
13 |
14 | // PathExists reports whether the path exists
15 | func (fs *FileSystem) PathExists(path string) bool {
16 | if _, err := os.Stat(path); os.IsNotExist(err) {
17 | return false
18 | }
19 | return true
20 | }
21 |
22 | // FileExists reports whether the named file exists
23 | func (fs *FileSystem) FileExists(path string) bool {
24 | if fi, err := os.Stat(path); err == nil {
25 | if fi.Mode().IsRegular() {
26 | return true
27 | }
28 | }
29 | return false
30 | }
31 |
32 | // DirExists reports whether the dir exists
33 | func (fs *FileSystem) DirExists(path string) bool {
34 | if fi, err := os.Stat(path); err == nil {
35 | if fi.Mode().IsDir() {
36 | return true
37 | }
38 | }
39 | return false
40 | }
41 |
42 | // EnsureDir ensures that directory exists
43 | func (fs *FileSystem) EnsureDir(dirName string, mode int) (bool, error) {
44 | err := os.MkdirAll(dirName, os.FileMode(mode))
45 |
46 | if err == nil || os.IsExist(err) {
47 | return true, nil
48 | }
49 | return false, err
50 | }
51 |
--------------------------------------------------------------------------------
/core/util/helpers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package util
6 |
7 | import (
8 | "io/ioutil"
9 | "os"
10 | "path/filepath"
11 | "reflect"
12 | "strings"
13 |
14 | "github.com/satori/go.uuid"
15 | )
16 |
17 | // InArray check if value is on array
18 | func InArray(val interface{}, array interface{}) bool {
19 | switch reflect.TypeOf(array).Kind() {
20 | case reflect.Slice:
21 | s := reflect.ValueOf(array)
22 |
23 | for i := 0; i < s.Len(); i++ {
24 | if reflect.DeepEqual(val, s.Index(i).Interface()) {
25 | return true
26 | }
27 | }
28 | }
29 |
30 | return false
31 | }
32 |
33 | // GenerateUUID4 create a UUID
34 | func GenerateUUID4() string {
35 | u := uuid.Must(uuid.NewV4(), nil)
36 | return u.String()
37 | }
38 |
39 | // ListFiles lists all files inside a dir
40 | func ListFiles(basePath string) []string {
41 | var files []string
42 |
43 | err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
44 | if basePath != path && !info.IsDir() {
45 | files = append(files, path)
46 | }
47 | return nil
48 | })
49 | if err != nil {
50 | return files
51 | }
52 |
53 | return files
54 | }
55 |
56 | // ReadFile get the file content
57 | func ReadFile(path string) (string, error) {
58 | data, err := ioutil.ReadFile(path)
59 | if err != nil {
60 | return "", err
61 | }
62 | return string(data), nil
63 | }
64 |
65 | // FilterFiles filters files list based on specific sub-strings
66 | func FilterFiles(files, filters []string) []string {
67 | var filteredFiles []string
68 |
69 | for _, file := range files {
70 | ok := true
71 | for _, filter := range filters {
72 |
73 | ok = ok && strings.Contains(file, filter)
74 | }
75 | if ok {
76 | filteredFiles = append(filteredFiles, file)
77 | }
78 | }
79 |
80 | return filteredFiles
81 | }
82 |
83 | // Unset remove element at position i
84 | func Unset(a []string, i int) []string {
85 | a[i] = a[len(a)-1]
86 | a[len(a)-1] = ""
87 | return a[:len(a)-1]
88 | }
89 |
--------------------------------------------------------------------------------
/core/util/helpers_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package util
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/clivern/rhino/pkg"
11 | )
12 |
13 | // TestInArray test cases
14 | func TestInArray(t *testing.T) {
15 | // TestInArray
16 | t.Run("TestInArray", func(t *testing.T) {
17 | pkg.Expect(t, InArray("A", []string{"A", "B", "C", "D"}), true)
18 | pkg.Expect(t, InArray("B", []string{"A", "B", "C", "D"}), true)
19 | pkg.Expect(t, InArray("H", []string{"A", "B", "C", "D"}), false)
20 | pkg.Expect(t, InArray(1, []int{2, 3, 1}), true)
21 | pkg.Expect(t, InArray(9, []int{2, 3, 1}), false)
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/deployment/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clivern/Rhino/fb1513d5f8d2c6a0710e10c352297a27f02c4c3c/deployment/.gitkeep
--------------------------------------------------------------------------------
/deployment/advanced/docker-compose/configs/config.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "mode": "prod",
4 | "port": "8080",
5 | "domain": "http://127.0.0.1:8080",
6 | "tls": {
7 | "status": "off",
8 | "pemPath": "/cert/server.pem",
9 | "keyPath": "/cert/server.key"
10 | }
11 | },
12 | "mock": [
13 | {
14 | "path": "/api/v2/service1/mock/:id",
15 | "request": {
16 | "method": "get",
17 | "parameters": {}
18 | },
19 | "response": {
20 | "statusCode": 200,
21 | "headers": [
22 | {"key": "Content-Type", "value": "application/json"}
23 | ],
24 | "body": "{\"id\": \":id\"}"
25 | },
26 | "chaos": {
27 | "latency": "0s",
28 | "failRate": "0%"
29 | }
30 | },
31 | {
32 | "path": "/api/v2/service2/mock/:id",
33 | "request": {
34 | "method": "get",
35 | "parameters": {
36 | "var_param": ":var_param",
37 | "fixed_param": 10
38 | }
39 | },
40 | "response": {
41 | "statusCode": 200,
42 | "headers": [
43 | {"key": "Content-Type", "value": "application/json"}
44 | ],
45 | "body": "@json:/app/configs/service2.getItem.response.json"
46 | },
47 | "chaos": {
48 | "latency": "0s",
49 | "failRate": "0%"
50 | }
51 | }
52 | ],
53 | "debug": [
54 | {
55 | "path": "/api/v2/service/debug",
56 | "chaos": {
57 | "latency": "0s",
58 | "failRate": "0%"
59 | }
60 | }
61 | ],
62 | "log": {
63 | "level": "info",
64 | "output": "/app/configs/prod.log",
65 | "format": "json"
66 | }
67 | }
--------------------------------------------------------------------------------
/deployment/advanced/docker-compose/configs/service2.getItem.response.json:
--------------------------------------------------------------------------------
1 | {"id":":id","var_param":":var_param"}
--------------------------------------------------------------------------------
/deployment/advanced/docker-compose/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | rhino:
5 | image: 'clivern/rhino:release-1.6.2'
6 | ports:
7 | - "8080:8080"
8 | command: '/app/rhino serve -c /app/configs/config.prod.json'
9 | volumes:
10 | - './configs/:/app/configs'
11 | restart: unless-stopped
12 |
13 | loki:
14 | image: grafana/loki:2.9.6
15 | ports:
16 | - "3100:3100"
17 | command: -config.file=/etc/loki/local-config.yaml
18 | networks:
19 | - loki
20 |
21 | promtail:
22 | image: grafana/promtail:2.9.6
23 | volumes:
24 | - './configs/:/var/log'
25 | command: -config.file=/etc/promtail/config.yml
26 | networks:
27 | - loki
28 |
29 | prometheus:
30 | image: 'prom/prometheus:v2.51.0'
31 | volumes:
32 | - './prometheus/:/etc/prometheus'
33 | command: '--config.file=/etc/prometheus/prometheus.yml'
34 | ports:
35 | - '9090:9090'
36 | restart: unless-stopped
37 |
38 | grafana:
39 | image: 'grafana/grafana:10.4.1'
40 | environment:
41 | - GF_SECURITY_ADMIN_USER=${ADMIN_USER:-admin}
42 | - GF_SECURITY_ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin}
43 | - GF_USERS_ALLOW_SIGN_UP=false
44 | ports:
45 | - '3000:3000'
46 | depends_on:
47 | - prometheus
48 | restart: unless-stopped
49 | networks:
50 | - loki
51 |
52 |
53 | networks:
54 | loki:
55 |
--------------------------------------------------------------------------------
/deployment/advanced/docker-compose/prometheus/prometheus.yml:
--------------------------------------------------------------------------------
1 | # my global config
2 | global:
3 | evaluation_interval: 15s
4 | external_labels:
5 | monitor: rhino-monitor
6 | scrape_interval: 15s
7 | rule_files: ~
8 | scrape_configs:
9 | -
10 | job_name: prometheus
11 | scrape_interval: 5s
12 | static_configs:
13 | -
14 | targets:
15 | - "localhost:9090"
16 | -
17 | job_name: rhino
18 | metrics_path: /_metrics
19 | scrape_interval: 5s
20 | static_configs:
21 | -
22 | targets:
23 | - "rhino:8080"
24 |
--------------------------------------------------------------------------------
/deployment/basic/docker-compose/configs/config.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "mode": "prod",
4 | "port": "8080",
5 | "domain": "http://127.0.0.1:8080",
6 | "tls": {
7 | "status": "off",
8 | "pemPath": "/cert/server.pem",
9 | "keyPath": "/cert/server.key"
10 | }
11 | },
12 | "mock": [
13 | {
14 | "path": "/api/v2/service1/mock/:id",
15 | "request": {
16 | "method": "get",
17 | "parameters": {}
18 | },
19 | "response": {
20 | "statusCode": 200,
21 | "headers": [
22 | {"key": "Content-Type", "value": "application/json"}
23 | ],
24 | "body": "{\"id\": \":id\"}"
25 | },
26 | "chaos": {
27 | "latency": "0s",
28 | "failRate": "0%"
29 | }
30 | },
31 | {
32 | "path": "/api/v2/service2/mock/:id",
33 | "request": {
34 | "method": "get",
35 | "parameters": {
36 | "var_param": ":var_param",
37 | "fixed_param": 10
38 | }
39 | },
40 | "response": {
41 | "statusCode": 200,
42 | "headers": [
43 | {"key": "Content-Type", "value": "application/json"}
44 | ],
45 | "body": "@json:/app/configs/service2.getItem.response.json"
46 | },
47 | "chaos": {
48 | "latency": "0s",
49 | "failRate": "0%"
50 | }
51 | }
52 | ],
53 | "debug": [
54 | {
55 | "path": "/api/v2/service/debug",
56 | "chaos": {
57 | "latency": "0s",
58 | "failRate": "0%"
59 | }
60 | }
61 | ],
62 | "log": {
63 | "level": "info",
64 | "output": "stdout",
65 | "format": "json"
66 | }
67 | }
--------------------------------------------------------------------------------
/deployment/basic/docker-compose/configs/service2.getItem.response.json:
--------------------------------------------------------------------------------
1 | {"id":":id","var_param":":var_param"}
--------------------------------------------------------------------------------
/deployment/basic/docker-compose/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | rhino:
5 | image: 'clivern/rhino:release-1.6.2'
6 | ports:
7 | - "8080:8080"
8 | command: '/app/rhino serve -c /app/configs/config.prod.json'
9 | volumes:
10 | - './configs/:/app/configs'
11 | restart: unless-stopped
12 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/clivern/rhino
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/bxcodec/faker/v3 v3.8.1
7 | github.com/drone/envsubst v1.0.3
8 | github.com/gin-gonic/gin v1.9.1
9 | github.com/prometheus/client_golang v1.18.0
10 | github.com/satori/go.uuid v1.2.0
11 | github.com/sirupsen/logrus v1.9.3
12 | github.com/spf13/cobra v1.8.0
13 | github.com/spf13/viper v1.18.2
14 | )
15 |
16 | require (
17 | github.com/beorn7/perks v1.0.1 // indirect
18 | github.com/bytedance/sonic v1.9.1 // indirect
19 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
20 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
21 | github.com/fsnotify/fsnotify v1.7.0 // indirect
22 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
23 | github.com/gin-contrib/sse v0.1.0 // indirect
24 | github.com/go-playground/locales v0.14.1 // indirect
25 | github.com/go-playground/universal-translator v0.18.1 // indirect
26 | github.com/go-playground/validator/v10 v10.14.0 // indirect
27 | github.com/goccy/go-json v0.10.2 // indirect
28 | github.com/golang/protobuf v1.5.3 // indirect
29 | github.com/hashicorp/hcl v1.0.0 // indirect
30 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
31 | github.com/json-iterator/go v1.1.12 // indirect
32 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
33 | github.com/leodido/go-urn v1.2.4 // indirect
34 | github.com/magiconair/properties v1.8.7 // indirect
35 | github.com/mattn/go-isatty v0.0.19 // indirect
36 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
37 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
38 | github.com/mitchellh/mapstructure v1.5.0 // indirect
39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
40 | github.com/modern-go/reflect2 v1.0.2 // indirect
41 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect
42 | github.com/prometheus/client_model v0.5.0 // indirect
43 | github.com/prometheus/common v0.45.0 // indirect
44 | github.com/prometheus/procfs v0.12.0 // indirect
45 | github.com/sagikazarmark/locafero v0.4.0 // indirect
46 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
47 | github.com/sourcegraph/conc v0.3.0 // indirect
48 | github.com/spf13/afero v1.11.0 // indirect
49 | github.com/spf13/cast v1.6.0 // indirect
50 | github.com/spf13/pflag v1.0.5 // indirect
51 | github.com/subosito/gotenv v1.6.0 // indirect
52 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
53 | github.com/ugorji/go/codec v1.2.11 // indirect
54 | go.uber.org/atomic v1.9.0 // indirect
55 | go.uber.org/multierr v1.9.0 // indirect
56 | golang.org/x/arch v0.3.0 // indirect
57 | golang.org/x/crypto v0.16.0 // indirect
58 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
59 | golang.org/x/net v0.19.0 // indirect
60 | golang.org/x/sys v0.15.0 // indirect
61 | golang.org/x/text v0.14.0 // indirect
62 | google.golang.org/protobuf v1.31.0 // indirect
63 | gopkg.in/ini.v1 v1.67.0 // indirect
64 | gopkg.in/yaml.v3 v3.0.1 // indirect
65 | )
66 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
42 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
43 | github.com/bxcodec/faker/v3 v3.8.1 h1:qO/Xq19V6uHt2xujwpaetgKhraGCapqY2CRWGD/SqcM=
44 | github.com/bxcodec/faker/v3 v3.8.1/go.mod h1:DdSDccxF5msjFo5aO4vrobRQ8nIApg8kq3QWPEQD6+o=
45 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
46 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
47 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
48 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
49 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
50 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
51 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
52 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
53 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
54 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
55 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
56 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
57 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
58 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
59 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
60 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
61 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
62 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
63 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
64 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
65 | github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
66 | github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
67 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
68 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
69 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
70 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
71 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
72 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
73 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
74 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
75 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
76 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
77 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
78 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
79 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
80 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
81 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
82 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
83 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
84 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
85 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
86 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
87 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
88 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
89 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
90 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
91 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
92 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
93 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
94 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
95 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
96 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
97 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
98 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
99 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
100 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
101 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
102 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
103 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
104 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
105 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
106 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
107 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
108 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
109 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
110 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
111 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
112 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
113 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
114 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
115 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
116 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
117 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
118 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
119 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
120 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
121 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
122 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
123 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
124 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
125 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
126 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
127 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
128 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
129 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
130 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
131 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
132 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
133 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
134 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
135 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
136 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
137 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
138 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
139 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
140 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
141 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
142 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
143 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
144 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
145 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
146 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
147 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
148 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
149 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
150 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
151 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
152 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
153 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
154 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
155 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
156 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
157 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
158 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
159 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
160 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
161 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
162 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
163 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
164 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
165 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
166 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
167 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
168 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
169 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
170 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
171 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
172 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
173 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
174 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
175 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
176 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
177 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
178 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
179 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
180 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
181 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
182 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
183 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
184 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
185 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
186 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
187 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
188 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
189 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
190 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
191 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
192 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
193 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
194 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
195 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
196 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
197 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
198 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
199 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
200 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
201 | github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
202 | github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
203 | github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
204 | github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
205 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
206 | github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
207 | github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
208 | github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
209 | github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
210 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
211 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
212 | github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
213 | github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
214 | github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
215 | github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
216 | github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
217 | github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
218 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
219 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
220 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
221 | github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
222 | github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
223 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
224 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
225 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
226 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
227 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
228 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
229 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
230 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
231 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
232 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
233 | github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
234 | github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
235 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
236 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
237 | github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
238 | github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
239 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
240 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
241 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
242 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
243 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
244 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
245 | github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
246 | github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
247 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
248 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
249 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
250 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
251 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
252 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
253 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
254 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
255 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
256 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
257 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
258 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
259 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
260 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
261 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
262 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
263 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
264 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
265 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
266 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
267 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
268 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
269 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
270 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
271 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
272 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
273 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
274 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
275 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
276 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
277 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
278 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
279 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
280 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
281 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
282 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
283 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
284 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
285 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
286 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
287 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
288 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
289 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
290 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
291 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
292 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
293 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
294 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
295 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
296 | golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
297 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
298 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
299 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
300 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
301 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
302 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
303 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
304 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
305 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
306 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
307 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
308 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
309 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
310 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
311 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
312 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
313 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
314 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
315 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
316 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
317 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
318 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
319 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
320 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
321 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
322 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
323 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
324 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
325 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
326 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
327 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
328 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
329 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
330 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
331 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
332 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
333 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
334 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
335 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
336 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
337 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
338 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
339 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
340 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
341 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
342 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
343 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
344 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
345 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
346 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
347 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
348 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
349 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
350 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
351 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
352 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
353 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
354 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
355 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
356 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
357 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
358 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
359 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
360 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
361 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
362 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
363 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
364 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
365 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
366 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
367 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
368 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
369 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
370 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
371 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
372 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
373 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
374 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
375 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
376 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
377 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
378 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
379 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
380 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
381 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
382 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
383 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
384 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
385 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
386 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
387 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
388 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
389 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
390 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
391 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
392 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
393 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
394 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
395 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
396 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
397 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
398 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
399 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
400 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
401 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
402 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
403 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
404 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
405 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
406 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
407 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
408 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
409 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
410 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
411 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
412 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
413 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
414 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
415 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
416 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
417 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
418 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
419 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
420 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
421 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
422 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
423 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
424 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
425 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
426 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
427 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
428 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
429 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
430 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
431 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
432 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
433 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
434 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
435 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
436 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
437 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
438 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
439 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
440 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
441 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
442 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
443 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
444 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
445 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
446 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
447 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
448 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
449 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
450 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
451 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
452 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
453 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
454 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
455 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
456 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
457 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
458 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
459 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
460 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
461 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
462 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
463 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
464 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
465 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
466 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
467 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
468 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
469 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
470 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
471 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
472 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
473 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
474 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
475 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
476 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
477 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
478 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
479 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
480 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
481 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
482 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
483 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
484 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
485 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
486 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
487 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
488 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
489 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
490 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
491 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
492 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
493 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
494 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
495 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
496 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
497 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
498 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
499 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
500 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
501 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
502 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
503 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
504 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
505 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
506 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
507 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
508 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
509 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
510 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
511 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
512 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
513 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
514 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
515 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
516 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
517 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
518 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
519 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
520 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
521 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
522 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
523 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
524 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
525 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
526 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
527 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
528 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
529 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
530 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
531 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
532 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
533 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
534 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
535 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
536 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
537 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
538 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
539 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
540 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
541 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
542 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
543 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
544 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
545 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
546 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
547 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
548 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
549 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
550 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
551 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
552 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
553 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
554 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
555 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
556 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
557 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
558 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
559 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
560 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
561 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
562 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
563 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
564 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
565 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
566 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
567 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
568 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
569 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
570 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
571 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
572 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
573 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
574 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
575 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
576 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
577 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
578 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
579 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
580 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
581 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
582 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
583 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
584 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
585 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
586 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
587 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
588 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
589 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
590 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
591 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
592 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
593 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
594 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
595 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
596 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
597 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
598 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
599 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
600 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
601 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
602 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
603 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
604 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
605 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
606 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
607 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
608 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
609 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
610 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
611 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
612 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
613 |
--------------------------------------------------------------------------------
/pkg/expect.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package pkg
6 |
7 | import (
8 | "reflect"
9 | "testing"
10 | )
11 |
12 | // Expect compare two values for testing
13 | func Expect(t *testing.T, got, want interface{}) {
14 | t.Logf(`Comparing values %v, %v`, got, want)
15 |
16 | if !reflect.DeepEqual(got, want) {
17 | t.Errorf(`got %v, want %v`, got, want)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/rhino.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Clivern. All rights reserved.
2 | // Use of this source code is governed by the MIT
3 | // license that can be found in the LICENSE file.
4 |
5 | package main
6 |
7 | import (
8 | "github.com/clivern/rhino/core/cmd"
9 | )
10 |
11 | var (
12 | version = "dev"
13 | commit = "none"
14 | date = "unknown"
15 | builtBy = "unknown"
16 | )
17 |
18 | func main() {
19 | // Expose build info to cmd subpackage to avoid custom ldflags
20 | cmd.Version = version
21 | cmd.Commit = commit
22 | cmd.Date = date
23 | cmd.BuiltBy = builtBy
24 |
25 | cmd.Execute()
26 | }
27 |
--------------------------------------------------------------------------------
/web/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Rhino
7 |
8 |
9 |
10 |
11 |
12 |
213 |
214 |
215 |
216 |
227 |
234 |
235 | Contributing
236 | Want to contribute? Follow these Recommendations.
237 |
238 |
243 |
244 |
245 |
246 |
--------------------------------------------------------------------------------