Compare commits

...

111 Commits

Author SHA1 Message Date
8b18d02666 Update CHANGELOG.md 2023-11-20 12:28:22 +04:00
8e7570eee3 fix: "Too big request header" (#244)
* Added environment variable and CLI parameter for adjusting the read buffer size

* Changed readBufferSize to int and some bugfixing

* Small fixes

* ci: 👷 CI system updated

* ci: 👷 CI system updated

---------

Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2023-11-20 12:27:40 +04:00
2c8ba9c0f3 build(deps): bump github.com/prometheus/client_model (#241) 2023-11-02 05:14:07 +00:00
1ab0973011 build(deps): bump github.com/fasthttp/router from 1.4.20 to 1.4.21 (#242) 2023-11-02 05:13:41 +00:00
540139db3a chore(deps): update golangci/golangci-lint docker tag to v1.55 (#239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-02 09:13:02 +04:00
ff3d16d294 Merge pull request #243 from tarampampam/dependabot/github_actions/aquasecurity/trivy-action-0.13.1
build(deps): bump aquasecurity/trivy-action from 0.12.0 to 0.13.1
2023-11-01 22:55:13 +00:00
ebbded51bf build(deps): bump aquasecurity/trivy-action from 0.12.0 to 0.13.1
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.12.0 to 0.13.1.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.12.0...0.13.1)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 22:54:59 +00:00
384e45ce7f Merge pull request #240 from tarampampam/dependabot/go_modules/go.uber.org/goleak-1.3.0
build(deps): bump go.uber.org/goleak from 1.2.1 to 1.3.0
2023-11-01 22:19:14 +00:00
cdac8665de build(deps): bump go.uber.org/goleak from 1.2.1 to 1.3.0
Bumps [go.uber.org/goleak](https://github.com/uber-go/goleak) from 1.2.1 to 1.3.0.
- [Release notes](https://github.com/uber-go/goleak/releases)
- [Changelog](https://github.com/uber-go/goleak/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/goleak/compare/v1.2.1...v1.3.0)

---
updated-dependencies:
- dependency-name: go.uber.org/goleak
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 22:18:58 +00:00
fac512bd74 Fix spanish translated tags (#237) 2023-10-10 15:10:11 +04:00
ea85191d9e build(deps): bump actions/checkout from 3 to 4 (#232) 2023-10-09 12:46:36 +00:00
38ce1f9cf3 build(deps): bump docker/build-push-action from 4 to 5 (#234) 2023-10-09 12:46:25 +00:00
9e4a1451f5 build(deps): bump docker/setup-qemu-action from 2 to 3 (#235) 2023-10-09 12:42:41 +00:00
9feca1f509 Update dependabot.yml 2023-10-05 11:15:01 +04:00
38bf4abc1e Update dependabot.yml 2023-10-02 12:30:56 +04:00
ffc2af1c27 Update dependabot.yml 2023-10-02 11:35:41 +04:00
763c4ad109 build(deps): bump github.com/valyala/fasthttp from 1.49.0 to 1.50.0 (#227) 2023-10-02 07:07:00 +00:00
dcfd8ab3a7 build(deps): bump go.uber.org/zap from 1.25.0 to 1.26.0 (#229) 2023-10-02 06:54:19 +00:00
62f309cefd build(deps): bump docker/login-action from 2 to 3 (#233) 2023-10-02 06:53:40 +00:00
b5504de6d8 build(deps): bump github.com/prometheus/client_golang (#228) 2023-10-02 06:49:50 +00:00
4d91e17273 build(deps): bump aquasecurity/trivy-action from 0.11.2 to 0.12.0 (#230) 2023-10-02 06:49:03 +00:00
86e182c25d build(deps): bump docker/setup-buildx-action from 2 to 3 (#231) 2023-10-02 06:48:39 +00:00
8006cce4b0 Polish translation added. (#226)
* Polish translation added.

* Update CHANGELOG.md

* Update CHANGELOG.md

---------

Co-authored-by: Pаramtamtām <7326800+tarampampam@users.noreply.github.com>
2023-09-28 10:51:55 +04:00
caf4e33193 Update l10n.js (#225) 2023-09-24 21:11:49 -07:00
7ab0fa6f23 chore: The docker environment is refactored 2023-09-03 20:17:07 +04:00
81570b42c0 chore: The docker environment is refactored 2023-09-03 20:16:50 +04:00
4c3ebc055d Update CHANGELOG.md 2023-09-02 07:17:47 -07:00
61c1958717 build(deps): bump golang from 1.20-alpine to 1.21-alpine (#222)
* build(deps): bump golang from 1.20-alpine to 1.21-alpine

Bumps golang from 1.20-alpine to 1.21-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* wip: 🔕 temporary commit

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2023-09-02 07:17:15 -07:00
3ab1a23ac5 Create dependabot.yml 2023-09-02 06:46:02 -07:00
a81c780e1e build(deps): bump go.uber.org/zap from 1.24.0 to 1.25.0 (#221) 2023-09-02 12:59:52 +00:00
2baeb2eb5b build(deps): bump github.com/valyala/fasthttp from 1.48.0 to 1.49.0 (#220) 2023-09-02 12:54:58 +00:00
e6b011b41b Add Indonesian translation (#218)
* Add Indonesian

Add all strings in bahasa indonesia.

* Update README.md

Add Indonesian flag emoji

* Add Indonesian language credit

* Update CHANGELOG.md (add 🇮🇩  language)

* Update l10n/l10n.js

---------

Co-authored-by: Pаramtamtām <7326800+tarampampam@users.noreply.github.com>
2023-09-01 10:25:17 +04:00
308467006b Add CatchAll functionality (#217)
* Add CatchAll functionality

* Added link to PR to changelog
2023-09-01 10:17:11 +04:00
ecf1359336 chore(deps): update golangci/golangci-lint docker tag to v1.54 (#215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-09 21:09:41 +04:00
a12dc4882e build(deps): bump github.com/fasthttp/router from 1.4.19 to 1.4.20 (#213) 2023-08-02 06:39:59 +00:00
980d0a5810 build(deps): bump go.uber.org/automaxprocs from 1.5.2 to 1.5.3 (#214) 2023-08-02 06:38:58 +00:00
eb3d84ee9d Change Ukrainian translation (#211)
* Update l10n.js

* Update l10n.js
2023-07-24 17:16:52 +04:00
6b43057333 build(deps): bump github.com/valyala/fasthttp from 1.47.0 to 1.48.0 (#208) 2023-07-01 23:48:11 +00:00
071ded0eac build(deps): bump github.com/prometheus/client_golang (#210) 2023-07-01 23:47:44 +00:00
47c4338c9e build(deps): bump aquasecurity/trivy-action from 0.10.0 to 0.11.2 (#207) 2023-07-01 23:43:11 +00:00
dfdeea4b6c build(deps): bump github.com/urfave/cli/v2 from 2.25.5 to 2.25.7 (#209) 2023-07-01 23:42:31 +00:00
cbb7936149 chore(deps): update golangci/golangci-lint docker tag to v1.53 (#200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-04 01:32:57 -07:00
c08e1307e8 build(deps): bump github.com/urfave/cli/v2 from 2.25.3 to 2.25.5 (#201) 2023-06-04 08:29:11 +00:00
25b86a057f build(deps): bump github.com/prometheus/client_model from 0.3.0 to 0.4.0 (#203) 2023-06-04 08:24:31 +00:00
ef72fa405d build(deps): bump github.com/fasthttp/router from 1.4.18 to 1.4.19 (#202) 2023-06-04 08:24:25 +00:00
1eb773fb57 depguard disabled 2023-06-04 01:18:45 -07:00
439b6d0326 build(deps): bump github.com/prometheus/client_golang (#199) 2023-06-02 06:35:01 +00:00
ef0081f711 build(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.4 (#198) 2023-06-02 06:34:58 +00:00
8c7a24b3d7 build(deps): bump github.com/urfave/cli/v2 from 2.25.1 to 2.25.3 (#197) 2023-05-04 11:20:25 +00:00
c76026c9f1 build(deps): bump github.com/prometheus/client_golang (#196) 2023-05-02 03:46:37 +00:00
37e4ecbf47 build(deps): bump aquasecurity/trivy-action from 0.9.2 to 0.10.0 (#194) 2023-05-02 03:46:24 +00:00
58dc38f72e build(deps): bump github.com/valyala/fasthttp from 1.45.0 to 1.47.0 (#195) 2023-05-02 03:46:09 +00:00
36c5472987 feat: IPv6 support (#192)
* 🐛 fix(server.go): validate IP address before starting server
 feat(server.go): add support for IPv6 addresses

*  feat(cli): add support for IPv6 addresses in the `--listen` flag

* 🐛 fix(server.go): add nolint comment to ignore magic number warning in ipv6 check

* 🐛 fix(server.go): use fmt.Sprintf to format IP and port instead of strconv.Itoa and string concatenation
2023-04-21 16:33:33 +04:00
717542e045 Update README.md 2023-04-18 10:48:55 +04:00
940bd0405f New template orient added (#190)
* added orient theme

* added creation test for orient theme

* Added new Orient theme to CHANGELOG.md

* fix: Template fixed a bit

---------

Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2023-04-17 15:06:28 +04:00
d40e8879d1 feat: Non-existing pages now return styled 404 status page (with 404 status code) (#189)
* 🎨 style(CHANGELOG.md): add UNRELEASED section
🔧 chore(notfound/handler.go): refactor to use core.RespondWithErrorPage
🔧 chore(http/server.go): pass config, templatePicker, renderer and options to notfoundHandler
🔧 chore(hurl/404.hurl): update test to expect HTML response instead of plain text

* 📝 docs(CHANGELOG.md): fix typo in changed section

* 🔥 chore(CHANGELOG.md): release version 2.22.0
🔥 chore(flags.go): remove unused code for serve command
2023-04-07 14:42:00 +04:00
6a67510bdc build(deps): bump go.uber.org/automaxprocs from 1.5.1 to 1.5.2 (#183) 2023-04-02 02:27:23 +00:00
a79521a37d build(deps): bump github.com/urfave/cli/v2 from 2.24.4 to 2.25.1 (#185) 2023-04-02 02:24:01 +00:00
4667194271 build(deps): bump github.com/fasthttp/router from 1.4.17 to 1.4.18 (#184) 2023-04-02 02:23:55 +00:00
a20852b03a build(deps): bump aquasecurity/trivy-action from 0.9.1 to 0.9.2 (#182) 2023-04-02 02:22:48 +00:00
ec0f1cc0d3 build(deps): bump github.com/valyala/fasthttp from 1.44.0 to 1.45.0 (#186) 2023-04-02 02:19:54 +00:00
49d9650d35 build(deps): bump github.com/fatih/color from 1.14.1 to 1.15.0 (#187) 2023-04-02 02:19:27 +00:00
1b876c45cc chore(deps): update golangci/golangci-lint docker tag to v1.52 (#181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-18 17:17:13 +04:00
23f52f25e2 Bump aquasecurity/trivy-action from 0.8.0 to 0.9.1 (#177) 2023-03-02 03:53:22 +00:00
16d7d80183 Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#178) 2023-03-02 03:49:08 +00:00
5389fe00dd Bump github.com/fasthttp/router from 1.4.16 to 1.4.17 (#179) 2023-03-02 03:48:51 +00:00
cd67674976 Bump go.uber.org/goleak from 1.2.0 to 1.2.1 (#180) 2023-03-02 03:48:33 +00:00
36673a49a4 ci: 👷 CI system updated 2023-02-23 22:03:32 +04:00
b84b3ba9f4 chore: 🔧 Changes that do not modify src or test files 2023-02-23 21:50:35 +04:00
5343d8c934 docs(changelog): 📚 Changelog file updated 2023-02-23 21:50:31 +04:00
80891b0b46 ci: 👷 CI system updated 2023-02-23 21:50:20 +04:00
48313685ec chore: Module name changed, deps updated 2023-02-23 21:49:45 +04:00
7dc47c00ac chore(deps): update golangci/golangci-lint docker tag to v1.51 (#174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-02 17:08:43 +04:00
830b5bb934 chore(deps): update golang docker tag to v1.20 (#173)
* chore(deps): update golang docker tag to v1.20

* updated

* update

* update

* Update .github/workflows/tests.yml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2023-02-02 13:09:29 +04:00
8d43984644 Bump github.com/urfave/cli/v2 from 2.24.1 to 2.24.2 (#168) 2023-02-02 07:25:44 +00:00
1b152c1a80 Bump github.com/fasthttp/router from 1.4.14 to 1.4.16 (#169) 2023-02-02 03:26:30 +00:00
175b9f0cfb Bump go.uber.org/goleak from 1.1.11 to 1.2.0 (#170) 2023-02-02 03:25:57 +00:00
16180be7e2 Bump github.com/valyala/fasthttp from 1.43.0 to 1.44.0 (#171) 2023-02-02 03:22:36 +00:00
11b270ee68 Bump github.com/fatih/color from 1.13.0 to 1.14.1 (#172) 2023-02-02 03:22:15 +00:00
97ab8a4475 Bump docker/build-push-action from 3 to 4 (#167) 2023-02-02 03:20:00 +00:00
da3b864e02 fix: docker-compose server running command 2023-01-29 15:56:58 +04:00
0bd989e493 docs(changelog): file updated 2023-01-29 15:49:28 +04:00
59c4d2022c ci: workflow files have been updated 2023-01-29 15:39:49 +04:00
1ec17caa1d feat: Possibility to use custom env variables in templates (#165) 2023-01-29 15:25:38 +04:00
252618a975 chore: Better CLI (#163) 2023-01-29 14:54:56 +04:00
315c7660d1 Update README.md 2022-12-27 11:41:51 +04:00
6a6809b07f Bump go.uber.org/zap from 1.23.0 to 1.24.0 (#159) 2022-12-02 06:09:55 +00:00
a960b5928e Bump github.com/valyala/fasthttp from 1.41.0 to 1.43.0 (#157) 2022-12-02 06:06:16 +00:00
46d96d7bb4 Bump github.com/prometheus/client_golang from 1.13.0 to 1.14.0 (#160) 2022-12-02 06:02:10 +00:00
69063a9cf7 Bump github.com/fasthttp/router from 1.4.13 to 1.4.14 (#158) 2022-12-02 06:01:28 +00:00
7c8d2f54c7 Update orangeopensource/hurl Docker tag to v1.8.0 (#155)
* Update orangeopensource/hurl Docker tag to v1.8.0

* wip: temporary commit

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-11-18 23:46:46 +04:00
49aed23f8c Update README.md 2022-11-15 13:06:39 +04:00
3418f85292 Update CHANGELOG.md 2022-11-02 01:38:14 +04:00
361afd87aa Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#152)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-02 01:33:17 +04:00
7f6815c274 Bump github.com/spf13/cobra from 1.5.0 to 1.6.1 (#151) 2022-11-01 21:31:51 +00:00
4ecd70330a Bump aquasecurity/trivy-action from 0.7.1 to 0.8.0 (#148) 2022-11-01 21:28:27 +00:00
ae13905512 Bump github.com/prometheus/client_model from 0.2.0 to 0.3.0 (#149) 2022-11-01 21:27:39 +00:00
e3377d0f28 Bump github.com/fasthttp/router from 1.4.12 to 1.4.13 (#153) 2022-11-01 21:27:23 +00:00
b15061a110 wip: Readme file updated 2022-11-02 01:25:19 +04:00
CDN
6d6945bf44 Add 🇨🇳 Chinese Translation (#147) 2022-11-02 01:23:29 +04:00
cf7c526d4f golangci-lint issues fixed 2022-11-02 01:13:03 +04:00
8e21be0340 Update golangci/golangci-lint Docker tag to v1.50 (#144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-04 15:41:42 +04:00
37265ccb4f Bump github.com/fasthttp/router from 1.4.11 to 1.4.12 (#142) 2022-10-02 05:28:04 +00:00
169fbe3b93 Bump github.com/valyala/fasthttp from 1.39.0 to 1.40.0 (#143) 2022-10-02 02:22:55 +00:00
617b378c36 Update orangeopensource/hurl Docker tag to v1.7.0 (#141)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-24 10:31:48 +04:00
438e954dd6 Bump go.uber.org/zap from 1.21.0 to 1.23.0 (#135) 2022-09-02 02:57:20 +00:00
df1a0e20ee Bump aquasecurity/trivy-action from 0.6.1 to 0.7.1 (#137) 2022-09-02 02:53:52 +00:00
7b3c286790 Bump github.com/valyala/fasthttp from 1.38.0 to 1.39.0 (#136) 2022-09-02 02:53:34 +00:00
fb7d7c75cf Bump github.com/prometheus/client_golang from 1.12.2 to 1.13.0 (#138) 2022-09-02 02:52:41 +00:00
1eafe58d16 Update golang Docker tag to v1.19 (#127)
* Update golang Docker tag to v1.19

* wip: temporary commit

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-08-24 21:38:26 +04:00
e7a909dc4e Update golangci/golangci-lint Docker tag to v1.49 (#134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-24 20:38:41 +04:00
65 changed files with 1792 additions and 1507 deletions

View File

@ -1,14 +1,9 @@
.dockerignore ## Ignore everything
Dockerfile *
.github
.git ## Except the following files and directories
.gitignore !/cmd
.editorconfig !/internal
.idea !/templates
.vscode !/error-pages.yml
test !/go.*
temp
tmp
LICENSE
Makefile
error-pages

View File

@ -7,11 +7,8 @@ charset = utf-8
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
indent_style = space indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.{yml, yaml, sh, conf, json}]
indent_size = 2 indent_size = 2
trim_trailing_whitespace = true
[{Makefile, go.mod, *.go}] [{Makefile, go.mod, *.go}]
indent_style = tab indent_style = tab

View File

@ -6,17 +6,14 @@ updates:
- package-ecosystem: gomod - package-ecosystem: gomod
directory: / directory: /
schedule: {interval: monthly} schedule: {interval: monthly}
reviewers: [tarampampam]
assignees: [tarampampam] assignees: [tarampampam]
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /
schedule: {interval: monthly} schedule: {interval: monthly}
reviewers: [tarampampam]
assignees: [tarampampam] assignees: [tarampampam]
- package-ecosystem: docker - package-ecosystem: docker
directory: / directory: /
schedule: {interval: monthly} schedule: {interval: monthly}
reviewers: [tarampampam]
assignees: [tarampampam] assignees: [tarampampam]

25
.github/workflows/dependabot.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: dependabot
on:
pull_request: {}
permissions:
contents: write
pull-requests: write
jobs:
dependabot: # https://tinyurl.com/e69djmen
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- uses: dependabot/fetch-metadata@v1
id: metadata
with: {github-token: "${{ secrets.GITHUB_TOKEN }}"}
- name: Enable auto-merge for Dependabot PRs
if: ${{ contains(fromJSON('["version-update:semver-minor", "version-update:semver-patch"]'), steps.metadata.outputs.update-type) }}
run: gh pr merge --auto --merge "$PR_URL"
continue-on-error: true
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -8,9 +8,9 @@ on:
jobs: jobs:
docker-hub-description: docker-hub-description:
name: Docker Hub Description name: Docker Hub Description
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: peter-evans/dockerhub-description@v3 # Action page: <https://github.com/peter-evans/dockerhub-description> - uses: peter-evans/dockerhub-description@v3 # Action page: <https://github.com/peter-evans/dockerhub-description>
with: with:

View File

@ -7,7 +7,7 @@ on:
jobs: jobs:
purge-cdn-cache: purge-cdn-cache:
name: Purge jsDelivr CDN cache name: Purge jsDelivr CDN cache
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: gacts/purge-jsdelivr-cache@v1 # Action page: <https://github.com/gacts/purge-jsdelivr-cache> - uses: gacts/purge-jsdelivr-cache@v1 # Action page: <https://github.com/gacts/purge-jsdelivr-cache>
with: with:
@ -19,31 +19,30 @@ jobs:
build: build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }}) name: Build for ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [linux, darwin] # linux, freebsd, darwin, windows os: [linux, darwin] # linux, freebsd, darwin, windows
arch: [amd64] # amd64, 386 arch: [amd64] # amd64, 386
steps: steps:
- uses: actions/setup-go@v3 - uses: actions/checkout@v4
with: {go-version: 1.18}
- uses: actions/checkout@v3 - uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- uses: gacts/github-slug@v1 - {uses: gacts/github-slug@v1, id: slug}
id: slug
- name: Generate builder values - name: Generate builder values
id: values id: values
run: echo "::set-output name=binary-name::error-pages-${{ matrix.os }}-${{ matrix.arch }}" run: echo "binary-name=error-pages-${{ matrix.os }}-${{ matrix.arch }}" >> $GITHUB_OUTPUT
- name: Build application - name: Build application
env: env:
GOOS: ${{ matrix.os }} GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }} GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 0 CGO_ENABLED: 0
LDFLAGS: -s -w -X github.com/tarampampam/error-pages/internal/version.version=${{ steps.slug.outputs.version }} LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.version }}
run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/error-pages/ run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/error-pages/
- name: Upload binary file to release - name: Upload binary file to release
@ -56,29 +55,28 @@ jobs:
docker-image: docker-image:
name: Build docker image name: Build docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: gacts/github-slug@v1 - {uses: gacts/github-slug@v1, id: slug}
id: slug
- uses: docker/setup-qemu-action@v2 # Action page: <https://github.com/docker/setup-qemu-action> - uses: docker/setup-qemu-action@v3 # Action page: <https://github.com/docker/setup-qemu-action>
- uses: docker/setup-buildx-action@v2 # Action page: <https://github.com/docker/setup-buildx-action> - uses: docker/setup-buildx-action@v3 # Action page: <https://github.com/docker/setup-buildx-action>
- uses: docker/login-action@v2 # Action page: <https://github.com/docker/login-action> - uses: docker/login-action@v3 # Action page: <https://github.com/docker/login-action>
with: with:
username: ${{ secrets.DOCKER_LOGIN }} username: ${{ secrets.DOCKER_LOGIN }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/login-action@v2 # Action page: <https://github.com/docker/login-action> - uses: docker/login-action@v3 # Action page: <https://github.com/docker/login-action>
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v3 # Action page: <https://github.com/docker/build-push-action> - uses: docker/build-push-action@v5 # Action page: <https://github.com/docker/build-push-action>
with: with:
context: . context: .
file: Dockerfile file: Dockerfile
@ -97,11 +95,10 @@ jobs:
demo: demo:
name: Update the demonstration name: Update the demonstration
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
needs: [docker-image] needs: [docker-image]
steps: steps:
- uses: gacts/github-slug@v1 - {uses: gacts/github-slug@v1, id: slug}
id: slug
- name: Take rendered templates from the built docker image - name: Take rendered templates from the built docker image
run: | run: |

View File

@ -15,9 +15,9 @@ concurrency:
jobs: # Docs: <https://git.io/JvxXE> jobs: # Docs: <https://git.io/JvxXE>
gitleaks: gitleaks:
name: Gitleaks name: Gitleaks
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: {fetch-depth: 0} with: {fetch-depth: 0}
- name: Check for GitLeaks - name: Check for GitLeaks
@ -25,20 +25,24 @@ jobs: # Docs: <https://git.io/JvxXE>
golangci-lint: golangci-lint:
name: Golang-CI (lint) name: Golang-CI (lint)
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: golangci/golangci-lint-action@v3 # Action page: <https://github.com/golangci/golangci-lint-action> - uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- uses: golangci/golangci-lint-action@v3
with: {skip-pkg-cache: true, skip-build-cache: true}
validate-config-file: validate-config-file:
name: Validate config file name: Validate config file
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: gacts/setup-node-with-cache@v1
with: {node-version: '16'} with: {node-version: 16}
- name: Install linter - name: Install linter
run: npm install -g ajv-cli # Package page: <https://www.npmjs.com/package/ajv-cli> run: npm install -g ajv-cli # Package page: <https://www.npmjs.com/package/ajv-cli>
@ -48,12 +52,12 @@ jobs: # Docs: <https://git.io/JvxXE>
lint-l10n: lint-l10n:
name: Lint l10n file(s) name: Lint l10n file(s)
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: gacts/setup-node-with-cache@v1
with: {node-version: '16'} with: {node-version: 16}
- name: Install eslint - name: Install eslint
run: npm install -g eslint@v8 # Package page: <https://www.npmjs.com/package/eslint> run: npm install -g eslint@v8 # Package page: <https://www.npmjs.com/package/eslint>
@ -64,24 +68,13 @@ jobs: # Docs: <https://git.io/JvxXE>
go-test: go-test:
name: Unit tests name: Unit tests
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@v3 - uses: actions/checkout@v4
with: {go-version: 1.18}
- uses: actions/checkout@v3
with: {fetch-depth: 2} # Fixes codecov error 'Issue detecting commit SHA' with: {fetch-depth: 2} # Fixes codecov error 'Issue detecting commit SHA'
- name: Go modules Cache # Docs: <https://git.io/JfAKn#go---modules> - uses: gacts/setup-go-with-cache@v1
uses: actions/cache@v3 with: {go-version-file: go.mod}
id: go-cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- if: steps.go-cache.outputs.cache-hit != 'true'
run: go mod download
- name: Run Unit tests - name: Run Unit tests
run: go test -race -covermode=atomic -coverprofile /tmp/coverage.txt ./... run: go test -race -covermode=atomic -coverprofile /tmp/coverage.txt ./...
@ -94,7 +87,7 @@ jobs: # Docs: <https://git.io/JvxXE>
build: build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }}) name: Build for ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -102,34 +95,24 @@ jobs: # Docs: <https://git.io/JvxXE>
arch: [amd64] # amd64, 386 arch: [amd64] # amd64, 386
needs: [golangci-lint, go-test, validate-config-file] needs: [golangci-lint, go-test, validate-config-file]
steps: steps:
- uses: actions/setup-go@v3 - uses: actions/checkout@v4
with: {go-version: 1.18}
- uses: actions/checkout@v3 - uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- uses: gacts/github-slug@v1 - {uses: gacts/github-slug@v1, id: slug}
id: slug
- name: Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- run: go mod download
- name: Build application - name: Build application
env: env:
GOOS: ${{ matrix.os }} GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }} GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 0 CGO_ENABLED: 0
LDFLAGS: -s -w -X github.com/tarampampam/error-pages/internal/version.version=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }} LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}
run: go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/ run: go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/
- name: Try to execute - name: Try to execute
if: matrix.os == 'linux' if: matrix.os == 'linux'
run: ./error-pages version && ./error-pages -h run: ./error-pages --version && ./error-pages -h
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
@ -140,10 +123,10 @@ jobs: # Docs: <https://git.io/JvxXE>
generate: generate:
name: Run templates generator name: Run templates generator
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
needs: [build] needs: [build]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
@ -155,7 +138,7 @@ jobs: # Docs: <https://git.io/JvxXE>
run: mv ./error-pages ./../error-pages && chmod +x ./../error-pages run: mv ./error-pages ./../error-pages && chmod +x ./../error-pages
- name: Run generator - name: Run generator
run: ./error-pages build ./out --verbose --index run: ./error-pages --verbose build --index ./out
- name: Test files creation - name: Test files creation
run: | run: |
@ -171,18 +154,18 @@ jobs: # Docs: <https://git.io/JvxXE>
test -f ./out/app-down/404.html test -f ./out/app-down/404.html
test -f ./out/connection/404.html test -f ./out/connection/404.html
test -f ./out/matrix/404.html test -f ./out/matrix/404.html
test -f ./out/orient/404.html
docker-image: docker-image:
name: Build docker image name: Build docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
needs: [golangci-lint, go-test, validate-config-file] needs: [golangci-lint, go-test, validate-config-file]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: gacts/github-slug@v1 - {uses: gacts/github-slug@v1, id: slug}
id: slug
- uses: docker/build-push-action@v3 # Action page: <https://github.com/docker/build-push-action> - uses: docker/build-push-action@v5 # Action page: <https://github.com/docker/build-push-action>
with: with:
context: . context: .
file: Dockerfile file: Dockerfile
@ -200,17 +183,17 @@ jobs: # Docs: <https://git.io/JvxXE>
scan-docker-image: scan-docker-image:
name: Scan the docker image name: Scan the docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
needs: [docker-image] needs: [docker-image]
steps: steps:
- uses: actions/checkout@v3 # is needed for `upload-sarif` action - uses: actions/checkout@v4 # is needed for `upload-sarif` action
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
name: docker-image name: docker-image
path: .artifact path: .artifact
- uses: aquasecurity/trivy-action@0.6.1 # action page: <https://github.com/aquasecurity/trivy-action> - uses: aquasecurity/trivy-action@0.13.1 # action page: <https://github.com/aquasecurity/trivy-action>
with: with:
input: .artifact/docker-image.tar input: .artifact/docker-image.tar
format: sarif format: sarif
@ -225,11 +208,11 @@ jobs: # Docs: <https://git.io/JvxXE>
poke-docker-image: poke-docker-image:
name: Run the docker image name: Run the docker image
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
needs: [docker-image] needs: [docker-image]
timeout-minutes: 2 timeout-minutes: 2
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
@ -247,7 +230,7 @@ jobs: # Docs: <https://git.io/JvxXE>
- name: Wait for container "healthy" state - name: Wait for container "healthy" state
run: until [[ "`docker inspect -f {{.State.Health.Status}} app`" == "healthy" ]]; do echo "wait 1 sec.."; sleep 1; done run: until [[ "`docker inspect -f {{.State.Health.Status}} app`" == "healthy" ]]; do echo "wait 1 sec.."; sleep 1; done
- run: hurl --color --test --fail-at-end --variable host=127.0.0.1 --variable port=8080 --summary ./test/hurl/*.hurl - run: hurl --color --test --fail-at-end --variable host=127.0.0.1 --variable port=8080 ./test/hurl/*.hurl
- name: Stop the container - name: Stop the container
if: always() if: always()

View File

@ -43,8 +43,6 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
enable: enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bidichk # Checks for dangerous unicode character sequences - bidichk # Checks for dangerous unicode character sequences
- deadcode # Finds unused code
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection - dupl # Tool for code clone detection
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
@ -78,7 +76,6 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
- unconvert # Remove unnecessary type conversions - unconvert # Remove unnecessary type conversions
- unused # Checks Go code for unused constants, variables, functions and types - unused # Checks Go code for unused constants, variables, functions and types
- varcheck # Finds unused global variables and constants
- whitespace # Tool for detection of leading and trailing whitespace - whitespace # Tool for detection of leading and trailing whitespace
- wsl # Whitespace Linter - Forces you to use empty lines! - wsl # Whitespace Linter - Forces you to use empty lines!

View File

@ -4,6 +4,88 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog][keepachangelog] and this project adheres to [Semantic Versioning][semver]. The format is based on [Keep a Changelog][keepachangelog] and this project adheres to [Semantic Versioning][semver].
## v2.26.0
### Added
- Error pages now translated into 🇵🇱 [#226]
- Possibility to set custom read buffer size (using `--read-buffer-size` flag or environment variable `READ_BUFFER_SIZE`) [#238], [#244]
[#226]:https://github.com/tarampampam/error-pages/pull/226
[#238]:https://github.com/tarampampam/error-pages/issues/238
[#244]:https://github.com/tarampampam/error-pages/pull/244
## v2.25.0
### Added
- Go updated from `1.20` up to `1.21`
- Error pages now translated into 🇮🇩 [#218]
- Possibility catch all paths with error page 404 (using `--catch-all` flag for the `serve` or environment variable `CATCH_ALL=true`) [#217]
[#218]:https://github.com/tarampampam/error-pages/pull/218
[#217]:https://github.com/tarampampam/error-pages/issues/217
## v2.24.0
### Added
- Support for IPv6 addresses in the `--listen` flag [#191]
[#191]:https://github.com/tarampampam/error-pages/issues/191
## v2.23.0
### Added
- Template `orient` [#190]
[#190]:https://github.com/tarampampam/error-pages/pull/190
## v2.22.0
### Changed
- Non-existing pages now return styled `404` status page (with `404` status code) [#188]
[#188]:https://github.com/tarampampam/error-pages/issues/188
## v2.21.0
### Changed
- Go updated from `1.19` up to `1.20`
- Go dependencies updated
- Module name changed from `github.com/tarampampam/error-pages` to `gh.tarampamp.am/error-pages`
## v2.20.0
### Changed
- `version` subcommand replaced by `--version` flag [#163]
- `--config-file` flag is not global anymore (use `error-pages (serve|build) --config-file ...` instead of `error-pages --config-file ... (serve|build) ...`) [#163]
- Flags `--verbose`, `--debug` and `--log-json` are deprecated, use `--log-level` and `--log-format` instead [#163]
### Added
- Possibility to use custom env variables in templates [#164], [#165]
[#164]:https://github.com/tarampampam/error-pages/issues/164
[#165]:https://github.com/tarampampam/error-pages/pull/165
[#163]:https://github.com/tarampampam/error-pages/pull/163
## v2.19.0
### Changed
- Go updated from `1.18` up to `1.19`
### Added
- Error pages now translated into Chinese 🇨🇳 [#147]
[#147]:https://github.com/tarampampam/error-pages/pull/147
## v2.18.0 ## v2.18.0
### Changed ### Changed

View File

@ -1,7 +1,17 @@
# syntax=docker/dockerfile:1.2 # syntax=docker/dockerfile:1
# Image page: <https://hub.docker.com/_/golang> # this stage is used to build the application
FROM golang:1.18-alpine as builder FROM golang:1.21-bullseye AS builder
COPY ./go.* /src/
WORKDIR /src
# burn the modules cache
RUN go mod download
# this stage is used to compile the application
FROM builder AS compiler
# can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3@GITHASH" .` # can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3@GITHASH" .`
ARG APP_VERSION="undefined@docker" ARG APP_VERSION="undefined@docker"
@ -11,12 +21,12 @@ WORKDIR /src
COPY . . COPY . .
# arguments to pass on each go tool link invocation # arguments to pass on each go tool link invocation
ENV LDFLAGS="-s -w -X github.com/tarampampam/error-pages/internal/version.version=$APP_VERSION" ENV LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$APP_VERSION"
# build the application
RUN set -x \ RUN set -x \
&& go version \
&& CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/ \ && CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/ \
&& ./error-pages version \ && ./error-pages --version \
&& ./error-pages -h && ./error-pages -h
WORKDIR /tmp/rootfs WORKDIR /tmp/rootfs
@ -38,11 +48,11 @@ WORKDIR /tmp/rootfs/opt
# generate static error pages (for usage inside another docker images, for example) # generate static error pages (for usage inside another docker images, for example)
RUN set -x \ RUN set -x \
&& ./../bin/error-pages --config-file ./error-pages.yml build ./html --verbose --index \ && ./../bin/error-pages --verbose build --config-file ./error-pages.yml --index ./html \
&& ls -l ./html && ls -l ./html
# use empty filesystem # use empty filesystem
FROM scratch as runtime FROM scratch AS runtime
ARG APP_VERSION="undefined@docker" ARG APP_VERSION="undefined@docker"
@ -57,7 +67,7 @@ LABEL \
org.opencontainers.image.licenses="MIT" org.opencontainers.image.licenses="MIT"
# Import from builder # Import from builder
COPY --from=builder /tmp/rootfs / COPY --from=compiler /tmp/rootfs /
# Use an unprivileged user # Use an unprivileged user
USER 10001:10001 USER 10001:10001
@ -69,11 +79,12 @@ ENV LISTEN_PORT="8080" \
DEFAULT_ERROR_PAGE="404" \ DEFAULT_ERROR_PAGE="404" \
DEFAULT_HTTP_CODE="404" \ DEFAULT_HTTP_CODE="404" \
SHOW_DETAILS="false" \ SHOW_DETAILS="false" \
DISABLE_L10N="false" DISABLE_L10N="false" \
READ_BUFFER_SIZE="2048"
# Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck> # Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck>
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "healthcheck", "--log-json"] HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "--log-json", "healthcheck"]
ENTRYPOINT ["/bin/error-pages"] ENTRYPOINT ["/bin/error-pages"]
CMD ["serve", "--log-json"] CMD ["--log-json", "serve"]

View File

@ -3,7 +3,7 @@
# Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents> # Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents>
SHELL = /bin/sh SHELL = /bin/sh
LDFLAGS = "-s -w -X github.com/tarampampam/error-pages/internal/version.version=$(shell git rev-parse HEAD)" LDFLAGS = "-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$(shell git rev-parse HEAD)"
DC_RUN_ARGS = --rm --user "$(shell id -u):$(shell id -g)" DC_RUN_ARGS = --rm --user "$(shell id -u):$(shell id -g)"
APP_NAME = $(notdir $(CURDIR)) APP_NAME = $(notdir $(CURDIR))
@ -43,7 +43,7 @@ gotest: ## Run app tests
docker-compose run $(DC_RUN_ARGS) --no-deps app go test -v -race -timeout 10s ./... docker-compose run $(DC_RUN_ARGS) --no-deps app go test -v -race -timeout 10s ./...
int-test: ## Run integration tests (docs: https://hurl.dev/docs/man-page.html#options) int-test: ## Run integration tests (docs: https://hurl.dev/docs/man-page.html#options)
docker-compose run --rm hurl --color --test --fail-at-end --variable host=web --variable port=8080 --summary ./test/hurl/*.hurl docker-compose run --rm hurl --color --test --fail-at-end --variable host=web --variable port=8080 ./test/hurl/*.hurl
test: lint gotest int-test ## Run app tests and linters test: lint gotest int-test ## Run app tests and linters

View File

@ -5,14 +5,17 @@
<p align="center"> <p align="center">
<a href="#"><img src="https://img.shields.io/github/go-mod/go-version/tarampampam/error-pages?longCache=true&label=&logo=go&logoColor=white&style=flat-square" alt="" /></a> <a href="#"><img src="https://img.shields.io/github/go-mod/go-version/tarampampam/error-pages?longCache=true&label=&logo=go&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://codecov.io/gh/tarampampam/error-pages"><img src="https://img.shields.io/codecov/c/github/tarampampam/error-pages/master.svg?maxAge=30&label=&logo=codecov&logoColor=white&style=flat-square" alt="" /></a> <a href="https://codecov.io/gh/tarampampam/error-pages"><img src="https://img.shields.io/codecov/c/github/tarampampam/error-pages/master.svg?maxAge=30&label=&logo=codecov&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/workflow/status/tarampampam/error-pages/tests?maxAge=30&label=tests&logo=github&style=flat-square" alt="" /></a> <a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/actions/workflow/status/tarampampam/error-pages/tests.yml?branch=master&maxAge=30&label=tests&logo=github&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/workflow/status/tarampampam/error-pages/release?maxAge=30&label=release&logo=github&style=flat-square" alt="" /></a> <a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/actions/workflow/status/tarampampam/error-pages/release.yml?maxAge=30&label=release&logo=github&style=flat-square" alt="" /></a>
<a href="https://hub.docker.com/r/tarampampam/error-pages"><img src="https://img.shields.io/docker/pulls/tarampampam/error-pages.svg?maxAge=30&label=pulls&logo=docker&logoColor=white&style=flat-square" alt="" /></a> <a href="https://hub.docker.com/r/tarampampam/error-pages"><img src="https://img.shields.io/docker/pulls/tarampampam/error-pages.svg?maxAge=30&label=pulls&logo=docker&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://hub.docker.com/r/tarampampam/error-pages"><img src="https://img.shields.io/docker/image-size/tarampampam/error-pages/latest?maxAge=30&label=size&logo=docker&logoColor=white&style=flat-square" alt="" /></a> <a href="https://hub.docker.com/r/tarampampam/error-pages"><img src="https://img.shields.io/docker/image-size/tarampampam/error-pages/latest?maxAge=30&label=size&logo=docker&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/blob/master/LICENSE"><img src="https://img.shields.io/github/license/tarampampam/error-pages.svg?maxAge=30&style=flat-square" alt="" /></a> <a href="https://github.com/tarampampam/error-pages/blob/master/LICENSE"><img src="https://img.shields.io/github/license/tarampampam/error-pages.svg?maxAge=30&style=flat-square" alt="" /></a>
</p> </p>
<p align="center"><sup>22 feb. 2022 - ⚡ Our Docker image was downloaded <strong>one MILLION times</strong> from the docker hub! ⚡</sup></p> <p align="center"><sup>
22 feb. 2022 - ⚡ Our Docker image was downloaded <strong>one MILLION times</strong> from the docker hub! ⚡<br/>
10 apr. 2023 - ⚡ <strong>Two million times</strong> from the docker hub and <strong>one million</strong> from the ghcr! ⚡
</sup></p>
One day you may want to replace the standard error pages of your HTTP server with something more original and pretty. That's what this repository was created for :) It contains: One day you may want to replace the standard error pages of your HTTP server with something more original and pretty. That's what this repository was created for :) It contains:
@ -34,14 +37,12 @@ One day you may want to replace the standard error pages of your HTTP server wit
- Error pages can be [embedded into your own `nginx`][wiki-usage-with-nginx] docker image - Error pages can be [embedded into your own `nginx`][wiki-usage-with-nginx] docker image
- Fully configurable (take a look at the [configuration file](https://github.com/tarampampam/error-pages/blob/master/error-pages.yml) and [project Wiki][wiki]) - Fully configurable (take a look at the [configuration file](https://github.com/tarampampam/error-pages/blob/master/error-pages.yml) and [project Wiki][wiki])
- Distributed using docker image and compiled binary files - Distributed using docker image and compiled binary files
- Localized (🇺🇸, 🇫🇷, 🇺🇦, 🇷🇺, 🇵🇹, 🇳🇱, 🇩🇪, 🇪🇸) HTML error pages (translation process [described here](https://github.com/tarampampam/error-pages/tree/master/l10n) - other translations are welcome!) - Localized (🇺🇸, 🇫🇷, 🇺🇦, 🇷🇺, 🇵🇹, 🇳🇱, 🇩🇪, 🇪🇸, 🇨🇳, 🇮🇩, 🇵🇱) HTML error pages (translation process [described here](https://github.com/tarampampam/error-pages/tree/master/l10n) - other translations are welcome!)
## 🧩 Install ## 🧩 Install
Download the latest binary file for your os/arch from the [releases page][releases] or use our docker image: Download the latest binary file for your os/arch from the [releases page][releases] or use our docker image:
[![image stats](https://dockeri.co/image/tarampampam/error-pages)][docker-hub-tags]
| Registry | Image | | Registry | Image |
|-----------------------------------|-----------------------------------| |-----------------------------------|-----------------------------------|
| [Docker Hub][docker-hub] | `tarampampam/error-pages` | | [Docker Hub][docker-hub] | `tarampampam/error-pages` |
@ -125,6 +126,7 @@ Transfer/sec: 140.23MB
| `app-down` | [![app-down][app-down-screen]][app-down-link] | | `app-down` | [![app-down][app-down-screen]][app-down-link] |
| `connection` | [![connection][connection-screen]][connection-link] | | `connection` | [![connection][connection-screen]][connection-link] |
| `matrix` | [![matrix][matrix-screen]][matrix-link] | | `matrix` | [![matrix][matrix-screen]][matrix-link] |
| `orient` | [![orient][orient-screen]][orient-link] |
> Note: `noise` template highly uses the CPU, be careful > Note: `noise` template highly uses the CPU, be careful
@ -150,6 +152,8 @@ Transfer/sec: 140.23MB
[connection-link]:https://tarampampam.github.io/error-pages/connection/404.html [connection-link]:https://tarampampam.github.io/error-pages/connection/404.html
[matrix-screen]:https://hsto.org/webt/ng/tf/oi/ngtfoiolvmq6hf15kimcxmhprhk.gif [matrix-screen]:https://hsto.org/webt/ng/tf/oi/ngtfoiolvmq6hf15kimcxmhprhk.gif
[matrix-link]:https://tarampampam.github.io/error-pages/matrix/404.html [matrix-link]:https://tarampampam.github.io/error-pages/matrix/404.html
[orient-screen]:https://hsto.org/webt/pz/eu/v_/pzeuv_lyeqr0xpusa4zfrtgk7sa.png
[orient-link]:https://tarampampam.github.io/error-pages/orient/404.html
## 🦾 Contributors ## 🦾 Contributors

View File

@ -5,9 +5,14 @@ import (
"path/filepath" "path/filepath"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/tarampampam/error-pages/internal/cli" "go.uber.org/automaxprocs/maxprocs"
"gh.tarampamp.am/error-pages/internal/cli"
) )
// set GOMAXPROCS to match Linux container CPU quota.
var _, _ = maxprocs.Set(maxprocs.Min(1), maxprocs.Logger(func(_ string, _ ...any) {}))
// exitFn is a function for application exiting. // exitFn is a function for application exiting.
var exitFn = os.Exit //nolint:gochecknoglobals var exitFn = os.Exit //nolint:gochecknoglobals
@ -17,9 +22,7 @@ func main() { exitFn(run()) }
// run this CLI application. // run this CLI application.
// Exit codes documentation: <https://tldp.org/LDP/abs/html/exitcodes.html> // Exit codes documentation: <https://tldp.org/LDP/abs/html/exitcodes.html>
func run() int { func run() int {
cmd := cli.NewCommand(filepath.Base(os.Args[0])) if err := (cli.NewApp(filepath.Base(os.Args[0]))).Run(os.Args); err != nil {
if err := cmd.Execute(); err != nil {
_, _ = color.New(color.FgHiRed, color.Bold).Fprintln(os.Stderr, err.Error()) _, _ = color.New(color.FgHiRed, color.Bold).Fprintln(os.Stderr, err.Error())
return 1 return 1

View File

@ -6,36 +6,16 @@ import (
"github.com/kami-zh/go-capturer" "github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_Main(t *testing.T) { func Test_MainHelp(t *testing.T) {
os.Args = []string{"", "--help"} os.Args = []string{"", "--help"}
exitFn = func(code int) { assert.Equal(t, 0, code) } exitFn = func(code int) { require.Equal(t, 0, code) }
output := capturer.CaptureStdout(main) output := capturer.CaptureStdout(main)
assert.Contains(t, output, "Usage:") assert.Contains(t, output, "USAGE:")
assert.Contains(t, output, "Available Commands:") assert.Contains(t, output, "COMMANDS:")
assert.Contains(t, output, "Flags:") assert.Contains(t, output, "GLOBAL OPTIONS:")
}
func Test_MainWithoutCommands(t *testing.T) {
os.Args = []string{""}
exitFn = func(code int) { assert.Equal(t, 0, code) }
output := capturer.CaptureStdout(main)
assert.Contains(t, output, "Usage:")
assert.Contains(t, output, "Available Commands:")
assert.Contains(t, output, "Flags:")
}
func Test_MainUnknownSubcommand(t *testing.T) {
os.Args = []string{"", "foobar"}
exitFn = func(code int) { assert.Equal(t, 1, code) }
output := capturer.CaptureStderr(main)
assert.Contains(t, output, "unknown command")
assert.Contains(t, output, "foobar")
} }

View File

@ -4,12 +4,12 @@ version: '3.8'
volumes: volumes:
tmp-data: {} tmp-data: {}
golint-go: {}
golint-cache: {} golint-cache: {}
services: services:
app: &app-service app: &go
image: golang:1.18-buster # Image page: <https://hub.docker.com/_/golang> build: {target: builder}
working_dir: /src
environment: environment:
HOME: /tmp HOME: /tmp
GOPATH: /tmp GOPATH: /tmp
@ -18,32 +18,35 @@ services:
- /etc/group:/etc/group:ro - /etc/group:/etc/group:ro
- .:/src:rw - .:/src:rw
- tmp-data:/tmp:rw - tmp-data:/tmp:rw
security_opt: [no-new-privileges:true]
web: web:
<<: *app-service <<: *go
ports: ports:
- "8080:8080/tcp" # Open <http://127.0.0.1:8080> - "8080:8080/tcp" # Open <http://127.0.0.1:8080>
command: sh -c "go build -o /tmp/app ./cmd/error-pages && /tmp/app serve" command: sh -c "go build -buildvcs=false -o /tmp/app ./cmd/error-pages && /tmp/app serve --show-details --proxy-headers=X-Foo,Bar,Baz_blah --catch-all"
healthcheck: healthcheck:
test: ['CMD', '/tmp/app', 'healthcheck', '--log-json'] test: ['CMD', '/tmp/app', '--log-json', 'healthcheck']
interval: 4s interval: 4s
timeout: 1s
start_period: 5s start_period: 5s
retries: 5
golint: golint:
image: golangci/golangci-lint:v1.48-alpine # Image page: <https://hub.docker.com/r/golangci/golangci-lint> image: golangci/golangci-lint:v1.55-alpine # Image page: <https://hub.docker.com/r/golangci/golangci-lint>
environment: environment:
GOLANGCI_LINT_CACHE: /tmp/golint # <https://github.com/golangci/golangci-lint/blob/v1.42.0/internal/cache/default.go#L68> GOLANGCI_LINT_CACHE: /tmp/golint # <https://github.com/golangci/golangci-lint/blob/v1.42.0/internal/cache/default.go#L68>
volumes: volumes:
- .:/src:ro - golint-go:/go:rw # go dependencies will be downloaded on each run without this
- golint-cache:/tmp/golint:rw - golint-cache:/tmp/golint:rw
- .:/src:ro
working_dir: /src working_dir: /src
command: /bin/true security_opt: [no-new-privileges:true]
hurl: hurl:
image: orangeopensource/hurl:1.6.1 image: orangeopensource/hurl:1.8.0
volumes: volumes:
- .:/src:ro - .:/src:ro
working_dir: /src working_dir: /src
depends_on: depends_on:
web: {condition: service_healthy} web: {condition: service_healthy}
security_opt: [no-new-privileges:true]

View File

@ -15,6 +15,7 @@ templates:
- path: ./templates/app-down.html - path: ./templates/app-down.html
- path: ./templates/connection.html - path: ./templates/connection.html
- path: ./templates/matrix.html - path: ./templates/matrix.html
- path: ./templates/orient.html
formats: formats:
json: json:

56
go.mod
View File

@ -1,41 +1,43 @@
module github.com/tarampampam/error-pages module gh.tarampamp.am/error-pages
go 1.18 go 1.21
require ( require (
github.com/a8m/envsubst v1.3.0 github.com/a8m/envsubst v1.4.2
github.com/fasthttp/router v1.4.11 github.com/fasthttp/router v1.4.21
github.com/fatih/color v1.13.0 github.com/fatih/color v1.15.0
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.2 github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.5.0
github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4
github.com/spf13/pflag v1.0.5 github.com/urfave/cli/v2 v2.25.7
github.com/stretchr/testify v1.8.0 github.com/valyala/fasthttp v1.50.0
github.com/valyala/fasthttp v1.38.0 go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.21.0 go.uber.org/goleak v1.3.0
go.uber.org/zap v1.26.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/compress v1.16.3 // indirect
github.com/klauspost/compress v1.15.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.11.1 // indirect
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
go.uber.org/atomic v1.7.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/sys v0.11.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.31.0 // indirect
) )

586
go.sum
View File

@ -1,544 +1,90 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/fasthttp/router v1.4.21 h1:Ysgck9aZwaovqxsfhv7nPx9EgsYvB7t3nthrBMQoeIg=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/fasthttp/router v1.4.21/go.mod h1:wtOlZHmOSGD048li7Nkuhw+ov40rr0tY2+IjT+mN9p4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fasthttp/router v1.4.11 h1:99BvgVxeS2oOZBHnKr/okpdPq1jkn8WvYA2trh/71LY=
github.com/fasthttp/router v1.4.11/go.mod h1:luEEYkGBSAmYyPaMeIUGNgqY+FdHHYDOK9Kivaw7aNo=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco= github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -8,7 +8,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/breaker"
"gh.tarampamp.am/error-pages/internal/breaker"
) )
func TestNewOSSignals(t *testing.T) { func TestNewOSSignals(t *testing.T) {

View File

@ -3,12 +3,13 @@ package checkers_test
import ( import (
"bytes" "bytes"
"context" "context"
"io/ioutil" "io"
"net/http" "net/http"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/checkers"
) )
type httpClientFunc func(*http.Request) (*http.Response, error) type httpClientFunc func(*http.Request) (*http.Response, error)
@ -22,7 +23,7 @@ func TestHealthChecker_CheckSuccess(t *testing.T) {
assert.Equal(t, "HealthChecker/internal", req.Header.Get("User-Agent")) assert.Equal(t, "HealthChecker/internal", req.Header.Get("User-Agent"))
return &http.Response{ return &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte{})), Body: io.NopCloser(bytes.NewReader([]byte{})),
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
}, nil }, nil
} }
@ -35,7 +36,7 @@ func TestHealthChecker_CheckSuccess(t *testing.T) {
func TestHealthChecker_CheckFail(t *testing.T) { func TestHealthChecker_CheckFail(t *testing.T) {
var httpMock httpClientFunc = func(req *http.Request) (*http.Response, error) { var httpMock httpClientFunc = func(req *http.Request) (*http.Response, error) {
return &http.Response{ return &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte{})), Body: io.NopCloser(bytes.NewReader([]byte{})),
StatusCode: http.StatusBadGateway, StatusCode: http.StatusBadGateway,
}, nil }, nil
} }

View File

@ -4,7 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/checkers"
) )
func TestLiveChecker_Check(t *testing.T) { func TestLiveChecker_Check(t *testing.T) {

103
internal/cli/app.go Normal file
View File

@ -0,0 +1,103 @@
package cli
import (
"context"
"fmt"
"runtime"
"strings"
"github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/cli/build"
"gh.tarampamp.am/error-pages/internal/cli/healthcheck"
"gh.tarampamp.am/error-pages/internal/cli/serve"
"gh.tarampamp.am/error-pages/internal/env"
"gh.tarampamp.am/error-pages/internal/logger"
"gh.tarampamp.am/error-pages/internal/version"
)
// NewApp creates new console application.
func NewApp(appName string) *cli.App { //nolint:funlen
const (
logLevelFlagName = "log-level"
logFormatFlagName = "log-format"
verboseFlagName = "verbose"
debugFlagName = "debug"
logJSONFlagName = "log-json"
defaultLogLevel = logger.InfoLevel
defaultLogFormat = logger.ConsoleFormat
)
// create "default" logger (will be overwritten later with customized)
var log, _ = logger.New(defaultLogLevel, defaultLogFormat) // error will never occurs
return &cli.App{
Usage: appName,
Before: func(c *cli.Context) (err error) {
_ = log.Sync() // sync previous logger instance
var logLevel, logFormat = defaultLogLevel, defaultLogFormat //nolint:ineffassign
if c.Bool(verboseFlagName) || c.Bool(debugFlagName) {
logLevel = logger.DebugLevel
} else {
// parse logging level
if logLevel, err = logger.ParseLevel(c.String(logLevelFlagName)); err != nil {
return err
}
}
if c.Bool(logJSONFlagName) {
logFormat = logger.JSONFormat
} else {
// parse logging format
if logFormat, err = logger.ParseFormat(c.String(logFormatFlagName)); err != nil {
return err
}
}
configured, err := logger.New(logLevel, logFormat) // create new logger instance
if err != nil {
return err
}
*log = *configured // replace "default" logger with customized
return nil
},
Commands: []*cli.Command{
healthcheck.NewCommand(checkers.NewHealthChecker(context.TODO())),
build.NewCommand(log),
serve.NewCommand(log),
},
Version: fmt.Sprintf("%s (%s)", version.Version(), runtime.Version()),
Flags: []cli.Flag{ // global flags
&cli.BoolFlag{ // kept for backward compatibility
Name: verboseFlagName,
Usage: "verbose output (DEPRECATED FLAG)",
},
&cli.BoolFlag{ // kept for backward compatibility
Name: debugFlagName,
Usage: "debug output (DEPRECATED FLAG)",
},
&cli.BoolFlag{ // kept for backward compatibility
Name: logJSONFlagName,
Usage: "logs in JSON format (DEPRECATED FLAG)",
},
&cli.StringFlag{
Name: logLevelFlagName,
Value: defaultLogLevel.String(),
Usage: "logging level (`" + strings.Join(logger.LevelStrings(), "/") + "`)",
EnvVars: []string{env.LogLevel.String()},
},
&cli.StringFlag{
Name: logFormatFlagName,
Value: defaultLogFormat.String(),
Usage: "logging format (`" + strings.Join(logger.FormatStrings(), "/") + "`)",
EnvVars: []string{env.LogFormat.String()},
},
},
}
}

19
internal/cli/app_test.go Normal file
View File

@ -0,0 +1,19 @@
package cli_test
import (
"testing"
"github.com/stretchr/testify/assert"
"gh.tarampamp.am/error-pages/internal/cli"
)
func TestNewCommand(t *testing.T) {
t.Parallel()
app := cli.NewApp("app")
assert.NotEmpty(t, app.Flags)
assert.NoError(t, app.Run([]string{"", "--log-level", "debug", "--log-format", "json"}))
}

View File

@ -5,60 +5,59 @@ import (
"path" "path"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/urfave/cli/v2"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/tpl"
"go.uber.org/zap" "go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/cli/shared"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
type command struct {
c *cli.Command
}
// NewCommand creates `build` command. // NewCommand creates `build` command.
func NewCommand(log *zap.Logger, configFile *string) *cobra.Command { func NewCommand(log *zap.Logger) *cli.Command {
var ( var cmd = command{}
generateIndex bool
disableL10n bool const (
cfg *config.Config generateIndexFlagName = "index"
disableL10nFlagName = "disable-l10n"
) )
cmd := &cobra.Command{ cmd.c = &cli.Command{
Use: "build <output-directory>", Name: "build",
Aliases: []string{"b"}, Aliases: []string{"b"},
Short: "Build the error pages", Usage: "build <output-directory>",
Args: cobra.ExactArgs(1), Description: "Build the error pages",
PreRunE: func(*cobra.Command, []string) (err error) { Action: func(c *cli.Context) error {
if configFile == nil { cfg, cfgErr := config.FromYamlFile(c.String(shared.ConfigFileFlag.Name))
return errors.New("path to the config file is required for this command") if cfgErr != nil {
return cfgErr
} }
if cfg, err = config.FromYamlFile(*configFile); err != nil { if c.Args().Len() != 1 {
return err
}
return
},
RunE: func(_ *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("wrong arguments count") return errors.New("wrong arguments count")
} }
return run(log, cfg, args[0], generateIndex, disableL10n) return cmd.Run(log, cfg, c.Args().First(), c.Bool(generateIndexFlagName), c.Bool(disableL10nFlagName))
},
Flags: []cli.Flag{ // global flags
&cli.BoolFlag{
Name: generateIndexFlagName,
Aliases: []string{"i"},
Usage: "generate index page",
},
&cli.BoolFlag{
Name: disableL10nFlagName,
Usage: "disable error pages localization",
},
shared.ConfigFileFlag,
}, },
} }
cmd.Flags().BoolVarP( return cmd.c
&generateIndex,
"index", "i",
false,
"generate index page",
)
cmd.Flags().BoolVarP(
&disableL10n,
"disable-l10n", "",
false,
"disable error pages localization",
)
return cmd
} }
const ( const (
@ -68,14 +67,14 @@ const (
outDirPerm = os.FileMode(0775) outDirPerm = os.FileMode(0775)
) )
func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateIndex, disableL10n bool) error { //nolint:funlen,lll func (cmd *command) Run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateIndex, disableL10n bool) error { //nolint:funlen,lll
if len(cfg.Templates) == 0 { if len(cfg.Templates) == 0 {
return errors.New("no loaded templates") return errors.New("no loaded templates")
} }
log.Info("output directory preparing", zap.String("path", outDirectoryPath)) log.Info("output directory preparing", zap.String("path", outDirectoryPath))
if err := createDirectory(outDirectoryPath, outDirPerm); err != nil { if err := cmd.createDirectory(outDirectoryPath, outDirPerm); err != nil {
return errors.Wrap(err, "cannot prepare output directory") return errors.Wrap(err, "cannot prepare output directory")
} }
@ -86,7 +85,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
log.Debug("template processing", zap.String("name", template.Name())) log.Debug("template processing", zap.String("name", template.Name()))
for _, page := range cfg.Pages { for _, page := range cfg.Pages {
if err := createDirectory(path.Join(outDirectoryPath, template.Name()), outDirPerm); err != nil { if err := cmd.createDirectory(path.Join(outDirectoryPath, template.Name()), outDirPerm); err != nil {
return err return err
} }
@ -138,7 +137,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
return nil return nil
} }
func createDirectory(path string, perm os.FileMode) error { func (cmd *command) createDirectory(path string, perm os.FileMode) error {
stat, err := os.Stat(path) stat, err := os.Stat(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {

View File

@ -1,7 +1,26 @@
package build_test package build_test
import "testing" import (
"flag"
"testing"
func TestNothing(t *testing.T) { "github.com/stretchr/testify/assert"
t.Skip("tests for this package have not been implemented yet") "github.com/urfave/cli/v2"
"go.uber.org/goleak"
"go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/cli/build"
)
func TestNewCommand(t *testing.T) {
defer goleak.VerifyNone(t)
cmd := build.NewCommand(zap.NewNop())
assert.NotEmpty(t, cmd.Flags)
assert.Error(t, cmd.Run(
cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil),
"",
), "should fail because of missing external services")
} }

View File

@ -34,7 +34,7 @@
<footer class="footer"> <footer class="footer">
<div class="container text-center text-muted mt-3 mb-3"> <div class="container text-center text-muted mt-3 mb-3">
For online documentation and support please refer to the For online documentation and support please refer to the
<a href="https://github.com/tarampampam/error-pages">project repository</a>. <a href="https://gh.tarampamp.am/error-pages">project repository</a>.
</div> </div>
</footer> </footer>
</body> </body>

View File

@ -2,56 +2,35 @@
package healthcheck package healthcheck
import ( import (
"fmt" "errors"
"strconv" "math"
"github.com/spf13/cobra" "github.com/urfave/cli/v2"
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/env" "gh.tarampamp.am/error-pages/internal/cli/shared"
) )
type checker interface { type checker interface {
Check(port uint16) error Check(port uint16) error
} }
const portFlagName = "port"
// NewCommand creates `healthcheck` command. // NewCommand creates `healthcheck` command.
func NewCommand(checker checker) *cobra.Command { func NewCommand(checker checker) *cli.Command {
var port uint16 return &cli.Command{
Name: "healthcheck",
cmd := &cobra.Command{
Use: "healthcheck",
Aliases: []string{"chk", "health", "check"}, Aliases: []string{"chk", "health", "check"},
Short: "Health checker for the HTTP server. Use case - docker healthcheck", Usage: "Health checker for the HTTP server. Use case - docker healthcheck",
PreRunE: func(c *cobra.Command, _ []string) (lastErr error) { Action: func(c *cli.Context) error {
c.Flags().VisitAll(func(flag *pflag.Flag) { var port = c.Uint(shared.ListenPortFlag.Name)
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed && flag.Name == portFlagName {
if envPort, exists := env.ListenPort.Lookup(); exists && envPort != "" {
if p, err := strconv.ParseUint(envPort, 10, 16); err == nil { //nolint:gomnd
port = uint16(p)
} else {
lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envPort)
}
}
}
})
return lastErr if port <= 0 || port > math.MaxUint16 {
return errors.New("port value out of range")
}
return checker.Check(uint16(port))
}, },
RunE: func(*cobra.Command, []string) error { Flags: []cli.Flag{
return checker.Check(port) shared.ListenPortFlag,
}, },
} }
cmd.Flags().Uint16VarP(
&port,
portFlagName,
"p",
8080, //nolint:gomnd // must be same as default serve `--port` flag value
fmt.Sprintf("TCP port number [$%s]", env.ListenPort),
)
return cmd
} }

View File

@ -2,13 +2,13 @@ package healthcheck_test
import ( import (
"errors" "errors"
"os" "flag"
"testing" "testing"
"github.com/kami-zh/go-capturer"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/cli/healthcheck" "github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/cli/healthcheck"
) )
type fakeChecker struct{ err error } type fakeChecker struct{ err error }
@ -18,77 +18,30 @@ func (c *fakeChecker) Check(port uint16) error { return c.err }
func TestProperties(t *testing.T) { func TestProperties(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil}) cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
assert.Equal(t, "healthcheck", cmd.Use) assert.Equal(t, "healthcheck", cmd.Name)
assert.ElementsMatch(t, []string{"chk", "health", "check"}, cmd.Aliases) assert.ElementsMatch(t, []string{"chk", "health", "check"}, cmd.Aliases)
assert.NotNil(t, cmd.RunE) assert.NotNil(t, cmd.Action)
} }
func TestCommandRun(t *testing.T) { func TestCommandRun(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil}) cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
cmd.SetArgs([]string{})
output := capturer.CaptureOutput(func() { assert.NoError(t, cmd.Run(cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)))
assert.NoError(t, cmd.Execute())
})
assert.Empty(t, output)
} }
func TestCommandRunFailed(t *testing.T) { func TestCommandRunFailed(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: errors.New("foo err")}) cmd := healthcheck.NewCommand(&fakeChecker{err: errors.New("foo err")})
cmd.SetArgs([]string{})
output := capturer.CaptureStderr(func() { assert.ErrorContains(t, cmd.Run(cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)), "foo err")
assert.Error(t, cmd.Execute())
})
assert.Contains(t, output, "foo err")
} }
func TestPortFlagWrongArgument(t *testing.T) { func TestPortFlagWrongArgument(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil}) cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
cmd.SetArgs([]string{"-p", "65536"}) // 65535 is max
var executed bool err := cmd.Run(
cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil),
"", "-p", "65536",
)
cmd.RunE = func(*cobra.Command, []string) error { assert.ErrorContains(t, err, "port value out of range")
executed = true
return nil
}
output := capturer.CaptureStderr(func() {
assert.Error(t, cmd.Execute())
})
assert.Contains(t, output, "invalid argument")
assert.Contains(t, output, "65536")
assert.Contains(t, output, "value out of range")
assert.False(t, executed)
}
func TestPortFlagWrongEnvValue(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
cmd.SetArgs([]string{})
assert.NoError(t, os.Setenv("LISTEN_PORT", "65536")) // 65535 is max
defer func() { assert.NoError(t, os.Unsetenv("LISTEN_PORT")) }()
var executed bool
cmd.RunE = func(*cobra.Command, []string) error {
executed = true
return nil
}
output := capturer.CaptureStderr(func() {
assert.Error(t, cmd.Execute())
})
assert.Contains(t, output, "wrong TCP port")
assert.Contains(t, output, "environment variable")
assert.Contains(t, output, "65536")
assert.False(t, executed)
} }

View File

@ -1,93 +0,0 @@
// Package cli contains CLI command handlers.
package cli
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/checkers"
buildCmd "github.com/tarampampam/error-pages/internal/cli/build"
healthcheckCmd "github.com/tarampampam/error-pages/internal/cli/healthcheck"
serveCmd "github.com/tarampampam/error-pages/internal/cli/serve"
versionCmd "github.com/tarampampam/error-pages/internal/cli/version"
"github.com/tarampampam/error-pages/internal/env"
"github.com/tarampampam/error-pages/internal/logger"
"github.com/tarampampam/error-pages/internal/version"
)
const configFileFlagName = "config-file"
// NewCommand creates root command.
func NewCommand(appName string) *cobra.Command { //nolint:funlen
var (
configFile string
verbose bool
debug bool
logJSON bool
)
ctx := context.Background() // main CLI context
// create "default" logger (will be overwritten later with customized)
log, err := logger.New(false, false, false)
if err != nil {
panic(err)
}
cmd := &cobra.Command{
Use: appName,
PersistentPreRunE: func(c *cobra.Command, _ []string) error {
_ = log.Sync() // sync previous logger instance
customizedLog, e := logger.New(verbose, debug, logJSON)
if e != nil {
return e
}
*log = *customizedLog // override "default" logger with customized
c.Flags().VisitAll(func(flag *pflag.Flag) {
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed && flag.Name == configFileFlagName {
if envConfigFile, exists := env.ConfigFilePath.Lookup(); exists && envConfigFile != "" {
configFile = envConfigFile
}
}
})
return nil
},
PersistentPostRun: func(*cobra.Command, []string) {
// error ignoring reasons:
// - <https://github.com/uber-go/zap/issues/772>
// - <https://github.com/uber-go/zap/issues/328>
_ = log.Sync()
},
SilenceErrors: true,
SilenceUsage: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}
cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
cmd.PersistentFlags().BoolVarP(&debug, "debug", "", false, "debug output")
cmd.PersistentFlags().BoolVarP(&logJSON, "log-json", "", false, "logs in JSON format")
cmd.PersistentFlags().StringVarP(
&configFile,
configFileFlagName, "c",
"./error-pages.yml",
fmt.Sprintf("path to the config file [$%s]", env.ConfigFilePath),
)
cmd.AddCommand(
versionCmd.NewCommand(version.Version()),
healthcheckCmd.NewCommand(checkers.NewHealthChecker(ctx)),
buildCmd.NewCommand(log, &configFile),
serveCmd.NewCommand(ctx, log, &configFile),
)
return cmd
}

View File

@ -1,84 +0,0 @@
package cli_test
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/cli"
)
func TestSubcommands(t *testing.T) {
cmd := cli.NewCommand("unit test")
cases := []struct {
giveName string
}{
{giveName: "build"},
{giveName: "version"},
{giveName: "healthcheck"},
{giveName: "serve"},
}
// get all existing subcommands and put into the map
subcommands := make(map[string]*cobra.Command)
for _, sub := range cmd.Commands() {
subcommands[sub.Name()] = sub
}
for _, tt := range cases {
tt := tt
t.Run(tt.giveName, func(t *testing.T) {
if _, exists := subcommands[tt.giveName]; !exists {
assert.Failf(t, "command not found", "command [%s] was not found", tt.giveName)
}
})
}
}
func TestFlags(t *testing.T) {
cmd := cli.NewCommand("unit test")
cases := []struct {
giveName string
wantShorthand string
wantDefault string
}{
{giveName: "verbose", wantShorthand: "v", wantDefault: "false"},
{giveName: "debug", wantShorthand: "", wantDefault: "false"},
{giveName: "log-json", wantShorthand: "", wantDefault: "false"},
{giveName: "config-file", wantShorthand: "c", wantDefault: "./error-pages.yml"},
}
for _, tt := range cases {
tt := tt
t.Run(tt.giveName, func(t *testing.T) {
flag := cmd.Flag(tt.giveName)
if flag == nil {
assert.Failf(t, "flag not found", "flag [%s] was not found", tt.giveName)
return
}
assert.Equal(t, tt.wantShorthand, flag.Shorthand)
assert.Equal(t, tt.wantDefault, flag.DefValue)
})
}
}
func TestExecuting(t *testing.T) {
cmd := cli.NewCommand("unit test")
cmd.SetArgs([]string{})
var executed bool
if cmd.Run == nil { // override "Run" property for test (if it was not set)
cmd.Run = func(cmd *cobra.Command, args []string) {
executed = true
}
}
assert.NoError(t, cmd.Execute())
assert.True(t, executed)
}

View File

@ -3,53 +3,183 @@ package serve
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net"
"os" "os"
"strings"
"time" "time"
"github.com/spf13/cobra" "github.com/urfave/cli/v2"
"github.com/tarampampam/error-pages/internal/breaker"
"github.com/tarampampam/error-pages/internal/config"
appHttp "github.com/tarampampam/error-pages/internal/http"
"github.com/tarampampam/error-pages/internal/pick"
"go.uber.org/zap" "go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/breaker"
"gh.tarampamp.am/error-pages/internal/cli/shared"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/env"
appHttp "gh.tarampamp.am/error-pages/internal/http"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/pick"
)
type command struct {
c *cli.Command
}
const (
templateNameFlagName = "template-name"
defaultErrorPageFlagName = "default-error-page"
defaultHTTPCodeFlagName = "default-http-code"
showDetailsFlagName = "show-details"
proxyHTTPHeadersFlagName = "proxy-headers"
disableL10nFlagName = "disable-l10n"
catchAllFlagName = "catch-all"
readBufferSizeFlagName = "read-buffer-size"
)
const (
useRandomTemplate = "random"
useRandomTemplateOnEachRequest = "i-said-random"
useRandomTemplateDaily = "random-daily"
useRandomTemplateHourly = "random-hourly"
) )
// NewCommand creates `serve` command. // NewCommand creates `serve` command.
func NewCommand(ctx context.Context, log *zap.Logger, configFile *string) *cobra.Command { func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
var ( var cmd = command{}
f flags
cfg *config.Config
)
cmd := &cobra.Command{ cmd.c = &cli.Command{
Use: "serve", Name: "serve",
Aliases: []string{"s", "server"}, Aliases: []string{"s", "server"},
Short: "Start HTTP server", Usage: "Start HTTP server",
PreRunE: func(cmd *cobra.Command, _ []string) (err error) { Action: func(c *cli.Context) error {
if configFile == nil { var cfg *config.Config
if configPath := c.String(shared.ConfigFileFlag.Name); configPath == "" { // load config from file
return errors.New("path to the config file is required for this command") return errors.New("path to the config file is required for this command")
} } else if loadedCfg, err := config.FromYamlFile(c.String(shared.ConfigFileFlag.Name)); err != nil {
if err = f.OverrideUsingEnv(cmd.Flags()); err != nil {
return err return err
} else {
cfg = loadedCfg
} }
if cfg, err = config.FromYamlFile(*configFile); err != nil { var (
return err ip = c.String(shared.ListenAddrFlag.Name)
port = uint16(c.Uint(shared.ListenPortFlag.Name))
o options.ErrorPage
)
if net.ParseIP(ip) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", ip)
} }
return f.Validate() { // fill options
o.Template.Name = c.String(templateNameFlagName)
o.L10n.Disabled = c.Bool(disableL10nFlagName)
o.Default.PageCode = c.String(defaultErrorPageFlagName)
o.Default.HTTPCode = uint16(c.Uint(defaultHTTPCodeFlagName))
o.ShowDetails = c.Bool(showDetailsFlagName)
o.CatchAll = c.Bool(catchAllFlagName)
if headers := c.String(proxyHTTPHeadersFlagName); headers != "" { //nolint:nestif
var m = make(map[string]struct{})
// make unique and ignore empty strings
for _, header := range strings.Split(headers, ",") {
if h := strings.TrimSpace(header); h != "" {
if strings.ContainsRune(h, ' ') {
return fmt.Errorf("whitespaces in the HTTP headers for proxying [%s] are not allowed", header)
}
if _, ok := m[h]; !ok {
m[h] = struct{}{}
}
}
}
// convert map into slice
o.ProxyHTTPHeaders = make([]string, 0, len(m))
for h := range m {
o.ProxyHTTPHeaders = append(o.ProxyHTTPHeaders, h)
}
}
}
if o.Default.HTTPCode > 599 { //nolint:gomnd
return fmt.Errorf("wrong default HTTP response code [%d]", o.Default.HTTPCode)
}
return cmd.Run(c.Context, log, cfg, ip, port, c.Uint(readBufferSizeFlagName), o)
},
Flags: []cli.Flag{
shared.ConfigFileFlag,
shared.ListenPortFlag,
shared.ListenAddrFlag,
&cli.StringFlag{
Name: templateNameFlagName,
Aliases: []string{"t"},
Usage: fmt.Sprintf(
"template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on "+
"each request or \"%s/%s\" daily/hourly randomized)",
useRandomTemplate,
useRandomTemplateOnEachRequest,
useRandomTemplateDaily,
useRandomTemplateHourly,
),
EnvVars: []string{env.TemplateName.String()},
},
&cli.StringFlag{
Name: defaultErrorPageFlagName,
Value: "404",
Usage: "default error page",
EnvVars: []string{env.DefaultErrorPage.String()},
},
&cli.UintFlag{
Name: defaultHTTPCodeFlagName,
Value: 404, //nolint:gomnd
Usage: "default HTTP response code",
EnvVars: []string{env.DefaultHTTPCode.String()},
},
&cli.BoolFlag{
Name: showDetailsFlagName,
Usage: "show request details in response",
EnvVars: []string{env.ShowDetails.String()},
},
&cli.StringFlag{
Name: proxyHTTPHeadersFlagName,
Usage: "proxy HTTP request headers list (comma-separated)",
EnvVars: []string{env.ProxyHTTPHeaders.String()},
},
&cli.BoolFlag{
Name: disableL10nFlagName,
Usage: "disable error pages localization",
EnvVars: []string{env.DisableL10n.String()},
},
&cli.BoolFlag{
Name: catchAllFlagName,
Usage: "catch all pages",
EnvVars: []string{env.CatchAll.String()},
},
&cli.UintFlag{
Name: readBufferSizeFlagName,
Usage: "read buffer size (0 = use default value)",
EnvVars: []string{env.ReadBufferSize.String()},
},
}, },
RunE: func(*cobra.Command, []string) error { return run(ctx, log, cfg, f) },
} }
f.Init(cmd.Flags()) return cmd.c
return cmd
} }
// run current command. // Run current command.
func run(parentCtx context.Context, log *zap.Logger, cfg *config.Config, f flags) error { //nolint:funlen func (cmd *command) Run( //nolint:funlen
parentCtx context.Context,
log *zap.Logger,
cfg *config.Config,
ip string,
port uint16,
readBufferSize uint,
opt options.ErrorPage,
) error {
var ( var (
ctx, cancel = context.WithCancel(parentCtx) // serve context creation ctx, cancel = context.WithCancel(parentCtx) // serve context creation
oss = breaker.NewOSSignals(ctx) // OS signals listener oss = breaker.NewOSSignals(ctx) // OS signals listener
@ -70,8 +200,6 @@ func run(parentCtx context.Context, log *zap.Logger, cfg *config.Config, f flags
var ( var (
templateNames = cfg.TemplateNames() templateNames = cfg.TemplateNames()
picker interface{ Pick() string } picker interface{ Pick() string }
opt = f.ToOptions()
) )
switch opt.Template.Name { switch opt.Template.Name {
@ -110,7 +238,7 @@ func run(parentCtx context.Context, log *zap.Logger, cfg *config.Config, f flags
} }
// create HTTP server // create HTTP server
server := appHttp.NewServer(log) server := appHttp.NewServer(log, readBufferSize)
// register server routes, middlewares, etc. // register server routes, middlewares, etc.
if err := server.Register(cfg, picker, opt); err != nil { if err := server.Register(cfg, picker, opt); err != nil {
@ -123,17 +251,24 @@ func run(parentCtx context.Context, log *zap.Logger, cfg *config.Config, f flags
go func(errCh chan<- error) { go func(errCh chan<- error) {
defer close(errCh) defer close(errCh)
log.Info("Server starting", var fields = []zap.Field{
zap.String("addr", f.Listen.IP), zap.String("addr", ip),
zap.Uint16("port", f.Listen.Port), zap.Uint16("port", port),
zap.String("default error page", opt.Default.PageCode), zap.String("default error page", opt.Default.PageCode),
zap.Uint16("default HTTP response code", opt.Default.HTTPCode), zap.Uint16("default HTTP response code", opt.Default.HTTPCode),
zap.Strings("proxy headers", opt.ProxyHTTPHeaders), zap.Strings("proxy headers", opt.ProxyHTTPHeaders),
zap.Bool("show request details", opt.ShowDetails), zap.Bool("show request details", opt.ShowDetails),
zap.Bool("localization disabled", opt.L10n.Disabled), zap.Bool("localization disabled", opt.L10n.Disabled),
) zap.Bool("catch all enabled", opt.CatchAll),
}
if err := server.Start(f.Listen.IP, f.Listen.Port); err != nil { if readBufferSize > 0 {
fields = append(fields, zap.Uint("read buffer size", readBufferSize))
}
log.Info("Server starting", fields...)
if err := server.Start(ip, port); err != nil {
errCh <- err errCh <- err
} }
}(startingErrCh) }(startingErrCh)

View File

@ -1,235 +0,0 @@
package serve
import (
"fmt"
"net"
"sort"
"strconv"
"strings"
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/env"
"github.com/tarampampam/error-pages/internal/options"
)
type flags struct {
Listen struct {
IP string
Port uint16
}
template struct {
name string
}
l10n struct {
disabled bool
}
defaultErrorPage string
defaultHTTPCode uint16
showDetails bool
proxyHTTPHeaders string // comma-separated
}
const (
listenFlagName = "listen"
portFlagName = "port"
templateNameFlagName = "template-name"
defaultErrorPageFlagName = "default-error-page"
defaultHTTPCodeFlagName = "default-http-code"
showDetailsFlagName = "show-details"
proxyHTTPHeadersFlagName = "proxy-headers"
disableL10nFlagName = "disable-l10n"
)
const (
useRandomTemplate = "random"
useRandomTemplateOnEachRequest = "i-said-random"
useRandomTemplateDaily = "random-daily"
useRandomTemplateHourly = "random-hourly"
)
func (f *flags) Init(flagSet *pflag.FlagSet) {
flagSet.StringVarP(
&f.Listen.IP,
listenFlagName, "l",
"0.0.0.0",
fmt.Sprintf("IP address to Listen on [$%s]", env.ListenAddr),
)
flagSet.Uint16VarP(
&f.Listen.Port,
portFlagName, "p",
8080, //nolint:gomnd // must be same as default healthcheck `--port` flag value
fmt.Sprintf("TCP prt number [$%s]", env.ListenPort),
)
flagSet.StringVarP(
&f.template.name,
templateNameFlagName, "t",
"",
fmt.Sprintf(
"template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on each request "+
"or \"%s/%s\" daily/hourly randomized) [$%s]",
useRandomTemplate,
useRandomTemplateOnEachRequest,
useRandomTemplateDaily,
useRandomTemplateHourly,
env.TemplateName,
),
)
flagSet.StringVarP(
&f.defaultErrorPage,
defaultErrorPageFlagName, "",
"404",
fmt.Sprintf("default error page [$%s]", env.DefaultErrorPage),
)
flagSet.Uint16VarP(
&f.defaultHTTPCode,
defaultHTTPCodeFlagName, "",
404, //nolint:gomnd
fmt.Sprintf("default HTTP response code [$%s]", env.DefaultHTTPCode),
)
flagSet.BoolVarP(
&f.showDetails,
showDetailsFlagName, "",
false,
fmt.Sprintf("show request details in response [$%s]", env.ShowDetails),
)
flagSet.StringVarP(
&f.proxyHTTPHeaders,
proxyHTTPHeadersFlagName, "",
"",
fmt.Sprintf("proxy HTTP request headers list (comma-separated) [$%s]", env.ProxyHTTPHeaders),
)
flagSet.BoolVarP(
&f.l10n.disabled,
disableL10nFlagName, "",
false,
fmt.Sprintf("disable error pages localization [$%s]", env.DisableL10n),
)
}
func (f *flags) OverrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nolint:gocognit,gocyclo
flagSet.VisitAll(func(flag *pflag.Flag) {
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed { //nolint:nestif
switch flag.Name {
case listenFlagName:
if envVar, exists := env.ListenAddr.Lookup(); exists {
f.Listen.IP = strings.TrimSpace(envVar)
}
case portFlagName:
if envVar, exists := env.ListenPort.Lookup(); exists {
if p, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd
f.Listen.Port = uint16(p)
} else {
lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envVar)
}
}
case templateNameFlagName:
if envVar, exists := env.TemplateName.Lookup(); exists {
f.template.name = strings.TrimSpace(envVar)
}
case defaultErrorPageFlagName:
if envVar, exists := env.DefaultErrorPage.Lookup(); exists {
f.defaultErrorPage = strings.TrimSpace(envVar)
}
case defaultHTTPCodeFlagName:
if envVar, exists := env.DefaultHTTPCode.Lookup(); exists {
if code, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd
f.defaultHTTPCode = uint16(code)
} else {
lastErr = fmt.Errorf("wrong default HTTP response code environment variable [%s] value", envVar)
}
}
case showDetailsFlagName:
if envVar, exists := env.ShowDetails.Lookup(); exists {
if b, err := strconv.ParseBool(envVar); err == nil {
f.showDetails = b
}
}
case proxyHTTPHeadersFlagName:
if envVar, exists := env.ProxyHTTPHeaders.Lookup(); exists {
f.proxyHTTPHeaders = strings.TrimSpace(envVar)
}
case disableL10nFlagName:
if envVar, exists := env.DisableL10n.Lookup(); exists {
if b, err := strconv.ParseBool(envVar); err == nil {
f.l10n.disabled = b
}
}
}
}
})
return lastErr
}
func (f *flags) Validate() error {
if net.ParseIP(f.Listen.IP) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", f.Listen.IP)
}
if f.defaultHTTPCode > 599 { //nolint:gomnd
return fmt.Errorf("wrong default HTTP response code [%d]", f.defaultHTTPCode)
}
if strings.ContainsRune(f.proxyHTTPHeaders, ' ') {
return fmt.Errorf("whitespaces in the HTTP headers for proxying [%s] are not allowed", f.proxyHTTPHeaders)
}
return nil
}
// headersToProxy converts a comma-separated string with headers list into strings slice (with a sorting and without
// duplicates).
func (f *flags) headersToProxy() []string {
var raw = strings.Split(f.proxyHTTPHeaders, ",")
if len(raw) == 0 {
return []string{}
} else if len(raw) == 1 {
if h := strings.TrimSpace(raw[0]); h != "" {
return []string{h}
} else {
return []string{}
}
}
var m = make(map[string]struct{}, len(raw))
// make unique and ignore empty strings
for _, h := range raw {
if h = strings.TrimSpace(h); h != "" {
if _, ok := m[h]; !ok {
m[h] = struct{}{}
}
}
}
// convert map into slice
var headers = make([]string, 0, len(m))
for h := range m {
headers = append(headers, h)
}
// make sort
sort.Strings(headers)
return headers
}
func (f *flags) ToOptions() (o options.ErrorPage) {
o.Default.PageCode = f.defaultErrorPage
o.Default.HTTPCode = f.defaultHTTPCode
o.L10n.Disabled = f.l10n.disabled
o.Template.Name = f.template.name
o.ShowDetails = f.showDetails
o.ProxyHTTPHeaders = f.headersToProxy()
return o
}

View File

@ -0,0 +1,31 @@
package shared
import (
"github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/env"
)
var ConfigFileFlag = &cli.StringFlag{ //nolint:gochecknoglobals
Name: "config-file",
Aliases: []string{"c"},
Usage: "path to the config file (yaml)",
Value: "./error-pages.yml",
EnvVars: []string{env.ConfigFilePath.String()},
}
var ListenAddrFlag = &cli.StringFlag{ //nolint:gochecknoglobals
Name: "listen",
Aliases: []string{"l"},
Usage: "IP (v4 or v6) address to Listen on",
Value: "0.0.0.0",
EnvVars: []string{env.ListenAddr.String()},
}
var ListenPortFlag = &cli.UintFlag{ //nolint:gochecknoglobals
Name: "port",
Aliases: []string{"p"},
Usage: "TCP port number",
Value: 8080, //nolint:gomnd
EnvVars: []string{env.ListenPort.String()},
}

View File

@ -1,24 +0,0 @@
// Package version contains CLI `version` command implementation.
package version
import (
"fmt"
"os"
"runtime"
"github.com/spf13/cobra"
)
// NewCommand creates `version` command.
func NewCommand(ver string) *cobra.Command {
return &cobra.Command{
Use: "version",
Aliases: []string{"v", "ver"},
Short: "Display application version",
RunE: func(*cobra.Command, []string) (err error) {
_, err = fmt.Fprintf(os.Stdout, "app version:\t%s (%s)\n", ver, runtime.Version())
return
},
}
}

View File

@ -1,30 +0,0 @@
package version_test
import (
"runtime"
"testing"
"github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/cli/version"
)
func TestProperties(t *testing.T) {
cmd := version.NewCommand("")
assert.Equal(t, "version", cmd.Use)
assert.ElementsMatch(t, []string{"v", "ver"}, cmd.Aliases)
assert.NotNil(t, cmd.RunE)
}
func TestCommandRun(t *testing.T) {
cmd := version.NewCommand("1.2.3@foobar")
cmd.SetArgs([]string{})
output := capturer.CaptureStdout(func() {
assert.NoError(t, cmd.Execute())
})
assert.Contains(t, output, "1.2.3@foobar")
assert.Contains(t, output, runtime.Version())
}

View File

@ -1,7 +1,6 @@
package config package config
import ( import (
"io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -68,7 +67,7 @@ func (t Template) Name() string { return t.name }
func (t Template) Content() []byte { return t.content } func (t Template) Content() []byte { return t.content }
func (t *Template) loadContentFromFile(filePath string) (err error) { func (t *Template) loadContentFromFile(filePath string) (err error) {
if t.content, err = ioutil.ReadFile(filePath); err != nil { if t.content, err = os.ReadFile(filePath); err != nil {
return errors.Wrap(err, "cannot load content for the template "+t.Name()+" from file "+filePath) return errors.Wrap(err, "cannot load content for the template "+t.Name()+" from file "+filePath)
} }
@ -236,7 +235,7 @@ func FromYaml(in []byte) (_ *Config, err error) {
// FromYamlFile creates new Config instance using YAML file. // FromYamlFile creates new Config instance using YAML file.
func FromYamlFile(filepath string) (*Config, error) { func FromYamlFile(filepath string) (*Config, error) {
bytes, err := ioutil.ReadFile(filepath) bytes, err := os.ReadFile(filepath)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "cannot read configuration file") return nil, errors.Wrap(err, "cannot read configuration file")
} }

View File

@ -5,7 +5,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/config"
) )
func TestFromYaml(t *testing.T) { func TestFromYaml(t *testing.T) {

5
internal/env/env.go vendored
View File

@ -6,6 +6,9 @@ import "os"
type envVariable string type envVariable string
const ( const (
LogLevel envVariable = "LOG_LEVEL" // logging level
LogFormat envVariable = "LOG_FORMAT" // logging format (json|console)
ListenAddr envVariable = "LISTEN_ADDR" // IP address for listening ListenAddr envVariable = "LISTEN_ADDR" // IP address for listening
ListenPort envVariable = "LISTEN_PORT" // port number for listening ListenPort envVariable = "LISTEN_PORT" // port number for listening
TemplateName envVariable = "TEMPLATE_NAME" // template name TemplateName envVariable = "TEMPLATE_NAME" // template name
@ -15,6 +18,8 @@ const (
ShowDetails envVariable = "SHOW_DETAILS" // show request details in response ShowDetails envVariable = "SHOW_DETAILS" // show request details in response
ProxyHTTPHeaders envVariable = "PROXY_HTTP_HEADERS" // proxy HTTP request headers list (request -> response) ProxyHTTPHeaders envVariable = "PROXY_HTTP_HEADERS" // proxy HTTP request headers list (request -> response)
DisableL10n envVariable = "DISABLE_L10N" // disable pages localization DisableL10n envVariable = "DISABLE_L10N" // disable pages localization
CatchAll envVariable = "CATCH_ALL" // catch all pages
ReadBufferSize envVariable = "READ_BUFFER_SIZE" // https://github.com/tarampampam/error-pages/issues/238
) )
// String returns environment variable name in the string representation. // String returns environment variable name in the string representation.

View File

@ -17,6 +17,8 @@ func TestConstants(t *testing.T) {
assert.Equal(t, "SHOW_DETAILS", string(ShowDetails)) assert.Equal(t, "SHOW_DETAILS", string(ShowDetails))
assert.Equal(t, "PROXY_HTTP_HEADERS", string(ProxyHTTPHeaders)) assert.Equal(t, "PROXY_HTTP_HEADERS", string(ProxyHTTPHeaders))
assert.Equal(t, "DISABLE_L10N", string(DisableL10n)) assert.Equal(t, "DISABLE_L10N", string(DisableL10n))
assert.Equal(t, "CATCH_ALL", string(CatchAll))
assert.Equal(t, "READ_BUFFER_SIZE", string(ReadBufferSize))
} }
func TestEnvVariable_Lookup(t *testing.T) { func TestEnvVariable_Lookup(t *testing.T) {
@ -32,6 +34,8 @@ func TestEnvVariable_Lookup(t *testing.T) {
{giveEnv: ShowDetails}, {giveEnv: ShowDetails},
{giveEnv: ProxyHTTPHeaders}, {giveEnv: ProxyHTTPHeaders},
{giveEnv: DisableL10n}, {giveEnv: DisableL10n},
{giveEnv: CatchAll},
{giveEnv: ReadBufferSize},
} }
for _, tt := range cases { for _, tt := range cases {

View File

@ -3,10 +3,11 @@ package core
import ( import (
"strconv" "strconv"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
type templatePicker interface { type templatePicker interface {

View File

@ -39,7 +39,7 @@ func ClientWantFormat(ctx *fasthttp.RequestCtx) ContentType {
f := format{b[0:idx], 0} f := format{b[0:idx], 0}
if len(b) > idx+3 { if len(b) > idx+3 {
if weight, err := strconv.ParseFloat(string(b[idx+3:]), 32); err == nil { //nolint:gomnd if weight, err := strconv.ParseFloat(string(b[idx+3:]), 32); err == nil {
f.weight = float32(weight) f.weight = float32(weight)
} }
} }

View File

@ -4,8 +4,9 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/http/core"
) )
func TestClientWantFormat(t *testing.T) { func TestClientWantFormat(t *testing.T) {

View File

@ -1,11 +1,12 @@
package errorpage package errorpage
import ( import (
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/core"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
type ( type (

View File

@ -3,11 +3,12 @@ package index
import ( import (
"strconv" "strconv"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/core"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
type ( type (

View File

@ -2,13 +2,27 @@ package notfound
import ( import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/core"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
)
type (
templatePicker interface {
// Pick the template name for responding.
Pick() string
}
renderer interface {
Render(content []byte, props tpl.Properties) ([]byte, error)
}
) )
// NewHandler creates handler missing requests handling. // NewHandler creates handler missing requests handling.
func NewHandler() fasthttp.RequestHandler { func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) { return func(ctx *fasthttp.RequestCtx) {
ctx.SetContentType("text/plain; charset=utf-8") core.RespondWithErrorPage(ctx, cfg, p, rdr, "404", fasthttp.StatusNotFound, opt)
ctx.SetStatusCode(fasthttp.StatusNotFound)
_, _ = ctx.WriteString("Wrong request URL. Error pages are available at the following URLs: /{code}.html")
} }
} }

View File

@ -1,25 +1,29 @@
package http package http
import ( import (
"strconv" "errors"
"fmt"
"net"
"strings"
"time" "time"
"github.com/fasthttp/router" "github.com/fasthttp/router"
"github.com/tarampampam/error-pages/internal/checkers"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/common"
errorpageHandler "github.com/tarampampam/error-pages/internal/http/handlers/errorpage"
healthzHandler "github.com/tarampampam/error-pages/internal/http/handlers/healthz"
indexHandler "github.com/tarampampam/error-pages/internal/http/handlers/index"
metricsHandler "github.com/tarampampam/error-pages/internal/http/handlers/metrics"
notfoundHandler "github.com/tarampampam/error-pages/internal/http/handlers/notfound"
versionHandler "github.com/tarampampam/error-pages/internal/http/handlers/version"
"github.com/tarampampam/error-pages/internal/metrics"
"github.com/tarampampam/error-pages/internal/options"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/tarampampam/error-pages/internal/version"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"go.uber.org/zap" "go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/common"
errorpageHandler "gh.tarampamp.am/error-pages/internal/http/handlers/errorpage"
healthzHandler "gh.tarampamp.am/error-pages/internal/http/handlers/healthz"
indexHandler "gh.tarampamp.am/error-pages/internal/http/handlers/index"
metricsHandler "gh.tarampamp.am/error-pages/internal/http/handlers/metrics"
notfoundHandler "gh.tarampamp.am/error-pages/internal/http/handlers/notfound"
versionHandler "gh.tarampamp.am/error-pages/internal/http/handlers/version"
"gh.tarampamp.am/error-pages/internal/metrics"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
"gh.tarampamp.am/error-pages/internal/version"
) )
type Server struct { type Server struct {
@ -35,13 +39,14 @@ const (
defaultIdleTimeout = time.Second * 6 defaultIdleTimeout = time.Second * 6
) )
func NewServer(log *zap.Logger) Server { func NewServer(log *zap.Logger, readBufferSize uint) Server {
rdr := tpl.NewTemplateRenderer() rdr := tpl.NewTemplateRenderer()
return Server{ return Server{
// fasthttp docs: <https://github.com/valyala/fasthttp> // fasthttp docs: <https://github.com/valyala/fasthttp>
fast: &fasthttp.Server{ fast: &fasthttp.Server{
WriteTimeout: defaultWriteTimeout, WriteTimeout: defaultWriteTimeout,
ReadBufferSize: int(readBufferSize),
ReadTimeout: defaultReadTimeout, ReadTimeout: defaultReadTimeout,
IdleTimeout: defaultIdleTimeout, IdleTimeout: defaultIdleTimeout,
NoDefaultServerHeader: true, NoDefaultServerHeader: true,
@ -56,8 +61,24 @@ func NewServer(log *zap.Logger) Server {
} }
// Start server. // Start server.
func (s *Server) Start(ip string, port uint16) error { func (s *Server) Start(ip string, port uint16) (err error) {
return s.fast.ListenAndServe(ip + ":" + strconv.Itoa(int(port))) if net.ParseIP(ip) == nil {
return errors.New("invalid IP address")
}
var ln net.Listener
if strings.Count(ip, ":") >= 2 { //nolint:gomnd // ipv6
if ln, err = net.Listen("tcp6", fmt.Sprintf("[%s]:%d", ip, port)); err != nil {
return err
}
} else { // ipv4
if ln, err = net.Listen("tcp4", fmt.Sprintf("%s:%d", ip, port)); err != nil {
return err
}
}
return s.fast.Serve(ln)
} }
type templatePicker interface { type templatePicker interface {
@ -87,7 +108,13 @@ func (s *Server) Register(cfg *config.Config, templatePicker templatePicker, opt
s.router.GET("/metrics", metricsHandler.NewHandler(reg)) s.router.GET("/metrics", metricsHandler.NewHandler(reg))
s.router.NotFound = notfoundHandler.NewHandler() // use index handler to catch all paths? Uses DEFAULT_ERROR_PAGE
if opt.CatchAll {
s.router.NotFound = indexHandler.NewHandler(cfg, templatePicker, s.rdr, opt)
} else {
// use default not found handler
s.router.NotFound = notfoundHandler.NewHandler(cfg, templatePicker, s.rdr, opt)
}
return nil return nil
} }

68
internal/logger/format.go Normal file
View File

@ -0,0 +1,68 @@
package logger
import (
"fmt"
"strings"
)
// A Format is a logging format.
type Format uint8
const (
ConsoleFormat Format = iota // useful for console output (for humans)
JSONFormat // useful for logging aggregation systems (for robots)
)
// String returns a lower-case ASCII representation of the log format.
func (f Format) String() string {
switch f {
case ConsoleFormat:
return "console"
case JSONFormat:
return "json"
}
return fmt.Sprintf("format(%d)", f)
}
// Formats returns a slice of all logging formats.
func Formats() []Format {
return []Format{ConsoleFormat, JSONFormat}
}
// FormatStrings returns a slice of all logging formats as strings.
func FormatStrings() []string {
var (
formats = Formats()
result = make([]string, len(formats))
)
for i := range formats {
result[i] = formats[i].String()
}
return result
}
// ParseFormat parses a format (case is ignored) based on the ASCII representation of the log format.
// If the provided ASCII representation is invalid an error is returned.
//
// This is particularly useful when dealing with text input to configure log formats.
func ParseFormat[T string | []byte](text T) (Format, error) {
var format string
if s, ok := any(text).(string); ok {
format = s
} else {
format = string(any(text).([]byte))
}
switch strings.ToLower(format) {
case "console", "": // make the zero value useful
return ConsoleFormat, nil
case "json":
return JSONFormat, nil
}
return Format(0), fmt.Errorf("unrecognized logging format: %q", text)
}

View File

@ -0,0 +1,62 @@
package logger_test
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestFormat_String(t *testing.T) {
for name, tt := range map[string]struct {
giveFormat logger.Format
wantString string
}{
"json": {giveFormat: logger.JSONFormat, wantString: "json"},
"console": {giveFormat: logger.ConsoleFormat, wantString: "console"},
"<unknown>": {giveFormat: logger.Format(255), wantString: "format(255)"},
} {
t.Run(name, func(t *testing.T) {
require.Equal(t, tt.wantString, tt.giveFormat.String())
})
}
}
func TestParseFormat(t *testing.T) {
for name, tt := range map[string]struct {
giveBytes []byte
giveString string
wantFormat logger.Format
wantError error
}{
"<empty value>": {giveBytes: []byte(""), wantFormat: logger.ConsoleFormat},
"<empty value> (string)": {giveString: "", wantFormat: logger.ConsoleFormat},
"console": {giveBytes: []byte("console"), wantFormat: logger.ConsoleFormat},
"console (string)": {giveString: "console", wantFormat: logger.ConsoleFormat},
"json": {giveBytes: []byte("json"), wantFormat: logger.JSONFormat},
"json (string)": {giveString: "json", wantFormat: logger.JSONFormat},
"foobar": {giveBytes: []byte("foobar"), wantError: errors.New("unrecognized logging format: \"foobar\"")}, //nolint:lll
} {
t.Run(name, func(t *testing.T) {
var (
f logger.Format
err error
)
if tt.giveString != "" {
f, err = logger.ParseFormat(tt.giveString)
} else {
f, err = logger.ParseFormat(tt.giveBytes)
}
if tt.wantError == nil {
require.NoError(t, err)
require.Equal(t, tt.wantFormat, f)
} else {
require.EqualError(t, err, tt.wantError.Error())
}
})
}
}

83
internal/logger/level.go Normal file
View File

@ -0,0 +1,83 @@
package logger
import (
"fmt"
"strings"
)
// A Level is a logging level.
type Level int8
const (
DebugLevel Level = iota - 1
InfoLevel // default level (zero-value)
WarnLevel
ErrorLevel
FatalLevel
)
// String returns a lower-case ASCII representation of the log level.
func (l Level) String() string {
switch l {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warn"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
}
return fmt.Sprintf("level(%d)", l)
}
// Levels returns a slice of all logging levels.
func Levels() []Level {
return []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, FatalLevel}
}
// LevelStrings returns a slice of all logging levels as strings.
func LevelStrings() []string {
var (
levels = Levels()
result = make([]string, len(levels))
)
for i := range levels {
result[i] = levels[i].String()
}
return result
}
// ParseLevel parses a level (case is ignored) based on the ASCII representation of the log level.
// If the provided ASCII representation is invalid an error is returned.
//
// This is particularly useful when dealing with text input to configure log levels.
func ParseLevel[T string | []byte](text T) (Level, error) {
var lvl string
if s, ok := any(text).(string); ok {
lvl = s
} else {
lvl = string(any(text).([]byte))
}
switch strings.ToLower(lvl) {
case "debug", "verbose", "trace":
return DebugLevel, nil
case "info", "": // make the zero value useful
return InfoLevel, nil
case "warn":
return WarnLevel, nil
case "error":
return ErrorLevel, nil
case "fatal":
return FatalLevel, nil
}
return Level(0), fmt.Errorf("unrecognized logging level: %q", text)
}

View File

@ -0,0 +1,84 @@
package logger_test
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestLevel_String(t *testing.T) {
for name, tt := range map[string]struct {
giveLevel logger.Level
wantString string
}{
"debug": {giveLevel: logger.DebugLevel, wantString: "debug"},
"info": {giveLevel: logger.InfoLevel, wantString: "info"},
"warn": {giveLevel: logger.WarnLevel, wantString: "warn"},
"error": {giveLevel: logger.ErrorLevel, wantString: "error"},
"fatal": {giveLevel: logger.FatalLevel, wantString: "fatal"},
"<unknown>": {giveLevel: logger.Level(127), wantString: "level(127)"},
} {
t.Run(name, func(t *testing.T) {
require.Equal(t, tt.wantString, tt.giveLevel.String())
})
}
}
func TestParseLevel(t *testing.T) {
for name, tt := range map[string]struct {
giveBytes []byte
giveString string
wantLevel logger.Level
wantError error
}{
"<empty value>": {giveBytes: []byte(""), wantLevel: logger.InfoLevel},
"<empty value> (string)": {giveString: "", wantLevel: logger.InfoLevel},
"trace": {giveBytes: []byte("debug"), wantLevel: logger.DebugLevel},
"verbose": {giveBytes: []byte("debug"), wantLevel: logger.DebugLevel},
"debug": {giveBytes: []byte("debug"), wantLevel: logger.DebugLevel},
"debug (string)": {giveString: "debug", wantLevel: logger.DebugLevel},
"info": {giveBytes: []byte("info"), wantLevel: logger.InfoLevel},
"warn": {giveBytes: []byte("warn"), wantLevel: logger.WarnLevel},
"error": {giveBytes: []byte("error"), wantLevel: logger.ErrorLevel},
"fatal": {giveBytes: []byte("fatal"), wantLevel: logger.FatalLevel},
"fatal (string)": {giveString: "fatal", wantLevel: logger.FatalLevel},
"foobar": {giveBytes: []byte("foobar"), wantError: errors.New("unrecognized logging level: \"foobar\"")}, //nolint:lll
} {
t.Run(name, func(t *testing.T) {
var (
l logger.Level
err error
)
if tt.giveString != "" {
l, err = logger.ParseLevel(tt.giveString)
} else {
l, err = logger.ParseLevel(tt.giveBytes)
}
if tt.wantError == nil {
require.NoError(t, err)
require.Equal(t, tt.wantLevel, l)
} else {
require.EqualError(t, err, tt.wantError.Error())
}
})
}
}
func TestLevels(t *testing.T) {
require.Equal(t, []logger.Level{
logger.DebugLevel,
logger.InfoLevel,
logger.WarnLevel,
logger.ErrorLevel,
logger.FatalLevel,
}, logger.Levels())
}
func TestLevelStrings(t *testing.T) {
require.Equal(t, []string{"debug", "info", "warn", "error", "fatal"}, logger.LevelStrings())
}

View File

@ -2,20 +2,27 @@
package logger package logger
import ( import (
"errors"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
// New creates new "zap" logger with little customization. // New creates new "zap" logger with a small customization.
func New(verbose, debug, logJSON bool) (*zap.Logger, error) { func New(l Level, f Format) (*zap.Logger, error) {
var config zap.Config var config zap.Config
if logJSON { switch f {
config = zap.NewProductionConfig() case ConsoleFormat:
} else {
config = zap.NewDevelopmentConfig() config = zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeLevel = zapcore.LowercaseColorLevelEncoder config.EncoderConfig.EncodeLevel = zapcore.LowercaseColorLevelEncoder
config.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05") config.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05")
case JSONFormat:
config = zap.NewProductionConfig() // json encoder is used by default
default:
return nil, errors.New("unsupported logging format")
} }
// default configuration for all encoders // default configuration for all encoders
@ -24,15 +31,31 @@ func New(verbose, debug, logJSON bool) (*zap.Logger, error) {
config.DisableStacktrace = true config.DisableStacktrace = true
config.DisableCaller = true config.DisableCaller = true
if debug { // enable additional features for debugging
if l <= DebugLevel {
config.Development = true config.Development = true
config.DisableStacktrace = false config.DisableStacktrace = false
config.DisableCaller = false config.DisableCaller = false
} }
if verbose || debug { var zapLvl zapcore.Level
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
switch l { // convert level to zap.Level
case DebugLevel:
zapLvl = zap.DebugLevel
case InfoLevel:
zapLvl = zap.InfoLevel
case WarnLevel:
zapLvl = zap.WarnLevel
case ErrorLevel:
zapLvl = zap.ErrorLevel
case FatalLevel:
zapLvl = zap.FatalLevel
default:
return nil, errors.New("unsupported logging level")
} }
config.Level = zap.NewAtomicLevelAt(zapLvl)
return config.Build() return config.Build()
} }

View File

@ -8,64 +8,51 @@ import (
"github.com/kami-zh/go-capturer" "github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/logger" "github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/logger"
) )
func TestNewNotVerboseDebugJSON(t *testing.T) { func TestNewDebugLevelConsoleFormat(t *testing.T) {
output := capturer.CaptureStderr(func() { output := capturer.CaptureStderr(func() {
log, err := logger.New(false, false, false) log, err := logger.New(logger.DebugLevel, logger.ConsoleFormat)
assert.NoError(t, err) require.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg") log.Debug("dbg msg")
log.Info("inf msg")
log.Error("err msg") log.Error("err msg")
}) })
assert.Contains(t, output, time.Now().Format("15:04:05")) assert.Contains(t, output, time.Now().Format("15:04:05"))
assert.Regexp(t, `\t.+info.+\tinf msg`, output) assert.Regexp(t, `\t.+info.+\tinf msg`, output)
assert.NotContains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewVerboseNotDebugJSON(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(true, false, false)
assert.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg")
log.Error("err msg")
})
assert.Contains(t, output, time.Now().Format("15:04:05"))
assert.Regexp(t, `\t.+info.+\tinf msg`, output)
assert.Contains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewVerboseDebugNotJSON(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(true, true, false)
assert.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg")
log.Error("err msg")
})
assert.Contains(t, output, time.Now().Format("15:04:05"))
assert.Regexp(t, `\t.+info.+\t.+logger_test\.go:\d+\tinf msg`, output) assert.Regexp(t, `\t.+info.+\t.+logger_test\.go:\d+\tinf msg`, output)
assert.Contains(t, output, "dbg msg") assert.Contains(t, output, "dbg msg")
assert.Contains(t, output, "err msg") assert.Contains(t, output, "err msg")
} }
func TestNewNotVerboseDebugButJSON(t *testing.T) { func TestNewErrorLevelConsoleFormat(t *testing.T) {
output := capturer.CaptureStderr(func() { output := capturer.CaptureStderr(func() {
log, err := logger.New(false, false, true) log, err := logger.New(logger.ErrorLevel, logger.ConsoleFormat)
assert.NoError(t, err) require.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg") log.Debug("dbg msg")
log.Info("inf msg")
log.Error("err msg")
})
assert.NotContains(t, output, "inf msg")
assert.NotContains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewWarnLevelJSONFormat(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(logger.WarnLevel, logger.JSONFormat)
require.NoError(t, err)
log.Debug("dbg msg")
log.Info("inf msg")
log.Warn("warn msg")
log.Error("err msg") log.Error("err msg")
}) })
@ -75,6 +62,14 @@ func TestNewNotVerboseDebugButJSON(t *testing.T) {
lines := strings.Split(strings.Trim(output, "\n"), "\n") lines := strings.Split(strings.Trim(output, "\n"), "\n")
assert.JSONEq(t, `{"level":"info","ts":0.1,"msg":"inf msg"}`, lines[0]) assert.JSONEq(t, `{"level":"warn","ts":0.1,"msg":"warn msg"}`, lines[0])
assert.JSONEq(t, `{"level":"error","ts":0.1,"msg":"err msg"}`, lines[1]) assert.JSONEq(t, `{"level":"error","ts":0.1,"msg":"err msg"}`, lines[1])
} }
func TestNewErrors(t *testing.T) {
_, err := logger.New(logger.Level(127), logger.ConsoleFormat)
require.EqualError(t, err, "unsupported logging level")
_, err = logger.New(logger.WarnLevel, logger.Format(255))
require.EqualError(t, err, "unsupported logging format")
}

View File

@ -8,7 +8,8 @@ import (
"github.com/prometheus/client_golang/prometheus/testutil" "github.com/prometheus/client_golang/prometheus/testutil"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/metrics"
"gh.tarampamp.am/error-pages/internal/metrics"
) )
func TestMetrics_Register(t *testing.T) { func TestMetrics_Register(t *testing.T) {

View File

@ -5,7 +5,8 @@ import (
"github.com/prometheus/client_golang/prometheus/testutil" "github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/metrics"
"gh.tarampamp.am/error-pages/internal/metrics"
) )
func TestNewRegistry(t *testing.T) { func TestNewRegistry(t *testing.T) {

View File

@ -13,4 +13,5 @@ type ErrorPage struct {
} }
ShowDetails bool // show request details in response ShowDetails bool // show request details in response
ProxyHTTPHeaders []string // proxy HTTP request headers list ProxyHTTPHeaders []string // proxy HTTP request headers list
CatchAll bool // catch all pages
} }

View File

@ -4,7 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/pick"
"gh.tarampamp.am/error-pages/internal/pick"
) )
func TestPicker_NextIndex_First(t *testing.T) { func TestPicker_NextIndex_First(t *testing.T) {

View File

@ -5,7 +5,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/pick"
"gh.tarampamp.am/error-pages/internal/pick"
) )
func TestStringsSlice_Pick(t *testing.T) { func TestStringsSlice_Pick(t *testing.T) {

View File

@ -4,7 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/tpl"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
func TestHashBytes(t *testing.T) { func TestHashBytes(t *testing.T) {

View File

@ -4,7 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/tpl"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
func TestProperties_Replaces(t *testing.T) { func TestProperties_Replaces(t *testing.T) {

View File

@ -10,7 +10,8 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tarampampam/error-pages/internal/version"
"gh.tarampamp.am/error-pages/internal/version"
) )
// These functions are always allowed in the templates. // These functions are always allowed in the templates.
@ -30,6 +31,7 @@ var tplFnMap = template.FuncMap{ //nolint:gochecknoglobals
return 0 return 0
}, },
"env": os.Getenv,
} }
var ErrClosed = errors.New("closed") var ErrClosed = errors.New("closed")

View File

@ -2,18 +2,25 @@ package tpl_test
import ( import (
"math/rand" "math/rand"
"os"
"strconv" "strconv"
"sync" "sync"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/tpl" "github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/tpl"
) )
func Test_Render(t *testing.T) { func Test_Render(t *testing.T) {
renderer := tpl.NewTemplateRenderer() renderer := tpl.NewTemplateRenderer()
defer func() { _ = renderer.Close() }() defer func() { _ = renderer.Close() }()
require.NoError(t, os.Setenv("TEST_ENV_VAR", "unit-test"))
defer func() { require.NoError(t, os.Unsetenv("TEST_ENV_VAR")) }()
for name, tt := range map[string]struct { for name, tt := range map[string]struct {
giveContent string giveContent string
giveProps tpl.Properties giveProps tpl.Properties
@ -67,6 +74,11 @@ func Test_Render(t *testing.T) {
giveProps: tpl.Properties{L10nDisabled: true}, giveProps: tpl.Properties{L10nDisabled: true},
wantContent: "Y", wantContent: "Y",
}, },
"env": {
giveContent: `{{ env "TEST_ENV_VAR" }}`,
wantContent: "unit-test",
},
} { } {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
content, err := renderer.Render([]byte(tt.giveContent), tt.giveProps) content, err := renderer.Render([]byte(tt.giveContent), tt.giveProps)

View File

@ -10,6 +10,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Fout', nl: 'Fout',
de: 'Fehler', de: 'Fehler',
es: 'Error', es: 'Error',
zh: '错误',
id: 'Kesalahan',
pl: 'Błąd',
}, },
'Good luck': { 'Good luck': {
fr: 'Bonne chance', fr: 'Bonne chance',
@ -19,15 +22,21 @@ Object.defineProperty(window, 'l10n', {
nl: 'Veel succes', nl: 'Veel succes',
de: 'Viel Glück', de: 'Viel Glück',
es: 'Buena Suerte', es: 'Buena Suerte',
zh: '祝好运',
id: 'Semoga berhasil!',
pl: 'Powodzenia',
}, },
'UH OH': { 'UH OH': {
fr: 'Oups', fr: 'Oups',
ru: 'Ох', ru: 'Ох',
uk: 'Ох', uk: 'Упс',
pt: 'Ops', pt: 'Ops',
nl: 'Oeps', nl: 'Oeps',
de: 'Hoppla', de: 'Hoppla',
es: 'Oups', es: 'Uy',
zh: '哎呀',
id: 'Ups',
pl: 'Ojej',
}, },
'Request details': { 'Request details': {
fr: 'Détails de la requête', fr: 'Détails de la requête',
@ -37,6 +46,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Details van verzoek', nl: 'Details van verzoek',
de: 'Details der Anfrage', de: 'Details der Anfrage',
es: 'Detalles de la petición', es: 'Detalles de la petición',
zh: '请求详情',
id: 'Rincian permintaan',
pl: 'Poproś o szczegóły',
}, },
'Double-check the URL': { 'Double-check the URL': {
fr: 'Vérifiez lURL', fr: 'Vérifiez lURL',
@ -46,33 +58,45 @@ Object.defineProperty(window, 'l10n', {
nl: 'Controleer de URL', nl: 'Controleer de URL',
de: 'Überprüfen Sie die URL', de: 'Überprüfen Sie die URL',
es: 'Verifique la url', es: 'Verifique la url',
zh: '请再次检查地址',
id: 'Periksa URL',
pl: 'Sprawdź adres URL',
}, },
'Alternatively, go back': { 'Alternatively, go back': {
fr: 'Essayer de revenir en arrière', fr: 'Essayer de revenir en arrière',
ru: 'Или можете вернуться назад', ru: 'Или можете вернуться назад',
uk: 'Або ви можете повернутися', uk: 'Або можете повернутися назад',
pt: "Como alternativa, tente voltar", pt: "Como alternativa, tente voltar",
nl: 'Of ga terug', nl: 'Of ga terug',
de: 'Alternativ gehen Sie zurück', de: 'Alternativ gehen Sie zurück',
es: 'Como alternativa, vuelva atrás', es: 'Como alternativa, vuelva atrás',
zh: '或返回上一页',
id: 'Atau, kembali',
pl: 'Alternatywnie wróć',
}, },
'Here\'s what might have happened': { 'Here\'s what might have happened': {
fr: 'Voici ce qui aurait pu se passer', fr: 'Voici ce qui aurait pu se passer',
ru: 'Из-за чего это могло случиться', ru: 'Из-за чего это могло случиться',
uk: 'Що це може статися', uk: 'Ось що могло трапитися',
pt: 'Aqui está o que pode ter acontecido', pt: 'Aqui está o que pode ter acontecido',
nl: 'Wat er gebeurd kan zijn', nl: 'Wat er gebeurd kan zijn',
de: 'Folgendes könnte passiert sein', de: 'Folgendes könnte passiert sein',
es: 'Esto es lo que ha podido pasar', es: 'Esto es lo que ha podido pasar',
zh: '可能原因有',
id: 'Inilah yang bisa saja terjadi',
pl: 'Oto, co mogło się wydarzyć',
}, },
'You may have mistyped the URL': { 'You may have mistyped the URL': {
fr: 'Vous avez peut-être mal tapé lURL', fr: 'Vous avez peut-être mal tapé lURL',
ru: 'Вы могли ошибиться в URL', ru: 'Вы могли ошибиться в URL',
uk: 'Ви можете зробити помилку в URL-адресі', uk: 'Ви могли помилитися в URL-адресі',
pt: 'Você pode ter digitado incorretamente a URL', pt: 'Você pode ter digitado incorretamente a URL',
nl: 'De URL bevat een typefout', nl: 'De URL bevat een typefout',
de: 'Möglicherweise haben Sie die URL falsch eingegeben', de: 'Möglicherweise haben Sie die URL falsch eingegeben',
es: 'Igual ha escrito mal la URL', es: 'Quizá ha escrito mal la URL',
zh: '您可能输入了错误的地址',
id: 'Anda mungkin tersalah memasukkan URL',
pl: 'Być może błędnie wpisałeś adres URL',
}, },
'The site was moved': { 'The site was moved': {
fr: 'Le site a été déplacé', fr: 'Le site a été déplacé',
@ -81,7 +105,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'O site foi movido', pt: 'O site foi movido',
nl: 'De site is verplaatst', nl: 'De site is verplaatst',
de: 'Die Seite wurde verschoben', de: 'Die Seite wurde verschoben',
es: 'El sitio se ha movido', es: 'El sitio se ha trasladado',
zh: '站点已被转移',
id: 'Halaman dipindahkan',
pl: 'Witryna została przeniesiona',
}, },
'It was never here': { 'It was never here': {
fr: 'Il na jamais été ici', fr: 'Il na jamais été ici',
@ -91,24 +118,33 @@ Object.defineProperty(window, 'l10n', {
nl: 'Het was hier nooit', nl: 'Het was hier nooit',
de: 'Es war nie hier', de: 'Es war nie hier',
es: 'Nunca ha estado aquí', es: 'Nunca ha estado aquí',
zh: '站点从未存在',
id: 'Itu Tidak pernah di sini',
pl: 'Nigdy jej nie było',
}, },
'Bad Request': { 'Bad Request': {
fr: 'Mauvaise requête', fr: 'Mauvaise requête',
ru: 'Некорректный запрос', ru: 'Некорректный запрос',
uk: 'Неправильний запит', uk: 'Хибний запит',
pt: 'Requisição inválida', pt: 'Requisição inválida',
nl: 'Foutieve anvraag', nl: 'Foutieve anvraag',
de: 'Fehlerhafte Anfrage', de: 'Fehlerhafte Anfrage',
es: 'Petición inválida' es: 'Petición inválida',
zh: '错误请求',
id: 'Permintaan yang salah',
pl: 'Nieprawidłowe żądanie',
}, },
'The server did not understand the request': { 'The server did not understand the request': {
fr: 'Le serveur ne comprend pas la requête', fr: 'Le serveur ne comprend pas la requête',
ru: 'Сервер не смог обработать запрос из-за ошибки в нём', ru: 'Сервер не смог обработать запрос из-за ошибки в нём',
uk: 'Сервер не міг обробити запит через помилку в ньому', uk: 'Сервер не зміг обробити запит через помилку в ньому',
pt: 'O servidor não entendeu a solicitação', pt: 'O servidor não entendeu a solicitação',
nl: 'De server begreep het verzoek niet', nl: 'De server begreep het verzoek niet',
de: 'Der Server hat die Anfrage nicht verstanden', de: 'Der Server hat die Anfrage nicht verstanden',
es: 'El servidor no entendió la petición' es: 'El servidor no entendió la petición',
zh: '服务器不理解该请求',
id: 'Server tidak memahami permintaan',
pl: 'Serwer nie zrozumiał żądania',
}, },
'Unauthorized': { 'Unauthorized': {
fr: 'Non autorisé', fr: 'Non autorisé',
@ -118,6 +154,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Niet geautoriseerd', nl: 'Niet geautoriseerd',
de: 'Nicht autorisiert', de: 'Nicht autorisiert',
es: 'No autorizado', es: 'No autorizado',
zh: '未经授权',
id: 'Tidak diotorisasi',
pl: 'Nieautoryzowany',
}, },
'The requested page needs a username and a password': { 'The requested page needs a username and a password': {
fr: 'La page demandée nécessite un nom dutilisateur et un mot de passe', fr: 'La page demandée nécessite un nom dutilisateur et un mot de passe',
@ -127,6 +166,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'De pagina heeft een gebruikersnaam en wachtwoord nodig', nl: 'De pagina heeft een gebruikersnaam en wachtwoord nodig',
de: 'Die angeforderte Seite benötigt einen Benutzernamen und ein Passwort', de: 'Die angeforderte Seite benötigt einen Benutzernamen und ein Passwort',
es: 'La página solicitada necesita un usuario y una contraseña', es: 'La página solicitada necesita un usuario y una contraseña',
zh: '请求的页面需要用户名和密码',
id: 'Halaman yang diminta membutuhkan nama pengguna dan kata sandi',
pl: 'Żądana strona wymaga podania nazwy użytkownika i hasła',
}, },
'Forbidden': { 'Forbidden': {
fr: 'Interdit', fr: 'Interdit',
@ -136,6 +178,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Verboden', nl: 'Verboden',
de: 'Verboten', de: 'Verboten',
es: 'Prohibido', es: 'Prohibido',
zh: '禁止访问',
id: 'Dilarang',
pl: 'Zabroniony',
}, },
'Access is forbidden to the requested page': { 'Access is forbidden to the requested page': {
fr: 'Accès interdit à la page demandée', fr: 'Accès interdit à la page demandée',
@ -145,24 +190,33 @@ Object.defineProperty(window, 'l10n', {
nl: 'Toegang tot de pagina is verboden', nl: 'Toegang tot de pagina is verboden',
de: 'Der Zugriff auf die angeforderte Seite ist verboten', de: 'Der Zugriff auf die angeforderte Seite ist verboten',
es: 'El acceso está prohibido para la página solicitada', es: 'El acceso está prohibido para la página solicitada',
zh: '禁止访问请求的页面',
id: 'Akses dilarang ke halaman yang diminta',
pl: 'Dostęp do żądanej strony jest zabroniony',
}, },
'Not Found': { 'Not Found': {
fr: 'Introuvable', fr: 'Introuvable',
ru: 'Страница не найдена', ru: 'Страница не найдена',
uk: 'Сторінка не знайдена', uk: 'Сторінку не знайдено',
pt: 'Não encontrado', pt: 'Não encontrado',
nl: 'Niet gevonden', nl: 'Niet gevonden',
de: 'Nicht gefunden', de: 'Nicht gefunden',
es: 'No encontrado', es: 'No encontrado',
zh: '未找到',
id: 'Tidak ditemukan',
pl: 'Nie znaleziono',
}, },
'The server can not find the requested page': { 'The server can not find the requested page': {
fr: 'Le serveur ne peut trouver la page demandée', fr: 'Le serveur ne peut trouver la page demandée',
ru: 'Сервер не смог найти запрашиваемую страницу', ru: 'Сервер не смог найти запрашиваемую страницу',
uk: 'Сервер не міг знайти запитану сторінку', uk: 'Сервер не зміг знайти запитану сторінку',
pt: 'O servidor não consegue encontrar a página solicitada', pt: 'O servidor não consegue encontrar a página solicitada',
nl: 'De server kan de pagina niet vinden', nl: 'De server kan de pagina niet vinden',
de: 'Der Server kann die angeforderte Seite nicht finden', de: 'Der Server kann die angeforderte Seite nicht finden',
es: 'El servidor no puede encontrar la página solicitada', es: 'El servidor no puede encontrar la página solicitada',
zh: '服务器找不到请求的页面',
id: 'Server tidak dapat menemukan halaman yang diminta',
pl: 'Serwer nie może znaleźć żądanej strony',
}, },
'Method Not Allowed': { 'Method Not Allowed': {
fr: 'Méthode Non Autorisée', fr: 'Méthode Non Autorisée',
@ -172,6 +226,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Methode niet toegestaan', nl: 'Methode niet toegestaan',
de: 'Methode nicht erlaubt', de: 'Methode nicht erlaubt',
es: 'Método no permitido', es: 'Método no permitido',
zh: '方法不被允许',
id: 'Metode tidak diizinkan',
pl: 'Niedozwolona metoda',
}, },
'The method specified in the request is not allowed': { 'The method specified in the request is not allowed': {
fr: 'La méthode spécifiée dans la requête nest pas autorisée', fr: 'La méthode spécifiée dans la requête nest pas autorisée',
@ -180,7 +237,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'O método especificado na solicitação não é permitido', pt: 'O método especificado na solicitação não é permitido',
nl: 'De methode in het verzoek is niet toegestaan', nl: 'De methode in het verzoek is niet toegestaan',
de: 'Die in der Anfrage angegebene Methode ist nicht zulässig', de: 'Die in der Anfrage angegebene Methode ist nicht zulässig',
es: 'El método especificado en la petición no está permitido' es: 'El método especificado en la petición no está permitido',
zh: '请求指定的方法不被允许',
id: 'Metode dalam permintaan tidak diizinkan',
pl: 'Metoda określona w żądaniu jest niedozwolona',
}, },
'Proxy Authentication Required': { 'Proxy Authentication Required': {
fr: 'Authentification proxy requise', fr: 'Authentification proxy requise',
@ -190,6 +250,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Authenticatie op de proxyserver verplicht', nl: 'Authenticatie op de proxyserver verplicht',
de: 'Proxy-Authentifizierung benötigt', de: 'Proxy-Authentifizierung benötigt',
es: 'Autenticación de proxy requerida', es: 'Autenticación de proxy requerida',
zh: '需要代理服务器身份验证',
id: 'Diperlukan otentikasi proxy',
pl: 'Wymagane uwierzytelnianie proxy',
}, },
'You must authenticate with a proxy server before this request can be served': { 'You must authenticate with a proxy server before this request can be served': {
fr: 'Vous devez vous authentifier avec un serveur proxy avant que cette requête puisse être servie', fr: 'Vous devez vous authentifier avec un serveur proxy avant que cette requête puisse être servie',
@ -199,24 +262,33 @@ Object.defineProperty(window, 'l10n', {
nl: 'Je moet authenticeren bij een proxyserver voordat dit verzoek uitgevoerd kan worden', nl: 'Je moet authenticeren bij een proxyserver voordat dit verzoek uitgevoerd kan worden',
de: 'Sie müssen sich bei einem Proxy-Server authentifizieren, bevor diese Anfrage bedient werden kann', de: 'Sie müssen sich bei einem Proxy-Server authentifizieren, bevor diese Anfrage bedient werden kann',
es: 'Debes autentificarte con un servidor proxy antes de que esta petición pueda ser atendida', es: 'Debes autentificarte con un servidor proxy antes de que esta petición pueda ser atendida',
zh: '您必须对代理服务器进行身份验证,然后才能让请求得到处理',
id: 'Anda harus mengautentikasi dengan server proxy sebelum permintaan ini dapat dilayani',
pl: 'Musisz uwierzytelnić się na serwerze proxy, zanim to żądanie będzie mogło zostać obsłużone',
}, },
'Request Timeout': { 'Request Timeout': {
fr: 'Requête expiré', fr: 'Requête expiré',
ru: 'Истекло время ожидания', ru: 'Истекло время ожидания',
uk: 'Час запиту закінчився', uk: 'Вичерпано час очікування',
pt: 'Tempo limite de solicitação excedido', pt: 'Tempo limite de solicitação excedido',
nl: 'Aanvraagtijd verstreken', nl: 'Aanvraagtijd verstreken',
de: 'Zeitüberschreitung der Anforderung', de: 'Zeitüberschreitung der Anforderung',
es: 'Tiempo límite de la petición excedido', es: 'Tiempo límite de la petición excedido',
zh: '请求超时',
id: 'Meminta batas waktu',
pl: 'Przekroczenie limitu czasu żądania',
}, },
'The request took longer than the server was prepared to wait': { 'The request took longer than the server was prepared to wait': {
fr: 'La requête prend plus de temps que prévu', fr: 'La requête prend plus de temps que prévu',
ru: 'Отправка запроса заняла слишком много времени', ru: 'Отправка запроса заняла слишком много времени',
uk: 'Надсилання запиту зайняв занадто багато часу', uk: 'Надсилання запиту зайняло надто багато часу',
pt: 'A solicitação demorou mais do que o servidor estava preparado para esperar', pt: 'A solicitação demorou mais do que o servidor estava preparado para esperar',
nl: 'Het verzoek duurde langer dan de server wilde wachten', nl: 'Het verzoek duurde langer dan de server wilde wachten',
de: 'Die Anfrage hat länger gedauert, als der Server bereit war zu warten', de: 'Die Anfrage hat länger gedauert, als der Server bereit war zu warten',
es: 'La petición esta tardando más de lo que el servidor estaba preparado para esperar', es: 'La petición esta tardando más de lo que el servidor estaba preparado para esperar',
zh: '请求用时超过了服务器设置的最长等待时间',
id: 'Permintaan memakan waktu lebih lama dari yang bisa ditunggu oleh server',
pl: 'Żądanie trwało dłużej niż serwer był gotowy czekać',
}, },
'Conflict': { 'Conflict': {
fr: 'Conflit', fr: 'Conflit',
@ -226,6 +298,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Conflict', nl: 'Conflict',
de: 'Konflikt', de: 'Konflikt',
es: 'Conflicto', es: 'Conflicto',
zh: '冲突',
id: 'Konflik',
pl: 'Konflikt',
}, },
'The request could not be completed because of a conflict': { 'The request could not be completed because of a conflict': {
fr: 'La requête na pas pu être complétée à cause dun conflit', fr: 'La requête na pas pu être complétée à cause dun conflit',
@ -235,6 +310,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Het verzoek kon niet worden verwerkt vanwege een conflict', nl: 'Het verzoek kon niet worden verwerkt vanwege een conflict',
de: 'Die Anfrage konnte aufgrund eines Konflikts nicht abgeschlossen werden', de: 'Die Anfrage konnte aufgrund eines Konflikts nicht abgeschlossen werden',
es: 'La petición no ha podido ser completada por un conflicto', es: 'La petición no ha podido ser completada por un conflicto',
zh: '由于冲突,请求无法完成',
id: 'Permintaan tidak dapat diselesaikan karena adanya konflik',
pl: 'Żądanie nie mogło zostać wykonane z powodu konfliktu',
}, },
'Gone': { 'Gone': {
fr: 'Supprimé', fr: 'Supprimé',
@ -244,24 +322,33 @@ Object.defineProperty(window, 'l10n', {
nl: 'Verdwenen', nl: 'Verdwenen',
de: 'Verschwunden', de: 'Verschwunden',
es: 'Eliminado', es: 'Eliminado',
zh: '已移除',
id: 'Menghilang',
pl: 'Usunięto',
}, },
'The requested page is no longer available': { 'The requested page is no longer available': {
fr: 'La page demandée nest plus disponible', fr: 'La page demandée nest plus disponible',
ru: 'Запрошенная страница была удалена', ru: 'Запрошенная страница была удалена',
uk: 'Запитана сторінка була видалена', uk: 'Запитувана сторінка більше не доступна',
pt: 'A página solicitada não está mais disponível', pt: 'A página solicitada não está mais disponível',
nl: 'De pagina is niet langer beschikbaar', nl: 'De pagina is niet langer beschikbaar',
de: 'Die angeforderte Seite ist nicht mehr verfügbar', de: 'Die angeforderte Seite ist nicht mehr verfügbar',
es: 'La página solicitada no está ya disponible', es: 'La página solicitada no está ya disponible',
zh: '请求的页面不再可用',
id: 'Halaman yang diminta tidak lagi tersedia',
pl: 'Żądana strona nie jest już dostępna',
}, },
'Length Required': { 'Length Required': {
fr: 'Longueur requise', fr: 'Longueur requise',
ru: 'Необходима длина', ru: 'Необходима длина',
uk: 'Потрібно вказати розмір', uk: 'Потрібно вказати довжину',
pt: 'Content-Length necessário', pt: 'Content-Length necessário',
nl: 'Lengte benodigd', nl: 'Lengte benodigd',
de: 'Länge benötigt', de: 'Länge benötigt',
es: 'Longitud requerida', es: 'Longitud requerida',
zh: '需要长度',
id: 'Panjang yang diperlukan',
pl: 'Wymagana długość',
}, },
'The "Content-Length" is not defined. The server will not accept the request without it': { 'The "Content-Length" is not defined. The server will not accept the request without it': {
fr: 'Le "Content-Length" nest pas défini. Le serveur ne prendra pas en compte la requête', fr: 'Le "Content-Length" nest pas défini. Le serveur ne prendra pas en compte la requête',
@ -271,6 +358,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'De "Content-Length" is niet gespecificeerd. De server accepteert het verzoek niet zonder', nl: 'De "Content-Length" is niet gespecificeerd. De server accepteert het verzoek niet zonder',
de: 'Die "Content-Length" ist nicht definiert. Ohne sie akzeptiert der Server die Anfrage nicht', de: 'Die "Content-Length" ist nicht definiert. Ohne sie akzeptiert der Server die Anfrage nicht',
es: 'El "Content-Legth" no eta definido. Este servidor no aceptará la petición sin él', es: 'El "Content-Legth" no eta definido. Este servidor no aceptará la petición sin él',
zh: '未指定Content-Length(内容长度)。服务器将不接受不包含此头信息的请求',
id: '"Content-Length" tidak ditentukan. Server tidak akan menerima permintaan tanpa itu',
pl: 'Wartość "Content-Length" nie jest zdefiniowana. Serwer nie zaakceptuje żądania bez tego parametru',
}, },
'Precondition Failed': { 'Precondition Failed': {
fr: 'Échec de la condition préalable', fr: 'Échec de la condition préalable',
@ -280,15 +370,20 @@ Object.defineProperty(window, 'l10n', {
nl: 'Niet voldaan aan vooraf gestelde voorwaarde', nl: 'Niet voldaan aan vooraf gestelde voorwaarde',
de: 'Vorbedingung fehlgeschlagen', de: 'Vorbedingung fehlgeschlagen',
es: 'Precondición fallida', es: 'Precondición fallida',
zh: '前置条件判定失败',
pl: 'Niespełnienie warunku wstępnego',
}, },
'The pre condition given in the request evaluated to false by the server': { 'The pre condition given in the request evaluated to false by the server': {
fr: 'La précondition donnée dans la requête a été évaluée comme étant fausse par le serveur', fr: 'La précondition donnée dans la requête a été évaluée comme étant fausse par le serveur',
ru: 'Ни одно из условных полей заголовка запроса не было выполнено', ru: 'Ни одно из условных полей заголовка запроса не было выполнено',
uk: 'Жодна з умовних полів заголовка запиту не була виконана', uk: 'Жодна з передумов запиту не була виконана',
pt: 'A pré-condição dada na solicitação avaliada como falsa pelo servidor', pt: 'A pré-condição dada na solicitação avaliada como falsa pelo servidor',
nl: 'De vooraf gestelde voorwaarde is afgewezen door de server', nl: 'De vooraf gestelde voorwaarde is afgewezen door de server',
de: 'Die in der Anfrage angegebene Vorbedingung wird vom Server als falsch bewertet', de: 'Die in der Anfrage angegebene Vorbedingung wird vom Server als falsch bewertet',
es: 'La precondición ha sido evaluada como negativa para esta petición por el servidor' es: 'La precondición ha sido evaluada como negativa para esta petición por el servidor',
zh: '服务器评估请求中给出的前置条件的结果为false(假)',
id: 'Prakondisi gagal',
pl: 'Warunek wstępny podany w żądaniu został oceniony przez serwer jako nieprawidłowy',
}, },
'Payload Too Large': { 'Payload Too Large': {
fr: 'Charge trop volumineuse', fr: 'Charge trop volumineuse',
@ -297,7 +392,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'Payload muito grande', pt: 'Payload muito grande',
nl: 'Aanvraag te grood', nl: 'Aanvraag te grood',
de: 'Anfrage zu groß', de: 'Anfrage zu groß',
es: 'Carga muy grande', es: 'Carga demasiado grande',
zh: '请求体过大',
id: 'Muatan terlalu besar',
pl: 'Żądanie jest zbyt duże',
}, },
'The server will not accept the request, because the request entity is too large': { 'The server will not accept the request, because the request entity is too large': {
fr: 'Le serveur ne prendra pas en compte la requête, car lentité de la requête est trop volumineuse', fr: 'Le serveur ne prendra pas en compte la requête, car lentité de la requête est trop volumineuse',
@ -307,6 +405,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'De server accepteert het verzoek niet omdat de aanvraag te groot is', nl: 'De server accepteert het verzoek niet omdat de aanvraag te groot is',
de: 'Der Server akzeptiert die Anfrage nicht, da die Datenmenge zu groß ist', de: 'Der Server akzeptiert die Anfrage nicht, da die Datenmenge zu groß ist',
es: 'El servidor no aceptará esta petición, porque la carga es demasiado grande', es: 'El servidor no aceptará esta petición, porque la carga es demasiado grande',
zh: '请求体过大,服务器将不接受该请求',
id: 'Server tidak akan menerima permintaan, karena entitas permintaan terlalu besar',
pl: 'Serwer nie zaakceptuje żądania, ponieważ żądanie jest zbyt duże',
}, },
'Requested Range Not Satisfiable': { 'Requested Range Not Satisfiable': {
fr: 'Requête non satisfaisante', fr: 'Requête non satisfaisante',
@ -316,15 +417,21 @@ Object.defineProperty(window, 'l10n', {
nl: 'Aangevraagd gedeelte niet opvraagbaar', nl: 'Aangevraagd gedeelte niet opvraagbaar',
de: 'Anfrage-Bereich nicht erfüllbar', de: 'Anfrage-Bereich nicht erfüllbar',
es: 'Intervalo solicitado no satisfactorio', es: 'Intervalo solicitado no satisfactorio',
zh: '不满足请求范围',
id: 'Rentang yang diminta tidak dapat dipenuhi',
pl: 'Żądany zakres nie jest satysfakcjonujący',
}, },
'The requested byte range is not available and is out of bounds': { 'The requested byte range is not available and is out of bounds': {
fr: 'Le byte range demandé nest pas disponible et est hors des limites', fr: 'Le byte range demandé nest pas disponible et est hors des limites',
ru: 'Запрошенный диапазон данных недоступен или вне допустимых пределов', ru: 'Запрошенный диапазон данных недоступен или вне допустимых пределов',
uk: 'Описаний діапазон даних недоступний або з допустимих меж', uk: 'Описаний діапазон даних недоступний або поза допустимими межами',
pt: 'O intervalo de bytes solicitado não está disponível e está fora dos limites', pt: 'O intervalo de bytes solicitado não está disponível e está fora dos limites',
nl: 'De aangevraagde bytes zijn buiten het limiet', nl: 'De aangevraagde bytes zijn buiten het limiet',
de: 'Der angefragte Teilbereich der Ressource existiert nicht oder ist ungültig', de: 'Der angefragte Teilbereich der Ressource existiert nicht oder ist ungültig',
es: 'El intervalo de bytes requerido no está disponible o se encuentra fuera de los límites', es: 'El intervalo de bytes requerido no está disponible o se encuentra fuera de los límites',
zh: '请求的字节范围不可用,超出边界',
id: 'Rentang byte yang diminta tidak tersedia dan di luar batas',
pl: 'Żądany zakres bajtów nie jest dostępny i znajduje się poza zakresem',
}, },
'I\'m a teapot': { 'I\'m a teapot': {
fr: 'Je suis une théière', fr: 'Je suis une théière',
@ -333,16 +440,22 @@ Object.defineProperty(window, 'l10n', {
pt: 'Eu sou um bule', pt: 'Eu sou um bule',
nl: 'Ik ben een theepot', nl: 'Ik ben een theepot',
de: 'Ich bin eine Teekanne', de: 'Ich bin eine Teekanne',
es: 'Soy una tetera' es: 'Soy una tetera',
zh: '我是一只茶壶',
id: 'Saya adalah teko',
pl: 'Jestem czajniczkiem',
}, },
'Attempt to brew coffee with a teapot is not supported': { 'Attempt to brew coffee with a teapot is not supported': {
fr: 'Tenter de préparer du café avec une théière nest pas pris en charge', fr: 'Tenter de préparer du café avec une théière nest pas pris en charge',
ru: 'Попытка заварить кофе в чайнике обречена на фиаско', ru: 'Попытка заварить кофе в чайнике обречена на фиаско',
uk: 'Спроба виварити каву в чайник приречена на фіаско', uk: 'Спроба заварити каву в чайнику приречена на фіаско',
pt: 'A tentativa de preparar café com um bule não é suportada', pt: 'A tentativa de preparar café com um bule não é suportada',
nl: 'Koffie maken met een theepot is niet ondersteund', nl: 'Koffie maken met een theepot is niet ondersteund',
de: 'Der Versuch, Kaffee mit einer Teekanne zuzubereiten, wird nicht unterstützt', de: 'Der Versuch, Kaffee mit einer Teekanne zuzubereiten, wird nicht unterstützt',
es: 'Intentar hacer un café en una tetera no está soportado', es: 'Intentar hacer un café en una tetera no está soportado',
zh: '用茶壶泡咖啡不受支持',
id: 'Upaya menyeduh kopi dengan teko tidak didukung',
pl: 'Próba zaparzenia kawy za pomocą czajniczka nie jest obsługiwana',
}, },
'Too Many Requests': { 'Too Many Requests': {
fr: 'Trop de requêtes', fr: 'Trop de requêtes',
@ -351,16 +464,22 @@ Object.defineProperty(window, 'l10n', {
pt: 'Excesso de solicitações', pt: 'Excesso de solicitações',
nl: 'Te veel requests', nl: 'Te veel requests',
de: 'Zu viele Anfragen', de: 'Zu viele Anfragen',
es: 'Demasiadas peticiones' es: 'Demasiadas peticiones',
zh: '请求过多',
id: 'Terlalu banyak permintaan',
pl: 'Zbyt wiele żądań',
}, },
'Too many requests in a given amount of time': { 'Too many requests in a given amount of time': {
fr: 'Trop de requêtes dans un délai donné', fr: 'Trop de requêtes dans un délai donné',
ru: 'Отправлено слишком много запросов за короткое время', ru: 'Отправлено слишком много запросов за короткое время',
uk: 'Надіслано занадто багато запитів на короткий час', uk: 'Надіслано занадто багато запитів за короткий проміжок час',
pt: 'Excesso de solicitações em um determinado período de tempo', pt: 'Excesso de solicitações em um determinado período de tempo',
nl: 'Te veel verzoeken binnen een bepaalde tijd', nl: 'Te veel verzoeken binnen een bepaalde tijd',
de: 'Der Client hat zu viele Anfragen in einem bestimmten Zeitraum gesendet', de: 'Der Client hat zu viele Anfragen in einem bestimmten Zeitraum gesendet',
es: 'Demasiadas peticiones en un determinado periodo de tiempo', es: 'Demasiadas peticiones en un determinado periodo de tiempo',
zh: '在给定的时间内发送了过多请求',
id: 'Terlalu banyak permintaan dalam waktu tertentu',
pl: 'Zbyt wiele żądań w określonym czasie',
}, },
'Internal Server Error': { 'Internal Server Error': {
fr: 'Erreur interne du serveur', fr: 'Erreur interne du serveur',
@ -370,15 +489,21 @@ Object.defineProperty(window, 'l10n', {
nl: 'Interne serverfout', nl: 'Interne serverfout',
de: 'Interner Server-Fehler', de: 'Interner Server-Fehler',
es: 'Error Interno', es: 'Error Interno',
zh: '内部服务器错误',
id: 'Kesalahan server internal',
pl: 'Wewnętrzny błąd serwera',
}, },
'The server met an unexpected condition': { 'The server met an unexpected condition': {
fr: 'Le serveur a rencontré une condition inattendue', fr: 'Le serveur a rencontré une condition inattendue',
ru: 'Произошло что-то неожиданное на сервере', ru: 'Произошло что-то неожиданное на сервере',
uk: 'На сервері було щось несподіване', uk: 'На сервері відбулось щось неочікуване',
pt: 'O servidor encontrou uma condição inesperada', pt: 'O servidor encontrou uma condição inesperada',
nl: 'De server ondervond een onverwachte conditie', nl: 'De server ondervond een onverwachte conditie',
de: 'Der Server hat einen internen Fehler festgestellt', de: 'Der Server hat einen internen Fehler festgestellt',
es: 'El servidor ha encontrado una condición no esperada', es: 'El servidor ha encontrado una condición no esperada',
zh: '服务器遇到了意外情况',
id: 'Server mengalami kondisi yang tidak terduga',
pl: 'Serwer napotkał nieoczekiwany stan',
}, },
'Bad Gateway': { 'Bad Gateway': {
fr: 'Mauvaise passerelle', fr: 'Mauvaise passerelle',
@ -386,17 +511,23 @@ Object.defineProperty(window, 'l10n', {
uk: 'Помилка шлюзу', uk: 'Помилка шлюзу',
pt: 'Gateway inválido', pt: 'Gateway inválido',
nl: 'Ongeldige Gateway', nl: 'Ongeldige Gateway',
de: 'Fehlerhafter Gateway', de: 'Fehlerhaftes Gateway',
es: 'Puerta de enlace no valida', es: 'Puerta de enlace no valida',
zh: '无效网关',
id: 'Gateway yang buruk',
pl: 'Błąd bramki',
}, },
'The server received an invalid response from the upstream server': { 'The server received an invalid response from the upstream server': {
fr: 'Le serveur a reçu une réponse invalide du serveur distant', fr: 'Le serveur a reçu une réponse invalide du serveur distant',
ru: 'Сервер получил некорректный ответ от вышестоящего сервера', ru: 'Сервер получил некорректный ответ от вышестоящего сервера',
uk: 'Сервер отримав неправильну відповідь з сервера Upstream', uk: 'Сервер отримав невірну відповідь від попереднього сервера',
pt: 'O servidor recebeu uma resposta inválida do servidor upstream', pt: 'O servidor recebeu uma resposta inválida do servidor upstream',
nl: 'De server ontving een ongeldig antwoord van een bovenliggende server', nl: 'De server ontving een ongeldig antwoord van een bovenliggende server',
de: 'Der Server hat eine ungültige Antwort vom Upstream-Server erhalten', de: 'Der Server hat eine ungültige Antwort vom Upstream-Server erhalten',
es: 'El servidor ha recibido una respuesta no válida del servidor de origen' es: 'El servidor ha recibido una respuesta no válida del servidor de origen',
zh: '服务器从上游服务器收到了无效的响应',
id: 'Server menerima respons yang tidak valid dari server induk',
pl: 'Serwer otrzymał nieprawidłową odpowiedź od serwera nadrzędnego',
}, },
'Service Unavailable': { 'Service Unavailable': {
fr: 'Service indisponible', fr: 'Service indisponible',
@ -406,6 +537,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Dienst niet beschikbaar', nl: 'Dienst niet beschikbaar',
de: 'Dienst nicht verfügbar', de: 'Dienst nicht verfügbar',
es: 'Servicio no disponible', es: 'Servicio no disponible',
zh: '服务不可用',
id: 'Layanan tidak tersedia',
pl: 'Serwis niedostępny',
}, },
'The server is temporarily overloading or down': { 'The server is temporarily overloading or down': {
fr: 'Le serveur est temporairement en surcharge ou indisponible', fr: 'Le serveur est temporairement en surcharge ou indisponible',
@ -415,6 +549,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'De server is tijdelijk overbelast of niet bereikbaar', nl: 'De server is tijdelijk overbelast of niet bereikbaar',
de: 'Der Server ist vorübergehend überlastet oder ausgefallen', de: 'Der Server ist vorübergehend überlastet oder ausgefallen',
es: 'El servidor está temporalmente sobrecargado o inactivo', es: 'El servidor está temporalmente sobrecargado o inactivo',
zh: '服务器暂时过载或不可用',
id: 'Server untuk sementara kelebihan beban atau tidak tersedia',
pl: 'Serwer jest tymczasowo przeciążony lub wyłączony',
}, },
'Gateway Timeout': { 'Gateway Timeout': {
fr: 'Expiration Passerelle', fr: 'Expiration Passerelle',
@ -424,15 +561,21 @@ Object.defineProperty(window, 'l10n', {
nl: 'Gateway Verlopen', nl: 'Gateway Verlopen',
de: 'Gateway Zeitüberschreitung', de: 'Gateway Zeitüberschreitung',
es: 'Tiempo límite de puerta de enlace excedido', es: 'Tiempo límite de puerta de enlace excedido',
zh: '网关超时',
id: 'Batas waktu gateway',
pl: 'Przekroczenie limitu czasu bramki',
}, },
'The gateway has timed out': { 'The gateway has timed out': {
fr: 'Le temps dattente de la passerelle est dépassé', fr: 'Le temps dattente de la passerelle est dépassé',
ru: 'Сервер не дождался ответа от вышестоящего сервера', ru: 'Сервер не дождался ответа от вышестоящего сервера',
uk: 'Сервер не чекав відповіді від сервера Upstream', uk: 'У шлюзу закінчився час очікування',
pt: 'O gateway esgotou o tempo limite', pt: 'O gateway esgotou o tempo limite',
nl: 'De verbinding naar de bovenliggende server is verlopen', nl: 'De verbinding naar de bovenliggende server is verlopen',
de: 'Das Zeitlimit für den Verbindungsaufbau mit dem Upstream-Server ist abgelaufen', de: 'Das Zeitlimit für den Verbindungsaufbau mit dem Upstream-Server ist abgelaufen',
es: 'La puerta de enlace ha sobrepasado el tiempo límite', es: 'La puerta de enlace ha sobrepasado el tiempo límite',
zh: '网关响应已经超时',
id: 'Sambungan ke server induk telah kedaluwarsa',
pl: 'Bramka przekroczyła limit czasu',
}, },
'HTTP Version Not Supported': { 'HTTP Version Not Supported': {
fr: 'Version HTTP non prise en charge', fr: 'Version HTTP non prise en charge',
@ -442,6 +585,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'HTTP-versie wordt niet ondersteunt', nl: 'HTTP-versie wordt niet ondersteunt',
de: 'HTTP-Version wird nicht unterstützt', de: 'HTTP-Version wird nicht unterstützt',
es: 'Versión de HTTP no soportada', es: 'Versión de HTTP no soportada',
zh: 'HTTP版本不受支持',
id: 'Versi HTTP tidak didukung',
pl: 'Wersja HTTP nie jest obsługiwana',
}, },
'The server does not support the "http protocol" version': { 'The server does not support the "http protocol" version': {
fr: 'Le serveur ne supporte pas la version du protocole HTTP', fr: 'Le serveur ne supporte pas la version du protocole HTTP',
@ -451,6 +597,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'De server ondersteunt deze HTTP-versie niet', nl: 'De server ondersteunt deze HTTP-versie niet',
de: 'Der Server unterstützt die HTTP-Protokoll-Version nicht', de: 'Der Server unterstützt die HTTP-Protokoll-Version nicht',
es: 'El servidor no soporta la versión del protocolo HTTP', es: 'El servidor no soporta la versión del protocolo HTTP',
zh: '服务器不支持该HTTP协议版本',
id: 'Server tidak mendukung versi HTTP ini',
pl: 'Serwer nie obsługuje wersji "protokołu http"',
}, },
'Host': { 'Host': {
@ -461,6 +610,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Host', nl: 'Host',
de: 'Host', de: 'Host',
es: 'Host', es: 'Host',
zh: '主机',
id: 'Host',
pl: 'Host',
}, },
'Original URI': { 'Original URI': {
fr: 'URI dorigine', fr: 'URI dorigine',
@ -470,6 +622,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Originele URI', nl: 'Originele URI',
de: 'Originale URI', de: 'Originale URI',
es: 'URI original', es: 'URI original',
zh: '原始URI',
id: 'URL asli',
pl: 'Oryginalny URI',
}, },
'Forwarded for': { 'Forwarded for': {
fr: 'Transmis pour', fr: 'Transmis pour',
@ -478,7 +633,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'Encaminhado para', pt: 'Encaminhado para',
nl: 'Doorgestuurd voor', nl: 'Doorgestuurd voor',
de: 'Weitergeleitet für', de: 'Weitergeleitet für',
es: 'Remitido por', es: 'Remitido para',
zh: '转发自',
id: 'Diteruskan untuk',
pl: 'Przekazane do',
}, },
'Namespace': { 'Namespace': {
fr: 'Espace de noms', fr: 'Espace de noms',
@ -488,15 +646,21 @@ Object.defineProperty(window, 'l10n', {
nl: 'Elementnaam', nl: 'Elementnaam',
de: 'Namensraum', de: 'Namensraum',
es: 'Namespace', es: 'Namespace',
zh: '命名空间',
id: 'Ruang nama',
pl: 'Przestrzeń nazw',
}, },
'Ingress name': { 'Ingress name': {
fr: 'Nom ingress', fr: 'Nom ingress',
ru: 'Имя Ingress', ru: 'Имя Ingress',
uk: 'Ім\'я Ingress', uk: 'Ім\'я входу',
pt: 'Nome Ingress', pt: 'Nome Ingress',
nl: 'Ingress naam', nl: 'Ingress naam',
de: 'Ingress Name', de: 'Ingress Name',
es: 'Nombre Ingress', es: 'Nombre Ingress',
zh: '入口名',
id: 'Nama ingress',
pl: 'Nazwa wejścia',
}, },
'Service name': { 'Service name': {
fr: 'Nom du service', fr: 'Nom du service',
@ -505,7 +669,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'Nome do Serviço', pt: 'Nome do Serviço',
nl: 'Service naam', nl: 'Service naam',
de: 'Service Name', de: 'Service Name',
es: 'Nombre del servicio' es: 'Nombre del servicio',
zh: '服务名',
id: 'Nama layanan',
pl: 'Nazwa usługi',
}, },
'Service port': { 'Service port': {
fr: 'Port du service', fr: 'Port du service',
@ -515,6 +682,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Service poort', nl: 'Service poort',
de: 'Service Port', de: 'Service Port',
es: 'Puerto del servicio', es: 'Puerto del servicio',
zh: '服务端口',
id: 'Port layanan',
pl: 'Port usługi',
}, },
'Request ID': { 'Request ID': {
fr: 'Identifiant de la requête', fr: 'Identifiant de la requête',
@ -524,15 +694,21 @@ Object.defineProperty(window, 'l10n', {
nl: 'ID van het verzoek', nl: 'ID van het verzoek',
de: 'Anfrage ID', de: 'Anfrage ID',
es: 'ID de la petición', es: 'ID de la petición',
zh: '请求ID',
id: 'ID permintaan',
pl: 'Identyfikator żądania',
}, },
'Timestamp': { 'Timestamp': {
fr: 'Horodatage', fr: 'Horodatage',
ru: 'Временная метка', ru: 'Временная метка',
uk: 'Тимчасова мітка', uk: 'Мітка часу',
pt: 'Timestamp', pt: 'Timestamp',
nl: 'Tijdstempel', nl: 'Tijdstempel',
de: 'Zeitstempel', de: 'Zeitstempel',
es: 'Timestamp', es: 'Timestamp',
zh: '时间戳',
id: 'Cap waktu',
pl: 'Sygnatura czasowa',
}, },
'client-side error': { 'client-side error': {
@ -543,6 +719,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'fout aan de gebruikerskant', nl: 'fout aan de gebruikerskant',
de: 'Clientseitiger Fehler', de: 'Clientseitiger Fehler',
es: 'Error del lado del cliente', es: 'Error del lado del cliente',
zh: '客户端错误',
id: 'Kesalahan sisi klien',
pl: 'błąd po stronie klienta',
}, },
'server-side error': { 'server-side error': {
fr: 'Erreur Serveur', fr: 'Erreur Serveur',
@ -552,6 +731,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'fout aan de serverkant', nl: 'fout aan de serverkant',
de: 'Serverseitiger Fehler', de: 'Serverseitiger Fehler',
es: 'Error del lado del servidor', es: 'Error del lado del servidor',
zh: '服务端错误',
id: 'Kesalahan sisi server',
pl: 'błąd po stronie serwera',
}, },
'Your Client': { 'Your Client': {
@ -561,25 +743,34 @@ Object.defineProperty(window, 'l10n', {
pt: 'Seu Cliente', pt: 'Seu Cliente',
nl: 'Jouw Client', nl: 'Jouw Client',
de: 'Ihr Client', de: 'Ihr Client',
es: 'Tu cliente', es: 'Tu Cliente',
zh: '您的客户端',
id: 'Klien Anda',
pl: 'Klient',
}, },
'Network': { 'Network': {
fr: 'Réseau', fr: 'Réseau',
ru: 'Сеть', ru: 'Сеть',
uk: 'Сіть', uk: 'Мережа',
pt: 'Rede', pt: 'Rede',
nl: 'Netwerk', nl: 'Netwerk',
de: 'Netzwerk', de: 'Netzwerk',
es: 'Red', es: 'Red',
zh: '网络',
id: 'Jaringan',
pl: 'Sieć',
}, },
'Web Server': { 'Web Server': {
fr: 'Serveur Web', fr: 'Serveur Web',
ru: 'Web Сервер', ru: 'Web Сервер',
uk: 'Web Сервер', uk: 'Web-сервер',
pt: 'Servidor web', pt: 'Servidor web',
nl: 'Web Server', nl: 'Web Server',
de: 'Webserver', de: 'Webserver',
es: 'Servidor web', es: 'Servidor Web',
zh: 'Web服务器',
id: 'Server web',
pl: 'Serwer WWW',
}, },
'What happened?': { 'What happened?': {
fr: 'Que sest-il passé ?', fr: 'Que sest-il passé ?',
@ -589,6 +780,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Wat is er gebeurd?', nl: 'Wat is er gebeurd?',
de: 'Was ist passiert?', de: 'Was ist passiert?',
es: '¿Que ha pasado?', es: '¿Que ha pasado?',
zh: '发生了什么?',
id: 'Apa yang terjadi?',
pl: 'Co się stało?',
}, },
'What can i do?': { 'What can i do?': {
fr: 'Que puis-je faire ?', fr: 'Que puis-je faire ?',
@ -598,6 +792,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Wat kan ik doen?', nl: 'Wat kan ik doen?',
de: 'Was kann ich machen?', de: 'Was kann ich machen?',
es: '¿Que puedo hacer?', es: '¿Que puedo hacer?',
zh: '我能做什么?',
id: 'Apa yang bisa saya lakukan?',
pl: 'Co mogę zrobić?',
}, },
'Please try again in a few minutes': { 'Please try again in a few minutes': {
fr: 'Veuillez réessayer dans quelques minutes', fr: 'Veuillez réessayer dans quelques minutes',
@ -607,6 +804,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Probeer het alstublieft opnieuw over een paar minuten', nl: 'Probeer het alstublieft opnieuw over een paar minuten',
de: 'Bitte versuchen Sie es in ein paar Minuten erneut', de: 'Bitte versuchen Sie es in ein paar Minuten erneut',
es: 'Por favor, intente nuevamente en unos minutos', es: 'Por favor, intente nuevamente en unos minutos',
zh: '请在几分钟后重试',
id: 'Silakan coba lagi dalam beberapa menit',
pl: 'Spróbuj ponownie za kilka minut',
}, },
'Working': { 'Working': {
fr: 'Opérationnel', fr: 'Opérationnel',
@ -615,7 +815,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'Funcionando', pt: 'Funcionando',
nl: 'Functioneel', nl: 'Functioneel',
de: 'Funktioniert', de: 'Funktioniert',
es: 'Trabajando', es: 'Funcionando',
zh: '正常运行',
id: 'Fungsi',
pl: 'Działa',
}, },
'Unknown': { 'Unknown': {
fr: 'Inconnu', fr: 'Inconnu',
@ -625,6 +828,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Onbekend', nl: 'Onbekend',
de: 'Unbekannt', de: 'Unbekannt',
es: 'Desconocido', es: 'Desconocido',
zh: '未知',
id: 'Tidak diketahui',
pl: 'Nieznany',
}, },
'Please try to change the request method, headers, payload, or URL': { 'Please try to change the request method, headers, payload, or URL': {
fr: 'Veuillez essayer de changer la méthode de requête, les en-têtes, le contenu ou lURL', fr: 'Veuillez essayer de changer la méthode de requête, les en-têtes, le contenu ou lURL',
@ -633,7 +839,10 @@ Object.defineProperty(window, 'l10n', {
pt: 'Tente alterar o método de solicitação, cabeçalhos, payload ou URL', pt: 'Tente alterar o método de solicitação, cabeçalhos, payload ou URL',
nl: 'Probeer het opnieuw met een andere methode, headers, payload of URL', nl: 'Probeer het opnieuw met een andere methode, headers, payload of URL',
de: 'Bitte versuchen Sie, die Anfragemethode, Header, Payload oder URL zu ändern', de: 'Bitte versuchen Sie, die Anfragemethode, Header, Payload oder URL zu ändern',
es: 'Por favor intente cambiar el método de la petición, cabeceras, carga o URL' es: 'Por favor intente cambiar el método de la petición, cabeceras, carga o URL',
zh: '请尝试更改请求方法、标头、有效负载或URL',
id: 'Coba lagi dengan metode, header, muatan, atau URL yang berbeda',
pl: 'Spróbuj zmienić metodę żądania, nagłówki, żądanie lub adres URL',
}, },
'Please check your authorization data': { 'Please check your authorization data': {
fr: 'Veuillez vérifier vos données dautorisation', fr: 'Veuillez vérifier vos données dautorisation',
@ -643,6 +852,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Controleer de authenticatiegegevens', nl: 'Controleer de authenticatiegegevens',
de: 'Bitte überprüfen Sie Ihre Zugangsdaten', de: 'Bitte überprüfen Sie Ihre Zugangsdaten',
es: 'Verifique sus datos de autorización', es: 'Verifique sus datos de autorización',
zh: '请检查您的授权数据',
id: 'Memeriksa detail autentikasi',
pl: 'Sprawdź swoje dane autoryzacyjne',
}, },
'Please double-check the URL and try again': { 'Please double-check the URL and try again': {
fr: 'Veuillez vérifier lURL et réessayer', fr: 'Veuillez vérifier lURL et réessayer',
@ -652,6 +864,9 @@ Object.defineProperty(window, 'l10n', {
nl: 'Controleer de URL en probeer het opnieuw', nl: 'Controleer de URL en probeer het opnieuw',
de: 'Bitte überprüfen Sie die URL und versuchen Sie es erneut', de: 'Bitte überprüfen Sie die URL und versuchen Sie es erneut',
es: 'Verifique de nuevo la URL y vuelva a probar', es: 'Verifique de nuevo la URL y vuelva a probar',
zh: '请再次检查URL并重试',
id: 'Periksa URL dan coba lagi',
pl: 'Sprawdź adres URL i spróbuj ponownie',
}, },
}; };

View File

@ -18,3 +18,6 @@ By default, pages markup contains strings in English (`en` locale). If you want
- 🇳🇱 Dutch by [@SchoNie](https://github.com/SchoNie) - 🇳🇱 Dutch by [@SchoNie](https://github.com/SchoNie)
- 🇩🇪 German by [@mschoeffmann](https://github.com/mschoeffmann) - 🇩🇪 German by [@mschoeffmann](https://github.com/mschoeffmann)
- 🇪🇸 Spanish by [@Runig006](https://github.com/Runig006) - 🇪🇸 Spanish by [@Runig006](https://github.com/Runig006)
- 🇨🇳 Chinese by [@CDN18](https://github.com/CDN18)
- 🇮🇩 Indonesian by [@getwisp](https://github.com/getwisp)
- 🇵🇱 Polish by [@wielorzeczownik](https://github.com/wielorzeczownik)

279
templates/orient.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,5 +3,5 @@ GET http://{{ host }}:{{ port }}/not-found
HTTP/* 404 HTTP/* 404
[Asserts] [Asserts]
header "Content-Type" contains "text/plain" header "Content-Type" contains "text/html"
body contains "Wrong request URL" body contains "The server can not find the requested page"