mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
83720999d8 | |||
79bbf3d71e | |||
1dec69d726 | |||
ef2db68430 | |||
e6f3250286 | |||
ca56f1dd07 | |||
6bd973a803 | |||
49dd703e12 | |||
0f27441225 | |||
97d76ddca8 | |||
891d491cdb | |||
2a1fb0eddf | |||
5c25fbe2c4 | |||
e3e618d3cf | |||
e2489a2487 | |||
bb17027cc9 | |||
6b17d3eb7d | |||
c5f11eff8b | |||
b36bc5e47d |
22
.github/dependabot.yml
vendored
Normal file
22
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Docs: <https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/customizing-dependency-updates>
|
||||
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule: {interval: monthly}
|
||||
reviewers: [tarampampam]
|
||||
assignees: [tarampampam]
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule: {interval: monthly}
|
||||
reviewers: [tarampampam]
|
||||
assignees: [tarampampam]
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule: {interval: monthly}
|
||||
reviewers: [tarampampam]
|
||||
assignees: [tarampampam]
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
arch: [amd64] # amd64, 386
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with: {go-version: 1.17.1}
|
||||
with: {go-version: 1.17.5}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@ -134,6 +134,7 @@ jobs: # Docs: <https://git.io/JvxXE>
|
||||
test -f ./out/shuffle/404.html
|
||||
test -f ./out/noise/404.html
|
||||
test -f ./out/hacker-terminal/404.html
|
||||
test -f ./out/cats/404.html
|
||||
|
||||
docker-image:
|
||||
name: Build docker image
|
||||
@ -195,15 +196,16 @@ jobs: # Docs: <https://git.io/JvxXE>
|
||||
run: docker load < docker-image.tar
|
||||
|
||||
- name: Run container with the app
|
||||
run: docker run --rm -d -p "8080:8080/tcp" --name app app:ci
|
||||
run: docker run --rm -d -p "8080:8080/tcp" -e "DEFAULT_HTTP_CODE=401" --name app app:ci
|
||||
|
||||
- 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: test $(curl --write-out %{http_code} --silent --output /dev/null http://127.0.0.1:8080/) -eq 401
|
||||
- run: curl --fail http://127.0.0.1:8080/500.html
|
||||
- run: curl --fail http://127.0.0.1:8080/400.html
|
||||
- run: curl --fail http://127.0.0.1:8080/health/live
|
||||
- run: test $(curl --write-out %{http_code} --silent --output /dev/null http://127.0.0.1:8080/) -eq 404
|
||||
- run: test $(curl --write-out %{http_code} --silent --output /dev/null http://127.0.0.1:8080/foobar) -eq 404
|
||||
|
||||
- name: Stop the container
|
||||
if: always()
|
||||
|
@ -5,6 +5,8 @@ run:
|
||||
skip-dirs:
|
||||
- .github
|
||||
- .git
|
||||
- tmp
|
||||
- temp
|
||||
modules-download-mode: readonly
|
||||
allow-parallel-runners: true
|
||||
|
||||
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -4,6 +4,38 @@ 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].
|
||||
|
||||
## v2.3.0
|
||||
|
||||
### Added
|
||||
|
||||
- Flag `--default-http-code` for the `serve` subcommand (`404` is used by default instead of `200`, environment name `DEFAULT_HTTP_CODE`) [#41]
|
||||
|
||||
### Changed
|
||||
|
||||
- Go updated from `1.17.1` up to `1.17.5`
|
||||
|
||||
[#41]:https://github.com/tarampampam/error-pages/issues/41
|
||||
|
||||
## v2.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- Template `cats` [#31]
|
||||
|
||||
[#31]:https://github.com/tarampampam/error-pages/pull/31
|
||||
|
||||
## v2.1.0
|
||||
|
||||
### Added
|
||||
|
||||
- `referer` field in access log records
|
||||
- Flag `--default-error-page` for the `serve` subcommand (`404` is used by default, environment name `DEFAULT_ERROR_PAGE`)
|
||||
|
||||
### Changed
|
||||
|
||||
- The source code has been refactored
|
||||
- The index page (`/`) now returns the error page with a code, declared using `--default-error-page` flag (HTTP code 200, when a page code exists)
|
||||
|
||||
## v2.0.0
|
||||
|
||||
### Changed
|
||||
|
@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1.2
|
||||
|
||||
# Image page: <https://hub.docker.com/_/golang>
|
||||
FROM golang:1.17.1-alpine as builder
|
||||
FROM golang:1.17.5-alpine as builder
|
||||
|
||||
# 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"
|
||||
@ -64,7 +64,9 @@ USER appuser:appuser
|
||||
WORKDIR /opt
|
||||
|
||||
ENV LISTEN_PORT="8080" \
|
||||
TEMPLATE_NAME="ghost"
|
||||
TEMPLATE_NAME="ghost" \
|
||||
DEFAULT_ERROR_PAGE="404" \
|
||||
DEFAULT_HTTP_CODE="404"
|
||||
|
||||
# Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck>
|
||||
HEALTHCHECK --interval=7s --timeout=2s CMD [ \
|
||||
|
77
README.md
77
README.md
@ -30,8 +30,6 @@ Also, this project can be used for the [**Traefik** error pages customization](h
|
||||
|
||||
Download the latest binary file for your os/arch from the [releases page][link_releases] or use our docker image:
|
||||
|
||||
[][link_docker_hub]
|
||||
|
||||
Registry | Image
|
||||
-------------------------------------- | -----
|
||||
[Docker Hub][link_docker_hub] | `tarampampam/error-pages`
|
||||
@ -56,13 +54,26 @@ $ docker run --rm -it \
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## Templates
|
||||
|
||||
Name | Preview
|
||||
:---------------: | :-----:
|
||||
`ghost` | [](https://tarampampam.github.io/error-pages/ghost/404.html)
|
||||
`l7-light` | [](https://tarampampam.github.io/error-pages/l7-light/404.html)
|
||||
`l7-dark` | [](https://tarampampam.github.io/error-pages/l7-dark/404.html)
|
||||
`shuffle` | [](https://tarampampam.github.io/error-pages/shuffle/404.html)
|
||||
`noise` | [](https://tarampampam.github.io/error-pages/noise/404.html)
|
||||
`hacker-terminal` | [](https://tarampampam.github.io/error-pages/hacker-terminal/404.html)
|
||||
`cats` | [](https://tarampampam.github.io/error-pages/cats/404.html)
|
||||
|
||||
> Note: `noise` template highly uses the CPU, be careful
|
||||
|
||||
## Usage
|
||||
|
||||
All of the examples below will use a docker image with the application, but you can also use a binary. By the way, our docker image uses the **unleveled user** by default and **distroless**.
|
||||
|
||||
### HTTP server
|
||||
<details>
|
||||
<summary><strong>HTTP server</strong></summary>
|
||||
|
||||
As mentioned above - our application can be run as an HTTP server. It only needs to specify the path to the configuration file (it does not need statically generated error pages). The server uses [FastHTTP][fasthttp] and stores all necessary data in memory - so it does not use the file system and very fast. Oh yes, the image with the app also contains a configured **healthcheck** and **logs in JSON** format :)
|
||||
|
||||
@ -86,8 +97,10 @@ To see the help run the following command:
|
||||
```bash
|
||||
$ docker run --rm tarampampam/error-pages serve --help
|
||||
```
|
||||
</details>
|
||||
|
||||
### Generator
|
||||
<details>
|
||||
<summary><strong>Generator</strong></summary>
|
||||
|
||||
Create a config file (`error-pages.yml`) with the following content:
|
||||
|
||||
@ -158,13 +171,15 @@ To see the usage help run the following command:
|
||||
```bash
|
||||
$ docker run --rm tarampampam/error-pages build --help
|
||||
```
|
||||
</details>
|
||||
|
||||
### Static error pages
|
||||
<details>
|
||||
<summary><strong>Static error pages</strong></summary>
|
||||
|
||||
You may want to use the generated error pages somewhere else, and you can simply extract them from the docker image to your local directory for this purpose:
|
||||
|
||||
```bash
|
||||
$ docker create --name error-pages tarampampam/error-pages:2.0.0-rc2
|
||||
$ docker create --name error-pages tarampampam/error-pages
|
||||
$ docker cp error-pages:/opt/html ./out
|
||||
$ docker rm -f error-pages
|
||||
$ ls ./out
|
||||
@ -187,7 +202,35 @@ $ tree
|
||||
...
|
||||
```
|
||||
|
||||
### Custom error pages for your image with [nginx][link_nginx]
|
||||
Or inside another docker image:
|
||||
|
||||
```dockerfile
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=tarampampam/error-pages /opt/html /error-pages
|
||||
|
||||
RUN ls -l /error-pages
|
||||
```
|
||||
|
||||
```bash
|
||||
$ docker build --rm .
|
||||
|
||||
...
|
||||
Step 3/3 : RUN ls -l /error-pages
|
||||
---> Running in 30095dc344a9
|
||||
total 12
|
||||
drwxr-xr-x 2 root root 326 Sep 29 15:44 ghost
|
||||
drwxr-xr-x 2 root root 326 Sep 29 15:44 hacker-terminal
|
||||
-rw-r--r-- 1 root root 11241 Sep 29 15:44 index.html
|
||||
drwxr-xr-x 2 root root 326 Sep 29 15:44 l7-dark
|
||||
drwxr-xr-x 2 root root 326 Sep 29 15:44 l7-light
|
||||
drwxr-xr-x 2 root root 326 Sep 29 15:44 noise
|
||||
drwxr-xr-x 2 root root 326 Sep 29 15:44 shuffle
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Custom error pages for your image with nginx</strong></summary>
|
||||
|
||||
You can build your own docker image with `nginx` and our error pages:
|
||||
|
||||
@ -220,12 +263,12 @@ server {
|
||||
```dockerfile
|
||||
# File `Dockerfile`
|
||||
|
||||
FROM nginx:1.21-alpine
|
||||
FROM nginx:1.23-alpine
|
||||
|
||||
COPY --chown=nginx \
|
||||
./nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --chown=nginx \
|
||||
--from=tarampampam/error-pages:2.0.0 \
|
||||
--from=tarampampam/error-pages:2.2.0 \
|
||||
/opt/html/ghost /usr/share/nginx/errorpages/_error-pages
|
||||
```
|
||||
|
||||
@ -234,19 +277,7 @@ $ docker build --tag your-nginx:local -f ./Dockerfile .
|
||||
```
|
||||
|
||||
> More info about `error_page` directive can be [found here](http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page).
|
||||
|
||||
## Templates
|
||||
|
||||
Name | Preview
|
||||
:---------------: | :-----:
|
||||
`ghost` | [](https://tarampampam.github.io/error-pages/ghost/404.html)
|
||||
`l7-light` | [](https://tarampampam.github.io/error-pages/l7-light/404.html)
|
||||
`l7-dark` | [](https://tarampampam.github.io/error-pages/l7-dark/404.html)
|
||||
`shuffle` | [](https://tarampampam.github.io/error-pages/shuffle/404.html)
|
||||
`noise` | [](https://tarampampam.github.io/error-pages/noise/404.html)
|
||||
`hacker-terminal` | [](https://tarampampam.github.io/error-pages/hacker-terminal/404.html)
|
||||
|
||||
> Note: `noise` template highly uses the CPU, be careful
|
||||
</details>
|
||||
|
||||
## Custom error pages for [Traefik][link_traefik]
|
||||
|
||||
@ -257,7 +288,7 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
error-pages:
|
||||
image: tarampampam/error-pages:2.0.0
|
||||
image: tarampampam/error-pages:2.2.0
|
||||
environment:
|
||||
TEMPLATE_NAME: l7-dark
|
||||
networks:
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Docker-compose file is used only for local development. This is not production-ready example.
|
||||
|
||||
version: '3.4'
|
||||
version: '3.8'
|
||||
|
||||
volumes:
|
||||
tmp-data: {}
|
||||
@ -8,7 +8,7 @@ volumes:
|
||||
|
||||
services:
|
||||
app: &app-service
|
||||
image: golang:1.17.1-buster # Image page: <https://hub.docker.com/_/golang>
|
||||
image: golang:1.17.5-buster # Image page: <https://hub.docker.com/_/golang>
|
||||
working_dir: /src
|
||||
environment:
|
||||
HOME: /tmp
|
||||
|
@ -10,6 +10,7 @@ templates:
|
||||
- path: ./templates/shuffle.html
|
||||
- path: ./templates/noise.html
|
||||
- path: ./templates/hacker-terminal.html
|
||||
- path: ./templates/cats.html
|
||||
|
||||
pages:
|
||||
400:
|
||||
|
18
go.mod
18
go.mod
@ -3,15 +3,15 @@ module github.com/tarampampam/error-pages
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/a8m/envsubst v1.2.0
|
||||
github.com/fasthttp/router v1.4.3
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/a8m/envsubst v1.3.0
|
||||
github.com/fasthttp/router v1.4.4
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/valyala/fasthttp v1.30.0
|
||||
github.com/valyala/fasthttp v1.31.0
|
||||
go.uber.org/zap v1.19.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
@ -21,12 +21,12 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
|
||||
)
|
||||
|
289
go.sum
289
go.sum
@ -18,6 +18,15 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
@ -26,7 +35,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
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=
|
||||
@ -39,30 +48,53 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
||||
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.2.0 h1:yvzAhJD2QKdo35Ut03wIfXQmg+ta3wC/1bskfZynz+Q=
|
||||
github.com/a8m/envsubst v1.2.0/go.mod h1:PpvLvNWa+Rvu/10qXmFbFiGICIU5hZvFJNPCCkUaObg=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
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/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
|
||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -72,22 +104,35 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fasthttp/router v1.4.3 h1:spS+LUnRryQ/+hbmYzs/xWGJlQCkeQI3hxGZdlVYhLU=
|
||||
github.com/fasthttp/router v1.4.3/go.mod h1:9ytWCfZ5LcCcbD3S7pEXyBX9vZnOZmN918WiiaYUzr8=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/fasthttp/router v1.4.4 h1:Z025tHFTjDp6T6QMBjloyGL6KV5wtakW365K/7KiE1c=
|
||||
github.com/fasthttp/router v1.4.4/go.mod h1:TiyF2kc+mogKcTxqkhUbiXpwklouv5dN58A0ZUo8J6s=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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-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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
@ -96,6 +141,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -127,10 +173,12 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
@ -142,41 +190,59 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@ -184,58 +250,92 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
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/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/savsgio/gotils v0.0.0-20210907153846-c06938798b52/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8=
|
||||
github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
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/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=
|
||||
@ -244,19 +344,20 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
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/fasthttp v1.30.0 h1:nBNzWrgZUUHohyLPU/jTvXdhrcaf2m5k3bWk+3Q049g=
|
||||
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE=
|
||||
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -264,6 +365,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
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-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
|
||||
@ -273,14 +375,17 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
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-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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=
|
||||
@ -316,10 +421,11 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-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=
|
||||
@ -327,9 +433,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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-20190923162816-aa69164e4478/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=
|
||||
@ -352,7 +460,10 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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=
|
||||
@ -364,7 +475,12 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -378,21 +494,30 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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-20200124204421-9fbb57f87de9/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=
|
||||
@ -411,6 +536,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -418,8 +544,20 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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=
|
||||
@ -429,6 +567,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/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=
|
||||
@ -438,7 +577,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@ -446,9 +584,9 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@ -482,7 +620,10 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -509,7 +650,17 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||
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=
|
||||
@ -557,7 +708,29 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -577,7 +750,15 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -590,13 +771,19 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
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/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// OSSignals allows to subscribe for system signals.
|
||||
// OSSignals allows subscribing for system signals.
|
||||
type OSSignals struct {
|
||||
ctx context.Context
|
||||
ch chan os.Signal
|
||||
@ -22,7 +22,7 @@ func NewOSSignals(ctx context.Context) OSSignals {
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe for some of system signals (call Stop for stopping).
|
||||
// Subscribe for some system signals (call Stop for stopping).
|
||||
func (oss *OSSignals) Subscribe(onSignal func(os.Signal), signals ...os.Signal) {
|
||||
if len(signals) == 0 {
|
||||
signals = []os.Signal{os.Interrupt, syscall.SIGINT, syscall.SIGTERM} // default signals
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
@ -19,7 +20,7 @@ type historyItem struct {
|
||||
}
|
||||
|
||||
// NewCommand creates `build` command.
|
||||
func NewCommand(log *zap.Logger, configFile *string) *cobra.Command { //nolint:funlen,gocognit
|
||||
func NewCommand(log *zap.Logger, configFile *string) *cobra.Command { //nolint:funlen,gocognit,gocyclo
|
||||
var (
|
||||
generateIndex bool
|
||||
cfg *config.Config
|
||||
@ -52,33 +53,34 @@ func NewCommand(log *zap.Logger, configFile *string) *cobra.Command { //nolint:f
|
||||
return errors.New("wrong arguments count")
|
||||
}
|
||||
|
||||
log.Info("loading templates")
|
||||
errorPages := tpl.NewErrorPages()
|
||||
|
||||
templates, err := cfg.LoadTemplates()
|
||||
if err != nil {
|
||||
log.Info("loading templates")
|
||||
if templates, err := cfg.LoadTemplates(); err == nil {
|
||||
if len(templates) > 0 {
|
||||
for templateName, content := range templates {
|
||||
errorPages.AddTemplate(templateName, content)
|
||||
}
|
||||
|
||||
for code, desc := range cfg.Pages {
|
||||
errorPages.AddPage(code, desc.Message, desc.Description)
|
||||
}
|
||||
} else {
|
||||
return errors.New("no loaded templates")
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
} else if len(templates) == 0 {
|
||||
return errors.New("no loaded templates")
|
||||
}
|
||||
|
||||
log.Debug("the output directory preparing", zap.String("Path", args[0]))
|
||||
|
||||
if err = createDirectory(args[0]); err != nil {
|
||||
if err := createDirectory(args[0]); err != nil {
|
||||
return errors.Wrap(err, "cannot prepare output directory")
|
||||
}
|
||||
|
||||
codes := make(map[string]tpl.Annotator)
|
||||
|
||||
for code, desc := range cfg.Pages {
|
||||
codes[code] = tpl.Annotator{Message: desc.Message, Description: desc.Description}
|
||||
}
|
||||
|
||||
history := make(map[string][]historyItem, len(templates))
|
||||
history, startedAt := make(map[string][]historyItem), time.Now()
|
||||
|
||||
log.Info("saving the error pages")
|
||||
startedAt := time.Now()
|
||||
|
||||
if err = tpl.NewErrors(templates, codes).VisitAll(func(template, code string, content []byte) error {
|
||||
if err := errorPages.IteratePages(func(template, code string, content []byte) error {
|
||||
if e := createDirectory(path.Join(args[0], template)); e != nil {
|
||||
return e
|
||||
}
|
||||
@ -90,27 +92,33 @@ func NewCommand(log *zap.Logger, configFile *string) *cobra.Command { //nolint:f
|
||||
}
|
||||
|
||||
if _, ok := history[template]; !ok {
|
||||
history[template] = make([]historyItem, 0, len(codes))
|
||||
history[template] = make([]historyItem, 0, len(cfg.Pages))
|
||||
}
|
||||
|
||||
history[template] = append(history[template], historyItem{
|
||||
Code: code,
|
||||
Message: codes[code].Message,
|
||||
Message: cfg.Pages[code].Message,
|
||||
Path: path.Join(template, fileName),
|
||||
})
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("saved", zap.Duration("duration", time.Since(startedAt)))
|
||||
|
||||
if generateIndex {
|
||||
for _, h := range history {
|
||||
sort.Slice(h, func(i, j int) bool {
|
||||
return h[i].Code < h[j].Code
|
||||
})
|
||||
}
|
||||
|
||||
log.Info("index file generation")
|
||||
startedAt = time.Now()
|
||||
|
||||
if err = writeIndexFile(path.Join(args[0], "index.html"), history); err != nil {
|
||||
if err := writeIndexFile(path.Join(args[0], "index.html"), history); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -4,16 +4,16 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/tarampampam/error-pages/internal/http/handlers/errorpage"
|
||||
"github.com/tarampampam/error-pages/internal/tpl"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"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"
|
||||
"github.com/tarampampam/error-pages/internal/tpl"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NewCommand creates `serve` command.
|
||||
@ -56,8 +56,6 @@ func NewCommand(ctx context.Context, log *zap.Logger, configFile *string) *cobra
|
||||
return cmd
|
||||
}
|
||||
|
||||
const serverShutdownTimeout = 15 * time.Second
|
||||
|
||||
// run current command.
|
||||
func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config) error { //nolint:funlen
|
||||
var (
|
||||
@ -77,35 +75,78 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
|
||||
oss.Stop() // stop system signals listening
|
||||
}()
|
||||
|
||||
// load templates content
|
||||
templates, loadingErr := cfg.LoadTemplates()
|
||||
if loadingErr != nil {
|
||||
return loadingErr
|
||||
} else if len(templates) == 0 {
|
||||
return errors.New("no loaded templates")
|
||||
}
|
||||
var (
|
||||
errorPages = tpl.NewErrorPages()
|
||||
templateNames = make([]string, 0) // slice with all possible template names
|
||||
)
|
||||
|
||||
if f.template.name != "" && f.template.name != errorpage.UseRandom && f.template.name != errorpage.UseRandomOnEachRequest { //nolint:lll
|
||||
if _, found := templates[f.template.name]; !found {
|
||||
return errors.New("requested nonexistent template: " + f.template.name) // requested unknown template
|
||||
log.Debug("Loading templates")
|
||||
|
||||
if templates, err := cfg.LoadTemplates(); err == nil {
|
||||
if len(templates) > 0 {
|
||||
for templateName, content := range templates {
|
||||
errorPages.AddTemplate(templateName, content)
|
||||
templateNames = append(templateNames, templateName)
|
||||
}
|
||||
|
||||
for code, desc := range cfg.Pages {
|
||||
errorPages.AddPage(code, desc.Message, desc.Description)
|
||||
}
|
||||
|
||||
log.Info("Templates loaded", zap.Int("templates", len(templates)), zap.Int("pages", len(cfg.Pages)))
|
||||
} else {
|
||||
return errors.New("no loaded templates")
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
// burn the error codes map
|
||||
codes := make(map[string]tpl.Annotator)
|
||||
for code, desc := range cfg.Pages {
|
||||
codes[code] = tpl.Annotator{Message: desc.Message, Description: desc.Description}
|
||||
sort.Strings(templateNames) // sorting is important for the first template picking
|
||||
|
||||
var picker *pick.StringsSlice
|
||||
|
||||
switch f.template.name {
|
||||
case useRandomTemplate:
|
||||
log.Info("A random template will be used")
|
||||
|
||||
picker = pick.NewStringsSlice(templateNames, pick.RandomOnce)
|
||||
|
||||
case useRandomTemplateOnEachRequest:
|
||||
log.Info("A random template on EACH request will be used")
|
||||
|
||||
picker = pick.NewStringsSlice(templateNames, pick.RandomEveryTime)
|
||||
|
||||
case "":
|
||||
log.Info("The first template (ordered by name) will be used")
|
||||
|
||||
picker = pick.NewStringsSlice(templateNames, pick.First)
|
||||
|
||||
default:
|
||||
var found bool
|
||||
|
||||
for i := 0; i < len(templateNames); i++ {
|
||||
if templateNames[i] == f.template.name {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("requested nonexistent template: " + f.template.name)
|
||||
}
|
||||
|
||||
log.Info("We will use the requested template", zap.String("name", f.template.name))
|
||||
picker = pick.NewStringsSlice([]string{f.template.name}, pick.First)
|
||||
}
|
||||
|
||||
// create HTTP server
|
||||
server := appHttp.NewServer(log)
|
||||
|
||||
// register server routes, middlewares, etc.
|
||||
if err := server.Register(f.template.name, templates, codes); err != nil {
|
||||
return err
|
||||
}
|
||||
server.Register(&errorPages, picker, f.defaultErrorPage, f.defaultHTTPCode)
|
||||
|
||||
startingErrCh := make(chan error, 1) // channel for server starting error
|
||||
startedAt, startingErrCh := time.Now(), make(chan error, 1) // channel for server starting error
|
||||
|
||||
// start HTTP server in separate goroutine
|
||||
go func(errCh chan<- error) {
|
||||
@ -114,7 +155,8 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
|
||||
log.Info("Server starting",
|
||||
zap.String("addr", f.listen.ip),
|
||||
zap.Uint16("port", f.listen.port),
|
||||
zap.String("template name", f.template.name),
|
||||
zap.String("default error page", f.defaultErrorPage),
|
||||
zap.Uint16("default HTTP response code", f.defaultHTTPCode),
|
||||
)
|
||||
|
||||
if err := server.Start(f.listen.ip, f.listen.port); err != nil {
|
||||
@ -128,20 +170,12 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
|
||||
return err
|
||||
|
||||
case <-ctx.Done(): // ..or context cancellation
|
||||
log.Info("Gracefully server stopping")
|
||||
|
||||
stoppedAt := time.Now()
|
||||
log.Info("Gracefully server stopping", zap.Duration("uptime", time.Since(startedAt)))
|
||||
|
||||
// stop the server using created context above
|
||||
if err := server.Stop(serverShutdownTimeout); err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
log.Error("Server stopping timeout exceeded", zap.Duration("timeout", serverShutdownTimeout))
|
||||
}
|
||||
|
||||
if err := server.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Server stopped", zap.Duration("stopping duration", time.Since(stoppedAt)))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tarampampam/error-pages/internal/http/handlers/errorpage"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/tarampampam/error-pages/internal/env"
|
||||
)
|
||||
@ -20,12 +18,21 @@ type flags struct {
|
||||
template struct {
|
||||
name string
|
||||
}
|
||||
defaultErrorPage string
|
||||
defaultHTTPCode uint16
|
||||
}
|
||||
|
||||
const (
|
||||
listenFlagName = "listen"
|
||||
portFlagName = "port"
|
||||
templateNameFlagName = "template-name"
|
||||
listenFlagName = "listen"
|
||||
portFlagName = "port"
|
||||
templateNameFlagName = "template-name"
|
||||
defaultErrorPageFlagName = "default-error-page"
|
||||
defaultHTTPCodeFlagName = "default-http-code"
|
||||
)
|
||||
|
||||
const (
|
||||
useRandomTemplate = "random"
|
||||
useRandomTemplateOnEachRequest = "i-said-random"
|
||||
)
|
||||
|
||||
func (f *flags) init(flagSet *pflag.FlagSet) {
|
||||
@ -46,13 +53,25 @@ func (f *flags) init(flagSet *pflag.FlagSet) {
|
||||
templateNameFlagName, "t",
|
||||
"",
|
||||
fmt.Sprintf(
|
||||
"template name (set \"%s\" to use the randomized or \"%s\" to use the randomized template on each request) [$%s]", //nolint:lll
|
||||
errorpage.UseRandom, errorpage.UseRandomOnEachRequest, env.TemplateName,
|
||||
"template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on each request) [$%s]", //nolint:lll
|
||||
useRandomTemplate, useRandomTemplateOnEachRequest, 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),
|
||||
)
|
||||
}
|
||||
|
||||
func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) {
|
||||
func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nolint:gocognit
|
||||
flagSet.VisitAll(func(flag *pflag.Flag) {
|
||||
// flag was NOT defined using CLI (flags should have maximal priority)
|
||||
if !flag.Changed { //nolint:nestif
|
||||
@ -75,6 +94,20 @@ func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -87,5 +120,9 @@ func (f *flags) validate() error {
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
10
internal/env/env.go
vendored
10
internal/env/env.go
vendored
@ -6,10 +6,12 @@ import "os"
|
||||
type envVariable string
|
||||
|
||||
const (
|
||||
ListenAddr envVariable = "LISTEN_ADDR" // IP address for listening
|
||||
ListenPort envVariable = "LISTEN_PORT" // port number for listening
|
||||
TemplateName envVariable = "TEMPLATE_NAME" // template name
|
||||
ConfigFilePath envVariable = "CONFIG_FILE" // path to the config file
|
||||
ListenAddr envVariable = "LISTEN_ADDR" // IP address for listening
|
||||
ListenPort envVariable = "LISTEN_PORT" // port number for listening
|
||||
TemplateName envVariable = "TEMPLATE_NAME" // template name
|
||||
ConfigFilePath envVariable = "CONFIG_FILE" // path to the config file
|
||||
DefaultErrorPage envVariable = "DEFAULT_ERROR_PAGE" // default error page (code)
|
||||
DefaultHTTPCode envVariable = "DEFAULT_HTTP_CODE" // default HTTP response code
|
||||
)
|
||||
|
||||
// String returns environment variable name in the string representation.
|
||||
|
4
internal/env/env_test.go
vendored
4
internal/env/env_test.go
vendored
@ -12,6 +12,8 @@ func TestConstants(t *testing.T) {
|
||||
assert.Equal(t, "LISTEN_PORT", string(ListenPort))
|
||||
assert.Equal(t, "TEMPLATE_NAME", string(TemplateName))
|
||||
assert.Equal(t, "CONFIG_FILE", string(ConfigFilePath))
|
||||
assert.Equal(t, "DEFAULT_ERROR_PAGE", string(DefaultErrorPage))
|
||||
assert.Equal(t, "DEFAULT_HTTP_CODE", string(DefaultHTTPCode))
|
||||
}
|
||||
|
||||
func TestEnvVariable_Lookup(t *testing.T) {
|
||||
@ -22,6 +24,8 @@ func TestEnvVariable_Lookup(t *testing.T) {
|
||||
{giveEnv: ListenPort},
|
||||
{giveEnv: TemplateName},
|
||||
{giveEnv: ConfigFilePath},
|
||||
{giveEnv: DefaultErrorPage},
|
||||
{giveEnv: DefaultHTTPCode},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
|
@ -1,35 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
const internalErrorPattern = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<title>Internal error occurred</title>
|
||||
<style>
|
||||
html,body {background-color: #0e0e0e;color:#fff;font-family:'Nunito',sans-serif;height:100%;margin:0}
|
||||
.message {height:100%;align-items:center;display:flex;justify-content:center;position:relative;font-size:1.4em}
|
||||
img {padding-right: .4em}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="message">
|
||||
<img src="https://hsto.org/webt/fs/sx/gt/fssxgtssfg689qxboqvjil5yz8g.png" alt="logo" height="32">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
func HandleInternalHTTPError(ctx *fasthttp.RequestCtx, statusCode int, message string) {
|
||||
ctx.SetStatusCode(statusCode)
|
||||
ctx.SetContentType("text/html; charset=UTF-8")
|
||||
|
||||
_, _ = ctx.WriteString(strings.ReplaceAll(internalErrorPattern, "{{ message }}", message))
|
||||
}
|
@ -25,6 +25,7 @@ func LogRequest(h fasthttp.RequestHandler, log *zap.Logger) fasthttp.RequestHand
|
||||
zap.String("useragent", ua),
|
||||
zap.String("method", string(ctx.Method())),
|
||||
zap.String("url", string(ctx.RequestURI())),
|
||||
zap.String("referer", string(ctx.Referer())),
|
||||
zap.Int("status_code", ctx.Response.StatusCode()),
|
||||
zap.Bool("connection_close", ctx.Response.ConnectionClose()),
|
||||
zap.Duration("duration", time.Since(startedAt)),
|
||||
|
@ -1,85 +1,38 @@
|
||||
package errorpage
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tarampampam/error-pages/internal/http/common"
|
||||
"github.com/tarampampam/error-pages/internal/tpl"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
const (
|
||||
UseRandom = "random"
|
||||
UseRandomOnEachRequest = "i-said-random"
|
||||
type (
|
||||
errorsPager interface {
|
||||
// GetPage with passed template name and error code.
|
||||
GetPage(templateName, code string) ([]byte, error)
|
||||
}
|
||||
|
||||
templatePicker interface {
|
||||
// Pick the template name for responding.
|
||||
Pick() string
|
||||
}
|
||||
)
|
||||
|
||||
// NewHandler creates handler for error pages serving.
|
||||
func NewHandler(
|
||||
templateName string,
|
||||
templates map[string][]byte,
|
||||
codes map[string]tpl.Annotator,
|
||||
) (fasthttp.RequestHandler, error) {
|
||||
if len(templates) == 0 {
|
||||
return nil, errors.New("empty templates map")
|
||||
}
|
||||
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec
|
||||
templateNames = templateTames(templates)
|
||||
)
|
||||
|
||||
if templateName == "" { // on empty template name
|
||||
templateName = templateNames[0] // pick the first
|
||||
} else if templateName == UseRandom { // on "random" template name
|
||||
templateName = templateNames[rnd.Intn(len(templateNames))] // pick the randomized
|
||||
}
|
||||
|
||||
if _, found := templates[templateName]; !found && templateName != UseRandomOnEachRequest {
|
||||
return nil, errors.New("wrong template name passed")
|
||||
}
|
||||
|
||||
var pages = tpl.NewErrors(templates, codes)
|
||||
|
||||
func NewHandler(e errorsPager, p templatePicker) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
var useTemplate = templateName // default
|
||||
|
||||
if templateName == UseRandomOnEachRequest {
|
||||
useTemplate = templateNames[rnd.Intn(len(templateNames))] // pick the randomized
|
||||
}
|
||||
ctx.SetContentType("text/plain; charset=utf-8") // default content type
|
||||
|
||||
if code, ok := ctx.UserValue("code").(string); ok {
|
||||
if content, err := pages.Get(useTemplate, code); err == nil {
|
||||
if content, err := e.GetPage(p.Pick(), code); err == nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||||
ctx.SetContentType("text/html; charset=utf-8")
|
||||
_, _ = ctx.Write(content)
|
||||
} else {
|
||||
common.HandleInternalHTTPError(
|
||||
ctx,
|
||||
fasthttp.StatusNotFound,
|
||||
"requested code not available: "+err.Error(),
|
||||
)
|
||||
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
||||
_, _ = ctx.WriteString("requested code not available: " + err.Error()) // TODO customize the output?
|
||||
}
|
||||
} else { // will never happen
|
||||
common.HandleInternalHTTPError(
|
||||
ctx,
|
||||
fasthttp.StatusInternalServerError,
|
||||
"cannot extract requested code from the request",
|
||||
)
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
_, _ = ctx.WriteString("cannot extract requested code from the request") // TODO customize the output?
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func templateTames(templates map[string][]byte) []string {
|
||||
var templateNames = make([]string, 0, len(templates))
|
||||
|
||||
for name := range templates {
|
||||
templateNames = append(templateNames, name)
|
||||
}
|
||||
|
||||
sort.Strings(templateNames)
|
||||
|
||||
return templateNames
|
||||
}
|
||||
|
41
internal/http/handlers/index/handler.go
Normal file
41
internal/http/handlers/index/handler.go
Normal file
@ -0,0 +1,41 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type (
|
||||
errorsPager interface {
|
||||
// GetPage with passed template name and error code.
|
||||
GetPage(templateName, code string) ([]byte, error)
|
||||
}
|
||||
|
||||
templatePicker interface {
|
||||
// Pick the template name for responding.
|
||||
Pick() string
|
||||
}
|
||||
)
|
||||
|
||||
// NewHandler creates handler for the index page serving.
|
||||
func NewHandler(
|
||||
e errorsPager,
|
||||
p templatePicker,
|
||||
defaultPageCode string,
|
||||
defaultHTTPCode uint16,
|
||||
) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
content, err := e.GetPage(p.Pick(), defaultPageCode)
|
||||
|
||||
if err == nil {
|
||||
ctx.SetContentType("text/html; charset=utf-8")
|
||||
ctx.SetStatusCode(int(defaultHTTPCode))
|
||||
_, _ = ctx.Write(content)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetContentType("text/plain; charset=utf-8")
|
||||
ctx.SetStatusCode(fasthttp.StatusNotAcceptable)
|
||||
_, _ = ctx.WriteString("default page code " + defaultPageCode + " is not available: " + err.Error())
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package common_test
|
||||
package index_test
|
||||
|
||||
import "testing"
|
||||
|
14
internal/http/handlers/notfound/handler.go
Normal file
14
internal/http/handlers/notfound/handler.go
Normal file
@ -0,0 +1,14 @@
|
||||
package notfound
|
||||
|
||||
import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// NewHandler creates handler missing requests handling.
|
||||
func NewHandler() fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
ctx.SetContentType("text/plain; charset=utf-8")
|
||||
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
||||
_, _ = ctx.WriteString("Wrong request URL. Error pages are available at the following URLs: /{code}.html")
|
||||
}
|
||||
}
|
7
internal/http/handlers/notfound/handler_test.go
Normal file
7
internal/http/handlers/notfound/handler_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
package notfound_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNothing(t *testing.T) {
|
||||
t.Skip("tests for this package have not been implemented yet")
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@ -10,8 +9,9 @@ import (
|
||||
"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"
|
||||
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/tpl"
|
||||
"github.com/tarampampam/error-pages/internal/version"
|
||||
"github.com/valyala/fasthttp"
|
||||
"go.uber.org/zap"
|
||||
@ -23,9 +23,9 @@ type Server struct {
|
||||
}
|
||||
|
||||
const (
|
||||
defaultWriteTimeout = time.Second * 7
|
||||
defaultReadTimeout = time.Second * 7
|
||||
defaultIdleTimeout = time.Second * 15
|
||||
defaultWriteTimeout = time.Second * 4
|
||||
defaultReadTimeout = time.Second * 4
|
||||
defaultIdleTimeout = time.Second * 6
|
||||
)
|
||||
|
||||
func NewServer(log *zap.Logger) Server {
|
||||
@ -52,55 +52,33 @@ func (s *Server) Start(ip string, port uint16) error {
|
||||
return s.fast.ListenAndServe(ip + ":" + strconv.Itoa(int(port)))
|
||||
}
|
||||
|
||||
type (
|
||||
errorsPager interface {
|
||||
// GetPage with passed template name and error code.
|
||||
GetPage(templateName, code string) ([]byte, error)
|
||||
}
|
||||
|
||||
templatePicker interface {
|
||||
// Pick the template name for responding.
|
||||
Pick() string
|
||||
}
|
||||
)
|
||||
|
||||
// Register server routes, middlewares, etc.
|
||||
// Router docs: <https://github.com/fasthttp/router>
|
||||
func (s *Server) Register(
|
||||
templateName string,
|
||||
templates map[string][]byte,
|
||||
codes map[string]tpl.Annotator,
|
||||
) error {
|
||||
s.router.GET("/", func(ctx *fasthttp.RequestCtx) {
|
||||
common.HandleInternalHTTPError(
|
||||
ctx,
|
||||
fasthttp.StatusNotFound,
|
||||
"Hi there! Error pages are available at the following URLs: /{code}.html",
|
||||
)
|
||||
})
|
||||
|
||||
s.router.NotFound = func(ctx *fasthttp.RequestCtx) {
|
||||
common.HandleInternalHTTPError(
|
||||
ctx,
|
||||
fasthttp.StatusNotFound,
|
||||
"Wrong request URL. Error pages are available at the following URLs: /{code}.html",
|
||||
)
|
||||
}
|
||||
|
||||
errorsPager errorsPager,
|
||||
templatePicker templatePicker,
|
||||
defaultPageCode string,
|
||||
defaultHTTPCode uint16,
|
||||
) {
|
||||
s.router.GET("/", indexHandler.NewHandler(errorsPager, templatePicker, defaultPageCode, defaultHTTPCode))
|
||||
s.router.GET("/version", versionHandler.NewHandler(version.Version()))
|
||||
s.router.ANY("/health/live", healthzHandler.NewHandler(checkers.NewLiveChecker()))
|
||||
s.router.GET("/{code}.html", errorpageHandler.NewHandler(errorsPager, templatePicker))
|
||||
|
||||
if h, err := errorpageHandler.NewHandler(templateName, templates, codes); err != nil {
|
||||
return err
|
||||
} else {
|
||||
s.router.GET("/{code}.html", h)
|
||||
}
|
||||
|
||||
return nil
|
||||
s.router.NotFound = notfoundHandler.NewHandler()
|
||||
}
|
||||
|
||||
// Stop server.
|
||||
func (s *Server) Stop(timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout) // TODO replace with simple time.After
|
||||
defer cancel()
|
||||
|
||||
ch := make(chan error, 1) // channel for server stopping error
|
||||
|
||||
go func() { defer close(ch); ch <- s.fast.Shutdown() }()
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
return err
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
func (s *Server) Stop() error { return s.fast.Shutdown() }
|
||||
|
64
internal/pick/strings_slice.go
Normal file
64
internal/pick/strings_slice.go
Normal file
@ -0,0 +1,64 @@
|
||||
package pick
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type pickMode byte
|
||||
|
||||
const (
|
||||
First pickMode = 1 + iota // Always pick the first element
|
||||
RandomOnce // Pick random element once (any future Pick calls will return the same element)
|
||||
RandomEveryTime // Always Pick the random element
|
||||
)
|
||||
|
||||
type StringsSlice struct {
|
||||
items []string
|
||||
mode pickMode
|
||||
lastUsedIdx int // -1 when unset, needed for RandomOnce mode
|
||||
rnd *rand.Rand // will be nil for the First mode
|
||||
}
|
||||
|
||||
// NewStringsSlice creates new StringsSlice.
|
||||
func NewStringsSlice(items []string, mode pickMode) *StringsSlice {
|
||||
var rnd *rand.Rand
|
||||
|
||||
if mode != First {
|
||||
rnd = rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec
|
||||
}
|
||||
|
||||
return &StringsSlice{
|
||||
items: items,
|
||||
mode: mode,
|
||||
lastUsedIdx: -1,
|
||||
rnd: rnd,
|
||||
}
|
||||
}
|
||||
|
||||
// Pick an element from the strings slice.
|
||||
func (s *StringsSlice) Pick() string {
|
||||
if l := len(s.items); l == 0 {
|
||||
return ""
|
||||
} else if l == 1 {
|
||||
return s.items[0]
|
||||
}
|
||||
|
||||
switch s.mode {
|
||||
case First:
|
||||
return s.items[0]
|
||||
|
||||
case RandomOnce:
|
||||
if s.lastUsedIdx == -1 {
|
||||
s.lastUsedIdx = s.rnd.Intn(len(s.items))
|
||||
}
|
||||
|
||||
return s.items[s.lastUsedIdx]
|
||||
|
||||
case RandomEveryTime:
|
||||
return s.items[s.rnd.Intn(len(s.items))]
|
||||
|
||||
default:
|
||||
panic("pick: unsupported mode")
|
||||
}
|
||||
}
|
102
internal/pick/strings_slice_test.go
Normal file
102
internal/pick/strings_slice_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
package pick_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tarampampam/error-pages/internal/pick"
|
||||
)
|
||||
|
||||
func TestStringsSlice_Pick_First(t *testing.T) {
|
||||
for name, items := range map[string][]string{
|
||||
"0 item": {},
|
||||
"1 item": {"foo"},
|
||||
"3 items": {"foo", "bar", "baz"},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
p := pick.NewStringsSlice(items, pick.First)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
if len(items) == 0 {
|
||||
assert.Equal(t, "", p.Pick())
|
||||
} else {
|
||||
assert.Equal(t, "foo", p.Pick())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringsSlice_Pick_RandomOnce(t *testing.T) {
|
||||
p := pick.NewStringsSlice([]string{}, pick.RandomOnce)
|
||||
assert.Equal(t, "", p.Pick())
|
||||
|
||||
p = pick.NewStringsSlice([]string{"foo"}, pick.RandomOnce)
|
||||
assert.Equal(t, "foo", p.Pick())
|
||||
|
||||
dataSet := randomStringsSlice(t, 2048) // if this test will fail - Increase this value
|
||||
p = pick.NewStringsSlice(dataSet, pick.RandomOnce)
|
||||
picked := p.Pick()
|
||||
|
||||
assert.NotEqual(t, dataSet[0], p.Pick())
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
assert.Equal(t, picked, p.Pick())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringsSlice_Pick_RandomEveryTime(t *testing.T) {
|
||||
p := pick.NewStringsSlice([]string{}, pick.RandomEveryTime)
|
||||
assert.Equal(t, "", p.Pick())
|
||||
|
||||
p = pick.NewStringsSlice([]string{"foo"}, pick.RandomEveryTime)
|
||||
assert.Equal(t, "foo", p.Pick())
|
||||
|
||||
dataSet := randomStringsSlice(t, 2048) // if this test will fail - Increase this value
|
||||
p = pick.NewStringsSlice(dataSet, pick.RandomEveryTime)
|
||||
|
||||
lastPicked := p.Pick()
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
picked := p.Pick()
|
||||
|
||||
assert.NotEqual(t, lastPicked, picked)
|
||||
lastPicked = picked
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringsSlice_Pick_UnsupportedMode(t *testing.T) {
|
||||
p := pick.NewStringsSlice([]string{}, 255)
|
||||
assert.Equal(t, "", p.Pick())
|
||||
|
||||
p = pick.NewStringsSlice([]string{"foo"}, 255)
|
||||
assert.Equal(t, "foo", p.Pick())
|
||||
|
||||
p = pick.NewStringsSlice([]string{"foo", "bar"}, 255)
|
||||
|
||||
assert.Panics(t, func() { p.Pick() })
|
||||
}
|
||||
|
||||
func randomStringsSlice(t *testing.T, itemsCount int) []string {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec
|
||||
items = make([]string, itemsCount)
|
||||
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+=")
|
||||
)
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
b := make([]rune, 32)
|
||||
|
||||
for j := range b {
|
||||
b[j] = letters[rnd.Intn(len(letters))]
|
||||
}
|
||||
|
||||
items[i] = string(b)
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
136
internal/tpl/error_pages.go
Normal file
136
internal/tpl/error_pages.go
Normal file
@ -0,0 +1,136 @@
|
||||
package tpl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
// ErrorPages is a error page templates generator.
|
||||
ErrorPages struct {
|
||||
mu sync.RWMutex
|
||||
templates map[string][]byte // map[template_name]raw_content
|
||||
pages map[string]*pageProperties // map[page_code]props
|
||||
state map[string]map[string][]byte // map[template_name]map[page_code]content
|
||||
}
|
||||
|
||||
pageProperties struct {
|
||||
message, description string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownTemplate = errors.New("unknown template") // unknown template
|
||||
ErrUnknownPageCode = errors.New("unknown page code") // unknown page code
|
||||
)
|
||||
|
||||
// NewErrorPages creates ErrorPages templates generator.
|
||||
func NewErrorPages() ErrorPages {
|
||||
return ErrorPages{
|
||||
templates: make(map[string][]byte),
|
||||
pages: make(map[string]*pageProperties),
|
||||
state: make(map[string]map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// AddTemplate to the generator. Template can contain the special placeholders for the error code, message and
|
||||
// description:
|
||||
// {{ code }} - for the code
|
||||
// {{ message }} - for the message
|
||||
// {{ description }} - for the description
|
||||
func (e *ErrorPages) AddTemplate(templateName string, content []byte) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
e.templates[templateName] = content
|
||||
e.state[templateName] = make(map[string][]byte)
|
||||
|
||||
for code, props := range e.pages { // update the state
|
||||
e.state[templateName][code] = e.makeReplaces(content, code, props.message, props.description)
|
||||
}
|
||||
}
|
||||
|
||||
// AddPage with the passed code, message and description. This page will ba available for the all templates.
|
||||
func (e *ErrorPages) AddPage(code, message, description string) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
e.pages[code] = &pageProperties{message, description}
|
||||
|
||||
for templateName, content := range e.templates { // update the state
|
||||
e.state[templateName][code] = e.makeReplaces(content, code, message, description)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPage with passed template name and error code.
|
||||
func (e *ErrorPages) GetPage(templateName, code string) (content []byte, err error) {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
|
||||
if pages, templateExists := e.state[templateName]; templateExists {
|
||||
if c, pageExists := pages[code]; pageExists {
|
||||
content = c
|
||||
} else {
|
||||
err = ErrUnknownPageCode
|
||||
}
|
||||
} else {
|
||||
err = ErrUnknownTemplate
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IteratePages will call the passed function for each page and template.
|
||||
func (e *ErrorPages) IteratePages(fn func(template, code string, content []byte) error) error {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
|
||||
for tplName, codes := range e.state {
|
||||
for code, content := range codes {
|
||||
if err := fn(tplName, code, content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
tknCode byte = iota + 1
|
||||
tknMessage
|
||||
tknDescription
|
||||
)
|
||||
|
||||
var tknSets = map[byte][][]byte{ //nolint:gochecknoglobals
|
||||
tknCode: {[]byte("{{code}}"), []byte("{{ code }}")},
|
||||
tknMessage: {[]byte("{{message}}"), []byte("{{ message }}")},
|
||||
tknDescription: {[]byte("{{description}}"), []byte("{{ description }}")},
|
||||
}
|
||||
|
||||
func (e *ErrorPages) makeReplaces(where []byte, code, message, description string) []byte {
|
||||
for tkn, set := range tknSets {
|
||||
var replaceWith []byte
|
||||
|
||||
switch tkn {
|
||||
case tknCode:
|
||||
replaceWith = []byte(code)
|
||||
case tknMessage:
|
||||
replaceWith = []byte(message)
|
||||
case tknDescription:
|
||||
replaceWith = []byte(description)
|
||||
default:
|
||||
panic("tpl: unsupported token") // this is like a fuse, will never occur during normal usage
|
||||
}
|
||||
|
||||
if len(replaceWith) > 0 {
|
||||
for i := 0; i < len(set); i++ {
|
||||
where = bytes.ReplaceAll(where, set[i], replaceWith)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return where
|
||||
}
|
132
internal/tpl/error_pages_test.go
Normal file
132
internal/tpl/error_pages_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
package tpl_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/tarampampam/error-pages/internal/tpl"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrorPages_GetPage(t *testing.T) {
|
||||
e := tpl.NewErrorPages()
|
||||
|
||||
e.AddTemplate("foo", []byte("{{code}}: {{ message }} {{description}}"))
|
||||
e.AddPage("200", "ok", "all is ok")
|
||||
e.AddTemplate("bar", []byte("{{ code }} _ {{message}} ({{ description }})"))
|
||||
e.AddPage("201", "lorem", "ipsum")
|
||||
|
||||
content, err := e.GetPage("foo", "200")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "200: ok all is ok", string(content))
|
||||
|
||||
content, err = e.GetPage("foo", "201")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "201: lorem ipsum", string(content))
|
||||
|
||||
content, err = e.GetPage("bar", "200")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "200 _ ok (all is ok)", string(content))
|
||||
|
||||
content, err = e.GetPage("bar", "201")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "201 _ lorem (ipsum)", string(content))
|
||||
|
||||
content, err = e.GetPage("foo", "666")
|
||||
assert.ErrorIs(t, err, tpl.ErrUnknownPageCode)
|
||||
assert.Nil(t, content)
|
||||
|
||||
content, err = e.GetPage("baz", "200")
|
||||
assert.ErrorIs(t, err, tpl.ErrUnknownTemplate)
|
||||
assert.Nil(t, content)
|
||||
}
|
||||
|
||||
func TestErrorPages_GetPage_Concurrent(t *testing.T) {
|
||||
e := tpl.NewErrorPages()
|
||||
|
||||
init := func() {
|
||||
e.AddTemplate("foo", []byte("{{ code }}: {{ message }} {{ description }}"))
|
||||
e.AddPage("200", "ok", "all is ok")
|
||||
e.AddPage("201", "lorem", "ipsum")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
init()
|
||||
|
||||
for i := 0; i < 1234; i++ {
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
init() // make re-initialization
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
content, err := e.GetPage("foo", "200")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "200: ok all is ok", string(content))
|
||||
|
||||
content, err = e.GetPage("foo", "201")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "201: lorem ipsum", string(content))
|
||||
|
||||
content, err = e.GetPage("foo", "666")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, content)
|
||||
|
||||
content, err = e.GetPage("bar", "200")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, content)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestErrorPages_IteratePages(t *testing.T) {
|
||||
e := tpl.NewErrorPages()
|
||||
|
||||
e.AddTemplate("foo", []byte("{{ code }}: {{ message }} {{ description }}"))
|
||||
e.AddTemplate("bar", []byte("{{ code }}: {{ message }} {{ description }}"))
|
||||
e.AddPage("200", "ok", "all is ok")
|
||||
e.AddPage("400", "Bad Request", "")
|
||||
|
||||
visited := make(map[string]map[string]bool) // map[template]codes
|
||||
|
||||
assert.NoError(t, e.IteratePages(func(template, code string, content []byte) error {
|
||||
if _, ok := visited[template]; !ok {
|
||||
visited[template] = make(map[string]bool)
|
||||
}
|
||||
|
||||
visited[template][code] = true
|
||||
|
||||
assert.NotNil(t, content)
|
||||
|
||||
return nil
|
||||
}))
|
||||
|
||||
assert.Len(t, visited, 2)
|
||||
assert.Len(t, visited["foo"], 2)
|
||||
assert.True(t, visited["foo"]["200"])
|
||||
assert.True(t, visited["foo"]["400"])
|
||||
assert.Len(t, visited["bar"], 2)
|
||||
assert.True(t, visited["bar"]["200"])
|
||||
assert.True(t, visited["bar"]["400"])
|
||||
}
|
||||
|
||||
func TestErrorPages_IteratePages_WillReturnTheError(t *testing.T) {
|
||||
e := tpl.NewErrorPages()
|
||||
|
||||
e.AddTemplate("foo", []byte("{{ code }}: {{ message }} {{ description }}"))
|
||||
e.AddPage("200", "ok", "all is ok")
|
||||
|
||||
assert.EqualError(t, e.IteratePages(func(template, code string, content []byte) error {
|
||||
return errors.New("foo error")
|
||||
}), "foo error")
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package tpl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Annotator allows to annotate error code.
|
||||
type Annotator struct {
|
||||
Message string
|
||||
Description string
|
||||
}
|
||||
|
||||
// Errors is a "cached storage" for the rendered error pages for the different templates and codes.
|
||||
type Errors struct {
|
||||
templates map[string][]byte
|
||||
codes map[string]Annotator
|
||||
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]map[string][]byte // map[template]map[code]content
|
||||
}
|
||||
|
||||
// NewErrors creates new Errors.
|
||||
func NewErrors(templates map[string][]byte, codes map[string]Annotator) *Errors {
|
||||
return &Errors{
|
||||
templates: templates,
|
||||
codes: codes,
|
||||
cache: make(map[string]map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Errors) existsInCache(template, code string) ([]byte, bool) {
|
||||
e.cacheMu.RLock()
|
||||
defer e.cacheMu.RUnlock()
|
||||
|
||||
if codes, tplOk := e.cache[template]; tplOk {
|
||||
if content, codeOk := codes[code]; codeOk {
|
||||
return content, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (e *Errors) putInCache(template, code string) error {
|
||||
if _, ok := e.templates[template]; !ok {
|
||||
return errors.New("template \"" + template + "\" does not exists")
|
||||
}
|
||||
|
||||
if _, ok := e.codes[code]; !ok {
|
||||
return errors.New("code \"" + code + "\" does not exists")
|
||||
}
|
||||
|
||||
e.cacheMu.Lock()
|
||||
defer e.cacheMu.Unlock()
|
||||
|
||||
if _, ok := e.cache[template]; !ok {
|
||||
e.cache[template] = make(map[string][]byte)
|
||||
}
|
||||
|
||||
e.cache[template][code] = Replace(e.templates[template], Replaces{
|
||||
Code: code,
|
||||
Message: e.codes[code].Message,
|
||||
Description: e.codes[code].Description,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the rendered error page content.
|
||||
func (e *Errors) Get(template, code string) ([]byte, error) {
|
||||
if content, ok := e.existsInCache(template, code); ok {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
if err := e.putInCache(template, code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.cacheMu.RLock()
|
||||
defer e.cacheMu.RUnlock()
|
||||
|
||||
return e.cache[template][code], nil
|
||||
}
|
||||
|
||||
// VisitAll allows to iterate all possible error pages and templates.
|
||||
func (e *Errors) VisitAll(fn func(template, code string, content []byte) error) error {
|
||||
for tpl := range e.templates {
|
||||
for code := range e.codes {
|
||||
content, err := e.Get(tpl, code)
|
||||
if err != nil {
|
||||
return err // will never happen
|
||||
}
|
||||
|
||||
if err = fn(tpl, code, content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package tpl_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tarampampam/error-pages/internal/tpl"
|
||||
)
|
||||
|
||||
func TestErrors_Get(t *testing.T) {
|
||||
e := tpl.NewErrors(
|
||||
map[string][]byte{"foo": []byte("{{ code }}: {{ message }} {{ description }}")},
|
||||
map[string]tpl.Annotator{"200": {"ok", "all is ok"}},
|
||||
)
|
||||
|
||||
content, err := e.Get("foo", "200")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "200: ok all is ok", string(content))
|
||||
|
||||
content, err = e.Get("foo", "666")
|
||||
assert.EqualError(t, err, "code \"666\" does not exists")
|
||||
assert.Nil(t, content)
|
||||
|
||||
content, err = e.Get("bar", "200")
|
||||
assert.EqualError(t, err, "template \"bar\" does not exists")
|
||||
assert.Nil(t, content)
|
||||
}
|
||||
|
||||
func TestErrors_GetConcurrent(t *testing.T) {
|
||||
e := tpl.NewErrors(
|
||||
map[string][]byte{"foo": []byte("{{ code }}: {{ message }} {{ description }}")},
|
||||
map[string]tpl.Annotator{"200": {"ok", "all is ok"}},
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 1234; i++ {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
content, err := e.Get("foo", "200")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "200: ok all is ok", string(content))
|
||||
|
||||
content, err = e.Get("foo", "666")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, content)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestErrors_VisitAll(t *testing.T) {
|
||||
e := tpl.NewErrors(
|
||||
map[string][]byte{
|
||||
"foo": []byte("{{ code }}: {{ message }} {{ description }}"),
|
||||
"bar": []byte("{{ code }}: {{ message }} {{ description }}"),
|
||||
},
|
||||
map[string]tpl.Annotator{
|
||||
"200": {"ok", "all is ok"},
|
||||
"400": {"Bad Request", "The server did not understand the request"},
|
||||
},
|
||||
)
|
||||
|
||||
visited := make(map[string]map[string]bool) // map[template]codes
|
||||
|
||||
assert.NoError(t, e.VisitAll(func(template, code string, content []byte) error {
|
||||
if _, ok := visited[template]; !ok {
|
||||
visited[template] = make(map[string]bool)
|
||||
}
|
||||
|
||||
visited[template][code] = true
|
||||
|
||||
assert.NotNil(t, content)
|
||||
|
||||
return nil
|
||||
}))
|
||||
|
||||
assert.Len(t, visited, 2)
|
||||
assert.Len(t, visited["foo"], 2)
|
||||
assert.True(t, visited["foo"]["200"])
|
||||
assert.True(t, visited["foo"]["400"])
|
||||
assert.Len(t, visited["bar"], 2)
|
||||
assert.True(t, visited["bar"]["200"])
|
||||
assert.True(t, visited["bar"]["400"])
|
||||
}
|
||||
|
||||
func TestErrors_VisitAllWillReturnTheError(t *testing.T) {
|
||||
e := tpl.NewErrors(
|
||||
map[string][]byte{
|
||||
"foo": []byte("{{ code }}: {{ message }} {{ description }}"),
|
||||
},
|
||||
map[string]tpl.Annotator{
|
||||
"200": {"ok", "all is ok"},
|
||||
},
|
||||
)
|
||||
|
||||
assert.EqualError(t, e.VisitAll(func(template, code string, content []byte) error {
|
||||
return errors.New("foo error")
|
||||
}), "foo error")
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package tpl
|
||||
|
||||
import "bytes"
|
||||
|
||||
type Replaces struct {
|
||||
Code string
|
||||
Message string
|
||||
Description string
|
||||
}
|
||||
|
||||
const (
|
||||
tknCode byte = iota + 1
|
||||
tknMessage
|
||||
tknDescription
|
||||
)
|
||||
|
||||
var tknSets = map[byte][][]byte{ //nolint:gochecknoglobals
|
||||
tknCode: {[]byte("{{code}}"), []byte("{{ code }}")},
|
||||
tknMessage: {[]byte("{{message}}"), []byte("{{ message }}")},
|
||||
tknDescription: {[]byte("{{description}}"), []byte("{{ description }}")},
|
||||
}
|
||||
|
||||
// Replace found tokens in the incoming slice with passed tokens.
|
||||
func Replace(in []byte, re Replaces) []byte {
|
||||
for tkn, set := range tknSets {
|
||||
var replaceWith []byte
|
||||
|
||||
switch tkn {
|
||||
case tknCode:
|
||||
replaceWith = []byte(re.Code)
|
||||
case tknMessage:
|
||||
replaceWith = []byte(re.Message)
|
||||
case tknDescription:
|
||||
replaceWith = []byte(re.Description)
|
||||
default:
|
||||
panic("tpl: unsupported token")
|
||||
}
|
||||
|
||||
if len(replaceWith) > 0 {
|
||||
for i := 0; i < len(set); i++ {
|
||||
in = bytes.ReplaceAll(in, set[i], replaceWith)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return in
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package tpl_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tarampampam/error-pages/internal/tpl"
|
||||
)
|
||||
|
||||
func ExampleReplace() {
|
||||
var in = []byte("{{ code }}: {{message}} ({{ description }})")
|
||||
|
||||
fmt.Println(string(tpl.Replace(in, tpl.Replaces{
|
||||
Code: "400",
|
||||
Message: "Bad Request",
|
||||
Description: "The server did not understand the request",
|
||||
})))
|
||||
|
||||
// Output:
|
||||
// 400: Bad Request (The server did not understand the request)
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
for name, tt := range map[string]struct {
|
||||
giveIn []byte
|
||||
giveRe tpl.Replaces
|
||||
wantResult []byte
|
||||
}{
|
||||
"common": {
|
||||
giveIn: []byte("-- {{ code }} {{code}} __ {{message}} {{ description }} "),
|
||||
giveRe: tpl.Replaces{
|
||||
Code: "123",
|
||||
Message: "message",
|
||||
Description: "desc",
|
||||
},
|
||||
wantResult: []byte("-- 123 123 __ message desc "),
|
||||
},
|
||||
"alpha and underline in the code": {
|
||||
giveIn: []byte("\t{{ code }}\t"),
|
||||
giveRe: tpl.Replaces{
|
||||
Code: " qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_ ",
|
||||
},
|
||||
wantResult: []byte("\t qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_ \t"),
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.wantResult, tpl.Replace(tt.giveIn, tt.giveRe))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplace(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.Replace([]byte("-- {{ code }} {{code}} __ {{message}} {{ description }} "), tpl.Replaces{
|
||||
Code: "123",
|
||||
Message: "message",
|
||||
Description: "desc",
|
||||
})
|
||||
}
|
||||
}
|
43
templates/cats.html
Normal file
43
templates/cats.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Error {{ code }}: {{ message }}
|
||||
Description: {{ description }}
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<title>{{ message }}</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
background-color: #000;
|
||||
color: #aaa;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.centered {
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.centered img {
|
||||
max-width: 750px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="centered">
|
||||
<!-- Pictures provider: <https://http.cat/> -->
|
||||
<img src="https://http.cat/{{ code }}.jpg" alt="{{ message }}">
|
||||
</div>
|
||||
</body>
|
||||
<!--
|
||||
Error {{ code }}: {{ message }}
|
||||
Description: {{ description }}
|
||||
-->
|
||||
</html>
|
Reference in New Issue
Block a user