├── .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 | Rhino Logo 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 |
243 |
244 | 245 |

Rhino

246 |

HTTP Mocking & Debugging Service.

247 |
248 |
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 |
228 |
229 | 230 |

Rhino

231 |

HTTP Mocking & Debugging Service.

232 |
233 |
234 |
235 |

Contributing

236 |

Want to contribute? Follow these Recommendations.

237 |
238 | 243 |
244 | 245 | 246 | --------------------------------------------------------------------------------